1st commit
This commit is contained in:
14
.gitignore
vendored
Normal file
14
.gitignore
vendored
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
**/.DS_Store
|
||||||
|
**/idx/
|
||||||
|
wwws/*/dist/**
|
||||||
|
!wwws/*/src/
|
||||||
|
!wwws/*/src/**
|
||||||
|
!wwws/itm/
|
||||||
|
!wwws/itm/**
|
||||||
|
devices
|
||||||
|
frenchlocation
|
||||||
|
nations
|
||||||
|
pagans
|
||||||
|
persons
|
||||||
|
towns
|
||||||
|
tribes
|
13
README.md
Normal file
13
README.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# apxtri Data
|
||||||
|
|
||||||
|
Any objects that are not under app management can be modify manualy by many dev
|
||||||
|
|
||||||
|
options/
|
||||||
|
Liste of referentials
|
||||||
|
|
||||||
|
wco/
|
||||||
|
Web component
|
||||||
|
|
||||||
|
wwws/*/src/
|
||||||
|
Any source code to build in wwws/*/dist/
|
||||||
|
|
1
options/conf.json
Normal file
1
options/conf.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"options","schema":"apxtri/schema/options.json","lastupdate":0,"lastupdatedata":"2025-05-18T10:14:03.610Z"}
|
1505
options/itm/country_en.json
Normal file
1505
options/itm/country_en.json
Normal file
File diff suppressed because it is too large
Load Diff
1505
options/itm/country_fr.json
Normal file
1505
options/itm/country_fr.json
Normal file
File diff suppressed because it is too large
Load Diff
31
options/itm/dayofweek_en.json
Normal file
31
options/itm/dayofweek_en.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"optionid":"dayofweek_en",
|
||||||
|
"title": "Country Code",
|
||||||
|
"description": "Country Code and Info",
|
||||||
|
"commment": "Alpha-2 country code (ISO 3166-2:XX)",
|
||||||
|
"lastupdatedata": "",
|
||||||
|
"lst_idx": ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
|
||||||
|
"itms": {
|
||||||
|
"Monday": {
|
||||||
|
"title": "Monday"
|
||||||
|
},
|
||||||
|
"Tuesday": {
|
||||||
|
"title": "Tuesday"
|
||||||
|
},
|
||||||
|
"Wednesday": {
|
||||||
|
"title": "Wednesday"
|
||||||
|
},
|
||||||
|
"Thursday": {
|
||||||
|
"title": "Thursday"
|
||||||
|
},
|
||||||
|
"Friday": {
|
||||||
|
"title": "Friday"
|
||||||
|
},
|
||||||
|
"Saturday": {
|
||||||
|
"title": "Saturday"
|
||||||
|
},
|
||||||
|
"Sunday": {
|
||||||
|
"title": "Sunday"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
31
options/itm/dayofweek_fr.json
Normal file
31
options/itm/dayofweek_fr.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"optionid":"dayofweek_fr",
|
||||||
|
"title": "Country Code",
|
||||||
|
"description": "Country Code and Info",
|
||||||
|
"commment": "Alpha-2 country code (ISO 3166-2:XX)",
|
||||||
|
"lastupdatedata": "",
|
||||||
|
"lst_idx": ["Monday","Tuesday","Wednesday","Thursday","Friday","Saturday","Sunday"],
|
||||||
|
"itms": {
|
||||||
|
"Monday": {
|
||||||
|
"title": "Lundi"
|
||||||
|
},
|
||||||
|
"Tuesday": {
|
||||||
|
"title": "Mardi"
|
||||||
|
},
|
||||||
|
"Wednesday": {
|
||||||
|
"title": "Mercredi"
|
||||||
|
},
|
||||||
|
"Thursday": {
|
||||||
|
"title": "Jeudi"
|
||||||
|
},
|
||||||
|
"Friday": {
|
||||||
|
"title": "Vendredi"
|
||||||
|
},
|
||||||
|
"Saturday": {
|
||||||
|
"title": "Samedi"
|
||||||
|
},
|
||||||
|
"Sunday": {
|
||||||
|
"title": "Dimanche"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
36
options/itm/profil_en.json
Normal file
36
options/itm/profil_en.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"optionid":"profil_en",
|
||||||
|
"title": "Profil",
|
||||||
|
"description": "Profil available in apxtri",
|
||||||
|
"commment": "",
|
||||||
|
"lastupdatedata": "",
|
||||||
|
"lst_idx": [
|
||||||
|
"anonymous",
|
||||||
|
"pagan",
|
||||||
|
"mayor",
|
||||||
|
"druid",
|
||||||
|
"person"
|
||||||
|
],
|
||||||
|
"itms": {
|
||||||
|
"anonymous": {
|
||||||
|
"title": "Unidentified user",
|
||||||
|
"description": "Unknown to the apxtri network"
|
||||||
|
},
|
||||||
|
"pagan": {
|
||||||
|
"title": "User with an apxtri identity",
|
||||||
|
"description": "This profile can digitally sign on the apxtri blockchain"
|
||||||
|
},
|
||||||
|
"mayor": {
|
||||||
|
"title": "Administrator of a city",
|
||||||
|
"description": "He manages the rules specific to a tribe grouping, he finances this city and can define its billing terms for welcoming tribes"
|
||||||
|
},
|
||||||
|
"druid": {
|
||||||
|
"title": "Administrator of a tribe",
|
||||||
|
"description": "He has a private space in a city, to apply and enforce the rules of the tribe."
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"title": "A member of a tribe",
|
||||||
|
"description": "A person has an account in a tribe with their alias, it allows them to exchange within a tribe according to the rules set by the druid."
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
35
options/itm/profil_fr.json
Normal file
35
options/itm/profil_fr.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"optionid":"profil_fr",
|
||||||
|
"title": "Profil",
|
||||||
|
"description": "Profil disponible dans apxtri",
|
||||||
|
"commment": "",
|
||||||
|
"lastupdatedata": "",
|
||||||
|
"lst_idx": [
|
||||||
|
"anonymous",
|
||||||
|
"pagan",
|
||||||
|
"mayor",
|
||||||
|
"druid",
|
||||||
|
"person"
|
||||||
|
],
|
||||||
|
"itms": {
|
||||||
|
"anonymous": {
|
||||||
|
"title": "Utilisateur non identifié",
|
||||||
|
"description": "Inconnu du réseau apxtri"
|
||||||
|
},
|
||||||
|
"pagan": {
|
||||||
|
"title": "Utilisateur avec une identité apxtri",
|
||||||
|
"description": "Ce profil peux signer numériquement sur la blockchain apxtri"
|
||||||
|
},
|
||||||
|
"mayor": {
|
||||||
|
"title": "Administrateur d'une ville",
|
||||||
|
"description": "Il gére les régles propres à un regroupement de tribu, il finance cette ville et peut definir ses modalités de facturation pour accueillir des tribus"
|
||||||
|
},
|
||||||
|
"druid": {
|
||||||
|
"title": "Administrateur d'une tribu",
|
||||||
|
"description": "Il dispose d'un espace privée dans une ville , pour y appliquer et faire respecter les régles de la tribu."
|
||||||
|
},
|
||||||
|
"person": {
|
||||||
|
"title": "Un membre d'une tribu",
|
||||||
|
"description": "Une personne dispose d'un compte dans une tribu avec son alias, il permet d'echanger au sein d'une tribu en fonction des régles mises en place par le druid."
|
||||||
|
} }
|
||||||
|
}
|
0
wco/admindata/admindata.js
Normal file
0
wco/admindata/admindata.js
Normal file
170
wco/adminskull/adminskull.js
Normal file
170
wco/adminskull/adminskull.js
Normal file
@@ -0,0 +1,170 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.adminskull = {};
|
||||||
|
|
||||||
|
apx.adminskull.show = (id) => {
|
||||||
|
document.getElementById("maincontent").innerHTML = `Contenu du wco ${id}`;
|
||||||
|
};
|
||||||
|
apx.adminskull.genereimg = (alias, size = 100) => {
|
||||||
|
const canvas = document.createElement("canvas");
|
||||||
|
canvas.width = size;
|
||||||
|
canvas.height = size;
|
||||||
|
const context = canvas.getContext("2d");
|
||||||
|
|
||||||
|
// Couleur de fond basée sur les lettres du nom
|
||||||
|
const colors = apx.data.tpldata.headnav.colorslist;
|
||||||
|
const charCodeSum = alias
|
||||||
|
.split("")
|
||||||
|
.reduce((acc, char) => acc + char.charCodeAt(0), 0);
|
||||||
|
const backgroundColor = colors[charCodeSum % colors.length];
|
||||||
|
context.fillStyle = backgroundColor;
|
||||||
|
context.fillRect(0, 0, size, size);
|
||||||
|
// first and last letter in uppercase
|
||||||
|
const initials =
|
||||||
|
alias.charAt(0).toUpperCase() +
|
||||||
|
alias.charAt(alias.length - 1).toUpperCase();
|
||||||
|
context.font = `${size / 2}px Arial`;
|
||||||
|
context.fillStyle = "#FFFFFF"; // Couleur du texte
|
||||||
|
context.textAlign = "center";
|
||||||
|
context.textBaseline = "middle";
|
||||||
|
context.fillText(initials, size / 2, size / 2);
|
||||||
|
return canvas.toDataURL();
|
||||||
|
};
|
||||||
|
apx.adminskull.togglesidebarmobile = (eltbtn) => {
|
||||||
|
//for mobile need to manage hidden case with other button in headnav
|
||||||
|
sidebar=document.getElementById("sidebar");
|
||||||
|
sidebar.classList.toggle('w-0');
|
||||||
|
sidebar.classList.toggle('w-full'); // Affiche la sidebar sur toute la largeur
|
||||||
|
sidebar.classList.toggle('-translate-x-full');
|
||||||
|
if (sidebar.classList.contains('w-full')){
|
||||||
|
|
||||||
|
sidebar.classList.remove('hidden');
|
||||||
|
}else{
|
||||||
|
sidebar.classList.add('hidden');
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.adminskull.toggleheadnav = (icon) => {
|
||||||
|
document
|
||||||
|
.querySelectorAll(`.dropdown`)
|
||||||
|
.forEach((el) => el.classList.add("hidden"));
|
||||||
|
document.querySelector(`.dropdown.${icon}`).classList.remove("hidden");
|
||||||
|
};
|
||||||
|
apx.adminskull.globalevent = () => {
|
||||||
|
// add here any
|
||||||
|
document.addEventListener("keydown", (e) => {
|
||||||
|
if (e.key === "Escape") {
|
||||||
|
document
|
||||||
|
.querySelectorAll("#headnav .dropdown")
|
||||||
|
.forEach((elt) => elt.classList.add("hidden"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
document.addEventListener("click", (e) => {
|
||||||
|
//sidebar open close summary details
|
||||||
|
if (e.target.tagName.toLowerCase() === "summary") {
|
||||||
|
document.querySelectorAll("#sidebar ul details").forEach((details) => {
|
||||||
|
if (details !== e.target.parentNode) {
|
||||||
|
details.removeAttribute("open");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (!document.getElementById("headnav").contains(e.target)) {
|
||||||
|
//if click somewhere else than headnav we close all dropdown that are eventually not hidden
|
||||||
|
document
|
||||||
|
.querySelectorAll("#headnav .dropdown")
|
||||||
|
.forEach((elt) => elt.classList.add("hidden"));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.adminskull.navigation = () => {
|
||||||
|
// test si authentification is valid
|
||||||
|
// genere les menu en fonction du contexte
|
||||||
|
const sidebar = document.getElementById("sidebar");
|
||||||
|
sidebar.querySelectorAll("ul").forEach((e) => e.remove());
|
||||||
|
sidebar.innerHTML =
|
||||||
|
sidebar.innerHTML +
|
||||||
|
Mustache.render(
|
||||||
|
apx.data.tpl.adminskullverticalnav,
|
||||||
|
apx.data.tpldata.verticalnav
|
||||||
|
);
|
||||||
|
const headnav = document.getElementById("headnav");
|
||||||
|
const datapagan = { alias: apx.data.headers.xalias };
|
||||||
|
/* for testing */
|
||||||
|
apx.data.itms.notifications = {
|
||||||
|
1: {
|
||||||
|
id: "1",
|
||||||
|
text: "notif 1",
|
||||||
|
from: "apxtri script toto.py",
|
||||||
|
date: "20250101",
|
||||||
|
},
|
||||||
|
2: {
|
||||||
|
id: "2",
|
||||||
|
text: "notif 2",
|
||||||
|
from: "apxtri script toto.py",
|
||||||
|
date: "20250101",
|
||||||
|
},
|
||||||
|
3: {
|
||||||
|
id: "3",
|
||||||
|
text: "notif 3",
|
||||||
|
from: "apxtri script toto.py",
|
||||||
|
date: "20250101",
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
if (
|
||||||
|
apx.data.itms.notifications &&
|
||||||
|
Object.keys(apx.data.itms.notifications).length > 0
|
||||||
|
) {
|
||||||
|
const notifarray = Object.values(apx.data.itms.notifications);
|
||||||
|
datapagan.numbernotif = notifarray.length;
|
||||||
|
datapagan.notif = notifarray
|
||||||
|
.sort((a, b) => b.date.localeCompare(a.date))
|
||||||
|
.slice(0, 5);
|
||||||
|
datapagan.notifcolor = "green";
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
apx.data.itms.messages &&
|
||||||
|
Object.keys(apx.data.itms.messages).length > 0
|
||||||
|
) {
|
||||||
|
const msgarray = Object.values(apx.data.itms.messages);
|
||||||
|
datapagan.numbermsg = msgarray.length;
|
||||||
|
datapagan.msg = msgarray
|
||||||
|
.sort((a, b) => b.date.localeCompare(a.date))
|
||||||
|
.slice(0, 3);
|
||||||
|
datapagan.msgcolor = "green";
|
||||||
|
}
|
||||||
|
datapagan.aliasimg = apx.adminskull.genereimg(datapagan.alias);
|
||||||
|
headnav.innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl.adminskullheadnav,
|
||||||
|
datapagan
|
||||||
|
);
|
||||||
|
apx.adminskull.globalevent();
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.adminskull.search=(element)=>{
|
||||||
|
const input=element.previousElementSibling
|
||||||
|
if (!input || input.tagName !== 'INPUT'){
|
||||||
|
console.log("Check your component no input avaiilable close to this button")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
document.getElementById("searchtitle").innerHTML+=" "+input.value
|
||||||
|
// analyse search string + - to convert in json search object with common rules to follow to send some action search in an object
|
||||||
|
const searchjson={searchtxt:input.value}
|
||||||
|
|
||||||
|
if (!apx.data.searchfunction || !apx[apx.data.searchfunction]){
|
||||||
|
console.log("ERROR: your settings is not correct to use search in your project you must define in apxtri the propertie searchfunction with a value and you must have a function in your project apx[apxtri.searchfunction](search) that return search with in search.results=[{thumbnail,titile,description,..}]")
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = apx[apx.data.searchfunction](searchjson);
|
||||||
|
result.results.forEach(r=>{
|
||||||
|
if (!r.thumbnail || r.thumbnail=="") {
|
||||||
|
r.thumbnail=apx.adminskull.genereimg(r.title,200)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
document.getElementById("searchresults").innerHTML=Mustache.render(apx.data.tpl.adminskullresult,result)
|
||||||
|
|
||||||
|
document.getElementById("maincontent").classList.add('hidden')
|
||||||
|
document.getElementById("searchcontent").classList.remove('hidden')
|
||||||
|
}
|
||||||
|
|
||||||
|
apx.readyafterupdate(apx.adminskull.navigation);
|
26
wco/adminskull/headnav_fr.json
Normal file
26
wco/adminskull/headnav_fr.json
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
{
|
||||||
|
"colorslist": [
|
||||||
|
"#ffc332",
|
||||||
|
"#fa6a31",
|
||||||
|
"#2e7fc8",
|
||||||
|
"#6aa84f",
|
||||||
|
"#218787",
|
||||||
|
"#ffd966",
|
||||||
|
"#fb8c5a",
|
||||||
|
"#1c5a8a",
|
||||||
|
"#4a7c3a",
|
||||||
|
"#1a6d6d",
|
||||||
|
"#e6b800",
|
||||||
|
"#d9531e",
|
||||||
|
"#5fa8d3",
|
||||||
|
"#8fbf4d",
|
||||||
|
"#2d9d9d",
|
||||||
|
"#cca300",
|
||||||
|
"#ff8c69",
|
||||||
|
"#1a3d5c",
|
||||||
|
"#5c7c3a",
|
||||||
|
"#4db3b3"
|
||||||
|
],
|
||||||
|
"notifplus": "En voir plus",
|
||||||
|
"msgplus": "Voir tous..."
|
||||||
|
}
|
93
wco/adminskull/headnav_fr.mustache
Normal file
93
wco/adminskull/headnav_fr.mustache
Normal file
@@ -0,0 +1,93 @@
|
|||||||
|
|
||||||
|
<!-- Menu Button -->
|
||||||
|
<div class="flex items-center space-x-4">
|
||||||
|
<button class="text-gray-500 hover:text-gray-700 focus:outline-none" onclick="apx.adminskull.togglesidebarmobile(this)">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
|
||||||
|
<!-- Search Bar -->
|
||||||
|
<div class="relative">
|
||||||
|
<input type="text" class="bg-gray-100 rounded-full pl-10 pr-10 py-2 text-sm text-black focus:outline-none" placeholder="Rechercher...">
|
||||||
|
<!-- Icône de loupe cliquable à droite -->
|
||||||
|
<button onclick="apx.adminskull.search(this);" class="absolute inset-y-0 right-0 pr-3 flex items-center">
|
||||||
|
<svg class="h-5 w-5 text-black-400 hover:text-gray-600" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Right: Icons and Profile -->
|
||||||
|
<div class="flex items-center space-x-6">
|
||||||
|
|
||||||
|
<!-- Notification Icon -->
|
||||||
|
{{#notifnumber}}
|
||||||
|
<div class="relative">
|
||||||
|
<button class="relative text-gray-700 hover:text-gray-900 focus:outline-none" onclick="apx.adminskull.toggleheadnav('notifications')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 17h5l-1.405-1.405C18.79 14.79 19 13.895 19 13V10c0-3.866-3.134-7-7-7S5 6.134 5 10v3c0 .895.21 1.79.595 2.595L4 17h5m6 0v2a2 2 0 11-4 0v-2m4 0H9" />
|
||||||
|
</svg>
|
||||||
|
<span class="absolute top-0 right-0 inline-block w-3 h-3 bg-{{notifcolor}}-500 rounded-full text-white text-xs text-center">{{{notifnumber}}}</span>
|
||||||
|
</button>
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<div class="dropdown notifications hidden absolute left-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-50">
|
||||||
|
<ul class="py-2">
|
||||||
|
{{#notif}}
|
||||||
|
<li>
|
||||||
|
<a href="#" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">{{text}}<br>{{from}} -- {{date}}</a>
|
||||||
|
</li>
|
||||||
|
{{/notif}}
|
||||||
|
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">En voir plus...</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/notifnumber}}
|
||||||
|
<!-- Message Icon -->
|
||||||
|
{{#msgnumber}}
|
||||||
|
<div class="relative">
|
||||||
|
<button class="text-gray-700 hover:text-gray-900 focus:outline-none" onclick="apx.adminskull.toggleheadnav('messages')">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M7 8h10M7 12h8m-8 4h8M5 21l3.386-2.258A8.976 8.976 0 0112 19c4.971 0 9-3.806 9-8.5S16.971 2 12 2 3 5.806 3 10.5c0 1.771.651 3.411 1.748 4.742L5 21z" />
|
||||||
|
</svg>
|
||||||
|
<span class="absolute top-0 right-0 inline-block w-3 h-3 bg-{{msgcolor}}-500 rounded-full text-white text-xs text-center">{{{msgnumber}}}</span>
|
||||||
|
</button>
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<div class="dropdown messages hidden absolute left-0 mt-2 w-48 bg-white rounded-lg shadow-lg z-50">
|
||||||
|
<ul class="py-2">
|
||||||
|
{{#msg}}
|
||||||
|
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">{{{text}}}<br>{{from}} -- {{date}}</a></li>
|
||||||
|
{{/msg}}
|
||||||
|
<li><a href="#" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">Voir les discussions</a></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/msgnumber}}
|
||||||
|
<!-- Language Icon -->
|
||||||
|
<div class="relative">
|
||||||
|
<button class="text-gray-700 hover:text-gray-900 focus:outline-none" onclick="apx.adminskull.toggleheadnav('language')">
|
||||||
|
<span class="inline-block text-sm">🇬🇧</span>
|
||||||
|
</button>
|
||||||
|
<!-- Dropdown Menu -->
|
||||||
|
<div class="dropdown language hidden absolute right-0 mt-2 w-13 bg-white rounded-lg shadow-lg z-50">
|
||||||
|
<ul class="py-2">
|
||||||
|
<li>
|
||||||
|
<a href="admindata_en.html" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">
|
||||||
|
🇬🇧
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="admindata_fr.html" class="block px-4 py-2 text-gray-700 hover:bg-gray-100">
|
||||||
|
🇫🇷
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="relative flex flex-col items-center justify-center text-center">
|
||||||
|
<!-- Profile Avatar -->
|
||||||
|
<img src="{{{aliasimg}}}" class="rounded-full h-9 w-9" alt="{{alias}}">
|
||||||
|
<span class="text-sm mt-2">{{alias}}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
0
wco/adminskull/main_fr.mustache
Normal file
0
wco/adminskull/main_fr.mustache
Normal file
16
wco/adminskull/result_fr.mustache
Normal file
16
wco/adminskull/result_fr.mustache
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
|
||||||
|
{{#results}}
|
||||||
|
<div class="card bg-base-100 shadow-xl">
|
||||||
|
<!-- Thumbnail -->
|
||||||
|
<figure class="w-full h-48 overflow-hidden">
|
||||||
|
<img src="{{thumbnail}}" alt="{{title}}" class="w-full h-full object-cover" />
|
||||||
|
</figure>
|
||||||
|
|
||||||
|
<!-- Contenu (titre + description) -->
|
||||||
|
<div class="card-body">
|
||||||
|
<h2 class="card-title">{{title}}</h2>
|
||||||
|
<p>{{description}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{/results}}
|
||||||
|
</div>
|
68
wco/adminskull/verticalnav_fr.json
Normal file
68
wco/adminskull/verticalnav_fr.json
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
{
|
||||||
|
"sidebarmenutop": [
|
||||||
|
{
|
||||||
|
"profils": [
|
||||||
|
"pagans"
|
||||||
|
],
|
||||||
|
"title": "Articles",
|
||||||
|
"onclick": "apx.adminskull.show('articles','news')",
|
||||||
|
"submenu": [
|
||||||
|
{
|
||||||
|
"title": "Locaux",
|
||||||
|
"onclick": "apx.admindata.articles.show('localnews')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Rediger",
|
||||||
|
"onclick": "apx.admindata.articles.editor()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Mes articles",
|
||||||
|
"onclick": "apx.admindata.articles.show('myarticles')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profils": [
|
||||||
|
"pagans"
|
||||||
|
],
|
||||||
|
"title": "Messages",
|
||||||
|
"submenu": [
|
||||||
|
{
|
||||||
|
"title": "Discussions",
|
||||||
|
"onclick": "apx.admindata.messages.show('discussion')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Notification",
|
||||||
|
"onclick": "apx.admindata.messages.show('notification')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"profils": [
|
||||||
|
"major"
|
||||||
|
],
|
||||||
|
"title": "Admin apxtri",
|
||||||
|
"onclick": "apx.admindata.apxtri.show('dashboard')",
|
||||||
|
"submenu": [
|
||||||
|
{
|
||||||
|
"title": "Towns",
|
||||||
|
"onclick": "apx.admindata.apxtri.show('Town')"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Tribes",
|
||||||
|
"onclick": "apx.admindata.apxtri.show('Tribes')"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"sidebarmenubottom": [
|
||||||
|
{
|
||||||
|
"title": "Mon profil",
|
||||||
|
"onclick": "apx..()"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Log Out",
|
||||||
|
"onclick": "apx.admindata.logout()"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
25
wco/adminskull/verticalnav_fr.mustache
Normal file
25
wco/adminskull/verticalnav_fr.mustache
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
<!-- Menu Links Section -->
|
||||||
|
<ul class="flex-1 mt-4 space-y-1 px-2 text-sm overflow-y-auto">
|
||||||
|
{{#sidebarmenutop}}
|
||||||
|
<li class="px-4 py-2 hover:bg-gray-700 rounded-md">
|
||||||
|
<details>
|
||||||
|
<summary class="cursor-pointer hover:bg-blue-600">{{title}}</summary>
|
||||||
|
<ul class="pl-4 mt-2 space-y-2">
|
||||||
|
{{#submenu}}
|
||||||
|
<li class="px-4 py-2 cursor-pointer hover:bg-gray-600 rounded-md" onclick="{{{onclick}}}">
|
||||||
|
{{title}}
|
||||||
|
</li>
|
||||||
|
{{/submenu}}
|
||||||
|
</ul>
|
||||||
|
</details>
|
||||||
|
</li>
|
||||||
|
{{/sidebarmenutop}}
|
||||||
|
</ul>
|
||||||
|
<!-- Bottom Links Section -->
|
||||||
|
<ul class="mb-6 px-2 text-sm">
|
||||||
|
{{#sidebarmenubottom}}
|
||||||
|
<li class="px-4 py-2 cursor-pointer hover:bg-gray-700 rounded-md" onclick="{{onclick}}">
|
||||||
|
<span>{{{title}}}</span>
|
||||||
|
</li>
|
||||||
|
{{/sidebarmenubottom}}
|
||||||
|
</ul>
|
513
wco/apx/apx.js
Normal file
513
wco/apx/apx.js
Normal file
@@ -0,0 +1,513 @@
|
|||||||
|
/*eslint no-undef:0*/
|
||||||
|
/*eslint-env browser*/
|
||||||
|
|
||||||
|
"use strict";
|
||||||
|
var apx = apx || {};
|
||||||
|
|
||||||
|
/**************************************************************************
|
||||||
|
* apx.js manage data to interact with an apxtri instance from a webpage *
|
||||||
|
* component can be add with name-wco
|
||||||
|
*************************************************************************
|
||||||
|
* This code is not minify and target to be understood by a maximum of
|
||||||
|
* curious people to audit and give any feedback to support@ndda.fr
|
||||||
|
*
|
||||||
|
* To audit it in a browser like chrome:
|
||||||
|
* - open web developpement (F12)
|
||||||
|
* - Menu Sources: apx.js , see below for more information
|
||||||
|
* - : apxid.js, this is the authentification module that works as a microservice
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* Main principle:
|
||||||
|
* - Usage of localStorage to store any usefull data and save it between session
|
||||||
|
* - All data and template can be download from an apxtri
|
||||||
|
* get /api/apxtri/wwws/updatelocaldb{ano}/{tribe}/{xapp}/{pagename}/{version}
|
||||||
|
* ano = empty if authenticated anonymous if not
|
||||||
|
* tribe = the tribe where tha xapp is strore
|
||||||
|
* xapp = the folder name in /tribe/xapp/
|
||||||
|
* pagename = the name of the page without _xx (language)
|
||||||
|
* version = 0 or the version currently store (version is to manage cach)
|
||||||
|
*
|
||||||
|
* ------------------
|
||||||
|
* ? State management
|
||||||
|
* ------------------
|
||||||
|
* html page must have apxtri with at least headers key
|
||||||
|
*
|
||||||
|
<script>
|
||||||
|
const apxtri={
|
||||||
|
version:0, // 0 mean it is a first visit if you force to 0 then it will reload all app data
|
||||||
|
pagename:"pagename", // without _lg.html ex:pageadmin
|
||||||
|
pageauth:"apxid" local page with authentification without _lg,
|
||||||
|
headers:{
|
||||||
|
xalias:"anonymous",
|
||||||
|
xhash:"anonymous",
|
||||||
|
xtribe:"tribeId",
|
||||||
|
xapp:"app name",
|
||||||
|
xlang:"en",
|
||||||
|
xdays:0,
|
||||||
|
xuuid:"0",
|
||||||
|
xtrkversion:1}
|
||||||
|
}
|
||||||
|
wco:{}
|
||||||
|
//specific init data for this webpage
|
||||||
|
} ;
|
||||||
|
</script>
|
||||||
|
* ++++++++
|
||||||
|
* ? apx.ready(callback) await DOM is ready equivalent of jquery Document.ready()
|
||||||
|
* +++++++++
|
||||||
|
* ? apx.readyafterupdate(callback) allow to add function callback that will be load after apx.ready to add a function to execute apx.readyafterupdate(functname) without () after functname
|
||||||
|
* +++++++++
|
||||||
|
* ? apx.listendatawco(newpropertie) allow to store data in apx.data.wco and to listen any change to update HTML DOM content <tag data-wco="propertie"></tag> if apx.data.wco.propertie value change then it change every where data-wco=propertie exist innerHTML, textContent, class, ...value
|
||||||
|
* to add a new propertie to listen just add it apx.data.wco.propertie={innerHTML:"<p>test</p>"} and run apx.listendatawco(propertie)
|
||||||
|
* +++++++++
|
||||||
|
* ? apx.notification(selector, data) insert in tag selector an apx return call into data{status:xxx,ref:"Ref",msg:"key",data:{}}, it use apx.data.ref[data.ref].msg as template and render it with data
|
||||||
|
* +++++++++
|
||||||
|
* ? apx.save() save apx.data as {xapp} value in localStorage to be able for the same domain to share data
|
||||||
|
* +++++++++
|
||||||
|
* ? apx.update() :
|
||||||
|
* Run at least once after loading a pagename, what it does:
|
||||||
|
* - url/page.html?k1=v1&k2=v2#h1=v4&h2=v5 => store in apx.pagecontext = { search: {k1:v1,k2:v2}, hash: {h1:v4,h2:v5} };
|
||||||
|
* - updatelocaldb from api to localstorage name xapp (options / ref / schema / tpl /tpldata) store in {tribe}/wwws/{xapp}.json with key {pagename}
|
||||||
|
* - run apx.listendatawco() to allow any apx.data.wco variable to update DOM
|
||||||
|
* - run any function that were store in apx.afterupdate.
|
||||||
|
* - run apx.lazyload() to load image in background (this is to decrease time loading for search engine)
|
||||||
|
* ++++++++++
|
||||||
|
* */
|
||||||
|
|
||||||
|
apx.ready = (callback) => {
|
||||||
|
if (!callback) {
|
||||||
|
alert(
|
||||||
|
"You have an unknown callback apx.ready(callback), you need to order your code callback = ()=>{} then apx.ready(callback), boring but js rules ;-)"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (document.readyState != "loading") callback();
|
||||||
|
// modern browsers
|
||||||
|
else if (document.addEventListener)
|
||||||
|
document.addEventListener("DOMContentLoaded", callback);
|
||||||
|
// IE <= 8
|
||||||
|
else
|
||||||
|
document.attachEvent("onreadystatechange", function () {
|
||||||
|
if (document.readyState == "complete") callback();
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.readyafterupdate = (callback) => {
|
||||||
|
if (!apx.afterupdate) apx.afterupdate = [];
|
||||||
|
apx.afterupdate.push(callback);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.lazyload = () => {
|
||||||
|
document.querySelectorAll("img[data-lazysrc]").forEach((e) => {
|
||||||
|
// in src/page.html: src contain original img and data-lazysrc='true'
|
||||||
|
// in dist/page.html src is removed img src is in data-lazysrc=newimage.webp or svg
|
||||||
|
let src = e.getAttribute("src")
|
||||||
|
? e.getAttribute("src")
|
||||||
|
: e.getAttribute("data-lazysrc");
|
||||||
|
if (e.getAttribute("data-trksrckey")) {
|
||||||
|
src = `/trk/${src}?alias=${apx.data.headers.xalias}&uuid=${
|
||||||
|
apx.data.headers.xuuid
|
||||||
|
}&srckey=${e.getAttribute("data-trksrckey")}&version=${
|
||||||
|
apx.data.headers.xtrkversion
|
||||||
|
}&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
|
||||||
|
apx.data.headers.xlang
|
||||||
|
}`;
|
||||||
|
}
|
||||||
|
e.setAttribute("src", src);
|
||||||
|
console.log("lazyload track:", src);
|
||||||
|
e.removeAttribute("data-lazysrc");
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[data-lazybgsrc]").forEach((e) => {
|
||||||
|
e.style.backgroundImage = `url(${e.getAttribute("src")})`;
|
||||||
|
e.removeAttribute("data-lazybgsrc");
|
||||||
|
});
|
||||||
|
document.querySelectorAll("a[data-trksrckey]").forEach((e) => {
|
||||||
|
let urldestin = e.getAttribute("href");
|
||||||
|
if (
|
||||||
|
urldestin.substring(0, 4) != "http" &&
|
||||||
|
urldestin.substring(0, 1) != "/"
|
||||||
|
) {
|
||||||
|
urldestin = "/" + urldestin;
|
||||||
|
}
|
||||||
|
const hreftrack = `/trk/redirect?alias=${apx.data.headers.xalias}&uuid=${
|
||||||
|
apx.data.headers.xuuid
|
||||||
|
}&srckey=${e.getAttribute("data-trksrckey")}&version=${
|
||||||
|
apx.data.headers.xtrkversion
|
||||||
|
}&consentcookie=${localStorage.getItem("consentcookie")}&lg=${
|
||||||
|
apx.data.headers.xlang
|
||||||
|
}&url=${urldestin}`;
|
||||||
|
console.log("href track:", hreftrack);
|
||||||
|
e.setAttribute("href", hreftrack);
|
||||||
|
e.removeAttribute("data-trksrckey");
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.notification = (selector, data, clearbefore) => {
|
||||||
|
/**
|
||||||
|
* @selector a text to use querySelector() in document
|
||||||
|
* @data apxtri return from any request {status:200,ref:"",msg:"key", data }
|
||||||
|
* if {status,multimsg:[{ref,msg,data}]} then a multi feedback are in
|
||||||
|
* @clearbefore boolean if true then it remove the previous message
|
||||||
|
* @return update the dom selctor with the relevant render message in the relevant language
|
||||||
|
*/
|
||||||
|
console.log("notification of ", data);
|
||||||
|
const el = document.querySelector(selector);
|
||||||
|
if (!el) {
|
||||||
|
console.log(
|
||||||
|
`WARNING !!! check apx.notification selector:${selector} does not exist in this page`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (clearbefore) el.innerHTML = "";
|
||||||
|
const multimsg = data.multimsg ? data.multimsg : [data];
|
||||||
|
multimsg.forEach((info) => {
|
||||||
|
if (!apx.data.ref[info.ref]) {
|
||||||
|
console.log(
|
||||||
|
`check apx.data.ref, ${info.ref} does not exist in this page, add it `
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
} else if (!apx.data.ref[info.ref][info.msg]) {
|
||||||
|
console.log(
|
||||||
|
`check apx.data.ref.${info.ref} does not contain ${info.msg} update /schema/lg or /model/lg`
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
el.innerHTML +=
|
||||||
|
" " + Mustache.render(apx.data.ref[info.ref][info.msg], info.data);
|
||||||
|
if (data.status == 200) {
|
||||||
|
el.classList.remove("text-red");
|
||||||
|
el.classList.add("text-green");
|
||||||
|
} else {
|
||||||
|
el.classList.add("text-red");
|
||||||
|
el.classList.remove("text-green");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.listendatawco = (newpropertie) => {
|
||||||
|
// listen any change in apx.wco.newpropertie and update interface with this new value
|
||||||
|
// < data-wco="propertie of apx.wco"> is updated with content text, html any attribute in this new value
|
||||||
|
// to init run apx.listendatawco() this is done by apx.update()
|
||||||
|
// to dynamicaly add a new propertie to listen just run apx.listendatawco(propertietoadd);
|
||||||
|
// Then manage your data by modifying apx.wco and it will be update anywhere it use in the webpage
|
||||||
|
//example:
|
||||||
|
// <img data-wco="logodetoto" src="urltoimage" "alt">
|
||||||
|
// apx.wco.logodetoto={src:"newurltoimage",alt:"newalt"} then it will change every where data-wco=logodetoto
|
||||||
|
//
|
||||||
|
// <p data-wco="claim">Blabla</p>
|
||||||
|
// apx.wco.claim={html:"newblabla"}
|
||||||
|
console.log("From apx.data.wco:", apx.data.wco);
|
||||||
|
if (!apx.wco) apx.wco = {};
|
||||||
|
console.log(
|
||||||
|
"wco dynamic into the webpage",
|
||||||
|
apx.wco,
|
||||||
|
"no propertie to add:",
|
||||||
|
!newpropertie
|
||||||
|
);
|
||||||
|
if (!apx.data.wco || Object.keys(apx.data.wco).length == 0) return false;
|
||||||
|
newpropertie = !newpropertie ? Object.keys(apx.data.wco) : [newpropertie];
|
||||||
|
console.log("listen apx.data.wco properties:", newpropertie);
|
||||||
|
newpropertie.forEach((p) => {
|
||||||
|
const actionprop = (val, elt) => {
|
||||||
|
if (val.innerHTML) elt.innerHTML = val.innerHTML;
|
||||||
|
if (val.textContent) elt.textContent = val.textContent;
|
||||||
|
for (const h in ["innerHTML", "textContent"]) {
|
||||||
|
if (val[h]) elt[h] = val[h];
|
||||||
|
}
|
||||||
|
for (const a in ["src", "alt", "placeholder", "class", "href"]) {
|
||||||
|
if (val[a]) elt.setAttribute(a, val[a]);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const elements = document.querySelectorAll(`[data-wco='${p}']`);
|
||||||
|
elements.forEach((e) => actionprop(apx.data.wco[p], e));
|
||||||
|
//console.log(p, Object.hasOwnProperty(apx.wco));
|
||||||
|
if (Object.hasOwnProperty(apx.wco)) {
|
||||||
|
Object.defineProperty(apx.wco, p, {
|
||||||
|
set: (newv) => {
|
||||||
|
this[p] = newv;
|
||||||
|
elements.forEach((e) => actionprop(newv, e));
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.wcoobserver = () => {
|
||||||
|
/**
|
||||||
|
* wco web component observer if apxtri.wcoobserver==true,
|
||||||
|
* Observe existing or creation of any element in DOM with <div wco-name="wconame" id="uniqueid" wco-YYY="aa"></div>
|
||||||
|
* if create or if any wco-YYY value change it runs apx[wconame].loadwco(id,ctx) where ctx={YYY:aa}
|
||||||
|
* Example:
|
||||||
|
* <div id="monComponent" wco-name="monComposant" wco-screen="dashboard" wco-theme="dark" wco-ref="123"></div>
|
||||||
|
* if innerHTML this OR if any wco-YYYY change =>
|
||||||
|
* run apx.monComposant.loadwco('monComponent',
|
||||||
|
* {'wco-name': 'monComposant',
|
||||||
|
* 'wco-screen': 'dashboard',
|
||||||
|
* 'wco-theme': 'dark',
|
||||||
|
* 'wco-ref': '123'});
|
||||||
|
* Used with wco component manage in apx to communicate between autonomous component to reload content if contexte change.
|
||||||
|
* Typical example of a wco menu that will load another wco content if not exist and will change a wco-screen to change the content of the wco content
|
||||||
|
*/
|
||||||
|
console.log("listen wcoobserver");
|
||||||
|
const processElement = (element) => {
|
||||||
|
if (element.nodeType === 1 && element.tagName === "DIV") {
|
||||||
|
if (element.id && element.hasAttribute("wco-name")) {
|
||||||
|
const ctx = {};
|
||||||
|
for (const attr of element.attributes) {
|
||||||
|
if (attr.name.startsWith("wco-")) {
|
||||||
|
ctx[attr.name.slice(4)] = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wcoName = element.getAttribute("wco-name");
|
||||||
|
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||||
|
apx[wcoName].loadwco(element.id, ctx);
|
||||||
|
} else {
|
||||||
|
console.log(`ERROR: apx.${wcoName}.loadwco() not found`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Process children recursively
|
||||||
|
if (element.children) {
|
||||||
|
Array.from(element.children).forEach(processElement);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const observer = new MutationObserver((mutations) => {
|
||||||
|
mutations.forEach((mutation) => {
|
||||||
|
if (mutation.type === "childList") {
|
||||||
|
mutation.addedNodes.forEach((node) => {
|
||||||
|
processElement(node);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
mutation.type === "attributes" &&
|
||||||
|
mutation.attributeName.startsWith("wco-")
|
||||||
|
) {
|
||||||
|
const element = mutation.target;
|
||||||
|
if (element.id && element.hasAttribute("wco-name")) {
|
||||||
|
const ctx = {};
|
||||||
|
for (const attr of element.attributes) {
|
||||||
|
if (attr.name.startsWith("wco-")) {
|
||||||
|
ctx[attr.name.slice(4)] = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const wcoName = element.getAttribute("wco-name");
|
||||||
|
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||||
|
apx[wcoName].loadwco(element.id, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
observer.observe(document.body, {
|
||||||
|
childList: true,
|
||||||
|
subtree: true,
|
||||||
|
attributes: true, // <-- active la détection de changements d'attributs
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pour les éléments déjà présents au chargement
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
document.querySelectorAll("div[wco-name]").forEach((element) => {
|
||||||
|
if (element.id) {
|
||||||
|
const ctx = {};
|
||||||
|
for (const attr of element.attributes) {
|
||||||
|
if (attr.name.startsWith("wco-")) {
|
||||||
|
ctx[attr.name.slice(4)] = attr.value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const wcoName = element.getAttribute("wco-name");
|
||||||
|
console.log(`load observer of wco for ${wcoName} in id=${element.id}`);
|
||||||
|
if (apx[wcoName] && typeof apx[wcoName].loadwco === "function") {
|
||||||
|
apx[wcoName].loadwco(element.id, ctx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//load existing wco-name in the html page to initiate the wco process
|
||||||
|
// it read and write the wco-name that will trig the observer as a change
|
||||||
|
document.querySelectorAll("div[wco-name]").forEach((e) => {
|
||||||
|
const wconame = e.getAttribute("wco-name");
|
||||||
|
console.log("load wco-name:", wconame);
|
||||||
|
e.setAttribute("wco-name", wconame);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// State management
|
||||||
|
apx.save = () => {
|
||||||
|
localStorage.setItem(apx.data.headers.xapp, JSON.stringify(apx.data));
|
||||||
|
};
|
||||||
|
apx.update = async () => {
|
||||||
|
if (!apxtri) {
|
||||||
|
console.log(
|
||||||
|
'Please add to the html page header, this line const apxtri = { headers: { xtrkversion: 1, xtribe: "smatchit", xapp: "pwa", xlang: "fr", xalias: "anonymous", xhash: "anonymous", xdays: 0} ,pagename:"apxid"} '
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
//if (apxtri.forcereload){localStorage.setItem("forcereload",true)};
|
||||||
|
if (document.querySelector("html").getAttribute("lang")) {
|
||||||
|
apxtri.headers.xlang = document.querySelector("html").getAttribute("lang");
|
||||||
|
}
|
||||||
|
//alert(localStorage.getItem(apxtri.headers.xapp))
|
||||||
|
if (localStorage.getItem(apxtri.headers.xapp)) {
|
||||||
|
apx.data = JSON.parse(localStorage.getItem(apxtri.headers.xapp));
|
||||||
|
//update with current pagename and eventualy pageauth
|
||||||
|
apx.data.pagename = apxtri.pagename;
|
||||||
|
if (apxtri.pageauth) apx.data.pageauth = apxtri.pageauth;
|
||||||
|
// check localstorage in line with current webpage
|
||||||
|
if (
|
||||||
|
apx.data.headers.xtribe != apxtri.headers.xtribe ||
|
||||||
|
apx.data.headers.xlang != apxtri.headers.xlang ||
|
||||||
|
apx.data.headers.xtrkversion != apxtri.headers.xtrkversion
|
||||||
|
) {
|
||||||
|
// if an app change of tribe
|
||||||
|
localStorage.removeItem(apxtri.headers.xapp);
|
||||||
|
delete apx.data;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!apx.data) {
|
||||||
|
console.log("init or reinit apx.data");
|
||||||
|
apx.data = apxtri;
|
||||||
|
}
|
||||||
|
apx.pagecontext = { search: {}, hash: {} };
|
||||||
|
if (window.location.hash != "") {
|
||||||
|
window.location.hash
|
||||||
|
.slice(1)
|
||||||
|
.split("&")
|
||||||
|
.forEach((kv) => {
|
||||||
|
const keyval = kv.split("=");
|
||||||
|
apx.pagecontext.hash[keyval[0]] = keyval[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (window.location.search != "") {
|
||||||
|
window.location.search
|
||||||
|
.slice(1)
|
||||||
|
.split("&")
|
||||||
|
.forEach((kv) => {
|
||||||
|
const keyval = kv.split("=");
|
||||||
|
apx.pagecontext.hash[keyval[0]] = keyval[1];
|
||||||
|
});
|
||||||
|
}
|
||||||
|
console.log("apx.pagecontext:", apx.pagecontext);
|
||||||
|
|
||||||
|
// Set authenticate parameter if in pagecontext and redirect to the requested url
|
||||||
|
console.log(
|
||||||
|
apx.pagecontext.hash.xdays,
|
||||||
|
apx.pagecontext.hash.xprofils,
|
||||||
|
apx.pagecontext.hash.xtribe,
|
||||||
|
dayjs(apx.pagecontext.hash.xdays),
|
||||||
|
dayjs(apx.pagecontext.hash.xdays).diff(dayjs(), "hours") < 25,
|
||||||
|
apx.pagecontext.hash.xhash
|
||||||
|
);
|
||||||
|
if (
|
||||||
|
apx.pagecontext.hash.xhash &&
|
||||||
|
apx.pagecontext.hash.xdays &&
|
||||||
|
apx.pagecontext.hash.xprofils &&
|
||||||
|
apx.pagecontext.hash.xtribe &&
|
||||||
|
dayjs(apx.pagecontext.hash.xdays) &&
|
||||||
|
dayjs(apx.pagecontext.hash.xdays).diff(dayjs(), "hours") < 25
|
||||||
|
) {
|
||||||
|
//Means this page is called from an external auth app
|
||||||
|
let headervalid = true;
|
||||||
|
const headerkey = [
|
||||||
|
"xalias",
|
||||||
|
"xhash",
|
||||||
|
"xdays",
|
||||||
|
"xprofils",
|
||||||
|
"xtribe",
|
||||||
|
"xlang",
|
||||||
|
];
|
||||||
|
headerkey.forEach((h) => {
|
||||||
|
if (apx.pagecontext.hash[h]) {
|
||||||
|
apx.data.headers[h] = (h==="xprofils")? apx.pagecontext.hash[h].split(","):apx.pagecontext.hash[h];
|
||||||
|
} else {
|
||||||
|
headervalid = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
console.log(headervalid, apx.data.headers);
|
||||||
|
if (headervalid) {
|
||||||
|
apx.save();
|
||||||
|
if (apx.pagecontext.hash.url) {
|
||||||
|
window.location.href = apx.pagecontext.hash.url;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log("Your try to access a page failled with ", apx.pagecontext);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
apx.data.allowedprofils &&
|
||||||
|
!apx.data.allowedprofils.includes("anonymous") &&
|
||||||
|
apx.data.pagename !== apx.data.pageauth
|
||||||
|
) {
|
||||||
|
const profilintersect = apx.data.allowedprofils.filter((x) =>
|
||||||
|
apx.data.headers.xprofils.includes(x)
|
||||||
|
);
|
||||||
|
console.log("profils authorized:", profilintersect);
|
||||||
|
if (profilintersect.length == 0) {
|
||||||
|
alert(apx.data.ref.Middlewares.notallowtoaccess);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (dayjs().valueOf() - apx.data.headers.xdays > 86400000) {
|
||||||
|
// need to refresh authentification if possible by opening the pageauth with url context
|
||||||
|
// the pageauth redirect to this current page after authentification, if not then wait credential
|
||||||
|
document.location.href = `/${apx.data.pageauth}_${apx.data.headers.xlang}.html#url=${apx.data.pagename}_${apx.data.headers.xlang}.html`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log("authorized to access");
|
||||||
|
/* à voir si utile redirect to authentification page pageauth with a redirection if authentify to the pagename (check if /src/ then add it)
|
||||||
|
window.location.href = `${apxtri.pageauth}_${
|
||||||
|
apxtri.headers.xlang
|
||||||
|
}.html?url=${window.location.href.includes("/src/") ? "/src/" : ""}${
|
||||||
|
apxtri.pagename
|
||||||
|
}_${apxtri.headers.xlang}.html`;
|
||||||
|
*/
|
||||||
|
////////////////////////////////////////////
|
||||||
|
apx.data.version = 0; //this force an update to be removed in production
|
||||||
|
///////////////////////////////////////////
|
||||||
|
const ano = apx.data.headers.xalias == "anonymous" ? "anonymous" : "";
|
||||||
|
const initdb = `/api/apxtri/wwws/updatelocaldb${ano}/${apx.data.headers.xtribe}/${apx.data.headers.xapp}/${apx.data.pagename}/${apx.data.version}`;
|
||||||
|
let initset = {};
|
||||||
|
try {
|
||||||
|
initset = await axios.get(initdb, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
timeout: 2000,
|
||||||
|
});
|
||||||
|
} catch (err) {
|
||||||
|
console.log(err);
|
||||||
|
initset = { data: { msg: "unavailableAPI" } };
|
||||||
|
}
|
||||||
|
console.log("recupe inidb for ", initdb, initset);
|
||||||
|
if (initset.data.msg == "forbidenaccess") {
|
||||||
|
alert(apx.data.ref.Middlewares.notallowtoaccess);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (initset.data.msg == "unavailableAPI") {
|
||||||
|
console.log("Your api endpoint is down check your hosted server");
|
||||||
|
//try again in 30 seconds
|
||||||
|
setTimeout(apx.update, 30000);
|
||||||
|
}
|
||||||
|
if (initset.data.msg == "datamodelupdate") {
|
||||||
|
// mise à jour local
|
||||||
|
/*if (initset.data.data.wco) {
|
||||||
|
|
||||||
|
console.log("WARNING!!, local apxtri.wco was erase by updatelocaldb.wco");
|
||||||
|
}*/
|
||||||
|
Object.keys(initset.data.data).forEach((k) => {
|
||||||
|
if (k != "headers") {
|
||||||
|
apx.data[k] = initset.data.data[k];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
/* if (apx.data.confpage.wco && !apx.data.wco){
|
||||||
|
console.log("update apx.data.wco with localdb cause does not exist")
|
||||||
|
apx.data.wco=apx.data.confpage.wco;
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
console.log("local update done");
|
||||||
|
apx.save();
|
||||||
|
}
|
||||||
|
|
||||||
|
apx.listendatawco(); // listen any data-wco tag and update it when apxdatawco propertie change
|
||||||
|
if (apxtri.wcoobserver) apx.wcoobserver();
|
||||||
|
if (apx.afterupdate) apx.afterupdate.forEach((cb) => cb()); //run all function store in apx.afterupdate in order
|
||||||
|
apx.lazyload(); //reload image or any media that takes time to load to improve engine search
|
||||||
|
apx.save(); //store in local the modification
|
||||||
|
};
|
||||||
|
apx.ready(apx.update); //2nd param optional=> true mean does not wait same if apx.lock is set
|
698
wco/apxauth/apxauth.js
Normal file
698
wco/apxauth/apxauth.js
Normal file
@@ -0,0 +1,698 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.apxauth = {};
|
||||||
|
apx.apxauth.loadwco = async (id, ctx) => {
|
||||||
|
// check if not authenticate, do nothing cause by default screensignin and wait authentification
|
||||||
|
// if authenticate, if url xhash then redirect if no url then change wco-link=screenmytribes
|
||||||
|
// if (dayjs(apx.data.headers.xdays).diff(dayjs(), "hours") >= 24) apx.apxauth.checkisauth();
|
||||||
|
//load main.mustache of the component
|
||||||
|
//when wco-xxx change it run this function
|
||||||
|
console.log(`Load wconame:apxauth apx.apxauth.loadwco with id:${id} and ctx: ${JSON.stringify(ctx)}`);
|
||||||
|
const tpldataname = `${apx.data.pagename}_${id}_apxauth`;
|
||||||
|
const apxauthid = document.getElementById(id)
|
||||||
|
const data = apx.apxauth.getdata(id, ctx);
|
||||||
|
if (apxauthid.innerHTML.trim() === "") {
|
||||||
|
apxauthid.innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl.apxauthmain,
|
||||||
|
data
|
||||||
|
);
|
||||||
|
}
|
||||||
|
apxauthid.querySelector(`.screenaction`).innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl[`apxauthscreen${ctx.link}`],
|
||||||
|
data
|
||||||
|
);
|
||||||
|
apxauthid.querySelector(`.msginfo`).innerHTML = "";
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.apxauth.getdata = (id, ctx) => {
|
||||||
|
const tpldataname = `${apx.data.pagename}_${id}_apxauth`;
|
||||||
|
const data = JSON.parse(JSON.stringify(apx.data.tpldata[tpldataname]));
|
||||||
|
data.id = id;
|
||||||
|
data.xalias = apx.data.headers.xalias;
|
||||||
|
data.xtribe = apx.data.headers.xtribe;
|
||||||
|
data.emailssuport = apx.data.appdata.emailsupport;
|
||||||
|
switch (ctx.link) {
|
||||||
|
case "logout":
|
||||||
|
if (!data.profils) data.profils = [];
|
||||||
|
apx.data.headers.xprofils.forEach((p) => {
|
||||||
|
if (!["anonymous", "pagans", "persons"].includes(p)) {
|
||||||
|
data.profils.push(apx.data.options.profil.itms[p].title);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
data.noprofils = data.profils.length == 0;
|
||||||
|
data.member = apx.data.headers.xprofils.includes("persons");
|
||||||
|
data.websites = apx.data.appdata.websites;
|
||||||
|
// get tribes activities
|
||||||
|
/*["", "https://wall-ants.ndda.fr"];
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/tribes/activities`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {})
|
||||||
|
.catch((err) => {});
|
||||||
|
*/
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
console.log("data for tpl:", data);
|
||||||
|
return data
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.apxauth.redirecturlwithauth = (url, tribe, webapp, newwindow, windowname = '_blank') => {
|
||||||
|
url = url.replace(/_[a-zA-Z0-9]{2}\.html/, `_${apx.data.headers.xlang}.html`)
|
||||||
|
url += `?xtribe=${tribe}&xapp=${webapp}&xalias=${apx.data.headers.xalias}`
|
||||||
|
url += `&xdays=${apx.data.headers.xdays}&xhash=${apx.data.headers.xhash}`
|
||||||
|
url += `&xprofils=${apx.data.headers.xprofils.join(',')}`
|
||||||
|
url += `&xtrkversion=${apx.data.headers.xtrkversion}&xuuid=${apx.data.headers.xuuid}`
|
||||||
|
if (newwindow) {
|
||||||
|
try {
|
||||||
|
const newwin = window.open(url, windowname)
|
||||||
|
if (newwin === null || typeof newwin === 'undefined') {
|
||||||
|
console.warn("L'ouverture de la fenêtre a été bloquée par un bloqueur de pop-up.");
|
||||||
|
// Vous pouvez informer l'utilisateur ici qu'il doit désactiver son bloqueur de pop-up
|
||||||
|
alert("Votre navigateur a bloqué l'ouverture d'un nouvel onglet. Veuillez autoriser les pop-ups pour ce site.");
|
||||||
|
} else {
|
||||||
|
// Optionnel: Mettre le focus sur la nouvelle fenêtre/onglet
|
||||||
|
newwin.focus();
|
||||||
|
}
|
||||||
|
return newwin;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Une erreur est survenue lors de l'ouverture de l'onglet :", error);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* logout
|
||||||
|
* Clean any private key into memory of this app and in the backend
|
||||||
|
*/
|
||||||
|
apx.apxauth.logout = () => {
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/pagans/logout`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
console.log("logout", rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("Erreur logout check:", err);
|
||||||
|
});
|
||||||
|
apx.data = apxtri;
|
||||||
|
apx.save();
|
||||||
|
if (apx.pagecontext.hash.url) {
|
||||||
|
window.location.href = apx.pagecontext.hash.url;
|
||||||
|
} else {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.apxauth.setheadersauth = async (
|
||||||
|
alias,
|
||||||
|
passphrase,
|
||||||
|
publickey,
|
||||||
|
privatekey,
|
||||||
|
rememberme
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Set header with relevant authentification data
|
||||||
|
* @return {status=200 if apx.data.headers and apx.data.auth properly set}
|
||||||
|
* {status: 406 or 500 in case issue}
|
||||||
|
*/
|
||||||
|
//console.log(alias, passphrase, publickey, privatekey);
|
||||||
|
if (
|
||||||
|
alias.length < 3 ||
|
||||||
|
publickey.length < 200 ||
|
||||||
|
(privatekey && privatekey.lengtht < 200)
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
status: 406,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "aliasorprivkeytooshort",
|
||||||
|
data: {},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (!passphrase) passphrase = "";
|
||||||
|
if (rememberme) {
|
||||||
|
apx.data.auth = {
|
||||||
|
alias: alias,
|
||||||
|
publickey: publickey,
|
||||||
|
privatekey: privatekey,
|
||||||
|
passphrase: passphrase,
|
||||||
|
};
|
||||||
|
} else if (apx.data.auth) {
|
||||||
|
delete apx.data.auth;
|
||||||
|
apx.save();
|
||||||
|
}
|
||||||
|
apx.data.headers.xalias = alias;
|
||||||
|
apx.data.headers.xdays = dayjs().valueOf();
|
||||||
|
const msg = `${alias}_${apx.data.headers.xdays}`;
|
||||||
|
//console.log("pvk", privatekey);
|
||||||
|
try {
|
||||||
|
apx.data.headers.xhash = await apx.apxauth.clearmsgSignature(
|
||||||
|
publickey,
|
||||||
|
privatekey,
|
||||||
|
passphrase,
|
||||||
|
msg
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return {
|
||||||
|
status: 500,
|
||||||
|
ref: "Middlewares",
|
||||||
|
msg: "unconsistentpgp",
|
||||||
|
data: { err: err },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
apx.save();
|
||||||
|
console.log("xhash set with:", apx.data.headers.xhash);
|
||||||
|
return { status: 200 };
|
||||||
|
};
|
||||||
|
apx.apxauth.authentifyme = async (
|
||||||
|
id,
|
||||||
|
alias,
|
||||||
|
passphrase,
|
||||||
|
privatekey,
|
||||||
|
rememberme
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Set apx.data.auth with pub, priv, passphrase alias that allow authentification
|
||||||
|
* set headers with xdays (timestamp) and xhash of message: {alias}_{timestamp} generate with pub & priv key
|
||||||
|
*
|
||||||
|
* @Param {key} publickeycreate optional when alias does not exist
|
||||||
|
*/
|
||||||
|
//console.log(alias, passphrase);
|
||||||
|
//console.log(privatekey);
|
||||||
|
//clean previous answer if exist
|
||||||
|
|
||||||
|
const idparent=document.getElementById(id).parentElement?.closest('[wco-name]').getAttribute('id')
|
||||||
|
document.querySelector(`#${id} .msginfo`).innerHTML = "";
|
||||||
|
if (alias.length < 3 || privatekey.length < 200) {
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 500,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "aliasorprivkeytooshort",
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
console.log(`get /api/apxtri/pagans/alias/${alias}`);
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/pagans/alias/${alias}`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then(async (rep) => {
|
||||||
|
//console.log(rep.data);
|
||||||
|
const setheaders = await apx.apxauth.setheadersauth(
|
||||||
|
alias,
|
||||||
|
passphrase,
|
||||||
|
rep.data.data.publickey,
|
||||||
|
privatekey,
|
||||||
|
rememberme
|
||||||
|
);
|
||||||
|
if (setheaders.status != 200) {
|
||||||
|
apx.notification(`#${id} .msginfo`, setheaders);
|
||||||
|
} else {
|
||||||
|
console.log("SetheadersOK");
|
||||||
|
console.log(`/api/apxtri/pagans/isauth`);
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/pagans/isauth`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
// Authenticate then store profils in header
|
||||||
|
apx.data.headers.xprofils = rep.data.data.xprofils;
|
||||||
|
apx.save();
|
||||||
|
// if this page is call with apxid_fr.html?url=httpsxxx then it redirect to this page.
|
||||||
|
//alert(`${window.location.href.includes("/src/")?"/src/":""}${apx.pagecontext.hash.url}`)
|
||||||
|
if (apx.pagecontext.hash.url) {
|
||||||
|
window.location.href = `${apx.pagecontext.hash.url}`;
|
||||||
|
} else {
|
||||||
|
//location.reload();
|
||||||
|
document.getElementById(idparent).setAttribute('wco-link','mytribes');
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("Not authentify:", err);
|
||||||
|
delete apx.data.auth;
|
||||||
|
apx.save();
|
||||||
|
document.getElementById(idparent).setAttribute("wco-link", "signin")
|
||||||
|
if (err.response) {
|
||||||
|
apx.notification(`#${id} .msginfo`, err.response.data);
|
||||||
|
} else if (err.request) {
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 500,
|
||||||
|
ref: "Middlewares",
|
||||||
|
msg: "errrequest",
|
||||||
|
data: { err: err.request.response },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
//console.log(err.response);
|
||||||
|
//console.log(err.request);
|
||||||
|
console.log("checkalias:", err);
|
||||||
|
if (err.response && err.response.data.msg) {
|
||||||
|
//remove auth if not well created previously
|
||||||
|
//console.log(err.response.data.msg);
|
||||||
|
if (err.response.data.msg == "aliasdoesnotexist") {
|
||||||
|
delete apx.data.auth;
|
||||||
|
apx.save();
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 404,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "aliasdoesnotexist",
|
||||||
|
data: { alias },
|
||||||
|
});
|
||||||
|
//document.getElementById("inputaliasauth").value="";
|
||||||
|
//document.getElementById("inputpassphraseauth").value="";
|
||||||
|
//document.getElementById("privatekeyauth").value=""
|
||||||
|
//window.location.reload();
|
||||||
|
}
|
||||||
|
apx.notification(`#${id} .msginfo`, err.response.data);
|
||||||
|
} else {
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 500,
|
||||||
|
ref: "Middlewares",
|
||||||
|
msg: "errrequest",
|
||||||
|
data: { err },
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.apxauth.recoverykey = (id, aliasoremail) => {
|
||||||
|
if (aliasoremail.length < 3) {
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 406,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "recoveryemailnotfound",
|
||||||
|
data: { tribe: apx.data.headers.xtribe, search: aliasoremail },
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const recodata = { tribe: apx.data.headers.xtribe, search: aliasoremail };
|
||||||
|
recodata.emailalias = Checkjson.testformat(aliasoremail, "email")
|
||||||
|
? "email"
|
||||||
|
: "alias";
|
||||||
|
document.querySelector(`#${id} .msginfo`).innerHTML = "";
|
||||||
|
axios
|
||||||
|
.post(`/api/apxtri/pagans/keyrecovery`, recodata, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
rep.data.data.search = aliasoremail;
|
||||||
|
apx.notification(`#${id} .msginfo`, rep.data, true);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
//console.log("error:", err);
|
||||||
|
const dataerr =
|
||||||
|
err.response && err.response.data
|
||||||
|
? err.response.data
|
||||||
|
: { status: 500, ref: "Pagans", msg: "checkconsole", data: {} };
|
||||||
|
dataerr.data.search = aliasoremail;
|
||||||
|
apx.notification(`#${id} .msginfo`, dataerr, true);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.apxauth.generateKey = async (alias, passphrase) => {
|
||||||
|
/**
|
||||||
|
* @param {string} alias a unique alias that identify an identity
|
||||||
|
* @param {string} passphrase a string to cipher the publickey (can be empty, less secure but simpler)
|
||||||
|
* @return {publickey,privatekey} with userIds = [{alias}]
|
||||||
|
*/
|
||||||
|
const pgpparam = {
|
||||||
|
type: "ecc", // Type of the key, defaults to ECC
|
||||||
|
curve: "curve25519", // ECC curve name, defaults to curve25519
|
||||||
|
userIDs: [{ alias: alias }], // you can pass multiple user IDs
|
||||||
|
passphrase: passphrase, // protects the private key
|
||||||
|
format: "armored", // output key format, defaults to 'armored' (options: 'armored', 'binary' or 'object')
|
||||||
|
};
|
||||||
|
const { privateKey, publicKey } = await openpgp.generateKey(pgpparam);
|
||||||
|
// key start by '-----BEGIN PGP PRIVATE KEY BLOCK ... '
|
||||||
|
// get liste of alias:pubklickey await axios.get('api/v0/pagans')
|
||||||
|
// check alias does not exist
|
||||||
|
return { alias, privatekey: privateKey, publickey: publicKey };
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.apxauth.verifyKeys = async (
|
||||||
|
publicKeyArmored,
|
||||||
|
privateKeyArmored,
|
||||||
|
passphrase
|
||||||
|
) => {
|
||||||
|
try {
|
||||||
|
// Charger la clé publique
|
||||||
|
const publicKey = await openpgp.readKey({ armoredKey: publicKeyArmored });
|
||||||
|
|
||||||
|
// Charger la clé privée
|
||||||
|
const privateKey = await openpgp.decryptKey({
|
||||||
|
privateKey: await openpgp.readPrivateKey({
|
||||||
|
armoredKey: privateKeyArmored,
|
||||||
|
}),
|
||||||
|
passphrase: passphrase, // Passphrase de la clé privée (si nécessaire)
|
||||||
|
});
|
||||||
|
|
||||||
|
// Créer un message simple à signer
|
||||||
|
const message = await openpgp.createMessage({ text: "Test message" });
|
||||||
|
|
||||||
|
// Signer le message avec la clé privée
|
||||||
|
const signedMessage = await openpgp.sign({
|
||||||
|
message: message, // Message à signer
|
||||||
|
signingKeys: privateKey, // Clé privée pour signer
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vérifier la signature avec la clé publique
|
||||||
|
const verificationResult = await openpgp.verify({
|
||||||
|
message: await openpgp.readCleartextMessage({
|
||||||
|
cleartextMessage: signedMessage,
|
||||||
|
}),
|
||||||
|
verificationKeys: publicKey, // Clé publique pour vérifier
|
||||||
|
});
|
||||||
|
|
||||||
|
// Vérifier si la signature est valide
|
||||||
|
const { verified } = verificationResult.signatures[0];
|
||||||
|
await verified; // Resolve la promesse
|
||||||
|
|
||||||
|
console.log("Les clés correspondent et sont valides !");
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error("Erreur lors de la vérification des clés : ", error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.apxauth.testcreatekey = async (alias, passphrase) => {
|
||||||
|
const pgpparam = {
|
||||||
|
type: "ecc", // Type of the key, defaults to ECC
|
||||||
|
curve: "curve25519", // ECC curve name, defaults to curve25519
|
||||||
|
userIDs: [{ alias: alias }], // you can pass multiple user IDs
|
||||||
|
passphrase: passphrase, // protects the private key
|
||||||
|
format: "armored", // output key format, defaults to 'armored' (options: 'armored', 'binary' or 'object')
|
||||||
|
};
|
||||||
|
const { privateKey, publicKey } = await openpgp.generateKey(pgpparam);
|
||||||
|
|
||||||
|
console.log(verifyKeys(publicKey, privateKey, passphrase));
|
||||||
|
};
|
||||||
|
apx.apxauth.detachedSignature = async (privK, passphrase, message) => {
|
||||||
|
/**
|
||||||
|
* @privK {string} a test priv key
|
||||||
|
* @passphrase {string} used to read privK
|
||||||
|
* @message {string} message to sign
|
||||||
|
* @Return a detached Signature of the message
|
||||||
|
*/
|
||||||
|
let privatekey;
|
||||||
|
if (passphrase == "" || passphrase == undefined) {
|
||||||
|
privatekey = await openpgp.readKey({ armoredKey: privK });
|
||||||
|
} else {
|
||||||
|
privatekey = await openpgp.decryptKey({
|
||||||
|
privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
|
||||||
|
passphrase,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
//console.log(message);
|
||||||
|
const msg = await openpgp.createMessage({ text: message });
|
||||||
|
//console.log(msg);
|
||||||
|
const sig = await openpgp.sign({
|
||||||
|
message: msg,
|
||||||
|
signingKeys: privatekey,
|
||||||
|
detached: true,
|
||||||
|
});
|
||||||
|
return btoa(sig);
|
||||||
|
};
|
||||||
|
apx.apxauth.clearmsgSignature = async (pubK, privK, passphrase, message) => {
|
||||||
|
/**
|
||||||
|
* @privK {string} a test priv key
|
||||||
|
* @passphrase {string} used to read privK
|
||||||
|
* @message {string} message to sign
|
||||||
|
* @Return an base64 Signature of the message or error
|
||||||
|
*/
|
||||||
|
const publickey = await openpgp.readKey({ armoredKey: pubK });
|
||||||
|
let privatekey;
|
||||||
|
if (passphrase == "" || passphrase == undefined) {
|
||||||
|
privatekey = await openpgp.readKey({ armoredKey: privK });
|
||||||
|
} else {
|
||||||
|
privatekey = await openpgp.decryptKey({
|
||||||
|
privateKey: await openpgp.readPrivateKey({ armoredKey: privK }),
|
||||||
|
passphrase,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const cleartextMessage = await openpgp.sign({
|
||||||
|
message: await openpgp.createCleartextMessage({ text: message }),
|
||||||
|
signingKeys: privatekey,
|
||||||
|
});
|
||||||
|
console.log(cleartextMessage);
|
||||||
|
const verificationResult = await openpgp.verify({
|
||||||
|
message: await openpgp.readCleartextMessage({ cleartextMessage }),
|
||||||
|
verificationKeys: publickey,
|
||||||
|
});
|
||||||
|
|
||||||
|
const verified = verificationResult.signatures[0];
|
||||||
|
const validity = await verified.verified;
|
||||||
|
if (!validity) throw new Error("invalidsignature");
|
||||||
|
|
||||||
|
return btoa(cleartextMessage);
|
||||||
|
};
|
||||||
|
apx.apxauth.authenticatedetachedSignature = async (
|
||||||
|
alias,
|
||||||
|
pubK,
|
||||||
|
detachedSignature,
|
||||||
|
message
|
||||||
|
) => {
|
||||||
|
/**
|
||||||
|
* Check that alias (pubkey) signe a message
|
||||||
|
* @alias {string} alias link to the publickey
|
||||||
|
* @pubK {string} publiKey text format
|
||||||
|
* @detachedSignature {string} a detachedsignatured get from apx.apxauth.detachedSignature
|
||||||
|
* @message {string} the message signed
|
||||||
|
* @return {boolean} true the message was signed by alias
|
||||||
|
* false the message was not signed by alias
|
||||||
|
*/
|
||||||
|
const publickey = await openpgp.readKey({ armoredKey: pubK });
|
||||||
|
const msg = await openpgp.createMessage({ text: message });
|
||||||
|
const signature = await openpgp.readSignature({
|
||||||
|
armoredSignature: atob(detachedSignature), // parse detached signature
|
||||||
|
});
|
||||||
|
const verificationResult = await openpgp.verify({
|
||||||
|
msg, // Message object
|
||||||
|
signature,
|
||||||
|
verificationKeys: publickey,
|
||||||
|
});
|
||||||
|
const { verified, keyID } = verificationResult.signatures[0];
|
||||||
|
try {
|
||||||
|
await verified; // throws on invalid signature
|
||||||
|
//console.log("Signed by key id " + keyID.toHex());
|
||||||
|
return KeyId.toHex().alias == alias;
|
||||||
|
} catch (e) {
|
||||||
|
console.log("Signature could not be verified: " + e.message);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.apxauth.createIdentity = async (
|
||||||
|
id,
|
||||||
|
alias,
|
||||||
|
recoemail,
|
||||||
|
passphrase = ""
|
||||||
|
) => {
|
||||||
|
document.querySelector(`#${id} .msginfo`).innerHTML = ""
|
||||||
|
const aliasregex = /^[a-z0-9]*$/;
|
||||||
|
//console.log(aliasregex.test(alias));
|
||||||
|
if (!(alias && alias.length > 3 && aliasregex.test(alias))) {
|
||||||
|
apx.notification(
|
||||||
|
`#${id} .msginfo`,
|
||||||
|
{
|
||||||
|
status: "406",
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "invalidalias",
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (recoemail.length > 0 && !Checkjson.testformat(recoemail, "email")) {
|
||||||
|
apx.notification(`#${id} .msginfo`, {
|
||||||
|
status: 406,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "invalidemail",
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/pagans/alias/${alias}`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
console.log(rep);
|
||||||
|
apx.notification(
|
||||||
|
`#${id} .msginfo`,
|
||||||
|
{
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "aliasexist",
|
||||||
|
data: { alias },
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch(async (err) => {
|
||||||
|
console.log("checkalias:", err);
|
||||||
|
if (err.response && err.response.status == 404) {
|
||||||
|
// alias does not exist create it is possible
|
||||||
|
const keys = await apx.apxauth.generateKey(alias, passphrase);
|
||||||
|
apx.data.tmpauth = { keys, recoemail, passphrase };
|
||||||
|
//console.log(apx.data.tmpauth);
|
||||||
|
["publickey", "privatekey"].forEach((k) => {
|
||||||
|
console.log(`${id} button.signup${k}`);
|
||||||
|
const btn = document.querySelector(
|
||||||
|
`#${id} button.signup${k}`
|
||||||
|
);
|
||||||
|
btn.addEventListener("click", () => {
|
||||||
|
const blob = new Blob([keys[k]], { type: "text/plain" });
|
||||||
|
const url = URL.createObjectURL(blob);
|
||||||
|
const a = document.createElement("a");
|
||||||
|
a.href = url;
|
||||||
|
a.download = `${alias}_${k}.txt`;
|
||||||
|
document.body.appendChild(a);
|
||||||
|
a.click();
|
||||||
|
document.body.removeChild(a);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.querySelectorAll(
|
||||||
|
`#${id} .signupalias, #${id} .signupemailrecovery, #${id} .signuppassphrase`
|
||||||
|
)
|
||||||
|
.forEach((e) => e.setAttribute("disabled", "disabled"));
|
||||||
|
document
|
||||||
|
.querySelector(`#${id} .getmykeys`)
|
||||||
|
.classList.remove("hidden");
|
||||||
|
document
|
||||||
|
.querySelector(`#${id} .btncreatekey`)
|
||||||
|
.classList.add("hidden");
|
||||||
|
} else {
|
||||||
|
apx.notification(
|
||||||
|
`#${id} .msginfo`,
|
||||||
|
{
|
||||||
|
ref: "Middlewares",
|
||||||
|
msg: "errrequest",
|
||||||
|
data: {},
|
||||||
|
},
|
||||||
|
true
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param {string} alias to create
|
||||||
|
* @param {string} publickey
|
||||||
|
* @param {string} trustedtribe if none => means no passphrase, no privatekey, no trustedtribe
|
||||||
|
* @param {string} passphrase
|
||||||
|
* @param {string} privatekey
|
||||||
|
* @param {string} email if none => means no passphrase, no privatekey, no trustedtribe
|
||||||
|
*
|
||||||
|
* if email!=none and trustedtribe!= none create a person with parson profil in trustedtribe
|
||||||
|
* if email!=none and trustedtribe==none then send an email at registration with all element but doi not store in backend for futur recovery
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
apx.apxauth.test = () => {
|
||||||
|
//"apx.apxauth.registerIdentity(document.getElementById('inputalias').value,document.getElementById('publickey').document.getElementById('inputpassphrase').value)"
|
||||||
|
console.log(apx.data.tmpauth);
|
||||||
|
};
|
||||||
|
apx.apxauth.registerIdentity = async (id, trustedtribe) => {
|
||||||
|
const authid = document.getElementById(id);
|
||||||
|
// trustedtribe boolean
|
||||||
|
//previously store in apx.data.tmpauth={keys:{alias,privatekey,publickey},recoemail,passphrase}
|
||||||
|
const setheaders = await apx.apxauth.setheadersauth(
|
||||||
|
apx.data.tmpauth.keys.alias,
|
||||||
|
apx.data.tmpauth.passphrase,
|
||||||
|
apx.data.tmpauth.keys.publickey,
|
||||||
|
apx.data.tmpauth.keys.privatekey,
|
||||||
|
false
|
||||||
|
);
|
||||||
|
if (setheaders.status != 200) {
|
||||||
|
apx.notification(`#${id} .msginfo`, setheaders);
|
||||||
|
} else {
|
||||||
|
// add withpublickeyforcreate to check isAuthenticated alias does not already exist
|
||||||
|
|
||||||
|
const data = {};
|
||||||
|
data.alias = apx.data.tmpauth.keys.alias;
|
||||||
|
data.publickey = apx.data.tmpauth.keys.publickey;
|
||||||
|
console.log(apx.data.tmpauth.recoemail, Checkjson.testformat(apx.data.tmpauth.recoemail, "email"))
|
||||||
|
if (apx.data.tmpauth.recoemail && Checkjson.testformat(apx.data.tmpauth.recoemail, "email")) {
|
||||||
|
data.passphrase = apx.data.tmpauth.keyspassphrase;
|
||||||
|
data.privatekey = apx.data.tmpauth.keysprivatekey;
|
||||||
|
data.email = apx.data.tmpauth.recoemail;
|
||||||
|
}
|
||||||
|
data.trustedtribe = trustedtribe;
|
||||||
|
axios
|
||||||
|
.post(`/api/apxtri/pagans`, data, { headers: apx.data.headers })
|
||||||
|
.then((reppagan) => {
|
||||||
|
//console.log(reppagan.data);
|
||||||
|
apx.notification(`#${id} .msginfo`, reppagan.data);
|
||||||
|
authid.querySelector(`.btncreateidentity`)
|
||||||
|
.classList.add("hidden");
|
||||||
|
authid.querySelector(`.signupbtnreload`)
|
||||||
|
.classList.remove("hidden");
|
||||||
|
//remove tmp cause create phc change to keep tplauth in memory and avoid asking again the pasword
|
||||||
|
//delete apx.data.tmpauth;
|
||||||
|
//apx.save();
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("error:", err);
|
||||||
|
const dataerr =
|
||||||
|
err.response && err.response.data
|
||||||
|
? err.response.data
|
||||||
|
: { status: 500, ref: "Pagans", msg: "", data: {} };
|
||||||
|
apx.notification(`#${id} .msginfo`, dataerr);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.apxauth.jointribe = (id) => {
|
||||||
|
/**
|
||||||
|
* Allow a pagan to register as a person into a tribe
|
||||||
|
* header must be authenticated with alias into an app belonging to xtribe AND schema person must have apxaccessright with role "pagan": {"C": []}
|
||||||
|
*/
|
||||||
|
//console.log(apx.data);
|
||||||
|
if (!apx.data.headers.xprofils.includes("persons")) {
|
||||||
|
apx.data.headers.xprofils.push("persons");
|
||||||
|
}
|
||||||
|
const data = {
|
||||||
|
alias: apx.data.headers.xalias,
|
||||||
|
profils: apx.data.headers.xprofils,
|
||||||
|
};
|
||||||
|
axios
|
||||||
|
.put(`/api/apxtri/pagans/person/${apx.data.headers.xtribe}`, data, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
apx.notification(`#${id} .msginfo`, rep.data);
|
||||||
|
axios
|
||||||
|
.get(`/api/apxtri/pagans/logout`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
console.log("logout", rep);
|
||||||
|
apx.apxauth.authentifyme(
|
||||||
|
id,
|
||||||
|
apx.data.auth.alias,
|
||||||
|
apx.data.auth.passphrase,
|
||||||
|
apx.data.auth.privatekey
|
||||||
|
);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("Erreur logout check:", err);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
console.log("sorry", err);
|
||||||
|
if (err.response && err.response.data)
|
||||||
|
apx.notification("#msginfo", err.response.data);
|
||||||
|
else
|
||||||
|
apx.notification("#msginfo", {
|
||||||
|
status: 500,
|
||||||
|
ref: "Pagans",
|
||||||
|
msg: "errcreate",
|
||||||
|
data: {},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
5
wco/apxauth/exampleapxauth_fr.json
Normal file
5
wco/apxauth/exampleapxauth_fr.json
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"aliasinvalid": "Combinaison de 3 à 150 caractères <br /> composée de minuscules (a à z) et/ou de chiffres (0 à 9)",
|
||||||
|
"aliastitle": "Uniquement minuscules ou chiffres",
|
||||||
|
"privatekeyplaceholder": "Votre clé privée"
|
||||||
|
}
|
7
wco/apxauth/main_fr.mustache
Normal file
7
wco/apxauth/main_fr.mustache
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<!-- screen action-->
|
||||||
|
<div class="screenaction mt-5 sm:mx-auto sm:w-full sm:max-w-sm">
|
||||||
|
</div>
|
||||||
|
<!-- feedback action-->
|
||||||
|
<div class="my-5">
|
||||||
|
<p class="msginfo text-center text-info"></p>
|
||||||
|
</div>
|
44
wco/apxauth/screenforgetkey_fr.mustache
Normal file
44
wco/apxauth/screenforgetkey_fr.mustache
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
<div class="mt-1">
|
||||||
|
<label class="input validator mbt-1">
|
||||||
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M11.89 4.111a5.5 5.5 0 1 0 0 7.778.75.75 0 1 1 1.06 1.061A7 7 0 1 1 15 8a2.5 2.5 0 0 1-4.083 1.935A3.5 3.5 0 1 1 11.5 8a1 1 0 0 0 2 0 5.48 5.48 0 0 0-1.61-3.889ZM10 8a2 2 0 1 0-4 0 2 2 0 0 0 4 0Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
|
|
||||||
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input id="inputaliasrecovery" type="text" placeholder="mail@site.com | alias" required />
|
||||||
|
</label>
|
||||||
|
<div class="validator-hint hidden">
|
||||||
|
Enter a valid email or an alias (lowercase a-z and 0-9)
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="my-5">
|
||||||
|
<p>
|
||||||
|
Si vous avez fait confiance à ce domaine pour garder vos clés, un email va être envoyé avec vos clés.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="my-5">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||||
|
onclick="apx.apxauth.recoverykey('{{id}}',document.getElementById('inputaliasrecovery').value);"
|
||||||
|
>
|
||||||
|
M'envoyer un email avec mes clés
|
||||||
|
</button>
|
||||||
|
</div>
|
41
wco/apxauth/screeninformation_fr.mustache
Normal file
41
wco/apxauth/screeninformation_fr.mustache
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
<div class="space-y-6 text-justify">
|
||||||
|
<h2>Qu'est-ce qu'une identité numérique décentralisée?</h2>
|
||||||
|
<p>
|
||||||
|
C'est <span class="text-secondary">un moyen de s'identifier en prouvant qu'on est le propriétaire
|
||||||
|
d'un alias ou d'une clé publique</span>. Cette clé publique est accessible à tous et utilisée dans le
|
||||||
|
monde numérique pour informer, payer, échanger,... et porte une
|
||||||
|
réputation publique.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Concrètement, c'est une paire de fichiers texte appelée clé publique
|
||||||
|
et clé privée. La clé publique ne porte pas d'information
|
||||||
|
personnelle autre que celles que vous avez bien voulu y associer.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Une fonction mathématique permet au propriétaire de la clé privée de
|
||||||
|
signer un message. Le destinataire dispose d'une autre fonction qui
|
||||||
|
permet de vérifier que la signature a été faite avec la clé privée.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Cette interface permet de créer une identité et de l'utiliser pour
|
||||||
|
s'authentifier pour 24 heures. Elle n'envoie que le couple alias/clé
|
||||||
|
publique sur internet, la clé privée est
|
||||||
|
<span class="text-secondary">votre propriété et ne doit jamais être communiquée</span>. Si vous
|
||||||
|
la perdez, vous ne pourrez plus récupérer les informations
|
||||||
|
associées. Sauf si vous
|
||||||
|
<span class="text-secondary">avez fait confiance à ce nom de domaine</span>, vous pourrez lui
|
||||||
|
demander d'envoyer un email avec ces clés.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Vous pouvez avoir autant d'identités que vous voulez, vous pouvez
|
||||||
|
créer une identité pour des objets uniques. La seule limite est qu'à
|
||||||
|
partir du moment où vous associez des informations personnelles à
|
||||||
|
cette clé, le destinataire de ces informations peut les relier aux
|
||||||
|
activités de cette identité inscrite dans la blockchain apxtri.
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
Pour auditer le code js, utiliser l'outil de développement de votre
|
||||||
|
navigateur. Pour toute remarque, question ou détection de failles :
|
||||||
|
{{supportemail}}
|
||||||
|
</p>
|
||||||
|
</div>
|
39
wco/apxauth/screenlogout_fr.mustache
Normal file
39
wco/apxauth/screenlogout_fr.mustache
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="flex flex-col space-y-1 text-center">
|
||||||
|
<div class="mt-1">
|
||||||
|
<h1 class="mb-6">
|
||||||
|
Bonjour {{xalias}},
|
||||||
|
</h1>
|
||||||
|
<p>
|
||||||
|
Si cet appareil ne vous appartiens pas et que vous n'utilisez pas l'application, vous devriez vous deconnecter.
|
||||||
|
</p>
|
||||||
|
<p class="text-center text-gray-500">
|
||||||
|
Nettoyer mes traces de cet appareil?
|
||||||
|
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||||
|
onclick="apx.apxauth.logout('{{id}}','logout','logout','apxauth')">Se deconnecter</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-center text-gray-500">
|
||||||
|
Voir mes échanges?
|
||||||
|
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||||
|
href="https://wall-ants.ndda.fr/apxwallet_fr.html" >Mon activité </a>
|
||||||
|
</p>
|
||||||
|
{{#member}}
|
||||||
|
<p>
|
||||||
|
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||||
|
</p>
|
||||||
|
{{/member}}
|
||||||
|
{{^member}}
|
||||||
|
<p> Vous n'êtes pas encore membre de {{xtribe}} </p>
|
||||||
|
<p class=" mt-1 text-center text-gray-500">
|
||||||
|
Envie d'jouter cette tribut {{xtribe}}?
|
||||||
|
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||||
|
onclick="apx.apxauth.jointribe('{{id}}')">Rejoindre {{xtribe}}</a>
|
||||||
|
</p>
|
||||||
|
{{/member}}
|
||||||
|
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||||
|
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
23
wco/apxauth/screenmytribes_fr.mustache
Normal file
23
wco/apxauth/screenmytribes_fr.mustache
Normal file
@@ -0,0 +1,23 @@
|
|||||||
|
<div class="flex flex-col space-y-1 text-center">
|
||||||
|
<div class="mt-1">
|
||||||
|
<h1 class="mb-6">
|
||||||
|
Bonjour {{xalias}},
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="mt-4">
|
||||||
|
<p class="text-center text-gray-500">
|
||||||
|
Redirige vers
|
||||||
|
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||||
|
onclick="apx.apxauth.redirecturlwithauth('http://recruiter.smatchit.newdev.ants/src/offer_fr.html','smatchit','recruiter',true);" >Redirige vers recruiter.smatchit.io/offer_fr.html&xhash....</a>
|
||||||
|
</p>
|
||||||
|
{{#member}}
|
||||||
|
<p>
|
||||||
|
Vous êtes membre de {{xtribe}} {{#noprofils}} sand profil particulier {{/noprofils}} {{^noprofils}}avec le(s) profil(s):<br><span class="text-info"> {{#profils}} {{.}}<br> {{/profils}} </span> {{/noprofils}}
|
||||||
|
</p>
|
||||||
|
{{/member}}
|
||||||
|
<p>Les applications ou pages web de {{xtribe}} à visiter:<br>
|
||||||
|
{{#websites}}<a class="font-semibold leading-6 text-secondary hover:text-primary" href='{{{href}}}'>{{{name}}}</a><br> {{/websites}}
|
||||||
|
</p>
|
||||||
|
<button class="btn btn-primary" onclick="apx.apxauth.runtest()">testbtn</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
69
wco/apxauth/screensignin_fr.mustache
Normal file
69
wco/apxauth/screensignin_fr.mustache
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<p data-wco="createid" class="text-center text-neutral-content">
|
||||||
|
{{{signintitle}}}
|
||||||
|
</p>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="input validator">
|
||||||
|
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
class="signinalias"
|
||||||
|
type="input"
|
||||||
|
required
|
||||||
|
placeholder="alias"
|
||||||
|
pattern="[a-z0-9\-]*"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="30"
|
||||||
|
title="{{{aliastitle}}}"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<p class="validator-hint hidden"> {{{aliasinvalid}}}</p>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="input mt-1">
|
||||||
|
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="text" class="signinpassphrase" placeholder="passphrase (option)" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<textarea rows=5 class="mt-2 textarea signinprivatekey" placeholder="{{{privatekeyplaceholder}}}"></textarea>
|
||||||
|
</div>
|
||||||
|
<div class="flex m-6">
|
||||||
|
<div class="w-14 flex-none">
|
||||||
|
<input type="checkbox" checked="checked" class="checkbox signinrememberme" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm text-justify" >{{{remembermetext}}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="m-4">
|
||||||
|
<button
|
||||||
|
class="btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||||
|
onclick="const loginid= document.getElementById('{{id}}');apx.apxauth.authentifyme(
|
||||||
|
'{{id}}',
|
||||||
|
loginid.querySelector('.signinalias').value,
|
||||||
|
loginid.querySelector('.signinpassphrase').value,
|
||||||
|
loginid.querySelector('.signinprivatekey').value,
|
||||||
|
loginid.querySelector('.signinrememberme').checked
|
||||||
|
)">
|
||||||
|
{{{authentifyme}}}
|
||||||
|
</button>
|
||||||
|
</div>
|
121
wco/apxauth/screensignup_fr.mustache
Normal file
121
wco/apxauth/screensignup_fr.mustache
Normal file
@@ -0,0 +1,121 @@
|
|||||||
|
<p data-wco="createid" class="text-center text-neutral-content">
|
||||||
|
{{{signuptitle}}}
|
||||||
|
</p>
|
||||||
|
<div class="paramid">
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="input validator mbt-1">
|
||||||
|
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M8 8a3 3 0 1 0 0-6 3 3 0 0 0 0 6ZM12.735 14c.618 0 1.093-.561.872-1.139a6.002 6.002 0 0 0-11.215 0c-.22.578.254 1.139.872 1.139h9.47Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input
|
||||||
|
class="signupalias"
|
||||||
|
type="input"
|
||||||
|
required
|
||||||
|
placeholder="alias"
|
||||||
|
pattern="[a-z0-9\-]*"
|
||||||
|
minlength="3"
|
||||||
|
maxlength="30"
|
||||||
|
title="{{{aliastitle}}}"
|
||||||
|
/>
|
||||||
|
</label>
|
||||||
|
<div class="validator-hint hidden">
|
||||||
|
<p>{{{aliasinvalid}}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="input validator mt-1">
|
||||||
|
<svg class="h-[1em] opacity-50" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<rect width="20" height="16" x="2" y="4" rx="2"></rect>
|
||||||
|
<path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input class="signupemailrecovery" type="email" placeholder="mail@site.com" required />
|
||||||
|
</label>
|
||||||
|
<div class="validator-hint hidden">
|
||||||
|
{{{emailinvalid}}}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="input mt-1">
|
||||||
|
<svg class="h-[1em] opacity-90" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16">
|
||||||
|
<g
|
||||||
|
stroke-linejoin="round"
|
||||||
|
stroke-linecap="round"
|
||||||
|
stroke-width="1"
|
||||||
|
fill="black"
|
||||||
|
stroke="black"
|
||||||
|
>
|
||||||
|
<path d="M11.5 1A3.5 3.5 0 0 0 8 4.5V7H2.5A1.5 1.5 0 0 0 1 8.5v5A1.5 1.5 0 0 0 2.5 15h7a1.5 1.5 0 0 0 1.5-1.5v-5A1.5 1.5 0 0 0 9.5 7V4.5a2 2 0 1 1 4 0v1.75a.75.75 0 0 0 1.5 0V4.5A3.5 3.5 0 0 0 11.5 1Z"></path>
|
||||||
|
</g>
|
||||||
|
</svg>
|
||||||
|
<input type="text" class="signuppassphrase" placeholder="passphrase (option)" />
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
<div class="mt-5">
|
||||||
|
<button
|
||||||
|
class="btncreatekey btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||||
|
onclick="const authid=document.getElementById('{{id}}');console.log('{{id}}'); apx.apxauth.createIdentity(
|
||||||
|
'{{id}}',
|
||||||
|
authid.querySelector('.signupalias').value,
|
||||||
|
authid.querySelector('.signupemailrecovery').value,
|
||||||
|
authid.querySelector('.signuppassphrase').value
|
||||||
|
)"
|
||||||
|
>
|
||||||
|
{{{createkey}}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="getmykeys hidden mt-1">
|
||||||
|
<div class="flex m-6">
|
||||||
|
<div class="w-14 flex-none">
|
||||||
|
<input type="checkbox" checked="checked" class="signuptrustedcheck checkbox checkbox-secondary" />
|
||||||
|
</div>
|
||||||
|
<div class="flex-1">
|
||||||
|
<p class="text-sm text-justify" >{{{trusttext}}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="downloadkeys text-center mt-1">
|
||||||
|
<button
|
||||||
|
class="signuppublickey btn btn-outline btn-accent text-white shadow-sm"
|
||||||
|
>
|
||||||
|
{{{downloadPuK}}}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="signupprivatekey btn btn-outline btn-accent text-white shadow-sm"
|
||||||
|
>
|
||||||
|
{{{downloadPrK}}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div class="mt-2">
|
||||||
|
<button
|
||||||
|
class="btncreateidentity btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||||
|
onclick="const authid=document.getElementById('{{id}}');apx.apxauth.registerIdentity(
|
||||||
|
'{{id}}',
|
||||||
|
authid.querySelector('.signuptrustedcheck').checked
|
||||||
|
)"
|
||||||
|
>{{{saveidentity}}}
|
||||||
|
</button>
|
||||||
|
<button
|
||||||
|
class="signupbtnreload hidden btn btn-primary w-full justify-center hover:bg-secondary focus:outline focus:outline-primary"
|
||||||
|
onclick="location.reload(true)"
|
||||||
|
>
|
||||||
|
{{{nextpage}}}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
6
wco/chatroom/chatroom.js
Normal file
6
wco/chatroom/chatroom.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.chat = {};
|
||||||
|
|
||||||
|
apx.chat.show=()=>{
|
||||||
|
|
||||||
|
}
|
1
wco/conf.json
Normal file
1
wco/conf.json
Normal file
@@ -0,0 +1 @@
|
|||||||
|
{"name":"wco","schema":"apxtri/schema/wco.json","lastupdate":0}
|
46
wco/formmanager/form_fr.mustache
Normal file
46
wco/formmanager/form_fr.mustache
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
<div class="contactform grid grid-cols-1 md:grid-cols-2 gap-4">
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<input type="text" id="name" placeholder="Votre nom"
|
||||||
|
class="input input-bordered bg-white" />
|
||||||
|
<label for="name" class="label floating-label">Votre nom</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<input type="email" id="mail" placeholder="Votre Email"
|
||||||
|
class="input input-bordered bg-white" />
|
||||||
|
<label for="mail" class="label floating-label">Votre Email</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<input type="text" id="mobile" placeholder="Votre téléphone"
|
||||||
|
class="input input-bordered bg-white" />
|
||||||
|
<label for="mobile" class="label floating-label">Votre téléphone</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control">
|
||||||
|
<select class="select select-bordered bg-white" id="service">
|
||||||
|
<option selected value="syndic">Un syndic</option>
|
||||||
|
<option value="particulier">Un particulier</option>
|
||||||
|
<option value="entreprise">Une entreprise</option>
|
||||||
|
<option value="collectivité">Une collectivité</option>
|
||||||
|
</select>
|
||||||
|
<label for="service" class="label floating-label">Vous êtes:</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-control md:col-span-2">
|
||||||
|
<textarea id="message" class="textarea textarea-bordered bg-white h-32"></textarea>
|
||||||
|
<label for="message" class="label floating-label">Message</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="md:col-span-2 text-center">
|
||||||
|
<button class="btn btn-primary w-full py-3"
|
||||||
|
onclick="apx.sendform(...)">Envoyer votre demande</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Messages de feedback -->
|
||||||
|
<div class="answerok alert alert-success hidden"></div>
|
||||||
|
<div class="answerko alert alert-error hidden">
|
||||||
|
<p class="text-error-content">En maintenance, ré-essayez plus tard</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
35
wco/formmanager/formdata_fr.json
Normal file
35
wco/formmanager/formdata_fr.json
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||||
|
"$id": "/schema/contact",
|
||||||
|
"title": "Contact information ",
|
||||||
|
"description": "A form submission data",
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"contactid": {
|
||||||
|
"title":"A unique string fromalias_fromuuid_toalias_touuid_timestamp",
|
||||||
|
"type":"string",
|
||||||
|
"default":"createcontactid",
|
||||||
|
"formclass":"hidden"
|
||||||
|
},
|
||||||
|
"name":{
|
||||||
|
"title":"Name",
|
||||||
|
"description":"",
|
||||||
|
"type":"string",
|
||||||
|
"formclass":"input",
|
||||||
|
"placeholder":"Votre nom"
|
||||||
|
},
|
||||||
|
"mailm":{
|
||||||
|
"title":"Email",
|
||||||
|
"description":"",
|
||||||
|
"type":"string",
|
||||||
|
"formclass":"input",
|
||||||
|
"placeholder":"Votre nom"
|
||||||
|
},
|
||||||
|
"mobile":{
|
||||||
|
"title":"",
|
||||||
|
"description":"",
|
||||||
|
"type":"string",
|
||||||
|
"formclass":"input"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
127
wco/formmanager/formmanager.js
Normal file
127
wco/formmanager/formmanager.js
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.form = {};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Data form schema
|
||||||
|
* {}
|
||||||
|
*
|
||||||
|
* Will produce a html expected:
|
||||||
|
* <div class="formmanager">
|
||||||
|
* <input data-name="keydata" data-format="checkJSON" value="defaultvalue" placeholder="" />
|
||||||
|
* .. any other data field
|
||||||
|
* <button class="getdata" onclick="apx.form.submit(this,cb)">save</button>
|
||||||
|
* </div>
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
apx.form.build = (elt,schema,data)=>{
|
||||||
|
// Build in html a form with all prerequest and prefill by data into a DOM element elt
|
||||||
|
|
||||||
|
};
|
||||||
|
apx.form.submit = (elt,cb)
|
||||||
|
|
||||||
|
/**
|
||||||
|
* To install in html input onclick=apx.form.enter to simulate a senddata
|
||||||
|
* @param {*} elt
|
||||||
|
* @param {*} event
|
||||||
|
*/
|
||||||
|
apx.form.enter = (elt, event) => {
|
||||||
|
if (event.keyCode === 13) {
|
||||||
|
const getform = elt.closest(".contactform");
|
||||||
|
event.preventDefault();
|
||||||
|
getform.querySelector(".sendregister").click();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// apx.sendform(this,{route:'actions/contact',order:'registercontact',srckey:'teasingwebpage',mlist:'getinform'},fctregisteremail)
|
||||||
|
registerlist.check = {
|
||||||
|
email:
|
||||||
|
/^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/,
|
||||||
|
telephone: /^0[1-9][0-9]{8}$/,
|
||||||
|
profil: /^(seeker|recruiter)$/,
|
||||||
|
};
|
||||||
|
registerlist.msg = {
|
||||||
|
email: "Vérifier votre email ",
|
||||||
|
telephone: "Verifier votre téléphone ",
|
||||||
|
profil: "Choisir votre profil ",
|
||||||
|
serverissue:
|
||||||
|
"Désolé, un probléme empeche votre inscription, réessayer plus tard",
|
||||||
|
register: "Vous êtes bien inscrits",
|
||||||
|
alreadysent: "Vous avez déjà envoyé votre email ",
|
||||||
|
};
|
||||||
|
registerlist.send = async (elt) => {
|
||||||
|
const formdata = {};
|
||||||
|
// formdata.uuid = (localStorage.getItem('xuuid')) ? localStorage.getItem('xuuid'):"uuidunknown";
|
||||||
|
const form = elt.closest(".contactform");
|
||||||
|
form.querySelector(".answer").innerHTML = "";
|
||||||
|
var valid = true;
|
||||||
|
form.querySelectorAll("input,textarea,option:checked").forEach((e) => {
|
||||||
|
if (!e.name) e.name = info.getAttribute("name");
|
||||||
|
if (e.name == "email") formdata.typekey = "email";
|
||||||
|
if (e.name == "telephone") formdata.typekey = "telephone";
|
||||||
|
|
||||||
|
if (!e.type || e.type !== "radio" || (e.type == "radio" && e.checked)) {
|
||||||
|
//check value
|
||||||
|
if (
|
||||||
|
registerlist.check[e.name] &&
|
||||||
|
!registerlist.check[e.name].test(e.value)
|
||||||
|
) {
|
||||||
|
form.querySelector(".answer").innerHTML +=
|
||||||
|
registerlist.msg[e.name] + " ";
|
||||||
|
form.querySelector(".answer").classList.remove("d-none");
|
||||||
|
valid = false;
|
||||||
|
}
|
||||||
|
if (!formdata[e.name]) {
|
||||||
|
formdata[e.name] = e.value;
|
||||||
|
} else {
|
||||||
|
// array case multi input with same name
|
||||||
|
formdata[e.name] = [formdata[e.name]];
|
||||||
|
formdata[e.name].push(e.value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
formdata.tribe=apxtri.headers.xtribe
|
||||||
|
if (!formdata.profil || formdata.profil == "") {
|
||||||
|
// Pas de choix
|
||||||
|
formdata.profil = "both";
|
||||||
|
//form.querySelector(".answer").innerHTML = registerlist.msg.profil;
|
||||||
|
//form.querySelector(".answer").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
//add phil
|
||||||
|
|
||||||
|
formdata.mlist += formdata.profil;
|
||||||
|
console.log(formdata);
|
||||||
|
if (
|
||||||
|
valid &&
|
||||||
|
formdata.profil &&
|
||||||
|
formdata.srckey &&
|
||||||
|
(formdata.email || formdata.telephone)
|
||||||
|
) {
|
||||||
|
form.querySelector(".submitbtn").classList.add("d-none");
|
||||||
|
form.querySelector(".loaderbtn").classList.remove("d-none");
|
||||||
|
const datasent = await axios.post(
|
||||||
|
"/api/apxtri/notifications/registeranonymous",
|
||||||
|
formdata,
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
);
|
||||||
|
//console.log(datasent)
|
||||||
|
if (datasent.data.status == 200) {
|
||||||
|
form.querySelector(".answer").innerHTML = registerlist.msg.register;
|
||||||
|
form.querySelector(".answer").classList.remove("d-none");
|
||||||
|
form.querySelector(".loaderbtn").classList.add("d-none");
|
||||||
|
elt.setAttribute(
|
||||||
|
"onclick",
|
||||||
|
`"alert('${registerlist.msg.alreadysent}');"`
|
||||||
|
);
|
||||||
|
//document.location.href = "thank-you.html";
|
||||||
|
} else {
|
||||||
|
form.querySelector(".answer").innerHTML = registerlist.msg.serverissue;
|
||||||
|
form.querySelector(".answer").classList.remove("d-none");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.log(
|
||||||
|
"Check your form it miss something profil or srckey or email or telephone"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
};
|
14
wco/itm/admindata.json
Normal file
14
wco/itm/admindata.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"wconame": "admindata",
|
||||||
|
"owner": "philc",
|
||||||
|
"price": 1,
|
||||||
|
"aliascode": [],
|
||||||
|
"commentaliascode": "if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "Manage admindata page",
|
||||||
|
"description": "",
|
||||||
|
"tpl": {},
|
||||||
|
"tpldata": {},
|
||||||
|
"ref": {}
|
||||||
|
}
|
36
wco/itm/adminskull.json
Normal file
36
wco/itm/adminskull.json
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"wconame": "adminskull",
|
||||||
|
"owner": "philc",
|
||||||
|
"price": 1,
|
||||||
|
"aliascode": [],
|
||||||
|
"commentaliascode": "if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "Vertical and horinzontal menu",
|
||||||
|
"description": "",
|
||||||
|
"lang": [
|
||||||
|
"fr"
|
||||||
|
],
|
||||||
|
"tpl": {
|
||||||
|
"adminskullverticalnav": "apxtri/objects/wco/adminskull/verticalnav",
|
||||||
|
"adminskullresult": "apxtri/objects/wco/adminskull/result",
|
||||||
|
"adminskullmain": "apxtri/objects/wco/adminskull/main",
|
||||||
|
"adminskullheadnav": "apxtri/objects/wco/adminskull/headnav"
|
||||||
|
},
|
||||||
|
"tpldatamodel": {
|
||||||
|
"headnav":"apxtri/objects/wco/adminskull/headnav",
|
||||||
|
"verticalnav":"apxtri/objects/wco/asminskull/verticalnav"
|
||||||
|
},
|
||||||
|
"options": {
|
||||||
|
"profil": "{{tribe}}/objects/options/profil"
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||||
|
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||||
|
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||||
|
},
|
||||||
|
"schema": [
|
||||||
|
"apxtri/objects/pagans",
|
||||||
|
"{{tribe}}/objects/persons"
|
||||||
|
]
|
||||||
|
}
|
17
wco/itm/apx.json
Normal file
17
wco/itm/apx.json
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
{
|
||||||
|
"wconame": "apx",
|
||||||
|
"owner": "philc",
|
||||||
|
"price": 1,
|
||||||
|
"aliascode": [],
|
||||||
|
"commentaliascode": "if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "Localstorage management and usefull tools",
|
||||||
|
"description": "",
|
||||||
|
"tpl": {},
|
||||||
|
"tpldata": {},
|
||||||
|
"ref": {
|
||||||
|
"Checkjson": "apxtri/objects/tplstrings/Checkjson",
|
||||||
|
"Notification": "apxtri/objects/tplstrings/Notifications", "Middlewares": "apxtri/objects/tplstrings/middlewares"
|
||||||
|
}
|
||||||
|
}
|
31
wco/itm/apxauth.json
Normal file
31
wco/itm/apxauth.json
Normal file
@@ -0,0 +1,31 @@
|
|||||||
|
{
|
||||||
|
"wconame": "apxauth",
|
||||||
|
"owner": "philc",
|
||||||
|
"price": 1,
|
||||||
|
"aliascode": [],
|
||||||
|
"commentaliascode": "if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "apXtri pagans create and authentification interface",
|
||||||
|
"description": "",
|
||||||
|
"lang": ["fr"],
|
||||||
|
"tpl": {
|
||||||
|
"apxauthmain":"apxtri/objects/wco/apxauth/main",
|
||||||
|
"apxauthscreensignup": "apxtri/objects/wco/apxauth/screensignup",
|
||||||
|
"apxauthscreensignin": "apxtri/objects/wco/apxauth/screensignin",
|
||||||
|
"apxauthscreenlogout": "apxtri/objects/wco/apxauth/screenlogout",
|
||||||
|
"apxauthscreenmytribes": "apxtri/objects/wco/apxauth/screenmytribes",
|
||||||
|
"apxauthscreeninformation": "apxtri/objects/wco/apxauth/screeninformation",
|
||||||
|
"apxauthscreenforgetkey": "apxtri/objects/wco/apxauth/screenforgetkey"
|
||||||
|
},
|
||||||
|
"tpldatamodel": { "apxauth": "apxtri/objects/wco/apxauth/exampleapxauth" },
|
||||||
|
"options": {
|
||||||
|
"profil": "{{tribeId}}/objects/options/profil"
|
||||||
|
},
|
||||||
|
"ref": {
|
||||||
|
"Odmdb": "apxtri/objects/tplstrings/Odmdb",
|
||||||
|
"Pagans": "apxtri/objects/tplstrings/Pagans",
|
||||||
|
"Persons": "apxtri/objects/tplstrings/Persons"
|
||||||
|
},
|
||||||
|
"schema": ["apxtri/objects/pagans", "{{tribe}}/objects/persons"]
|
||||||
|
}
|
20
wco/itm/simplemobnav.json
Normal file
20
wco/itm/simplemobnav.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"wconame": "simplemobnav",
|
||||||
|
"owner": "philc",
|
||||||
|
"price":1,
|
||||||
|
"aliascode":[],
|
||||||
|
"commentaliascode":"if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "A simple link kist to show and hide some block for mobile screen",
|
||||||
|
"description": "",
|
||||||
|
"lang":["fr"],
|
||||||
|
"tpl": {
|
||||||
|
"simplemobnavnavbuttonh":"apxtri/objects/wco/simplemobnav/navbuttonh.mustache",
|
||||||
|
"simplemobnavnavlist": "apxtri/objects/wco/simplemobnav/navlist.mustache",
|
||||||
|
"simplemobnavnavbutton": "apxtri/objects/wco/simplemobnav/navbuttonh.mustache",
|
||||||
|
"simplemobnavmain": "apxtri/objects/wco/simplemobnav/main.mustache"},
|
||||||
|
"tpldatamodel": {
|
||||||
|
"simplemobnav":"apxtri/objects/wco/simplemobnav/examplenav"
|
||||||
|
}
|
||||||
|
}
|
20
wco/itm/tracker.json
Normal file
20
wco/itm/tracker.json
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"wconame": "simplemobnavtracker",
|
||||||
|
"owner": "philc",
|
||||||
|
"price": 1,
|
||||||
|
"aliascode": [],
|
||||||
|
"commentaliascode": "if paid wco then [tribename_uniquecode,...]",
|
||||||
|
"codehash": "123",
|
||||||
|
"thumbnail": "",
|
||||||
|
"title": "A box to get cookies consent and data collection",
|
||||||
|
"description": "The tpl is independante of language this is why we store it with full extension",
|
||||||
|
"lang": [
|
||||||
|
"fr"
|
||||||
|
],
|
||||||
|
"tpl": {
|
||||||
|
"trackerconsentform": "apxtri/objects/wco/tracker/consentform.mustache"
|
||||||
|
},
|
||||||
|
"tpldatamodel": {
|
||||||
|
"trackerconsentform": "apxtri/objects/wco/tracker/exampleform"
|
||||||
|
}
|
||||||
|
}
|
99
wco/simplemobnav/examplenav_fr.json
Normal file
99
wco/simplemobnav/examplenav_fr.json
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
{
|
||||||
|
"contentwconame": "apxauth",
|
||||||
|
"contentid": "signature",
|
||||||
|
"logo": {
|
||||||
|
"src": "/src/static/img/logo/logobgdark.png",
|
||||||
|
"alt": "smatchit"
|
||||||
|
},
|
||||||
|
"claim": {
|
||||||
|
"textContent": "Never miss an opportunity"
|
||||||
|
},
|
||||||
|
"textlist": true,
|
||||||
|
"commentmenutype": "textlist: vertical list of menu with texte, buttonlist: horizontal btn",
|
||||||
|
"profilmenu": [
|
||||||
|
{
|
||||||
|
"mainprofil": "persons",
|
||||||
|
"link": "mytribes"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mainprofil": "pagans",
|
||||||
|
"link": "logout"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"mainprofil": "anonymous",
|
||||||
|
"link": "signin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"link": "signup",
|
||||||
|
"label": "Pas encore d'identité apxtri ?",
|
||||||
|
"textlink": "Créer mon identité",
|
||||||
|
"tpl": "apxauthscreensignup",
|
||||||
|
"allowedprofil":["anonymous"],
|
||||||
|
"next": [
|
||||||
|
"signin",
|
||||||
|
"forgetkey",
|
||||||
|
"information"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "signin",
|
||||||
|
"label": "S'identifier ?",
|
||||||
|
"textlink": "Accédez à vos données",
|
||||||
|
"tpl": "apxauthscreensignin",
|
||||||
|
"allowedprofil":["anonymous"],
|
||||||
|
"next": [
|
||||||
|
"signup",
|
||||||
|
"forgetkey",
|
||||||
|
"information"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "forgetkey",
|
||||||
|
"label": "Clé oubliée ?",
|
||||||
|
"textlink": "Récupérez par email",
|
||||||
|
"tpl": "apxauthscreenforgetkey",
|
||||||
|
"allowedprofil":["anonymous"],
|
||||||
|
"next": [
|
||||||
|
"signin",
|
||||||
|
"signup",
|
||||||
|
"information"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "information",
|
||||||
|
"label": " C'est quoi une identité apxtri ?",
|
||||||
|
"textlink": "En savoir plus",
|
||||||
|
"allowedprofil":["anonymous"],
|
||||||
|
"tpl": "apxauthscreeninformation",
|
||||||
|
"next": [
|
||||||
|
"back"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "back",
|
||||||
|
"label": "Retour au menu ",
|
||||||
|
"allowedprofil":["anonymous"],
|
||||||
|
"tpl": "sc",
|
||||||
|
"textlink": "Retour",
|
||||||
|
"next": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "logout",
|
||||||
|
"label": " ",
|
||||||
|
"allowedprofil":["pagans"],
|
||||||
|
"tpl": "apxauthscreenlogout",
|
||||||
|
"textlink": "",
|
||||||
|
"next": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"link": "mytribes",
|
||||||
|
"label": " ",
|
||||||
|
"tpl": "apxauthscreenmytribes",
|
||||||
|
"allowedprofil":["persons"],
|
||||||
|
"textlink": "",
|
||||||
|
"next": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
29
wco/simplemobnav/main.mustache
Normal file
29
wco/simplemobnav/main.mustache
Normal file
@@ -0,0 +1,29 @@
|
|||||||
|
<div class="py-1">
|
||||||
|
<img
|
||||||
|
class="mx-auto w-auto block dark:hidden"
|
||||||
|
data-wco="logo"
|
||||||
|
src="{{logobglight.src}}"
|
||||||
|
alt="{{logobglight.alt}}"
|
||||||
|
src="{{logobgdark.src}}"
|
||||||
|
alt="{{logobgdark.alt}}"
|
||||||
|
/>
|
||||||
|
<img
|
||||||
|
class="mx-auto w-auto hidden dark:block"
|
||||||
|
data-wco="logo"
|
||||||
|
src="{{logobgdark.src}}"
|
||||||
|
alt="{{logobgdark.alt}}"
|
||||||
|
/>
|
||||||
|
<h2
|
||||||
|
class="mt-10 text-center text-2xl font-bold leading-9 tracking-tight"
|
||||||
|
data-wco="claim"
|
||||||
|
>
|
||||||
|
{{claim.textContent}}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<div id="loading" class="flex min-h-full flex-col justify-center">
|
||||||
|
<div class="sm:mx-auto sm:w-full sm:max-w-sm py-4 text-center">
|
||||||
|
<span class="loading loading-spinner loading-lg text-secondary"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="{{contentid}}" wco-name="{{contentwconame}}" wco-link="{{contentscreen}}" class="mt-5 sm:mx-auto sm:w-full sm:max-w-sm"></div>
|
||||||
|
<div class="navlink"></div>
|
11
wco/simplemobnav/navbuttonh.mustache
Normal file
11
wco/simplemobnav/navbuttonh.mustache
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
<div class="flex justify-center gap-2 p-4">
|
||||||
|
{{#links}}
|
||||||
|
<button class="btn {{classnavbutton}} flex-col gap-1" onclick="apx.simplemobnav.action('{{id}}','{{link}}','{{action}}','{{wconame}}');">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" class="w-16 h-16 box-content">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="{{d}}" />
|
||||||
|
</svg>
|
||||||
|
<span class="text-sm">{{{shortlabel}}}</span>
|
||||||
|
</button>
|
||||||
|
{{/links}}
|
||||||
|
</div>
|
||||||
|
|
9
wco/simplemobnav/navlist.mustache
Normal file
9
wco/simplemobnav/navlist.mustache
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{{#links}}
|
||||||
|
<p class=" mt-1 text-center text-sm text-gray-500">
|
||||||
|
{{{label}}}
|
||||||
|
<a class="font-semibold leading-6 text-secondary hover:text-primary"
|
||||||
|
onclick="apx.simplemobnav.action('{{id}}','{{link}}','{{action}}','{{wconame}}');">
|
||||||
|
{{{textlink}}}
|
||||||
|
</a>
|
||||||
|
</p>
|
||||||
|
{{/links}}
|
0
wco/simplemobnav/readme.md
Normal file
0
wco/simplemobnav/readme.md
Normal file
102
wco/simplemobnav/simplemobnav.js
Normal file
102
wco/simplemobnav/simplemobnav.js
Normal file
@@ -0,0 +1,102 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.simplemobnav = {};
|
||||||
|
apx.simplemobnav.loadwco = (id, ctx) => {
|
||||||
|
|
||||||
|
const tpldataname = `${apx.data.pagename}_${id}_simplemobnav`;
|
||||||
|
const simplemobnavid = document.getElementById(id)
|
||||||
|
console.log("load simplemobnav with tpldataname:", tpldataname, " id:", id, " ctx:", ctx);
|
||||||
|
|
||||||
|
let initmenu;
|
||||||
|
if (simplemobnavid.innerHTML.trim() === "") {
|
||||||
|
// Get 1st menu matching the first profil in profilmenu
|
||||||
|
apx.simplemobnav.checktpldataname(tpldataname);
|
||||||
|
for (const menulist of apx.data.tpldata[tpldataname].profilmenu) {
|
||||||
|
if (apx.data.headers.xprofils.includes(menulist.mainprofil)) {
|
||||||
|
initmenu = menulist.link
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ctx.link = initmenu;
|
||||||
|
apx.data.tpldata[tpldataname].contentscreen = initmenu;
|
||||||
|
simplemobnavid.innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl.simplemobnavmain,
|
||||||
|
apx.data.tpldata[tpldataname]
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
//just update wco-link this will also run apx.{contentwconame}.loadwco()
|
||||||
|
simplemobnavid.querySelectorAll('[wco-name]').forEach(elt => { elt.setAttribute('wco-link', ctx.link) });
|
||||||
|
}
|
||||||
|
// shom menulist from next
|
||||||
|
const screendata = apx.data.tpldata[tpldataname].links.find(
|
||||||
|
(m) => m.link == ctx.link
|
||||||
|
);
|
||||||
|
// add in menulink the data needed to customize
|
||||||
|
const menulinks = apx.data.tpldata[tpldataname].links
|
||||||
|
.filter(m =>
|
||||||
|
screendata.next.includes(m.link) && m.allowedprofil.some(el => apx.data.headers.xprofils.includes(el)))
|
||||||
|
.map(m => {
|
||||||
|
const newm = { ...m }
|
||||||
|
if (!m.classnavbutton && apx.data.tpldata[tpldataname].classnavbutton) {
|
||||||
|
newm.classnavbutton = apx.data.tpldata[tpldataname].classnavbutton
|
||||||
|
}
|
||||||
|
if (!m.classnavlist && apx.data.tpldata[tpldataname].classnavlist) {
|
||||||
|
newm.classnavlist = apx.data.tpldata[tpldataname].classnavlist
|
||||||
|
}
|
||||||
|
return newm
|
||||||
|
})
|
||||||
|
console.log("menulminks", menulinks);
|
||||||
|
simplemobnavid.querySelector('.navlink').innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl[`simplemobnav${apx.data.tpldata[tpldataname].navtpl}`],
|
||||||
|
{ id, links: menulinks }
|
||||||
|
);
|
||||||
|
document.getElementById("loading").classList.add("hidden");
|
||||||
|
console.log(`Request to show screen ${ctx.link}`);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.simplemobnav.action = (id, link, action, wconame) => {
|
||||||
|
/**
|
||||||
|
* Manage action per menu
|
||||||
|
* if navigation then it just propagate wco-link in all the wco-name component
|
||||||
|
*/
|
||||||
|
if (action == "navigation") {
|
||||||
|
document.getElementById(id).setAttribute("wco-link", link);
|
||||||
|
/*document.getElementById(id).querySelectorAll("[wco-name]").forEach(lnk => {
|
||||||
|
console.log("lnk:",lnk)
|
||||||
|
console.log(link)
|
||||||
|
lnk.setAttribute('wco-link', link)
|
||||||
|
console.log(link,lnk)
|
||||||
|
});*/
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!apx[wconame]) {
|
||||||
|
console.log(`%c⚠️ warning:%c this requested compoment ${wconame}} does not exist`);
|
||||||
|
}
|
||||||
|
if (!apx[wconame][action]) {
|
||||||
|
console.log(`%c⚠️ warning:%c this function apx.${wconame}}.${action} does not exist`);
|
||||||
|
}
|
||||||
|
apx[wconame][action]();
|
||||||
|
}
|
||||||
|
|
||||||
|
apx.simplemobnav.reload = () => {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
apx.simplemobnav.checktpldataname = (tpldataname) => {
|
||||||
|
/**
|
||||||
|
* This is to help dev to build a correct json file
|
||||||
|
*/
|
||||||
|
if (!apx.data.tpldata[tpldataname]) {
|
||||||
|
console.log(`%c⚠️ warning:%c ${tpldataname} does not exist in localstorage tpldata `, 'color:red;')
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
const mandatoryprop = ["contentwconame", "contentid", "profilmenu", "links"]
|
||||||
|
let missingprop = ""
|
||||||
|
mandatoryprop.forEach(p => {
|
||||||
|
if (!apx.data.tpldata[tpldataname][p]) {
|
||||||
|
missingprop += ` ${p}`
|
||||||
|
}
|
||||||
|
});
|
||||||
|
if (missingprop !== "") {
|
||||||
|
console.log(`%c⚠️ warning:%c Missing property(ies) in ${tpldataname}: ${missingprop} `, 'color:red;')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
};
|
14
wco/testapi/nav_fr.json
Normal file
14
wco/testapi/nav_fr.json
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
{ "wcocaller":"testapi",
|
||||||
|
"logo": { "src": "/src/static/img/logo/logoentete.webp", "alt": "smatchit" },
|
||||||
|
"claim": "Never miss an opportunity",
|
||||||
|
"idhome":"home",
|
||||||
|
"links": [
|
||||||
|
{
|
||||||
|
"id": "home",
|
||||||
|
"label": "Runner",
|
||||||
|
"textlink": "de test",
|
||||||
|
"tpl": "home",
|
||||||
|
"next": ["home"]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
12
wco/testapi/screenhome_fr.mustache
Normal file
12
wco/testapi/screenhome_fr.mustache
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<div class="space-y-6 text-center">
|
||||||
|
<h2>Liste de test à lancer</h2>
|
||||||
|
<p>
|
||||||
|
<a class="" onclick="alert('test1')">Test1</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="" onclick="alert('test2')">Test2</a>
|
||||||
|
</p>
|
||||||
|
<p>
|
||||||
|
<a class="" onclick="alert('test3')">Test3</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
462
wco/testapi/test2.js
Normal file
462
wco/testapi/test2.js
Normal file
File diff suppressed because one or more lines are too long
922
wco/testapi/testapi.js
Normal file
922
wco/testapi/testapi.js
Normal file
@@ -0,0 +1,922 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.testapi = {};
|
||||||
|
apx.testapi.getdata={}
|
||||||
|
apx.testapi.getdata.home=()=>{return {}}
|
||||||
|
|
||||||
|
|
||||||
|
apx.testapi.deleteitm = async (tribe, objectname, apxid, primaryid) => {
|
||||||
|
// example: apx.testapi.deleteitm("smatchit","sirets","siret","91365310100018")
|
||||||
|
const delitm = {
|
||||||
|
method: "delete",
|
||||||
|
url: `/api/apxtri/odmdb/itm/${tribe}/${objectname}/${apxid}/${primaryid}`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const delres = await axios(delitm);
|
||||||
|
console.log(delres);
|
||||||
|
};
|
||||||
|
apx.testapi.getitm = async (tribe, objectname, primaryid) => {
|
||||||
|
console.log('get ',`/api/apxtri/odmdb/itm/${tribe}/${objectname}/${primaryid}`)
|
||||||
|
const getitm = {
|
||||||
|
method: "get",
|
||||||
|
url: `/api/apxtri/odmdb/itm/${tribe}/${objectname}/${primaryid}`,
|
||||||
|
data: {},
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const repitm = await axios(getitm);
|
||||||
|
if (repitm.status == 200) {
|
||||||
|
if (!apx.data.itm) apx.data.itm = {};
|
||||||
|
if (!apx.data.itm[objectname]) apx.data.itm[objectname] = {};
|
||||||
|
apx.data.itm[objectname][primaryid] = repitm.data.data;
|
||||||
|
apx.save();
|
||||||
|
return repitm.data.data;
|
||||||
|
} else {
|
||||||
|
return repitm;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.additm = async (tribe, objectname, data) => {
|
||||||
|
const additm = {
|
||||||
|
method: "post",
|
||||||
|
url: `/api/apxtri/odmdb/itm/${tribe}/${objectname}`,
|
||||||
|
data: data,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
return await axios(additm);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testbase = async () => {
|
||||||
|
console.log("run")
|
||||||
|
//console.log(await apx.testapi.getitm("smatchit","seekers","philc"))
|
||||||
|
//console.log(await apx.testapi.getitm("smatchit","persons","philc"))
|
||||||
|
//console.log(await apx.testapi.getitm("smatchit","quizz","seekerknowhow_en"))
|
||||||
|
const getinfo = await axios.put(
|
||||||
|
`/api/smatchit/jobads/interviewers`,
|
||||||
|
{ jobstepids:["philc_f46b2d7e-e242-421f-802b-d8d4d02a2000_0"] },
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
);
|
||||||
|
console.log(getinfo);
|
||||||
|
//await apx.testapi.deleteitm("smatchit","sirets","siret","34921281100021")
|
||||||
|
//apx.testapi.testaddsiret();
|
||||||
|
//apx.testapi.testgetlocaldb("smatchit","smatchapp","index","anonymous");
|
||||||
|
//console.log(await apx.testapi.getitm("smatchit","quizz","seekermbti"))
|
||||||
|
|
||||||
|
/*await apx.testapi.deleteitm(
|
||||||
|
"smatchit",
|
||||||
|
"jobads",
|
||||||
|
"jobadid",
|
||||||
|
"4570dadb-949b-4f6e-9d25-f389336055c1"
|
||||||
|
);*/
|
||||||
|
//apx.testapi.testaddjobads("4570dadb-949b-4f6e-9d25-f389336055c6");
|
||||||
|
//this include jobad matching
|
||||||
|
//apx.testapi.testpublish("4570dadb-949b-4f6e-9d25-f389336055c6");
|
||||||
|
|
||||||
|
//apx.testapi.testunpublish("4570dadb-949b-4f6e-9d25-f389336055ca")
|
||||||
|
//apx.testapi.testseekerinfoforecruiter(["13cdd66c-7a22-470c-b3c9-0ba1589a1db8"],"philc")
|
||||||
|
// test jobsteps process
|
||||||
|
//refresh user data apx.testapi.getItem
|
||||||
|
//match jobads to currentuser:
|
||||||
|
/*apx.testapi.testseekerjobstepprocess(
|
||||||
|
apx.data.headers.xalias,
|
||||||
|
"4570dadb-949b-4f6e-9d25-f389336055c5"
|
||||||
|
);
|
||||||
|
*/
|
||||||
|
//connected as a seeker run to apply:
|
||||||
|
//apx.testapi.testjobstepsstateapply("4570dadb-949b-4f6e-9d25-f389336055c5");
|
||||||
|
|
||||||
|
//apx.testapi.testnotificationsendmail(["phc@ndda.fr"],"requestregistration",{email:"phc@ndda.fr",firstname:"phil", lastname:"coco"})
|
||||||
|
//apx.testapi.testseekeronboardingmatching("philc")
|
||||||
|
//apx.testapi.testbooking({ date: "2024-08-01", start: "08:30", numberofslot: 2 });
|
||||||
|
/*await apx.testapi.invitedtoapply(
|
||||||
|
"paulseek",
|
||||||
|
"4570dadb-949b-4f6e-9d25-f389336055c2",
|
||||||
|
"philc"
|
||||||
|
);*/
|
||||||
|
//apx.testapi.testrecruiteremailtoalias();
|
||||||
|
};
|
||||||
|
apx.testapi.testrecruiteremailtoalias = () => {
|
||||||
|
console.log(
|
||||||
|
"you must be a recruiter to run this request, it add alias to jobsteps where it is empty"
|
||||||
|
);
|
||||||
|
axios.get("/api/smatchit/recruiters/emailtoaliasjobstep", {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => console.log(rep))
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.testapi.invitedtoapply = (seeker, jobadid) => {
|
||||||
|
axios
|
||||||
|
.put(
|
||||||
|
"/api/smatchit/jobads/invitedtoapply",
|
||||||
|
{ seeker: seeker, jobadid: jobadid },
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
)
|
||||||
|
.then((rep) => console.log(rep))
|
||||||
|
.catch((err) => {
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.testapi.testseekerjobstepprocess = (seeker, jobadid) => {
|
||||||
|
console.log('charge seeker info"');
|
||||||
|
//check in seeker jobadmatchscore the jobadid exist
|
||||||
|
apx.testapi.getitm("smatchit", "persons", seeker);
|
||||||
|
if (!apx.data.headers.xprofils.includes("seekers")) {
|
||||||
|
alert("Sorry must be a seeker to apply");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
apx.testapi.getitm("smatchit", "seekers", seeker);
|
||||||
|
//await apx.testapi.testseekeronboardingmatching(seeker);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testbook = () => {
|
||||||
|
const test = {
|
||||||
|
availableslot: {
|
||||||
|
"2024-08-10": {
|
||||||
|
"08:00": "D",
|
||||||
|
"08:30": "D",
|
||||||
|
"09:00": "D",
|
||||||
|
"09:30": "D",
|
||||||
|
"10:00": "D",
|
||||||
|
"10:30": "D",
|
||||||
|
"11:00": "D",
|
||||||
|
"11:30": "D",
|
||||||
|
"12:00": "D",
|
||||||
|
"12:30": "D",
|
||||||
|
"13:00": "D",
|
||||||
|
"13:30": "D",
|
||||||
|
"14:00": "D",
|
||||||
|
"14:30": "D",
|
||||||
|
"15:00": "D",
|
||||||
|
"15:30": "D",
|
||||||
|
"16:00": "D",
|
||||||
|
"16:30": "D",
|
||||||
|
"17:00": "D",
|
||||||
|
"17:30": "D",
|
||||||
|
"18:00": "D",
|
||||||
|
"18:30": "D",
|
||||||
|
"19:00": "D",
|
||||||
|
},
|
||||||
|
"2024-08-11": {
|
||||||
|
"08:00": "D",
|
||||||
|
"08:30": "D",
|
||||||
|
"09:00": "D",
|
||||||
|
"09:30": "D",
|
||||||
|
"10:00": "D",
|
||||||
|
"10:30": "D",
|
||||||
|
"11:00": "D",
|
||||||
|
"11:30": "D",
|
||||||
|
"12:00": "D",
|
||||||
|
"12:30": "D",
|
||||||
|
"13:00": "D",
|
||||||
|
"13:30": "D",
|
||||||
|
"14:00": "D",
|
||||||
|
"14:30": "D",
|
||||||
|
"15:00": "D",
|
||||||
|
"15:30": "D",
|
||||||
|
"16:00": "D",
|
||||||
|
"16:30": "D",
|
||||||
|
"17:00": "D",
|
||||||
|
"17:30": "D",
|
||||||
|
"18:00": "D",
|
||||||
|
"18:30": "D",
|
||||||
|
"19:00": "D",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
console.log(test.availableslot && Object.keys(test.availableslot).length > 0);
|
||||||
|
if (
|
||||||
|
test.availableslot &&
|
||||||
|
Object.keys(test.availableslot).length > 0 &&
|
||||||
|
dayjs(Object.keys(test.availableslot).sort()[0]) > dayjs()
|
||||||
|
) {
|
||||||
|
console.log("PASSe etape act.interviewersetjobstep");
|
||||||
|
} else {
|
||||||
|
console.log("passe pas");
|
||||||
|
}
|
||||||
|
const s = { date: "2024-08-12", start: "08:00" };
|
||||||
|
console.log(
|
||||||
|
dayjs(`${s.date} ${s.start}`)
|
||||||
|
.add(30 * 2, "minutes")
|
||||||
|
.format("HH:mm")
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testbooking = (slottime) => {
|
||||||
|
const tirage = (probabilites) => {
|
||||||
|
//distribution {A:0.7,B:0.3} return in 70% A else B
|
||||||
|
const entries = Object.entries(probabilites);
|
||||||
|
const cumulativeProbabilities = entries.reduce((acc, [value, prob]) => {
|
||||||
|
const prevProb = acc.length > 0 ? acc[acc.length - 1][1] : 0;
|
||||||
|
return [...acc, [value, prevProb + prob]];
|
||||||
|
}, []);
|
||||||
|
const random = Math.random();
|
||||||
|
return cumulativeProbabilities.find(([value, prob]) => random < prob)[0];
|
||||||
|
};
|
||||||
|
const getlistslottime = (startslot, endslot, interval) => {
|
||||||
|
const timearray = [];
|
||||||
|
let encours = dayjs("2020-01-01 " + startslot);
|
||||||
|
const end = dayjs("2020-01-01 " + endslot);
|
||||||
|
console.log(encours);
|
||||||
|
console.log(end);
|
||||||
|
console.log(encours.isBefore(end));
|
||||||
|
while (encours.isBefore(end)) {
|
||||||
|
timearray.push(encours.format("HH:mm"));
|
||||||
|
encours = encours.add(interval, "minutes");
|
||||||
|
}
|
||||||
|
return timearray;
|
||||||
|
};
|
||||||
|
const simuleinterviewer = (interviewer) => {
|
||||||
|
// const interviewer = {startslot: "08:00",endslot: "17:00",interval: 30, availableslot: {}, };
|
||||||
|
// set futur
|
||||||
|
const arraytime = getlistslottime(
|
||||||
|
interviewer.startslot,
|
||||||
|
interviewer.endslot,
|
||||||
|
interviewer.interval
|
||||||
|
);
|
||||||
|
for (let i = 1; i < 3; i++) {
|
||||||
|
const newd = dayjs().add(i, "days").format("YYYY-MM-DD");
|
||||||
|
interviewer.availableslot[newd] = {};
|
||||||
|
arraytime.forEach(
|
||||||
|
(t) => (interviewer.availableslot[newd][t] = tirage({ F: 0.7, D: 0.3 }))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return interviewer;
|
||||||
|
};
|
||||||
|
//simule un agenda
|
||||||
|
//console.log(simuleinterviewer({startslot: "08:00",endslot: "17:00",interval: 30, availableslot: {} }))
|
||||||
|
|
||||||
|
const interviewer = {
|
||||||
|
startslot: "08:00",
|
||||||
|
endslot: "17:00",
|
||||||
|
interval: 30,
|
||||||
|
availableslot: {
|
||||||
|
"2024-07-31": {
|
||||||
|
"08:00": "F",
|
||||||
|
"08:30": "F",
|
||||||
|
"09:00": "F",
|
||||||
|
"09:30": "F",
|
||||||
|
"10:00": "D",
|
||||||
|
"10:30": "D",
|
||||||
|
"11:00": "F",
|
||||||
|
"11:30": "D",
|
||||||
|
"12:00": "F",
|
||||||
|
"12:30": "F",
|
||||||
|
"13:00": "D",
|
||||||
|
"13:30": "F",
|
||||||
|
"14:00": "D",
|
||||||
|
"14:30": "F",
|
||||||
|
"15:00": "F",
|
||||||
|
"15:30": "D",
|
||||||
|
"16:00": "D",
|
||||||
|
"16:30": "D",
|
||||||
|
},
|
||||||
|
"2024-08-01": {
|
||||||
|
"08:00": "F",
|
||||||
|
"08:30": "F",
|
||||||
|
"09:00": "F",
|
||||||
|
"09:30": "D",
|
||||||
|
"10:00": "F",
|
||||||
|
"10:30": "F",
|
||||||
|
"11:00": "F",
|
||||||
|
"11:30": "D",
|
||||||
|
"12:00": "D",
|
||||||
|
"12:30": "F",
|
||||||
|
"13:00": "F",
|
||||||
|
"13:30": "D",
|
||||||
|
"14:00": "F",
|
||||||
|
"14:30": "D",
|
||||||
|
"15:00": "F",
|
||||||
|
"15:30": "F",
|
||||||
|
"16:00": "F",
|
||||||
|
"16:30": "F",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
//{ date: "2024-08-01", start: "08:30", numberofslot: 2 }
|
||||||
|
let matchslottime = true;
|
||||||
|
matchslottime = interviewer.availableslot[slottime.date] ? true : false;
|
||||||
|
matchslottime = interviewer.availableslot[slottime.date][slottime.start]
|
||||||
|
? true
|
||||||
|
: false;
|
||||||
|
matchslottime =
|
||||||
|
interviewer.availableslot[slottime.date][slottime.start] == "F";
|
||||||
|
let slottobook = [slottime.start];
|
||||||
|
console.log("1er slot:", matchslottime);
|
||||||
|
if (matchslottime) {
|
||||||
|
const slotarray = Object.keys(
|
||||||
|
interviewer.availableslot[slottime.date]
|
||||||
|
).sort();
|
||||||
|
console.log(slotarray);
|
||||||
|
for (
|
||||||
|
i = slotarray.indexOf(slottime.start) + 1;
|
||||||
|
i < slottime.numberofslot + 1;
|
||||||
|
i++
|
||||||
|
) {
|
||||||
|
matchslottime = interviewer.availableslot[slottime.date][i] != "F";
|
||||||
|
slottobook.push(slotarray[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
console.log(slottobook);
|
||||||
|
if (matchslottime) {
|
||||||
|
slottobook.forEach((t) => {
|
||||||
|
interviewer.availableslot[slottime.date][t] = "J";
|
||||||
|
});
|
||||||
|
console.log("booking", interviewer.availableslot[slottime.date]);
|
||||||
|
} else {
|
||||||
|
console.log("no booking:");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testseekeronboardingmatching = async (seeker) => {
|
||||||
|
await axios
|
||||||
|
.get(`/api/smatchit/seekers/onboarding/${seeker}`, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
})
|
||||||
|
.then((rep) => {
|
||||||
|
//alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
//alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testnotificationsendmail = async (emails, template, data) => {
|
||||||
|
await axios
|
||||||
|
.post(
|
||||||
|
`/api/apxtri/notifications/sendmail/smatchit/${template}`,
|
||||||
|
{ emails, data },
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testseekerinfoforecruiter = async (jobadids, aliasrecruiter) => {
|
||||||
|
// must be connectered as aliasrecruitrer or to have adminrecruiter profil for the same siret than jobads
|
||||||
|
await axios
|
||||||
|
.put(
|
||||||
|
`/api/smatchit/seekers/infoseeker/${aliasrecruiter}`,
|
||||||
|
{ jobadids: jobadids },
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.testapi.testjobstepsstateapply = async (jobadid) => {
|
||||||
|
//create a jobstep
|
||||||
|
console.log("getitm");
|
||||||
|
const jobad = await apx.testapi.getitm("smatchit", "jobads", jobadid);
|
||||||
|
if (jobad.status) {
|
||||||
|
console.log("issue to get jobad", getjobad);
|
||||||
|
}
|
||||||
|
console.log(jobad);
|
||||||
|
const jobstep = {
|
||||||
|
jobadid: jobadid,
|
||||||
|
recruiter: jobad.recruiter,
|
||||||
|
interviewer: jobad.jobsteps[0].interviewer,
|
||||||
|
jobstepid: `${apx.data.headers.xalias}_${jobadid}_0`,
|
||||||
|
seeker: apx.data.headers.xalias,
|
||||||
|
state: "apply",
|
||||||
|
};
|
||||||
|
console.log(jobstep);
|
||||||
|
//const apply = await apx.testapi.additm("smatchit", "jobsteps", jobstep);
|
||||||
|
//console.log(apply);
|
||||||
|
console.log(`/api/smatchit/jobads/jobstepstodo/${jobstep.jobstepid}`);
|
||||||
|
const jobstepstodo = await axios.put(
|
||||||
|
`/api/smatchit/jobads/jobstepstodo/${jobstep.jobstepid}`,
|
||||||
|
{ state: "apply" },
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
);
|
||||||
|
console.log(jobstepstodo);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testunpublish = async (jobadid) => {
|
||||||
|
await axios
|
||||||
|
.put(
|
||||||
|
`/api/smatchit/jobads/archive/${jobadid}`,
|
||||||
|
{},
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
apx.testapi.testpublish = async (jobadid) => {
|
||||||
|
await axios
|
||||||
|
.put(
|
||||||
|
`/api/smatchit/jobads/publish/${jobadid}`,
|
||||||
|
{},
|
||||||
|
{ headers: apx.data.headers }
|
||||||
|
)
|
||||||
|
.then((rep) => {
|
||||||
|
//alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
//alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testaddjobads = (jobadid) => {
|
||||||
|
/*const jobad = {
|
||||||
|
jobadid: jobadid,
|
||||||
|
state: "inprocess",
|
||||||
|
siret: "34921281100021",
|
||||||
|
category: "gaming",
|
||||||
|
jobtitle: "cuisinier",
|
||||||
|
jobdisplayname: "Cuisinier poissonier",
|
||||||
|
contactrecruiter: "philc",
|
||||||
|
state: "draft",
|
||||||
|
recruiter: "philc",
|
||||||
|
jobadtitle: "Cuisinier poissonier",
|
||||||
|
candidateexperience: "4to12",
|
||||||
|
specificskills: {
|
||||||
|
reserveeventticket: 2,
|
||||||
|
teamcoordination: 3,
|
||||||
|
aftersellservice: 2,
|
||||||
|
},
|
||||||
|
languageskills: {
|
||||||
|
french: 4,
|
||||||
|
english: 2,
|
||||||
|
},
|
||||||
|
knowhows: ["service", "reactif", "inspire"],
|
||||||
|
dealbreaker: ["ponctualitty"],
|
||||||
|
jobtype: ["cdi", "cdd", "interim", "freelance", "stage", "alternance"],
|
||||||
|
fulltime: "fullandpartial",
|
||||||
|
remote: 100,
|
||||||
|
workingdayshours: [
|
||||||
|
{
|
||||||
|
day: "Tuesday",
|
||||||
|
hours: [
|
||||||
|
{ start: "09:00", end: "00:00" },
|
||||||
|
{ start: "11:00", end: "00:00" },
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salary: 100000,
|
||||||
|
salarydevise: "€",
|
||||||
|
salaryunit: "peryear",
|
||||||
|
jobsteps: [
|
||||||
|
{
|
||||||
|
jobsteptype: "firstcontact",
|
||||||
|
interviewer: "philc",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobsteptype: "testaptitude",
|
||||||
|
interviewer: "interviewerpeter",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobsteptype: "interview",
|
||||||
|
interviewer: "philc",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
"Hello, About Job \\nLorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
|
||||||
|
};*/
|
||||||
|
const jobad = {
|
||||||
|
jobadid: jobadid,
|
||||||
|
state: "publish",
|
||||||
|
siret: "42818175400014",
|
||||||
|
category: "Automobile",
|
||||||
|
jobtitle: "vendorretail",
|
||||||
|
jobdisplayname: "Épicière",
|
||||||
|
jobadtitle: "Épicière Test",
|
||||||
|
candidateexperience: "12to15",
|
||||||
|
specificskills: {
|
||||||
|
productvalidity: 3,
|
||||||
|
trackproduct: 1,
|
||||||
|
informclient: 1,
|
||||||
|
chooseintervention: 0,
|
||||||
|
reserveeventticket: 2,
|
||||||
|
teamcoordination: 3,
|
||||||
|
aftersellservice: 3,
|
||||||
|
adaptsellstrategy: 3,
|
||||||
|
createsellarguments: 3,
|
||||||
|
technicalexpertise: 3,
|
||||||
|
instructemployee: 3,
|
||||||
|
deliverorder: 3,
|
||||||
|
},
|
||||||
|
knowhows: ["teamwork", "accurate", "service", "listening", "fits"],
|
||||||
|
dealbreaker: ["ponctualitty"],
|
||||||
|
languageskills: {
|
||||||
|
french: 4,
|
||||||
|
english: 1,
|
||||||
|
},
|
||||||
|
jobtype: ["cdi", "freelance", "stage"],
|
||||||
|
fulltime: "full",
|
||||||
|
remote: 50,
|
||||||
|
workingdayshours: [
|
||||||
|
{
|
||||||
|
day: "Tuesday",
|
||||||
|
hours: [
|
||||||
|
{
|
||||||
|
start: "10",
|
||||||
|
end: "00",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
start: "19",
|
||||||
|
end: "00",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salary: 100000,
|
||||||
|
salarydevise: "€",
|
||||||
|
salaryunit: "permonth",
|
||||||
|
jobsteps: [
|
||||||
|
{
|
||||||
|
jobsteptype: "firstcontact",
|
||||||
|
interviewer: "philc",
|
||||||
|
email: "smatchitdev@mailsac.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobsteptype: "testaptitude",
|
||||||
|
interviewer: "philc",
|
||||||
|
email: "smatchitdev@mailsac.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobsteptype: "interview",
|
||||||
|
interviewer: "philc",
|
||||||
|
email: "smatchitdev@mailsac.com",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
jobsteptype: "call",
|
||||||
|
interviewer: "philc",
|
||||||
|
email: "smatchitdev@mailsac.com",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
description:
|
||||||
|
'Contrary to popular belief, Lorem Ipsum is not simply random text. It is rooted in a piece of classical Latin literature from 45 BC, over 2000 years old. Richard McClintock, a Latin professor at Hampden-Sydney College in Virginia, looked up one of the more obscure Latin words, consectetur, from a Lorem Ipsum passage, and going through the cites of the word in classical literature, discovered the undoubtable source. Lorem Ipsum comes from sections 1.10.32 and 1.10.33 of "de Finibus Bonorum et Malorum" (The Extremes of Good and Evil) by Cicero, written in 45 BC. This book is a treatise on the theory of ethics, trendy during the Renaissance. The first line of Lorem Ipsum, "Lorem ipsum dolor sit amet..", comes from a line in section 1.10.32.',
|
||||||
|
recruiter: "philc",
|
||||||
|
urgenthiring: false,
|
||||||
|
jobadmbti: "",
|
||||||
|
jobadlocation: [],
|
||||||
|
critrulesalary: "",
|
||||||
|
owner: "philc",
|
||||||
|
dt_create: "2024-08-02T06:22:25.720Z",
|
||||||
|
};
|
||||||
|
console.log(apx.testapi.additm("smatchit", "jobads", jobad));
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testaddsiret = () => {
|
||||||
|
const siret = {
|
||||||
|
siret: "34921281100021",
|
||||||
|
businessname: "SAAS",
|
||||||
|
code_naf: "92.3H",
|
||||||
|
tradename: "SAAS",
|
||||||
|
category: "gaming",
|
||||||
|
billinglocation: {
|
||||||
|
type: "housenumber",
|
||||||
|
housenumber: "25",
|
||||||
|
street: "Rue de Ponthieu",
|
||||||
|
name: "25 Rue de Ponthieu",
|
||||||
|
label: "25 Rue de Ponthieu 75008 Paris",
|
||||||
|
postcode: "75008",
|
||||||
|
citycode: "75108",
|
||||||
|
city: "Paris",
|
||||||
|
position: {
|
||||||
|
properties: { longitude: 2.309055, latitude: 48.870756 },
|
||||||
|
},
|
||||||
|
context: "75, Paris, Île-de-France",
|
||||||
|
},
|
||||||
|
businesslocation: {
|
||||||
|
type: "housenumber",
|
||||||
|
housenumber: "25",
|
||||||
|
street: "Rue de Ponthieu",
|
||||||
|
name: "25 Rue de Ponthieu",
|
||||||
|
label: "25 Rue de Ponthieu 75008 Paris",
|
||||||
|
postcode: "75008",
|
||||||
|
citycode: "75108",
|
||||||
|
city: "Paris",
|
||||||
|
position: {
|
||||||
|
properties: { longitude: 2.309055, latitude: 48.870756 },
|
||||||
|
},
|
||||||
|
context: "75, Paris, Île-de-France",
|
||||||
|
},
|
||||||
|
website: "https://www.google.com",
|
||||||
|
socialnetworks: ["abcd"],
|
||||||
|
title: "Hello Compnay",
|
||||||
|
description: "lets good 👍 ",
|
||||||
|
agreetorespectnorms: true,
|
||||||
|
};
|
||||||
|
const addsiret = {
|
||||||
|
method: "post",
|
||||||
|
url: `/api/apxtri/odmdb/itm/smatchit/sirets`,
|
||||||
|
data: siret,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
axios(addsiret)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testdeviceid = () => {
|
||||||
|
function test() {
|
||||||
|
if (!navigator.mediaDevices || !navigator.mediaDevices.enumerateDevices) {
|
||||||
|
alert("enumerateDevices() not supported.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// List cameras and microphones.
|
||||||
|
|
||||||
|
navigator.mediaDevices
|
||||||
|
.enumerateDevices()
|
||||||
|
.then(function (devices) {
|
||||||
|
devices.forEach(function (device) {
|
||||||
|
alert(device.kind + ": " + device.label + " id = " + device.deviceId);
|
||||||
|
});
|
||||||
|
})
|
||||||
|
.catch(function (err) {
|
||||||
|
alert(err.name + ": " + err.message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
test();
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testdeletealias = async () => {
|
||||||
|
const aliastodel = ["totopoursup"];
|
||||||
|
aliastodel.forEach((a) => {
|
||||||
|
const axiosdel = {
|
||||||
|
method: "delete",
|
||||||
|
url: `/api/apxtri/pagans/person/smatchit/${a}`,
|
||||||
|
data: newseeker,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
axios(axiosdel)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testaddseeker = async () => {
|
||||||
|
const newseeker = {
|
||||||
|
alias: "philc",
|
||||||
|
email: "bhavesh@mailsac.com",
|
||||||
|
locationenabled: true,
|
||||||
|
notificationenabled: true,
|
||||||
|
seekstatus: "notlooking",
|
||||||
|
seekworkingyear: "4to8",
|
||||||
|
seekjobtitleexperience: ["chefpartie"],
|
||||||
|
seeklocation: [
|
||||||
|
{
|
||||||
|
type: "street",
|
||||||
|
housenumber: "",
|
||||||
|
street: "Igny",
|
||||||
|
name: "Igny",
|
||||||
|
label: "Igny 18200 La Perche",
|
||||||
|
postcode: "18200",
|
||||||
|
citycode: "18178",
|
||||||
|
city: "La Perche",
|
||||||
|
zoning: 3,
|
||||||
|
position: { properties: { longitude: 2.585915, latitude: 46.638536 } },
|
||||||
|
context: "18, Cher, Centre-Val de Loire",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
salaryexpectation: "2331",
|
||||||
|
salaryunit: "permonth",
|
||||||
|
salarydevise: "€",
|
||||||
|
mbti: { E: 1, N: 3, T: 0, nextq: 4, value: "INFP" },
|
||||||
|
};
|
||||||
|
const axiosaddseeker = {
|
||||||
|
method: "post",
|
||||||
|
url: `/api/apxtri/odmdb/itm/smatchit/seekers`,
|
||||||
|
data: newseeker,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const addseek = await axios(axiosaddseeker);
|
||||||
|
console.log("profil update", addseek);
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testremoveprofilrecruiter = async () => {
|
||||||
|
const user = "philc";
|
||||||
|
const axiosgetperson = {
|
||||||
|
method: "delete",
|
||||||
|
url: `/api/smatchit/persons/recruiters/${user}`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const axiosaddrecruiters = {
|
||||||
|
method: "put",
|
||||||
|
url: `/api/apxtri/pagans/person/smatchit`,
|
||||||
|
data: { addprofils: "recruiters", alias: user },
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const addrec = await axios(axiosaddrecruiters);
|
||||||
|
console.log("profil update", addrec);
|
||||||
|
const axiosperson = await axios(axiosgetperson);
|
||||||
|
if (axiosperson.status != 200) {
|
||||||
|
console.log("erreur getting person");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log("lecture person", axiosperson);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testaddprofilrecruiter = async () => {
|
||||||
|
const user = "philc";
|
||||||
|
const axiosgetperson = {
|
||||||
|
method: "get",
|
||||||
|
url: `/api/apxtri/odmdb/itm/smatchit/persons/${user}`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const axiosaddrecruiters = {
|
||||||
|
method: "put",
|
||||||
|
url: `/api/apxtri/pagans/person/smatchit`,
|
||||||
|
data: { addprofils: "recruiters", alias: user },
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const addrec = await axios(axiosaddrecruiters);
|
||||||
|
console.log("profil update", addrec);
|
||||||
|
const axiosperson = await axios(axiosgetperson);
|
||||||
|
if (axiosperson.status != 200) {
|
||||||
|
console.log("erreur getting person");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
console.log("lecture person", axiosperson);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.testapi.testseekerprofil = async () => {
|
||||||
|
alert("get person and modify ");
|
||||||
|
//get a person
|
||||||
|
const user = "philc";
|
||||||
|
const axiosgetperson = {
|
||||||
|
method: "get",
|
||||||
|
url: `/api/apxtri/odmdb/itm/smatchit/persons/${user}`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
const axiosputperson = {
|
||||||
|
method: "put",
|
||||||
|
url: `/api/apxtri/odmdb/itm/smatchit/persons/`,
|
||||||
|
data: {},
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
let userperson;
|
||||||
|
const axiosperson = await axios(axiosgetperson);
|
||||||
|
if (axiosperson.status != 200) {
|
||||||
|
console.log("erreur getting person");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
userperson = axiosperson.data.data;
|
||||||
|
console.log(userperson);
|
||||||
|
let updateperson = {
|
||||||
|
alias: user,
|
||||||
|
firebaseid: userperson.firebaseid + "test",
|
||||||
|
profils: ["seekers"],
|
||||||
|
};
|
||||||
|
axiosputperson.data = updateperson;
|
||||||
|
console.log(axiosputperson);
|
||||||
|
const axiosupdateperson = await axios(axiosputperson);
|
||||||
|
console.log("modification done", axiosupdateperson);
|
||||||
|
//alert("change again to remove test in firebase");
|
||||||
|
updateperson = {
|
||||||
|
alias: user,
|
||||||
|
firebaseid: userperson.firebaseid.replace(/test/g, ""),
|
||||||
|
};
|
||||||
|
axiosputperson.data = updateperson;
|
||||||
|
console.log(axiosputperson);
|
||||||
|
const axiosupdatepersonback = await axios(axiosputperson);
|
||||||
|
console.log(axiosupdatepersonback);
|
||||||
|
};
|
||||||
|
apx.testapi.testgetlocaldb = (tribe, appname, pagename, anonymous) => {
|
||||||
|
const testaxios = {
|
||||||
|
method: "get",
|
||||||
|
url: `/api/apxtri/wwws/updatelocaldb${anonymous}/${tribe}/${appname}/${pagename}/0`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
axios(testaxios)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testgetperson = () => {
|
||||||
|
const testaxios = {
|
||||||
|
method: "get",
|
||||||
|
url: `/api/apxtri/odmdb/itm/${apx.data.headers.xtribe}/persons/philc`,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
axios(testaxios)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testupdateseeker = () => {
|
||||||
|
apx.data.itm.seekers.jobadinvitedtoapply = [
|
||||||
|
"f46b2d7e-e242-421f-802b-d8d4d02a2004",
|
||||||
|
];
|
||||||
|
const testaxios = {
|
||||||
|
method: "put",
|
||||||
|
url: `/api/apxtri/odmdb/itm/${apx.data.headers.xtribe}/seekers`,
|
||||||
|
data: apx.data.itm.seekers,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
axios(testaxios)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.testapi.testbackend = () => {
|
||||||
|
const objsearch = {
|
||||||
|
apxid: [
|
||||||
|
"f46b2d7e-e242-421f-802b-d8d4d02a2000",
|
||||||
|
"f46b2d7e-e242-421f-802b-d8d4d02a2001",
|
||||||
|
"f46b2d7e-e242-421f-802b-d8d4d02a2002",
|
||||||
|
"f46b2d7e-e242-421f-802b-d8d4d02a2003",
|
||||||
|
],
|
||||||
|
fields: "all",
|
||||||
|
};
|
||||||
|
const objsearchaxios = {
|
||||||
|
method: "post",
|
||||||
|
url: `/api/apxtri/odmdb/searchitms/${apx.data.headers.xtribe}/jobads`,
|
||||||
|
data: objsearch,
|
||||||
|
headers: apx.data.headers,
|
||||||
|
};
|
||||||
|
|
||||||
|
axios(objsearchaxios)
|
||||||
|
.then((rep) => {
|
||||||
|
alert("success, check console");
|
||||||
|
console.log(rep);
|
||||||
|
})
|
||||||
|
.catch((err) => {
|
||||||
|
alert("err, check console");
|
||||||
|
console.log(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
/*
|
||||||
|
apx.testapi.apxlocal = {};
|
||||||
|
apx.testapi.apxlocal.setup = () => {
|
||||||
|
console.log("run apxlocal.setup");
|
||||||
|
apx.data.tpldata.apxlocal.categories.forEach((cat, i) => {
|
||||||
|
//Object.keys(apx.data[cat.categorie]).forEach((o)=>{apx.data.tpldata.apxlocal.categories[i].list=[]})
|
||||||
|
Object.keys(apx.data[cat.categorie]).forEach((o) => {
|
||||||
|
if (!apx.data.tpldata.apxlocal.categories[i].list.includes(o)) {
|
||||||
|
apx.data.tpldata.apxlocal.categories[i].list.push(o);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
document
|
||||||
|
.getElementsByName("apxlocal")
|
||||||
|
.forEach(
|
||||||
|
(e) =>
|
||||||
|
(e.innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl.apxlocal,
|
||||||
|
apx.data.tpldata.apxlocal
|
||||||
|
))
|
||||||
|
);
|
||||||
|
dscreen.refresh();
|
||||||
|
};
|
||||||
|
apx.readyafterupdate(apx.testapi.apxlocal.setup);
|
||||||
|
*/
|
33
wco/tracker/consentform.mustache
Normal file
33
wco/tracker/consentform.mustache
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
<style>
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-100px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.animate-fadeIn {
|
||||||
|
animation: fadeIn 2s ease-out;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
<div class="tracker bg-white rounded-lg shadow-lg p-6 w-full max-w-md md:max-w-lg lg:max-w-xl xl:max-w-2xl animate-fadeIn">
|
||||||
|
<!-- Texte d'information sur les cookies -->
|
||||||
|
<div class="mb-4">
|
||||||
|
<p class="text-sm text-gray-700">
|
||||||
|
{{introtext}}
|
||||||
|
<a href="{{CGU}}" target="_blank" class="text-blue-500 hover:underline">{{{CGUlabel}}}</a>
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<!-- Boutons d'action -->
|
||||||
|
<div class="flex flex-col space-y-2">
|
||||||
|
{{#btn}}
|
||||||
|
<button class="btn btn-primary w-full" onclick="apx.tracker.setconsent('{{tagid}}','{{action}}')">
|
||||||
|
{{{text}}}
|
||||||
|
</button>
|
||||||
|
{{/btn}}
|
||||||
|
</div>
|
||||||
|
</div>
|
19
wco/tracker/exampleform_en.json
Normal file
19
wco/tracker/exampleform_en.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"CGU": "https://smatchit.io/Smatchit_RGPD_app_web_fr.html",
|
||||||
|
"CGUlabel": "Terms of Service",
|
||||||
|
"introtext": "In accordance with the GDPR, we need your permission to store your personal data on this site. If you 'refuse all recording', you will not have access to the content with identification. If you wish to support us, your data may be shared with third parties. Please consult our terms and conditions for more information:",
|
||||||
|
"btn": [
|
||||||
|
{
|
||||||
|
"action": "acceptfullcookies",
|
||||||
|
"text": "I accept to support this site"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "acceptstatisticcookies",
|
||||||
|
"text": "I accept only to facilitate navigation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "refusecookies",
|
||||||
|
"text": "I refuse all recording"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
19
wco/tracker/exampleform_fr.json
Normal file
19
wco/tracker/exampleform_fr.json
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
{
|
||||||
|
"CGU": "https://smatchit.io/Smatchit_RGPD_app_web_fr.html",
|
||||||
|
"CGUlabel": "Conditions générales d'utilisation",
|
||||||
|
"introtext": "Conformément au RGPD, nous devons obtenir votre autorisation pour stocker vos données personnelles sur ce site. Si vous 'refusez tout enregistrement', vous n'aurez pas accès au contenu avec identification. Si vous souhaitez nous soutenir, vos données pourront être communiquées à des tiers. Consultez nos conditions pour en savoir plus :",
|
||||||
|
"btn": [
|
||||||
|
{
|
||||||
|
"action": "acceptfullcookies",
|
||||||
|
"text": "J'accepte pour soutenir ce site"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "acceptstatisticcookies",
|
||||||
|
"text": "J'accepte uniquement pour faciliter la navigation"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"action": "refusecookies",
|
||||||
|
"text": "Je refuse tout enregistrement"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
140
wco/tracker/tracker.js
Normal file
140
wco/tracker/tracker.js
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
var apx = apx || {};
|
||||||
|
apx.tracker = {};
|
||||||
|
apx.tracker.getinfodevice = async () => {
|
||||||
|
const device = {};
|
||||||
|
//console.log(navigator);
|
||||||
|
device.useragent = navigator.userAgent || navigator.vendor || window.opera;
|
||||||
|
device.typedevice = /iPad/i.test(device.useragent) ? "ipad" : "";
|
||||||
|
device.typedevice = /iPhone/i.test(device.useragent) ? "iphone" : "";
|
||||||
|
device.typedevice = /iPod/i.test(device.useragent) ? "ipod" : "";
|
||||||
|
device.typedevice = /Android/i.test(device.useragent) ? "android" : "";
|
||||||
|
device.typedevice =
|
||||||
|
device.typedevice == "" &&
|
||||||
|
/Windows NT|Macintosh|Linux/i.test(device.useragent)
|
||||||
|
? "PC"
|
||||||
|
: "";
|
||||||
|
console.log(
|
||||||
|
"test linux",
|
||||||
|
/Windows NT|Macintosh|Linux/i.test(device.useragent)
|
||||||
|
);
|
||||||
|
device.type =
|
||||||
|
device.type === "" && /Mobi/i.test(device.userAgent) ? "mobile" : "";
|
||||||
|
device.os = /Windows NT/i.test(device.useragent) ? "windows" : "";
|
||||||
|
device.os = /Macintosh/i.test(device.useragent) ? "mac" : "";
|
||||||
|
device.os = /Linux/i.test(device.useragent) ? "linux" : "";
|
||||||
|
const ipinfo = await axios.get("https://ipinfo.io/json");
|
||||||
|
console.log(ipinfo);
|
||||||
|
if (
|
||||||
|
ipinfo &&
|
||||||
|
ipinfo.data &&
|
||||||
|
ipinfo.data !== null &&
|
||||||
|
typeof ipinfo.data === "object" &&
|
||||||
|
Object.keys(ipinfo.data).length > 0
|
||||||
|
) {
|
||||||
|
if (ipinfo.data.ip) device.ip = ipinfo.data.ip;
|
||||||
|
if (ipinfo.data.city) device.city = ipinfo.data.city;
|
||||||
|
if (ipinfo.data.country) device.country = ipinfo.data.country;
|
||||||
|
}
|
||||||
|
device.screenWidth = window.screen.width;
|
||||||
|
device.screenHeight = window.screen.height;
|
||||||
|
const connection =
|
||||||
|
navigator.connection ||
|
||||||
|
navigator.mozConnection ||
|
||||||
|
navigator.webkitConnection;
|
||||||
|
if (connection) {
|
||||||
|
device.connection = `${connection.effectiveType}_${connection.downlink} Mbps`;
|
||||||
|
}
|
||||||
|
device.lang = navigator.language;
|
||||||
|
device.plugins = Array.from(navigator.plugins).map((plugin) => plugin.name);
|
||||||
|
//console.log(device)
|
||||||
|
return device;
|
||||||
|
};
|
||||||
|
apx.tracker.hit = (data) => {
|
||||||
|
if (!data.consentcookie && localStorage.getItem("consentcookie"))
|
||||||
|
data.consentcookie = localStorage.getItem("consentcookie");
|
||||||
|
if (!data.lasttm && localStorage.getItem("lasttm"))
|
||||||
|
data.lasttm = localStorage.getItem("lasttm");
|
||||||
|
if (!data.xuuid && localStorage.getItem("xuuid"))
|
||||||
|
data.xuuid = localStorage.getItem("xuuid");
|
||||||
|
if (!data.xapp && localStorage.getItem("xapp"))
|
||||||
|
data.xapp = localStorage.getItem("xapp");
|
||||||
|
if (apx.data.headers.xalias != "anonymous")
|
||||||
|
data.alias = apx.data.headers.xalias;
|
||||||
|
let missing = "";
|
||||||
|
[("xapp", "srckey", "xuuid", "lasttm", "consentcookie")].forEach((k) => {
|
||||||
|
missing += !data[k] ? `${k},` : "";
|
||||||
|
});
|
||||||
|
if (missing !== "") {
|
||||||
|
console.log(`Check your trktag ${missing} are missing`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let urltrack = "/trk/cdn/trkret/empty.json?";
|
||||||
|
Object.keys(data).forEach((d) => {
|
||||||
|
urltrack += `d=${data[d]}&`;
|
||||||
|
});
|
||||||
|
urltrack = urltrack.slice(0, -1);
|
||||||
|
//console.log(urltrack);
|
||||||
|
axios.get(urltrack);
|
||||||
|
};
|
||||||
|
apx.tracker.load = () => {
|
||||||
|
if (!localStorage.getItem("consentcookie")) {
|
||||||
|
document
|
||||||
|
.querySelectorAll('div:not([wco-name="tracker"])')
|
||||||
|
.forEach((el) => {
|
||||||
|
el.classList.add("opacity-40");
|
||||||
|
el.style.pointerEvents = "auto";
|
||||||
|
});
|
||||||
|
document.querySelectorAll("[wco-name='tracker']").forEach((el) => {
|
||||||
|
const tpldata = `${apx.data.pagename}_${el.id}_trackerconsentform`;
|
||||||
|
if (!apx.data.tpldata[tpldata] || !apx.data.tpldata[tpldata].introtext) {
|
||||||
|
console.log(` ${tpldata} does not exist check your file`);
|
||||||
|
} else {
|
||||||
|
el.innerHTML = Mustache.render(
|
||||||
|
apx.data.tpl.trackerconsentform,
|
||||||
|
apx.data.tpldata[tpldata]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// test if last loading was <10minutes then send a new tag
|
||||||
|
const tm = dayjs().valueOf();
|
||||||
|
const lasttmtxt = localStorage.getItem("lasttm");
|
||||||
|
const lasttm = lasttmtxt ? Number(lasttmtxt) : null;
|
||||||
|
//console.log(lasttm, tm, tm - lasttm > 10 * 60 * 1000);
|
||||||
|
if (lasttm && tm - lasttm > 10 * 60 * 1000) {
|
||||||
|
localStorage.setItem("lasttm", tm);
|
||||||
|
apx.tracker.hit({ srckey: "visitwwws" });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
apx.tracker.setconsent = async (tagid, choice) => {
|
||||||
|
//localStorage.setItem("consentcookie", choice);
|
||||||
|
if (!localStorage.getItem("xuuid")) {
|
||||||
|
const uuid = ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, (c) =>
|
||||||
|
(
|
||||||
|
c ^
|
||||||
|
(crypto.getRandomValues(new Uint8Array(1))[0] & (15 >> (c / 4)))
|
||||||
|
).toString(16)
|
||||||
|
);
|
||||||
|
localStorage.setItem("xuuid", uuid);
|
||||||
|
apx.data.headers.xuuid = uuid;
|
||||||
|
const tm = dayjs().valueOf();
|
||||||
|
localStorage.setItem("lasttm", tm);
|
||||||
|
apx.save();
|
||||||
|
}
|
||||||
|
if (["acceptstatisticcookies", "acceptfullcookies"].includes(choice)) {
|
||||||
|
const infodevice = await apx.tracker.getinfodevice();
|
||||||
|
//console.log(infodevice);
|
||||||
|
axios.post(`/api/apxtri/trackings/newdevice`, infodevice, {
|
||||||
|
headers: apx.data.headers,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
apx.tracker.hit({ srckey: "firstvisitwwws" });
|
||||||
|
document.querySelectorAll('div:not([wco-name="tracker"])').forEach((el) => {
|
||||||
|
el.classList.remove("opacity-40");
|
||||||
|
el.style.pointerEvents = "auto";
|
||||||
|
});
|
||||||
|
document.getElementById(tagid).classList.add("hidden");
|
||||||
|
};
|
||||||
|
|
||||||
|
apx.readyafterupdate(apx.tracker.load);
|
112
wwws/admin/src/admindata_fr.html
Normal file
112
wwws/admin/src/admindata_fr.html
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" data-theme="apxtri" class="h-full bg-neutral">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Admin Template</title>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<meta
|
||||||
|
content="saas, apxtri"
|
||||||
|
name="keywords"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
content="A saas solution that you can host at home to master your data"
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
<link data-wco="favicon" href="static/img/icons/iconbgdark.png" rel="icon" />
|
||||||
|
<link href="static/css/twstyle.css" rel="stylesheet" />
|
||||||
|
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Read apx.js to know more
|
||||||
|
*/
|
||||||
|
const apxtri = {
|
||||||
|
headers: {
|
||||||
|
xtrkversion: 1,
|
||||||
|
xtribe: "apxtri",
|
||||||
|
xapp: "admin",
|
||||||
|
xlang: "fr",
|
||||||
|
xalias: "anonymous",
|
||||||
|
xhash: "anonymous",
|
||||||
|
xprofils:["anonymous"],
|
||||||
|
xdays: 0,
|
||||||
|
},
|
||||||
|
pagename: "admindata",
|
||||||
|
pageauth: "apxid",
|
||||||
|
allowedprofils:["anonymous"],
|
||||||
|
searchfunction:"adminsearch", // must exist a function call apx.adminsearch(search) that return a relevant result check example
|
||||||
|
version:0
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="/apxtri/node_modules/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/dayjs/dayjs.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/openpgp/dist/openpgp.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/mustache/mustache.min.js"></script>
|
||||||
|
<script src="/apxtri/Checkjson.js"></script>
|
||||||
|
|
||||||
|
<script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=admindata&code=enjoy"></script>
|
||||||
|
<script src="/api/apxtri/wwws/getwco/adminskull.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=admindata&code=enjoy"></script>
|
||||||
|
|
||||||
|
<script src="static/js/adminsearch.js"></script>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="bg-gray-100">
|
||||||
|
<div class="flex min-h-screen">
|
||||||
|
<!-- Vertical Navigation Menu -->
|
||||||
|
<!--button onclick="apx.adminskull.togglesidebarmobile(this)" class="sm:hidden fixed top-4 left-4 z-50 p-2 bg-base-200 rounded-lg shadow">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16m-7 6h7" />
|
||||||
|
</svg>
|
||||||
|
</button-->
|
||||||
|
<nav id="sidebar" class="bg-neutral-800 text-white fixed sm:relative sm:w-64 w-0 transform transition-all duration-300 ease-in-out -translate-x-full sm:translate-x-0 p-4 shadow-lg z-40 overflow-hidden hidden flex flex-col h-screen">
|
||||||
|
<!--nav id="sidebar" class="hidden sm:block bg-neutral-800 text-white w-64 h-screen flex flex-col shadow-lg transition-width"-->
|
||||||
|
<!-- User Profile Section -->
|
||||||
|
<div class="p-4">
|
||||||
|
<div class="flex items-center justify-between"> <!-- Ajout de justify-between pour aligner la croix à droite -->
|
||||||
|
<!-- Image et texte -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<img data-wco="favicon" src="static/img/icons/iconbglight.png" class="rounded-full h-9 w-9 mr-3" alt="apXtri">
|
||||||
|
<div>
|
||||||
|
<h2 data-wco="companyname" class="text-base font-semibold">apXtri</h2>
|
||||||
|
<p data-wco="claim" class="text-sm text-gray-400">L'unique et sa propriété</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Icône de croix (fermeture) -->
|
||||||
|
<button onclick="apx.adminskull.togglesidebarmobile(this)" class="text-gray-400 hover:text-white sm:hidden">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Menu Links Section -->
|
||||||
|
</nav>
|
||||||
|
<!-- Main Content Area -->
|
||||||
|
<div class="flex-1 bg-gray-100">
|
||||||
|
<!-- Horizontal Navigation Bar -->
|
||||||
|
<header class="bg-white shadow-md p-4">
|
||||||
|
<div id="headnav" class="flex items-center justify-between">
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
<div id="maincontent" class=""> Contenu of id=maincontent</div>
|
||||||
|
<div id="searchcontent" class=" hidden flex flex-col border border-gray-200 rounded-lg p-4">
|
||||||
|
<!-- Ligne avec la flèche de retour -->
|
||||||
|
<div class="flex justify-between items-center mb-4">
|
||||||
|
<h2 id="searchtitle" class="text-lg font-semibold">Résultat sur: </h2>
|
||||||
|
<!-- Flèche de retour (icône DaisyUI) -->
|
||||||
|
<button class="btn btn-circle btn-sm btn-ghost" onclick="document.getElementById('searchcontent').classList.add('hidden');document.getElementById('maincontent').classList.remove('hidden');">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Div de contenu -->
|
||||||
|
<div id="searchresults" class="bg-base-200 text-base-content p-4 rounded-lg">
|
||||||
|
<p>Show search result here...</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
65
wwws/admin/src/apxid_fr.html
Normal file
65
wwws/admin/src/apxid_fr.html
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="fr" data-theme="apxtridark" class="h-full bg-base-200 text-neutral-content ">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8" />
|
||||||
|
<title>Authentification</title>
|
||||||
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
|
<meta
|
||||||
|
content="L'unique et sa propriété, authentification, apxtri, cle public, cle privée"
|
||||||
|
name="keywords"
|
||||||
|
/>
|
||||||
|
<meta
|
||||||
|
content="Porte d'entrée dans l'univers libre d'apXtri, là où vous pouvez être l'Unique et sa propriété."
|
||||||
|
name="description"
|
||||||
|
/>
|
||||||
|
<link data-wco="favicon" href="static/img/icons/iconbgdark.png" rel="icon" />
|
||||||
|
<!--script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4"></script-->
|
||||||
|
|
||||||
|
|
||||||
|
<link href="static/css/output.css" rel="stylesheet" />
|
||||||
|
<script>
|
||||||
|
/**
|
||||||
|
* Read apx.js to know more
|
||||||
|
*/
|
||||||
|
const apxtri = {
|
||||||
|
headers: {
|
||||||
|
xtrkversion: 1,
|
||||||
|
xtribe: "apxtri",
|
||||||
|
xapp: "admin",
|
||||||
|
xlang: "fr",
|
||||||
|
xalias: "anonymous",
|
||||||
|
xhash: "anonymous",
|
||||||
|
xprofils:["anonymous"],
|
||||||
|
xdays: 0,
|
||||||
|
xuuid:0
|
||||||
|
},
|
||||||
|
pagename: "apxid",
|
||||||
|
pageauth: "apxid",
|
||||||
|
wcoobserver:true,
|
||||||
|
allowedprofils:["anonymous"],
|
||||||
|
version:0
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
<script src="/apxtri/node_modules/axios/dist/axios.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/dayjs/dayjs.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/openpgp/dist/openpgp.min.js"></script>
|
||||||
|
<script src="/apxtri/node_modules/mustache/mustache.min.js"></script>
|
||||||
|
<script src="/apxtri/Checkjson.js"></script>
|
||||||
|
|
||||||
|
<script src="/api/apxtri/wwws/getwco/apx.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=apxid&code=enjoy"></script>
|
||||||
|
<script src="/api/apxtri/wwws/getwco/simplemobnav.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=apxid&code=enjoy&tagid=authentification"></script>
|
||||||
|
<script src="/api/apxtri/wwws/getwco/apxauth.js?wcotribe=apxtri&tribe=apxtri&xapp=admin&pagename=apxid&code=enjoy&tagid=signature"></script>
|
||||||
|
</head>
|
||||||
|
<body class="h-full">
|
||||||
|
<div class="flex items-center justify-center min-h-screen px-4">
|
||||||
|
|
||||||
|
<div
|
||||||
|
id="authentification"
|
||||||
|
wco-name="simplemobnav"
|
||||||
|
class="bg-base-100 min-h-screen w-full p-4 text-center">
|
||||||
|
</div>
|
||||||
|
<!--div wco-name="chatroom" class="hidden min-h-full flex-col justify-center px-6 py-12 lg:px-8">
|
||||||
|
</div-->
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
7
wwws/admin/src/components/ActiveJobAd.html
Normal file
7
wwws/admin/src/components/ActiveJobAd.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="mb-4 p-4 bg-green-50 rounded-lg shadow">
|
||||||
|
<p class="text-green-600">Currently active</p>
|
||||||
|
<p class="text-gray-800">Job Ad Title {{jobAdNumber}}</p>
|
||||||
|
<p class="text-gray-500">Expiry Date</p>
|
||||||
|
<p class="text-gray-800">DD month YYYY (X days remaining)</p>
|
||||||
|
</div>
|
||||||
|
|
26
wwws/admin/src/components/AttentionMessage.css
Normal file
26
wwws/admin/src/components/AttentionMessage.css
Normal file
@@ -0,0 +1,26 @@
|
|||||||
|
.attention-message {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attention-message-heading,
|
||||||
|
.attention-message-subheading {
|
||||||
|
color: #F04438;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 18px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 28px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attention-message-subheading {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.attention-message-body {
|
||||||
|
color: #18191E;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
5
wwws/admin/src/components/AttentionMessage.html
Normal file
5
wwws/admin/src/components/AttentionMessage.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="attention-message p-4">
|
||||||
|
<p class="attention-message-heading">Attention:</p>
|
||||||
|
<p class="attention-message-subheading">You currently have X job ads active.</p>
|
||||||
|
<p class="attention-message-body">When your plan expires, all active job ads will be removed and candidates notified the job has been canceled lorem ipsum.</p>
|
||||||
|
</div>
|
28
wwws/admin/src/components/Button.css
Normal file
28
wwws/admin/src/components/Button.css
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
.button-container {
|
||||||
|
display: flex;
|
||||||
|
width: 325px;
|
||||||
|
height: 48px;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: var(--app-button-background, #000);
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-label {
|
||||||
|
color: var(--_app-global-grayscale-50, #F9FAFB);
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 900;
|
||||||
|
line-height: 20px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.button-icon {
|
||||||
|
width: var(--Spacing-Link-Below-Textfield, 18px);
|
||||||
|
height: var(--Spacing-Link-Below-Textfield, 18px);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
3
wwws/admin/src/components/Button.html
Normal file
3
wwws/admin/src/components/Button.html
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
<button class="btn w-full flex justify-between items-center mb-2" id="button-element">
|
||||||
|
<span id="button-text">BUTTON TEXT</span>
|
||||||
|
</button>
|
6
wwws/admin/src/components/Button.js
Normal file
6
wwws/admin/src/components/Button.js
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
function initializeButton(buttonText, buttonAction) {
|
||||||
|
const buttonElement = document.getElementById('button-element');
|
||||||
|
buttonElement.innerText = buttonText;
|
||||||
|
buttonElement.addEventListener('click', buttonAction);
|
||||||
|
}
|
||||||
|
|
16
wwws/admin/src/components/ExtraJobAds.html
Normal file
16
wwws/admin/src/components/ExtraJobAds.html
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<div class="job-ad-detail p-4 bg-white rounded-lg shadow mb-4">
|
||||||
|
<p class="text-green-500">Currently active</p>
|
||||||
|
<p class="text-gray-800">Job Ad Title #1</p>
|
||||||
|
<p class="text-gray-600">Expiry Date</p>
|
||||||
|
<p class="text-gray-800">DD month YYYY (X days remaining)</p>
|
||||||
|
</div>
|
||||||
|
<div class="job-ad-detail p-4 bg-white rounded-lg shadow mb-4">
|
||||||
|
<p class="text-green-500">Currently active</p>
|
||||||
|
<p class="text-gray-800">Job Ad Title #2</p>
|
||||||
|
<p class="text-gray-600">Expiry Date</p>
|
||||||
|
<p class="text-gray-800">DD month YYYY (X days remaining)</p>
|
||||||
|
</div>
|
||||||
|
<div class="job-ad-detail p-4 bg-white rounded-lg shadow mb-4">
|
||||||
|
<p class="text-gray-500">Ready to be used / Not active</p>
|
||||||
|
<p class="text-gray-800">Job Ad Title #3</p>
|
||||||
|
</div>
|
19
wwws/admin/src/components/ExtraJobAds.js
Normal file
19
wwws/admin/src/components/ExtraJobAds.js
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
function showExtraJobAdsTab() {
|
||||||
|
document.getElementById('extra-job-ads-content').classList.remove('hidden');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.add('custom-tab-active');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.remove('custom-tab-inactive');
|
||||||
|
|
||||||
|
loadComponent('buttons-container', 'components/Button.html', () => {
|
||||||
|
document.getElementById('button-text').innerText = 'GET EXTRA JOB ADS';
|
||||||
|
const buttonElement = document.getElementById('button-element');
|
||||||
|
buttonElement.classList.add('bg-black', 'text-white');
|
||||||
|
buttonElement.innerHTML += `
|
||||||
|
<svg class="h-6 w-6 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
7
wwws/admin/src/components/Header.html
Normal file
7
wwws/admin/src/components/Header.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="flex justify-start mb-4">
|
||||||
|
<button class="btn btn-circle btn-outline" id="close-button">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"></path>
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
|
</div>
|
5
wwws/admin/src/components/InactiveJobAd.html
Normal file
5
wwws/admin/src/components/InactiveJobAd.html
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
<div class="mb-4 p-4 bg-gray-50 rounded-lg shadow">
|
||||||
|
<p class="text-gray-600">Ready to be used / Not active</p>
|
||||||
|
<p class="text-gray-800">Job Ad Title {{jobAdNumber}}</p>
|
||||||
|
</div>
|
||||||
|
|
7
wwws/admin/src/components/JobAdDetail.html
Normal file
7
wwws/admin/src/components/JobAdDetail.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div class="mb-4 p-4 bg-gray-50 rounded-lg shadow">
|
||||||
|
<p class="text-gray-500" id="job-ad-status"></p>
|
||||||
|
<p class="text-gray-800" id="job-ad-title"></p>
|
||||||
|
<p class="text-gray-600" id="job-ad-expiry-label"></p>
|
||||||
|
<p class="text-gray-800" id="job-ad-expiry"></p>
|
||||||
|
</div>
|
||||||
|
|
53
wwws/admin/src/components/JobAds.css
Normal file
53
wwws/admin/src/components/JobAds.css
Normal file
@@ -0,0 +1,53 @@
|
|||||||
|
.job-ad-detail {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 16px;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.active-job-ad {
|
||||||
|
background-color: #f9fafb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inactive-job-ad {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-ad-title {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
color: var(--app-text-stroke-text-stroke-day, #18191E);
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.job-title {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.expiry-label,
|
||||||
|
.expiry-date {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 14px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-top: 8px;
|
||||||
|
}
|
||||||
|
|
72
wwws/admin/src/components/JobAds.js
Normal file
72
wwws/admin/src/components/JobAds.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
function generateJobAds(activeCount, inactiveCount) {
|
||||||
|
const container = document.getElementById('extra-job-ads-content');
|
||||||
|
container.innerHTML = ''; // Clear existing content
|
||||||
|
|
||||||
|
// Update the tab text with the total number of job ads
|
||||||
|
const totalJobAds = activeCount + inactiveCount;
|
||||||
|
const extraJobAdsTab = document.getElementById('extra-job-ads-tab');
|
||||||
|
extraJobAdsTab.textContent = `Extra job ads (${totalJobAds})`;
|
||||||
|
|
||||||
|
// Generate active job ads
|
||||||
|
for (let i = 1; i <= activeCount; i++) {
|
||||||
|
const activeAdContainer = document.createElement('div');
|
||||||
|
activeAdContainer.className = 'job-ad-container mb-4';
|
||||||
|
|
||||||
|
const activeAdTitle = document.createElement('p');
|
||||||
|
activeAdTitle.className = 'job-ad-title text-gray-800';
|
||||||
|
activeAdTitle.textContent = `Extra job ad #${i} (currently in use)`;
|
||||||
|
|
||||||
|
const activeAd = document.createElement('div');
|
||||||
|
activeAd.className = 'job-ad-detail active-job-ad p-4 bg-white rounded-lg shadow';
|
||||||
|
activeAd.innerHTML = `
|
||||||
|
<p class="text-green-500 status">Currently active</p>
|
||||||
|
<p class="text-gray-800 job-title"><Job ad title></p>
|
||||||
|
<p class="text-gray-600 expiry-label">Expiry Date</p>
|
||||||
|
<p class="text-gray-800 expiry-date">DD month YYYY (X days remaining)</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
activeAdContainer.appendChild(activeAdTitle);
|
||||||
|
activeAdContainer.appendChild(activeAd);
|
||||||
|
container.appendChild(activeAdContainer);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generate inactive job ads
|
||||||
|
for (let i = 1; i <= inactiveCount; i++) {
|
||||||
|
const inactiveAdContainer = document.createElement('div');
|
||||||
|
inactiveAdContainer.className = 'job-ad-container mb-4';
|
||||||
|
|
||||||
|
const inactiveAdTitle = document.createElement('p');
|
||||||
|
inactiveAdTitle.className = 'job-ad-title text-gray-800';
|
||||||
|
inactiveAdTitle.textContent = `Extra job ad #${i + activeCount}`;
|
||||||
|
|
||||||
|
const inactiveAd = document.createElement('div');
|
||||||
|
inactiveAd.className = 'job-ad-detail inactive-job-ad p-4 bg-white rounded-lg shadow';
|
||||||
|
inactiveAd.innerHTML = `
|
||||||
|
<p class="text-gray-500 status">Ready to be used / Not active</p>
|
||||||
|
`;
|
||||||
|
|
||||||
|
inactiveAdContainer.appendChild(inactiveAdTitle);
|
||||||
|
inactiveAdContainer.appendChild(inactiveAd);
|
||||||
|
container.appendChild(inactiveAdContainer);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showExtraJobAdsTab() {
|
||||||
|
document.getElementById('extra-job-ads-content').classList.remove('hidden');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.add('custom-tab-active');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.remove('custom-tab-inactive');
|
||||||
|
|
||||||
|
loadComponent('buttons-container', 'components/Button.html', () => {
|
||||||
|
document.getElementById('button-text').innerText = 'GET EXTRA JOB ADS';
|
||||||
|
const buttonElement = document.getElementById('button-element');
|
||||||
|
buttonElement.classList.add('bg-black', 'text-white');
|
||||||
|
buttonElement.innerHTML += `
|
||||||
|
<svg class="h-6 w-6 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Generate job ads dynamically
|
||||||
|
generateJobAds(2, 1);
|
||||||
|
}
|
7
wwws/admin/src/components/NoExtraJobAds.html
Normal file
7
wwws/admin/src/components/NoExtraJobAds.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div id="extra-job-ads-content" class="p-4 bg-white rounded-lg shadow mb-4">
|
||||||
|
<h3 class="text-xl font-bold mb-4">You don't have any extra job ads at the moment lorem ipsum</h3>
|
||||||
|
<p class="text-gray-600 mb-4">Generic description talking about the benefits of extra job ads lorem ipsum</p>
|
||||||
|
<div class="w-full h-48 bg-gray-200 flex items-center justify-center rounded-lg mb-4">
|
||||||
|
<p class="text-gray-500">empty state illustration</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
42
wwws/admin/src/components/NoSubscription.css
Normal file
42
wwws/admin/src/components/NoSubscription.css
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
.no-subscription-container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 600px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-subscription-container h3 {
|
||||||
|
color: var(--app-text-stroke-text-stroke-day, #18191E);
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 24px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 32px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-subscription-container p {
|
||||||
|
color: var(--app-text-stroke-text-stroke-day, #18191E);
|
||||||
|
text-align: center;
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 24px;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.no-subscription-illustration {
|
||||||
|
width: var(--Chart-Module-Width, 325px);
|
||||||
|
height: 260px;
|
||||||
|
background-color: #e5e7eb;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border-radius: 8px;
|
||||||
|
margin: 0 auto;
|
||||||
|
}
|
7
wwws/admin/src/components/NoSubscription.html
Normal file
7
wwws/admin/src/components/NoSubscription.html
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<div id="no-subscription-content" class="no-subscription-container">
|
||||||
|
<h3 id="no-subscription-title">You don't have any extra job ads at the moment lorem ipsum</h3>
|
||||||
|
<p id="no-subscription-description">Generic description talking about the benefits of extra job ads lorem pisum</p>
|
||||||
|
<div class="no-subscription-illustration">
|
||||||
|
<img id="no-subscription-image" src="" alt="Illustration" class="w-full h-full object-cover">
|
||||||
|
</div>
|
||||||
|
</div>
|
9
wwws/admin/src/components/PageLayout.css
Normal file
9
wwws/admin/src/components/PageLayout.css
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
body {
|
||||||
|
font-family: var(--font-family-inter);
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
background-color: #f3f4f6;
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
}
|
||||||
|
|
4
wwws/admin/src/components/PlanDetail.html
Normal file
4
wwws/admin/src/components/PlanDetail.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<div id="plan-header" class="mb-4">
|
||||||
|
<h2 class="text-xl font-semibold text-gray-900">Your plan: <span class="text-gray-600"><Plan name from stripe></span></h2>
|
||||||
|
</div>
|
||||||
|
<div id="plan-detail-items" class="space-y-4 bg-white p-6 rounded-lg shadow"></div>
|
6
wwws/admin/src/components/PlanDetailItem.html
Normal file
6
wwws/admin/src/components/PlanDetailItem.html
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
<!-- PlanDetailItem.html -->
|
||||||
|
<div class="mb-4 p-4 bg-gray-50 rounded-lg shadow">
|
||||||
|
<p class="text-gray-500">{{label}}</p>
|
||||||
|
<p class="text-gray-800">{{value}}</p>
|
||||||
|
</div>
|
||||||
|
|
36
wwws/admin/src/components/PlanDetails.css
Normal file
36
wwws/admin/src/components/PlanDetails.css
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
#plan-detail-items {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-detail-item {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 345px;
|
||||||
|
padding: 0px 8px 9px 0px;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 2px;
|
||||||
|
margin-bottom: 9px;
|
||||||
|
border-bottom: 1px solid #e5e7eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-detail-item:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-detail-label {
|
||||||
|
color: var(--app-text-stroke-text-stroke-day, #18191E);
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-detail-value {
|
||||||
|
color: var(--app-text-stroke-text-stroke-day, #18191E);
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 16px;
|
||||||
|
}
|
22
wwws/admin/src/components/Subscriptions.html
Normal file
22
wwws/admin/src/components/Subscriptions.html
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
<div id="subscriptions-content" class="p-4 bg-white rounded-lg shadow mb-4">
|
||||||
|
<h3 class="text-xl font-bold mb-4">Your plan: <Plan name from stripe></h3>
|
||||||
|
|
||||||
|
<!-- Description -->
|
||||||
|
<div id="plan-detail-description"></div>
|
||||||
|
|
||||||
|
<!-- Price per month -->
|
||||||
|
<div id="plan-detail-price"></div>
|
||||||
|
|
||||||
|
<!-- Active job ads -->
|
||||||
|
<div id="plan-detail-ads"></div>
|
||||||
|
|
||||||
|
<!-- Renewal date -->
|
||||||
|
<div id="plan-detail-renewal"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Action Buttons for Subscriptions Tab -->
|
||||||
|
<div id="subscriptions-buttons" class="sticky-footer">
|
||||||
|
<div id="update-plan-button"></div>
|
||||||
|
<a href="#" class="block text-center text-red-600" id="cancel-subscription-button">Cancel subscription</a>
|
||||||
|
</div>
|
||||||
|
|
56
wwws/admin/src/components/Subscriptions.js
Normal file
56
wwws/admin/src/components/Subscriptions.js
Normal file
@@ -0,0 +1,56 @@
|
|||||||
|
function showSubscriptionTab(planCanceled) {
|
||||||
|
document.getElementById('subscriptions-content').classList.remove('hidden');
|
||||||
|
document.getElementById('subscriptions-tab').classList.add('custom-tab-active');
|
||||||
|
document.getElementById('subscriptions-tab').classList.remove('custom-tab-inactive');
|
||||||
|
|
||||||
|
loadComponent('buttons-container', 'components/Button.html', () => {
|
||||||
|
const buttonText = planCanceled ? 'RE-SUBSCRIBE' : 'UPDATE PLAN';
|
||||||
|
document.getElementById('button-text').innerText = buttonText;
|
||||||
|
const buttonElement = document.getElementById('button-element');
|
||||||
|
buttonElement.classList.add('bg-black', 'text-white');
|
||||||
|
buttonElement.innerHTML += `
|
||||||
|
<svg class="h-6 w-6 ml-2" fill="none" stroke="currentColor" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M14 5l7 7m0 0l-7 7m7-7H3"></path>
|
||||||
|
</svg>
|
||||||
|
`;
|
||||||
|
if (!planCanceled) {
|
||||||
|
const buttonsContainer = document.getElementById('buttons-container');
|
||||||
|
buttonsContainer.innerHTML += '<a href="#" class="block text-center text-red-600">Cancel subscription</a>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Plan details
|
||||||
|
const planDetails = [
|
||||||
|
{ label: 'Description', value: 'Plan description from stripe lorem ipsum', className: 'text-gray-800', labelClassName: 'text-gray-500' },
|
||||||
|
{ label: 'Price per month', value: 'XX.XX € / month', className: 'text-gray-800', labelClassName: 'text-gray-500' },
|
||||||
|
{ label: 'Active job ads', value: 'X / Total N', className: planCanceled ? 'text-red-500' : 'text-gray-800', labelClassName: planCanceled ? 'text-red-500' : 'text-gray-500' },
|
||||||
|
{ label: planCanceled ? 'Expiry date' : 'Renewal date', value: 'DD month YYYY', additionalText: planCanceled ? '(X days remaining)' : '', className: planCanceled ? 'text-red-500' : 'text-gray-800', labelClassName: planCanceled ? 'text-red-500' : 'text-gray-500' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const planDetailItemsContainer = document.getElementById('plan-detail-items');
|
||||||
|
planDetailItemsContainer.innerHTML = '';
|
||||||
|
|
||||||
|
planDetails.forEach(detail => {
|
||||||
|
loadComponent(null, 'components/PlanDetailItem.html', () => {
|
||||||
|
const item = document.createElement('div');
|
||||||
|
item.className = 'plan-detail-item';
|
||||||
|
item.innerHTML = `
|
||||||
|
<p class="${detail.labelClassName}">${detail.label}</p>
|
||||||
|
<p class="${detail.className}">${detail.value} ${detail.additionalText || ''}</p>
|
||||||
|
`;
|
||||||
|
planDetailItemsContainer.appendChild(item);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
if (planCanceled) {
|
||||||
|
const attentionMessageContainer = document.createElement('div');
|
||||||
|
attentionMessageContainer.id = 'attention-message-container';
|
||||||
|
document.getElementById('subscriptions-content').appendChild(attentionMessageContainer);
|
||||||
|
loadComponent('attention-message-container', 'components/AttentionMessage.html');
|
||||||
|
} else {
|
||||||
|
const attentionMessageContainer = document.getElementById('attention-message-container');
|
||||||
|
if (attentionMessageContainer) {
|
||||||
|
attentionMessageContainer.innerHTML = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
4
wwws/admin/src/components/Tabs.html
Normal file
4
wwws/admin/src/components/Tabs.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<div class="custom-tabs">
|
||||||
|
<div id="subscriptions-tab" class="custom-tab custom-tab-active" onclick="showTab('subscriptions')">Subscriptions</div>
|
||||||
|
<div id="extra-job-ads-tab" class="custom-tab custom-tab-inactive" onclick="showTab('extra-job-ads')">Extra job ads (0)</div>
|
||||||
|
</div>
|
48
wwws/admin/src/components/Tabs.js
Normal file
48
wwws/admin/src/components/Tabs.js
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
function loadComponent(containerId, filePath, callback) {
|
||||||
|
fetch(filePath)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (containerId) {
|
||||||
|
document.getElementById(containerId).innerHTML = data;
|
||||||
|
}
|
||||||
|
if (callback) callback();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading component:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(tab) {
|
||||||
|
document.getElementById('subscriptions-content').classList.add('hidden');
|
||||||
|
document.getElementById('extra-job-ads-content').classList.add('hidden');
|
||||||
|
|
||||||
|
document.getElementById('subscriptions-tab').classList.remove('custom-tab-active');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.remove('custom-tab-active');
|
||||||
|
document.getElementById('subscriptions-tab').classList.add('custom-tab-inactive');
|
||||||
|
document.getElementById('extra-job-ads-tab').classList.add('custom-tab-inactive');
|
||||||
|
|
||||||
|
document.getElementById('buttons-container').innerHTML = '';
|
||||||
|
|
||||||
|
if (tab === 'subscriptions') {
|
||||||
|
const planCanceled = false; // Set true or false
|
||||||
|
showSubscriptionTab(planCanceled);
|
||||||
|
} else if (tab === 'extra-job-ads') {
|
||||||
|
showExtraJobAdsTab();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadComponent('header-container', 'components/Header.html');
|
||||||
|
loadComponent('tabs-container', 'components/Tabs.html');
|
||||||
|
loadComponent('subscriptions-content', 'components/PlanDetail.html');
|
||||||
|
loadComponent('extra-job-ads-content', 'components/ExtraJobAds.html', () => {
|
||||||
|
|
||||||
|
generateJobAds(2, 1); //Active-Inactive Jobads
|
||||||
|
});
|
||||||
|
loadComponent('buttons-container', 'components/Button.html', () => {
|
||||||
|
showTab('subscriptions');
|
||||||
|
});
|
||||||
|
});
|
71
wwws/admin/src/components/Tabs2.js
Normal file
71
wwws/admin/src/components/Tabs2.js
Normal file
@@ -0,0 +1,71 @@
|
|||||||
|
function loadComponent(containerId, filePath, callback) {
|
||||||
|
fetch(filePath)
|
||||||
|
.then(response => {
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
return response.text();
|
||||||
|
})
|
||||||
|
.then(data => {
|
||||||
|
if (containerId) {
|
||||||
|
document.getElementById(containerId).innerHTML = data;
|
||||||
|
}
|
||||||
|
if (callback) callback();
|
||||||
|
})
|
||||||
|
.catch(error => console.error('Error loading component:', error));
|
||||||
|
}
|
||||||
|
|
||||||
|
function showTab(tabsConfig, activeTab) {
|
||||||
|
// Hide all content sections and reset tab classes
|
||||||
|
tabsConfig.forEach(tab => {
|
||||||
|
document.getElementById(tab.contentId).classList.add('hidden');
|
||||||
|
document.getElementById(tab.tabId).classList.remove('custom-tab-active');
|
||||||
|
document.getElementById(tab.tabId).classList.add('custom-tab-inactive');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Show the selected content section and activate the tab
|
||||||
|
const activeConfig = tabsConfig.find(tab => tab.name === activeTab);
|
||||||
|
document.getElementById(activeConfig.contentId).classList.remove('hidden');
|
||||||
|
document.getElementById(activeConfig.tabId).classList.add('custom-tab-active');
|
||||||
|
document.getElementById(activeConfig.tabId).classList.remove('custom-tab-inactive');
|
||||||
|
|
||||||
|
// Load buttons for the active tab
|
||||||
|
document.getElementById('buttons-container').innerHTML = '';
|
||||||
|
loadComponent('buttons-container', activeConfig.buttonFile, () => {
|
||||||
|
document.getElementById('button-text').innerText = activeConfig.buttonText;
|
||||||
|
const buttonElement = document.getElementById('button-element');
|
||||||
|
buttonElement.classList.add('bg-black', 'text-white');
|
||||||
|
buttonElement.innerHTML += activeConfig.buttonIcon;
|
||||||
|
});
|
||||||
|
|
||||||
|
// Update content based on the active tab
|
||||||
|
if (activeTab === 'subscriptions') {
|
||||||
|
updateNoSubscriptionContent('You are not subscribed to any plan lorem ipsum dolor sit amet', 'Generic description talking about the benefits of subscribing lorem ipsum', 'path/to/subscription-illustration.png');
|
||||||
|
} else if (activeTab === 'extra-job-ads') {
|
||||||
|
updateNoSubscriptionContent('You don\'t have any extra job ads at the moment lorem ipsum', 'Generic description talking about the benefits of extra job ads lorem pisum', 'path/to/extra-job-ads-illustration.png');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function initializePage(tabsConfig) {
|
||||||
|
document.addEventListener("DOMContentLoaded", () => {
|
||||||
|
loadComponent('header-container', 'components/Header.html');
|
||||||
|
loadComponent('tabs-container', 'components/Tabs.html', () => {
|
||||||
|
tabsConfig.forEach(tab => {
|
||||||
|
document.getElementById(tab.tabId).addEventListener('click', () => showTab(tabsConfig, tab.name));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
tabsConfig.forEach(tab => {
|
||||||
|
loadComponent(tab.contentId, tab.contentFile);
|
||||||
|
});
|
||||||
|
loadComponent('buttons-container', 'components/Button.html', () => {
|
||||||
|
// Show the first tab by default
|
||||||
|
showTab(tabsConfig, tabsConfig[0].name);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateNoSubscriptionContent(title, description, imagePath) {
|
||||||
|
document.getElementById('no-subscription-title').innerText = title;
|
||||||
|
document.getElementById('no-subscription-description').innerText = description;
|
||||||
|
document.getElementById('no-subscription-image').src = imagePath;
|
||||||
|
}
|
4
wwws/admin/src/components/caption.html
Normal file
4
wwws/admin/src/components/caption.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<p id="caption" class="mt-2 text-sm text-gray-500">
|
||||||
|
This is a descriptive caption for the media.
|
||||||
|
</p>
|
||||||
|
|
46
wwws/admin/src/components/carousel.css
Normal file
46
wwws/admin/src/components/carousel.css
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
.carousel {
|
||||||
|
display: flex;
|
||||||
|
gap: 16px;
|
||||||
|
overflow-x: scroll;
|
||||||
|
scroll-snap-type: x mandatory;
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
width: 420px;
|
||||||
|
height: 544px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
scroll-snap-align: start;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item .caption {
|
||||||
|
margin-top: 8px;
|
||||||
|
text-align: center;
|
||||||
|
font-size: 14px;
|
||||||
|
color: #555;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.carousel {
|
||||||
|
gap: 8px;
|
||||||
|
padding: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item {
|
||||||
|
width: 250px;
|
||||||
|
height: 320px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.carousel-item .caption {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
33
wwws/admin/src/components/carousel.js
Normal file
33
wwws/admin/src/components/carousel.js
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const carouselContainer = document.getElementById('carousel-container');
|
||||||
|
|
||||||
|
const mediaItems = Array(10).fill({
|
||||||
|
type: 'image',
|
||||||
|
src: 'https://s3-alpha-sig.figma.com/img/37a7/42fb/1daabd5424c6093ca79a6d502b3b84ef?Expires=1724630400&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=FgQK8BhmZm52syueYHL8d33N9v48-lePOhb~KQgE528f-GZn8a8HXap5OvWlCdKaf2nZLlFvmgWgpnuz7iekJqEAS95CknKbniYODvBDOGMVIcHrU7YthPZ~YqZSE7pEANVBVEwkB9-1zJ77gT9uEMryjd-xb44NjnBhfLPkP4B9qlqkbuehRRhLGPBnYA9q3PHpf5ocx7j0~xAbomT~EFX2bzwBu70gKN0qTFVRy8uNu8USYah2YotQH58ChxcEPokhPxENAdNCWeDodsWVFrldbeU0CgVzCrn3MLYu9Ep96r7tjAvKfgQQqC6eORHyM-citVFu1DlJx8-CUN26RQ__',
|
||||||
|
alt: 'Image with Caption'
|
||||||
|
});
|
||||||
|
|
||||||
|
const renderCarousel = () => {
|
||||||
|
mediaItems.forEach((item, index) => {
|
||||||
|
const halfWidthClass = index === 2 ? 'half-width' : '';
|
||||||
|
const captionText = `Optional image caption lorem ipsum dolor sit amet, consectetur adipiscing elit lorem ipsum`;
|
||||||
|
carouselContainer.innerHTML += `
|
||||||
|
<div class="carousel-item ${halfWidthClass}">
|
||||||
|
${renderMedia(item)}
|
||||||
|
<div class="caption">${captionText}</div>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const renderMedia = (item) => {
|
||||||
|
if (item.type === 'image') {
|
||||||
|
return `<img src="${item.src}" alt="${item.alt}">`;
|
||||||
|
} else if (item.type === 'video') {
|
||||||
|
return `<video src="${item.src}" controls class="w-full h-auto"></video>`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
renderCarousel();
|
||||||
|
});
|
77
wwws/admin/src/components/checkbox.css
Normal file
77
wwws/admin/src/components/checkbox.css
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
.checkbox-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input {
|
||||||
|
appearance: none;
|
||||||
|
border: 2px solid #0DC3FF;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 87px;
|
||||||
|
height: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: var(--Input-Element-Radius-CTA-M, 12px);
|
||||||
|
cursor: pointer;
|
||||||
|
position: relative;
|
||||||
|
flex-shrink: 0;
|
||||||
|
transition: width 0.3s ease, height 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input:checked {
|
||||||
|
background-color: #0DC3FF;
|
||||||
|
width: var(--Icon-Sizes-Input, 20px);
|
||||||
|
height: var(--Icon-Sizes-Input, 20px);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-input:checked::before {
|
||||||
|
content: '✔';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex: 1 0 0;
|
||||||
|
color: var(--app-input-text-label-day, #1E2024);
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label-left {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label-left .checkbox-input {
|
||||||
|
order: 1;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label-right {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.checkbox-label-right .checkbox-input {
|
||||||
|
order: 2;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
39
wwws/admin/src/components/checkbox.html
Normal file
39
wwws/admin/src/components/checkbox.html
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
<div class="checkbox-component">
|
||||||
|
<!-- Horizontal Checkbox with Label and Icon -->
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<input type="checkbox" id="checkbox1" class="checkbox checkbox-primary">
|
||||||
|
<label for="checkbox1" class="checkbox-label">Label with Icon</label>
|
||||||
|
<span class="checkbox-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11.41l-3.3 3.29-1.29-1.29a1 1 0 10-1.42 1.42l2 2a1 1 0 001.42 0l4-4a1 1 0 00-1.42-1.42z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Horizontal Checkbox with Label and No Icon -->
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<input type="checkbox" id="checkbox2" class="checkbox checkbox-primary">
|
||||||
|
<label for="checkbox2" class="checkbox-label">Label without Icon</label>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Horizontal Checkbox with No Label and Icon -->
|
||||||
|
<div class="checkbox-container">
|
||||||
|
<input type="checkbox" id="checkbox3" class="checkbox checkbox-primary">
|
||||||
|
<span class="checkbox-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11.41l-3.3 3.29-1.29-1.29a1 1 0 10-1.42 1.42l2 2a1 1 0 001.42 0l4-4a1 1 0 00-1.42-1.42z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Vertical Checkbox with Label and Icon -->
|
||||||
|
<div class="checkbox-container flex-col">
|
||||||
|
<input type="checkbox" id="checkbox4" class="checkbox checkbox-primary">
|
||||||
|
<label for="checkbox4" class="checkbox-label">Vertical Label with Icon</label>
|
||||||
|
<span class="checkbox-icon">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5" viewBox="0 0 20 20" fill="currentColor">
|
||||||
|
<path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-11.41l-3.3 3.29-1.29-1.29a1 1 0 10-1.42 1.42l2 2a1 1 0 001.42 0l4-4a1 1 0 00-1.42-1.42z" clip-rule="evenodd" />
|
||||||
|
</svg>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
51
wwws/admin/src/components/checkbox.js
Normal file
51
wwws/admin/src/components/checkbox.js
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
const checkboxData = [
|
||||||
|
{ label: 'Option 1', position: 'right' },
|
||||||
|
{ label: 'Option 2', icon: '📞', position: 'right' },
|
||||||
|
{ label: 'Option 3', icon: '📞', position: 'left' },
|
||||||
|
{ label: 'Option 4', icon: '📞', position: 'left' },
|
||||||
|
];
|
||||||
|
|
||||||
|
function createCheckboxComponent({ label, icon, position, orientation }) {
|
||||||
|
const checkboxContainer = document.createElement('div');
|
||||||
|
checkboxContainer.className = `checkbox-container ${orientation === 'vertical' ? 'flex-col' : ''}`;
|
||||||
|
|
||||||
|
const checkboxInput = document.createElement('input');
|
||||||
|
checkboxInput.type = 'checkbox';
|
||||||
|
checkboxInput.className = 'checkbox checkbox-primary';
|
||||||
|
checkboxInput.id = label;
|
||||||
|
|
||||||
|
const checkboxLabel = document.createElement('label');
|
||||||
|
checkboxLabel.className = `checkbox-label checkbox-label-${position}`;
|
||||||
|
checkboxLabel.htmlFor = label;
|
||||||
|
|
||||||
|
if (icon) {
|
||||||
|
const iconElement = document.createElement('span');
|
||||||
|
iconElement.className = 'icon';
|
||||||
|
iconElement.innerText = icon;
|
||||||
|
checkboxLabel.appendChild(iconElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
const labelText = document.createElement('span');
|
||||||
|
labelText.innerText = label;
|
||||||
|
checkboxLabel.appendChild(labelText);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (position === 'left' || orientation === 'vertical') {
|
||||||
|
checkboxContainer.appendChild(checkboxLabel);
|
||||||
|
checkboxContainer.appendChild(checkboxInput);
|
||||||
|
} else {
|
||||||
|
checkboxContainer.appendChild(checkboxInput);
|
||||||
|
checkboxContainer.appendChild(checkboxLabel);
|
||||||
|
}
|
||||||
|
|
||||||
|
return checkboxContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const checkboxContainer = document.getElementById('checkbox-container');
|
||||||
|
checkboxData.forEach(checkboxConfig => {
|
||||||
|
const checkboxComponent = createCheckboxComponent(checkboxConfig);
|
||||||
|
checkboxContainer.appendChild(checkboxComponent);
|
||||||
|
});
|
||||||
|
});
|
41
wwws/admin/src/components/content.css
Normal file
41
wwws/admin/src/components/content.css
Normal file
@@ -0,0 +1,41 @@
|
|||||||
|
body {
|
||||||
|
font-family: 'Inter', sans-serif;
|
||||||
|
background-color: var(--_app-global-grayscale-100, #F2F4F7);
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
min-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.content-container {
|
||||||
|
flex: 1;
|
||||||
|
padding-bottom: 4rem;
|
||||||
|
background-color: var(--_app-global-grayscale-100, #F2F4F7);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.sticky-footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-container {
|
||||||
|
width: 100%;
|
||||||
|
background-color: var(--_app-global-grayscale-100, #F2F4F7);
|
||||||
|
padding: 1rem;
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#plan-header {
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
color: #18191E;
|
||||||
|
}
|
114
wwws/admin/src/components/dropdown.css
Normal file
114
wwws/admin/src/components/dropdown.css
Normal file
@@ -0,0 +1,114 @@
|
|||||||
|
.dropdown-container {
|
||||||
|
width: var(--Chart-Module-Width, 325px);
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-label {
|
||||||
|
display: flex;
|
||||||
|
height: var(--Icon-Sizes-Input, 20px);
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
flex: 1 0 0;
|
||||||
|
color: var(--app-input-text-label-day, #1E2024);
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-description {
|
||||||
|
align-self: stretch;
|
||||||
|
color: var(--app-input-text-label-light-day, #344054);
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 13px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 16px;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-placeholder {
|
||||||
|
display: -webkit-box;
|
||||||
|
-webkit-box-orient: vertical;
|
||||||
|
-webkit-line-clamp: 1;
|
||||||
|
flex: 1 0 0;
|
||||||
|
overflow: hidden;
|
||||||
|
color: var(--app-input-text-placeholder-text-day, #7D8188);
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-button {
|
||||||
|
width: var(--Chart-Module-Width, 325px);
|
||||||
|
height: 48px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0.5rem;
|
||||||
|
border: 1px solid var(--app-input-element-stroke-day, #98A2B3);
|
||||||
|
border-radius: 4px;
|
||||||
|
background: var(--app-input-element-background-day, #F9FAFB);
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-list {
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
width: var(--Chart-Module-Width, 325px);
|
||||||
|
max-height: 200px;
|
||||||
|
overflow-y: auto;
|
||||||
|
padding: var(--Input-Element-Radius-CTA-S, 6px) 2px var(--Spacing-Keywords, 0px) 2px;
|
||||||
|
border: 1px solid var(--app-input-element-stroke-day, #98A2B3);
|
||||||
|
border-radius: 0 0 4px 4px;
|
||||||
|
background: var(--app-input-element-background-day, #F9FAFB);
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-list-with-description {
|
||||||
|
top: 75%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-list-no-description {
|
||||||
|
top: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item {
|
||||||
|
display: flex;
|
||||||
|
height: 40px;
|
||||||
|
padding: 8px var(--Icon-Sizes-Input, 20px) 8px 8px;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: flex-start;
|
||||||
|
align-self: stretch;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.3s, color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover {
|
||||||
|
background-color: #0DC3FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item-text {
|
||||||
|
flex: 1 0 0;
|
||||||
|
color: var(--app-input-text-user-input-day, #1E2024);
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
line-height: 24px;
|
||||||
|
transition: color 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.dropdown-item:hover .dropdown-item-text {
|
||||||
|
color: var(--app-input-text-user-input-day, #1E2024);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
20
wwws/admin/src/components/dropdown.html
Normal file
20
wwws/admin/src/components/dropdown.html
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<title>Dropdown Component Example</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@1.15.0/dist/full.css" rel="stylesheet">
|
||||||
|
<link rel="stylesheet" href="dropdown.css">
|
||||||
|
</head>
|
||||||
|
<body class="bg-gray-100 p-10">
|
||||||
|
<h1 class="text-2xl font-bold mb-4">Dropdown Component Example</h1>
|
||||||
|
<div id="dropdown-container"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
console.log("Loading dropdown.js...");
|
||||||
|
</script>
|
||||||
|
<script src="dropdown.js"></script>
|
||||||
|
</body>
|
||||||
|
</html>
|
87
wwws/admin/src/components/dropdown.js
Normal file
87
wwws/admin/src/components/dropdown.js
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
const dropdownData = [
|
||||||
|
{ label: 'Options', placeholder: 'Select an option', description: 'Choose one of the following items', items: ['Item 1', 'Item 2', 'Item 3'] },
|
||||||
|
{ placeholder: 'Select without label', items: ['Item A', 'Item B', 'Item C', 'Item D', 'Item E', 'Item F', 'Item G', 'Item H', 'Item I', 'Item J', 'Item K'] },
|
||||||
|
{ label: 'Options 2', placeholder: 'Choose an item', description: 'Additional description lorem ipsum', items: ['Option 1', 'Option 2', 'Option 3'] },
|
||||||
|
];
|
||||||
|
|
||||||
|
function createDropdownComponent({ label, placeholder, description, items }) {
|
||||||
|
const dropdownContainer = document.createElement('div');
|
||||||
|
dropdownContainer.className = 'dropdown-container mb-6';
|
||||||
|
|
||||||
|
if (label) {
|
||||||
|
const labelElement = document.createElement('label');
|
||||||
|
labelElement.className = 'dropdown-label block text-gray-700 text-sm font-bold mb-2';
|
||||||
|
labelElement.innerText = label;
|
||||||
|
dropdownContainer.appendChild(labelElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buttonElement = document.createElement('div');
|
||||||
|
buttonElement.className = 'dropdown-button';
|
||||||
|
buttonElement.innerText = placeholder || 'Select an option';
|
||||||
|
dropdownContainer.appendChild(buttonElement);
|
||||||
|
|
||||||
|
const dropdownList = document.createElement('div');
|
||||||
|
dropdownList.className = 'dropdown-list';
|
||||||
|
dropdownList.style.display = 'none';
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
dropdownList.classList.add('dropdown-list-with-description');
|
||||||
|
} else {
|
||||||
|
dropdownList.classList.add('dropdown-list-no-description');
|
||||||
|
}
|
||||||
|
|
||||||
|
items.forEach(item => {
|
||||||
|
const itemElement = document.createElement('div');
|
||||||
|
itemElement.className = 'dropdown-item';
|
||||||
|
|
||||||
|
const itemText = document.createElement('span');
|
||||||
|
itemText.className = 'dropdown-item-text';
|
||||||
|
itemText.innerText = item;
|
||||||
|
|
||||||
|
itemElement.appendChild(itemText);
|
||||||
|
itemElement.addEventListener('click', () => {
|
||||||
|
buttonElement.innerHTML = '';
|
||||||
|
buttonElement.className = 'dropdown-button selected-dropdown-item';
|
||||||
|
|
||||||
|
const selectedItemText = document.createElement('span');
|
||||||
|
selectedItemText.className = 'selected-dropdown-item-text';
|
||||||
|
selectedItemText.innerText = item;
|
||||||
|
buttonElement.appendChild(selectedItemText);
|
||||||
|
|
||||||
|
dropdownList.style.display = 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdownList.appendChild(itemElement);
|
||||||
|
});
|
||||||
|
|
||||||
|
buttonElement.addEventListener('click', (event) => {
|
||||||
|
event.stopPropagation();
|
||||||
|
dropdownList.style.display = dropdownList.style.display === 'none' ? 'flex' : 'none';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.addEventListener('click', (event) => {
|
||||||
|
if (!dropdownContainer.contains(event.target)) {
|
||||||
|
dropdownList.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
dropdownContainer.appendChild(dropdownList);
|
||||||
|
|
||||||
|
if (description) {
|
||||||
|
const descriptionElement = document.createElement('p');
|
||||||
|
descriptionElement.className = 'dropdown-description text-gray-600 text-xs italic mt-2';
|
||||||
|
descriptionElement.innerText = description;
|
||||||
|
dropdownContainer.appendChild(descriptionElement);
|
||||||
|
}
|
||||||
|
|
||||||
|
return dropdownContainer;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
console.log("Document loaded, rendering dropdowns...");
|
||||||
|
const dropdownContainer = document.getElementById('dropdown-container');
|
||||||
|
dropdownData.forEach(dropdownConfig => {
|
||||||
|
const dropdownComponent = createDropdownComponent(dropdownConfig);
|
||||||
|
dropdownContainer.appendChild(dropdownComponent);
|
||||||
|
});
|
||||||
|
});
|
43
wwws/admin/src/components/duoLayout.css
Normal file
43
wwws/admin/src/components/duoLayout.css
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
.duo-layout {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: 16px;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duo-layout .media-item {
|
||||||
|
width: 500px;
|
||||||
|
height: 720px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duo-layout .media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 640px) {
|
||||||
|
.duo-layout {
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
padding: 0 16px;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duo-layout .media-item {
|
||||||
|
width: calc(50% - 5px);
|
||||||
|
max-width: 200px;
|
||||||
|
height: 45vh;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.duo-layout .media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
10
wwws/admin/src/components/footer.css
Normal file
10
wwws/admin/src/components/footer.css
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
.sticky-footer {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
padding: 1rem;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 -2px 5px rgba(0, 0, 0, 0.1);
|
||||||
|
}
|
||||||
|
|
285
wwws/admin/src/components/header.css
Normal file
285
wwws/admin/src/components/header.css
Normal file
@@ -0,0 +1,285 @@
|
|||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-background {
|
||||||
|
height: 80vh;
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.remaining-background {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 20vh;
|
||||||
|
background-color: black;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: 78%;
|
||||||
|
margin: 0 auto;
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 160px;
|
||||||
|
height: auto;
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
color: #f6f6f6;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 16px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: normal;
|
||||||
|
letter-spacing: -0.32px;
|
||||||
|
margin-right: 1.5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
display: none;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
width: 30px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 0.5vh;
|
||||||
|
width: 30vw;
|
||||||
|
height: 35vh;
|
||||||
|
padding: 3vh 3.5vw;
|
||||||
|
border-radius: 0.5vh;
|
||||||
|
background-color: #161616;
|
||||||
|
color: #f6f6f6;
|
||||||
|
margin-bottom: 1vh;
|
||||||
|
transform: translateY(-5vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline1 {
|
||||||
|
color: #f6f6f6;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 36px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 44px;
|
||||||
|
letter-spacing: -0.72px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.headline2 {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 36px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 800;
|
||||||
|
line-height: 44px;
|
||||||
|
letter-spacing: -0.72px;
|
||||||
|
background: linear-gradient(104deg, #00FFC2 0%, #00C0FF 100%);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
color: transparent;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description-text {
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 22px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 32px;
|
||||||
|
color: #f6f6f6;
|
||||||
|
margin-top: 2vh;
|
||||||
|
max-width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-highlight {
|
||||||
|
background: linear-gradient(103deg, #00FFC2 15.48%, #00C0FF 39.9%);
|
||||||
|
background-clip: text;
|
||||||
|
-webkit-background-clip: text;
|
||||||
|
-webkit-text-fill-color: transparent;
|
||||||
|
font-family: 'Montserrat', sans-serif;
|
||||||
|
font-size: 22px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 25px;
|
||||||
|
letter-spacing: -0.44px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-signup-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 1vh;
|
||||||
|
width: 30vw;
|
||||||
|
padding: 2vh 3.5vw;
|
||||||
|
border-radius: 0.5vh;
|
||||||
|
background-color: #292F38;
|
||||||
|
color: #f6f6f6;
|
||||||
|
box-shadow: 0px 0.5vh 1.5vh rgba(0, 0, 0, 0.25);
|
||||||
|
transform: translateY(-7vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-input {
|
||||||
|
width: 284px;
|
||||||
|
height: 48px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #667085;
|
||||||
|
background-color: #161616;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 0.5vh 1vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-button {
|
||||||
|
background: linear-gradient(90deg, #00ffc2 0%, #00c0ff 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 1vh 2vw;
|
||||||
|
border-radius: 0.4vh;
|
||||||
|
border: none;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-gradient {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 200vh;
|
||||||
|
background: linear-gradient(to bottom, rgba(0, 0, 0, 0.7), rgba(0, 0, 0, 0) 80%);
|
||||||
|
z-index: 2;
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media only screen and (max-width: 768px) {
|
||||||
|
.header-container {
|
||||||
|
background-size: 250%;
|
||||||
|
background-position: center top;
|
||||||
|
flex-direction: column;
|
||||||
|
padding: 2vh 5vw;
|
||||||
|
}
|
||||||
|
|
||||||
|
.header-background {
|
||||||
|
background-size: cover;
|
||||||
|
background-position: center;
|
||||||
|
height: 80vh;
|
||||||
|
background-color: black;
|
||||||
|
}
|
||||||
|
|
||||||
|
.container {
|
||||||
|
width: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.logo {
|
||||||
|
width: 180px;
|
||||||
|
height: auto;
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-left: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.nav-link {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
margin-right: 40px;
|
||||||
|
margin-top: 45px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-icon {
|
||||||
|
display: block;
|
||||||
|
width: 33px;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-box, .email-signup-box {
|
||||||
|
width: 80vw;
|
||||||
|
margin-left: -7vw;
|
||||||
|
margin-right: 0;
|
||||||
|
text-align: left;
|
||||||
|
transform: translateY(3vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-box {
|
||||||
|
padding: 3vh 4vw;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-signup-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: stretch;
|
||||||
|
padding: 20px;
|
||||||
|
background-color: #292F38;
|
||||||
|
color: #f6f6f6;
|
||||||
|
box-sizing: border-box;
|
||||||
|
border-radius: 8px;
|
||||||
|
height: auto;
|
||||||
|
min-height: 210px;
|
||||||
|
transform: translateY(1vh);
|
||||||
|
}
|
||||||
|
|
||||||
|
.email-input {
|
||||||
|
width: 87.5%;
|
||||||
|
height: 48px;
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
border: 1px solid #667084;
|
||||||
|
background-color: #161616;
|
||||||
|
color: #f6f6f6;
|
||||||
|
margin: 0;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 76px;
|
||||||
|
left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-button {
|
||||||
|
width: 87.5%;
|
||||||
|
height: 48px;
|
||||||
|
padding: 12px;
|
||||||
|
background: linear-gradient(90deg, #00ffc2 0%, #00c0ff 100%);
|
||||||
|
color: #ffffff;
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
border: none;
|
||||||
|
border-radius: 16px;
|
||||||
|
box-sizing: border-box;
|
||||||
|
position: absolute;
|
||||||
|
top: 140px;
|
||||||
|
left: 13px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gradient-button:hover {
|
||||||
|
background: linear-gradient(90deg, #00c0ff 0%, #00ffc2 100%);
|
||||||
|
}
|
||||||
|
}
|
66
wwws/admin/src/components/header.html
Normal file
66
wwws/admin/src/components/header.html
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
<!-- components/header.html -->
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta http-equiv="X-UA-Compatible" content="IE=edge">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
|
<!-- Tailwind CSS CDN -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/tailwindcss@2.2.19/dist/tailwind.min.css" rel="stylesheet">
|
||||||
|
<!-- DaisyUI CDN -->
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@2.15.3/dist/full.css" rel="stylesheet">
|
||||||
|
<!-- Link to the CSS file -->
|
||||||
|
<link rel="stylesheet" href="components/header.css">
|
||||||
|
<title>Header Component</title>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<header class="relative w-full bg-cover bg-center header-background" style="height: 100vh; background-image: url('https://s3-alpha-sig.figma.com/img/193d/32f1/73586125aba9fb9700efa4c194bb4fd2?Expires=1725840000&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=XPsu3f44VaAU9oU0vw4rWpKWYIEyYYXX3IKJLZGipBD0FqufIBt6fB5Ng3kuY~hx~TWh5iNa4vggL5IwCXf6rLyyZWLv6dydPIdGKzM3dSiVnVwQA5xwtJ5rXvxUMBbI9kXJRxq96~byLWCPjOPNWtbibTG7ptbcs98bkBFF8TBSPskgvx~XNorIq1Q2P2uaxMe25m7LQ89wFXsVSbWDn75j7Gkvyutu2vAiz2E9bxG7~o3naMwz1fpec8tSkgN9XGqnXKruYYyzJ2pDc4tz3PC80vVncK1-Oh-MPXVdlLQPQjd8wM1CmNWGuz2FN7Q7KCF-bXeRl8eq-jXoeNjRmw__');">
|
||||||
|
<!-- Gradient overlay -->
|
||||||
|
<div class="header-gradient"></div>
|
||||||
|
<div class="absolute inset-0 bg-black opacity-20"></div>
|
||||||
|
|
||||||
|
<!-- Black background for the remaining 20% -->
|
||||||
|
<div class="remaining-background"></div>
|
||||||
|
|
||||||
|
<div class="relative z-10 flex items-center justify-between p-4 container">
|
||||||
|
<!-- Logo -->
|
||||||
|
<div class="flex items-center">
|
||||||
|
<img src='./static/img/icons/Smatchit_Logo.png' class="logo" alt="Logo">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Navigation Links (Visible in Desktop Mode) -->
|
||||||
|
<nav class="space-x-8 text-white nav-links">
|
||||||
|
<a href="#" class="nav-link">Pour les entreprises</a>
|
||||||
|
<a href="#" class="nav-link">Contact</a>
|
||||||
|
</nav>
|
||||||
|
|
||||||
|
<!-- Menu Button (Visible in Mobile Mode) -->
|
||||||
|
<button class="menu-button">
|
||||||
|
<img src='./static/img/icons/menu.png' class="menu-icon" alt="Menu">
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Main Content -->
|
||||||
|
<div class="relative z-20 flex flex-col items-start justify-center h-full text-left text-white px-8" style="padding-top: 4vh; margin-left: 10vw;">
|
||||||
|
<!-- Text Box -->
|
||||||
|
<div class="text-box">
|
||||||
|
<p class="headline1">Votre</p>
|
||||||
|
<p class="headline2">agent de recrutement</p>
|
||||||
|
<p class="headline1">personnel 24/7.</p>
|
||||||
|
<p class="description-text">
|
||||||
|
Laissez <span class="text-highlight">smatchit</span> faire pour trouver votre job, détendez-vous et soyez à l’heure à vos futurs entretiens 😊
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Email Signup Box -->
|
||||||
|
<div class="email-signup-box mt-4">
|
||||||
|
<p class="mb-2">Soyez alerté du lancement de <span class="text-highlight">smatchit</span> :</p>
|
||||||
|
<div class="flex items-center w-full space-x-2">
|
||||||
|
<input type="email" placeholder="Enter your e-mail" class="email-input" />
|
||||||
|
<button type="submit" class="gradient-button">SEND</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</header>
|
||||||
|
</body>
|
||||||
|
</html>
|
13
wwws/admin/src/components/header.js
Normal file
13
wwws/admin/src/components/header.js
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
// components/header/header.js
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
const button = document.querySelector('.btn-primary');
|
||||||
|
button.addEventListener('click', () => {
|
||||||
|
const emailInput = document.querySelector('.input[type="email"]').value;
|
||||||
|
if (emailInput) {
|
||||||
|
alert(`Email ${emailInput} submitted successfully!`);
|
||||||
|
} else {
|
||||||
|
alert('Please enter a valid email.');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
8
wwws/admin/src/components/inset.css
Normal file
8
wwws/admin/src/components/inset.css
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
.inset-wrapper {
|
||||||
|
max-width: 1024px;
|
||||||
|
margin: 0 auto;
|
||||||
|
box-shadow: none;
|
||||||
|
border-radius: 0;
|
||||||
|
padding: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
4
wwws/admin/src/components/media-item.html
Normal file
4
wwws/admin/src/components/media-item.html
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<!-- media-item.html -->
|
||||||
|
<div class="media-item">
|
||||||
|
<img src="{{src}}" alt="{{alt}}" class="w-full h-auto object-cover">
|
||||||
|
</div>
|
72
wwws/admin/src/components/media-layout.js
Normal file
72
wwws/admin/src/components/media-layout.js
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
// layout-manager.js
|
||||||
|
document.addEventListener("DOMContentLoaded", function () {
|
||||||
|
const mediaContainer = document.getElementById('media-container');
|
||||||
|
|
||||||
|
// Media items with the specified image URL repeated
|
||||||
|
const mediaItems = [
|
||||||
|
{ type: 'image', src: 'https://s3-alpha-sig.figma.com/img/37a7/42fb/1daabd5424c6093ca79a6d502b3b84ef?Expires=1724630400&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=FgQK8BhmZm52syueYHL8d33N9v48-lePOhb~KQgE528f-GZn8a8HXap5OvWlCdKaf2nZLlFvmgWgpnuz7iekJqEAS95CknKbniYODvBDOGMVIcHrU7YthPZ~YqZSE7pEANVBVEwkB9-1zJ77gT9uEMryjd-xb44NjnBhfLPkP4B9qlqkbuehRRhLGPBnYA9q3PHpf5ocx7j0~xAbomT~EFX2bzwBu70gKN0qTFVRy8uNu8USYah2YotQH58ChxcEPokhPxENAdNCWeDodsWVFrldbeU0CgVzCrn3MLYu9Ep96r7tjAvKfgQQqC6eORHyM-citVFu1DlJx8-CUN26RQ__', alt: 'Image 1' },
|
||||||
|
{ type: 'image', src: 'https://s3-alpha-sig.figma.com/img/37a7/42fb/1daabd5424c6093ca79a6d502b3b84ef?Expires=1724630400&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=FgQK8BhmZm52syueYHL8d33N9v48-lePOhb~KQgE528f-GZn8a8HXap5OvWlCdKaf2nZLlFvmgWgpnuz7iekJqEAS95CknKbniYODvBDOGMVIcHrU7YthPZ~YqZSE7pEANVBVEwkB9-1zJ77gT9uEMryjd-xb44NjnBhfLPkP4B9qlqkbuehRRhLGPBnYA9q3PHpf5ocx7j0~xAbomT~EFX2bzwBu70gKN0qTFVRy8uNu8USYah2YotQH58ChxcEPokhPxENAdNCWeDodsWVFrldbeU0CgVzCrn3MLYu9Ep96r7tjAvKfgQQqC6eORHyM-citVFu1DlJx8-CUN26RQ__', alt: 'Image 2' },
|
||||||
|
// { type: 'image', src: 'https://s3-alpha-sig.figma.com/img/37a7/42fb/1daabd5424c6093ca79a6d502b3b84ef?Expires=1724630400&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=FgQK8BhmZm52syueYHL8d33N9v48-lePOhb~KQgE528f-GZn8a8HXap5OvWlCdKaf2nZLlFvmgWgpnuz7iekJqEAS95CknKbniYODvBDOGMVIcHrU7YthPZ~YqZSE7pEANVBVEwkB9-1zJ77gT9uEMryjd-xb44NjnBhfLPkP4B9qlqkbuehRRhLGPBnYA9q3PHpf5ocx7j0~xAbomT~EFX2bzwBu70gKN0qTFVRy8uNu8USYah2YotQH58ChxcEPokhPxENAdNCWeDodsWVFrldbeU0CgVzCrn3MLYu9Ep96r7tjAvKfgQQqC6eORHyM-citVFu1DlJx8-CUN26RQ__', alt: 'Image 3' },
|
||||||
|
{ type: 'image', src: 'https://s3-alpha-sig.figma.com/img/37a7/42fb/1daabd5424c6093ca79a6d502b3b84ef?Expires=1724630400&Key-Pair-Id=APKAQ4GOSFWCVNEHN3O4&Signature=FgQK8BhmZm52syueYHL8d33N9v48-lePOhb~KQgE528f-GZn8a8HXap5OvWlCdKaf2nZLlFvmgWgpnuz7iekJqEAS95CknKbniYODvBDOGMVIcHrU7YthPZ~YqZSE7pEANVBVEwkB9-1zJ77gT9uEMryjd-xb44NjnBhfLPkP4B9qlqkbuehRRhLGPBnYA9q3PHpf5ocx7j0~xAbomT~EFX2bzwBu70gKN0qTFVRy8uNu8USYah2YotQH58ChxcEPokhPxENAdNCWeDodsWVFrldbeU0CgVzCrn3MLYu9Ep96r7tjAvKfgQQqC6eORHyM-citVFu1DlJx8-CUN26RQ__', alt: 'Image 4' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const useQuatreA = false; // Set this to true for Quatre A, false for Quatre B
|
||||||
|
|
||||||
|
// Apply the appropriate layout based on the number of media items
|
||||||
|
const applyLayout = () => {
|
||||||
|
mediaContainer.innerHTML = ''; // Clear existing content
|
||||||
|
const itemCount = mediaItems.length;
|
||||||
|
|
||||||
|
if (itemCount === 1) {
|
||||||
|
// Single Image Layout
|
||||||
|
mediaContainer.className = 'single-layout';
|
||||||
|
mediaContainer.innerHTML += `<div class="media-item">${renderMedia(mediaItems[0])}</div>`;
|
||||||
|
} else if (itemCount === 2) {
|
||||||
|
// Duo Layout
|
||||||
|
mediaContainer.className = 'duo-layout';
|
||||||
|
mediaItems.forEach(item => {
|
||||||
|
mediaContainer.innerHTML += `<div class="media-item">${renderMedia(item)}</div>`;
|
||||||
|
});
|
||||||
|
} else if (itemCount === 3) {
|
||||||
|
// Trio Layout
|
||||||
|
mediaContainer.className = 'trio-layout';
|
||||||
|
mediaContainer.innerHTML += `<div class="left-column media-item">${renderMedia(mediaItems[0])}</div>`;
|
||||||
|
mediaContainer.innerHTML += `
|
||||||
|
<div class="right-column">
|
||||||
|
<div class="media-item">${renderMedia(mediaItems[1])}</div>
|
||||||
|
<div class="media-item">${renderMedia(mediaItems[2])}</div>
|
||||||
|
</div>`;
|
||||||
|
return;
|
||||||
|
} else if (itemCount >= 4) {
|
||||||
|
if (useQuatreA) {
|
||||||
|
// Quatre A Layout
|
||||||
|
mediaContainer.className = 'grid grid-cols-2 gap-4';
|
||||||
|
mediaItems.forEach(item => {
|
||||||
|
mediaContainer.innerHTML += `<div class="media-item">${renderMedia(item)}</div>`;
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Quatre B Layout
|
||||||
|
mediaContainer.className = 'quatre-b-layout';
|
||||||
|
mediaContainer.innerHTML += `<div class="top-image">${renderMedia(mediaItems[0])}</div>`;
|
||||||
|
mediaContainer.innerHTML += `<div class="bottom-row">`;
|
||||||
|
for (let i = 1; i < Math.min(5, itemCount); i++) {
|
||||||
|
mediaContainer.innerHTML += `<div class="media-item">${renderMedia(mediaItems[i])}</div>`;
|
||||||
|
}
|
||||||
|
mediaContainer.innerHTML += `</div>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Function to render media based on its type
|
||||||
|
const renderMedia = (item) => {
|
||||||
|
if (item.type === 'image') {
|
||||||
|
return `<img src="${item.src}" alt="${item.alt}">`; /* Ensure images fill their containers */
|
||||||
|
} else if (item.type === 'video') {
|
||||||
|
return `<video src="${item.src}" controls class="w-full h-auto"></video>`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// Apply the layout
|
||||||
|
applyLayout();
|
||||||
|
});
|
21
wwws/admin/src/components/media.js
Normal file
21
wwws/admin/src/components/media.js
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
document.addEventListener("DOMContentLoaded", function() {
|
||||||
|
const mediaType = 'image';
|
||||||
|
const hasPlayButton = true;
|
||||||
|
const image = document.getElementById('media-image');
|
||||||
|
const video = document.getElementById('media-video');
|
||||||
|
const playButton = document.getElementById('play-button');
|
||||||
|
const caption = document.getElementById('caption');
|
||||||
|
|
||||||
|
|
||||||
|
if (mediaType === 'image') {
|
||||||
|
image.classList.remove('hidden');
|
||||||
|
} else if (mediaType === 'video') {
|
||||||
|
video.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if (hasPlayButton && mediaType === 'video') {
|
||||||
|
playButton.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
72
wwws/admin/src/components/quatreLayout.css
Normal file
72
wwws/admin/src/components/quatreLayout.css
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
.quatre-b-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.quatre-b-layout .top-image {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
height: 440px;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: flex-start;
|
||||||
|
gap: 16px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
grid-column: 1 / span 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .top-image img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .bottom-row {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .media-item {
|
||||||
|
overflow: hidden;
|
||||||
|
grid-column: span 1;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
object-fit: contain;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
.quatre-b-layout {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: repeat(3, 1fr);
|
||||||
|
grid-template-rows: auto 1fr;
|
||||||
|
gap: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .top-image {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
grid-column: 1 / span 3;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .bottom-row {
|
||||||
|
display: contents;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .media-item {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
grid-column: span 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.quatre-b-layout .media-item img {
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
67
wwws/admin/src/components/radiobutton.css
Normal file
67
wwws/admin/src/components/radiobutton.css
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
.radio-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
width: 130px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-input {
|
||||||
|
appearance: none;
|
||||||
|
border: 2px solid #98A2B3;
|
||||||
|
border-radius: 50%;
|
||||||
|
width: var(--Chart-Column-Margin, 24px);
|
||||||
|
height: var(--Chart-Column-Margin, 24px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
flex-shrink: 0;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-input:checked {
|
||||||
|
border-color: #0DC3FF;
|
||||||
|
background-color: #0DC3FF;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-input:checked::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: white;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--app-input-text-label-day, #1E2024);
|
||||||
|
font-family: Inter, sans-serif;
|
||||||
|
font-size: 14px;
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label-left {
|
||||||
|
justify-content: flex-end;
|
||||||
|
margin-left: 10px;
|
||||||
|
order: 2;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.radio-label-right {
|
||||||
|
justify-content: flex-start;
|
||||||
|
margin-right: 10px;
|
||||||
|
order: 1;
|
||||||
|
flex-grow: 1;
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user