1st commit

This commit is contained in:
2025-07-01 11:09:51 +02:00
commit 2d3e33d643
787 changed files with 185055 additions and 0 deletions

View File

View 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);

View 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..."
}

View 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>

View File

View 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>

View 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()"
}
]
}

View 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
View 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
View 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: {},
});
});
};

View 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"
}

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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
View File

@@ -0,0 +1,6 @@
var apx = apx || {};
apx.chat = {};
apx.chat.show=()=>{
}

1
wco/conf.json Normal file
View File

@@ -0,0 +1 @@
{"name":"wco","schema":"apxtri/schema/wco.json","lastupdate":0}

View 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>

View 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"
}
}
}

View 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
View 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
View 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
View 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
View 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
View 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
View 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"
}
}

View 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": []
}
]
}

View 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>

View 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>

View 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}}

View File

View 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
View 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"]
}
]
}

View 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

File diff suppressed because one or more lines are too long

922
wco/testapi/testapi.js Normal file
View 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);
*/

View 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>

View 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"
}
]
}

View 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
View 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);