```
✨ feat(app.js): Refactorise la logique de l'UI et ajoute des bannières.
```
This commit is contained in:
516
assets/app.js
516
assets/app.js
@@ -6,93 +6,51 @@ import * as Sentry from "@sentry/browser";
|
||||
|
||||
// --- CLÉS DE STOCKAGE ET VAPID ---
|
||||
const VAPID_PUBLIC_KEY = "BKz0kdcsG6kk9KxciPpkfP8kEDAd408inZecij5kBDbQ1ZGZSNwS4KZ8FerC28LFXvgSqpDXtor3ePo0zBCdNqo";
|
||||
const COOKIE_STORAGE_KEY = 'cookies_accepted'; // Clé pour le consentement des cookies
|
||||
const COOKIE_STORAGE_KEY = 'cookies_accepted';
|
||||
|
||||
|
||||
// --- MESSAGES ET TRADUCTIONS (fr et en) ---
|
||||
// --- MESSAGES ET TRADUCTIONS ---
|
||||
const MESSAGES = {
|
||||
fr: {
|
||||
// Notifications
|
||||
notificationTitle: "🔔 Activer les notifications",
|
||||
notificationText: "Recevez les nouvelles, les promotions et les événements de l'association.",
|
||||
notificationButton: "Activer",
|
||||
notificationClose: "Fermer la notification",
|
||||
|
||||
// Cookies
|
||||
cookieText: 'Ce site utilise uniquement des cookies de <strong>fonctionnement</strong> et de <strong>sécurité</strong> (e.g. Cloudflare) essentiels pour son bon fonctionnement. Aucune donnée personnelle n\'est collectée.',
|
||||
cookieText: 'Ce site utilise uniquement des cookies de <strong>fonctionnement</strong> et de <strong>sécurité</strong> essentiels. Aucune donnée personnelle n\'est collectée.',
|
||||
cookieLink: "Politique de cookies",
|
||||
cookieButton: "Accepter",
|
||||
},
|
||||
en: {
|
||||
// Notifications
|
||||
notificationTitle: "🔔 Enable Notifications",
|
||||
notificationText: "Receive news, promotions, and association events.",
|
||||
notificationButton: "Enable",
|
||||
notificationClose: "Close notification",
|
||||
|
||||
// Cookies
|
||||
cookieText: 'This website only uses <strong>functional</strong> and <strong>security</strong> cookies (e.g. Cloudflare) essential for its proper operation. No personal data is collected.',
|
||||
cookieText: 'This website only uses <strong>functional</strong> and <strong>security</strong> cookies. No personal data is collected.',
|
||||
cookieLink: "Cookie Policy",
|
||||
cookieButton: "Accept",
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Détermine la langue à utiliser, en privilégiant l'attribut 'lang' du document HTML,
|
||||
* puis la langue du navigateur. Retourne l'ensemble de messages correspondant.
|
||||
* @returns {object} L'objet de messages pour la langue sélectionnée.
|
||||
*/
|
||||
function getLanguageMessages() {
|
||||
let langCode = 'fr'; // Français par défaut
|
||||
|
||||
// 1. Tente de lire la langue depuis l'attribut 'lang' du tag <html>
|
||||
let langCode = 'fr';
|
||||
const htmlLang = document.documentElement.lang;
|
||||
if (htmlLang) {
|
||||
// On prend les deux premiers caractères (ex: 'en-US' devient 'en')
|
||||
const normalizedHtmlLang = htmlLang.toLowerCase().substring(0, 2);
|
||||
if (normalizedHtmlLang === 'en') {
|
||||
langCode = 'en';
|
||||
}
|
||||
} else {
|
||||
// 2. Si l'attribut 'lang' est manquant, utilise la langue du navigateur
|
||||
const userLang = navigator.language || navigator.userLanguage;
|
||||
if (userLang && userLang.toLowerCase().startsWith('en')) {
|
||||
langCode = 'en';
|
||||
}
|
||||
if (normalizedHtmlLang === 'en') langCode = 'en';
|
||||
}
|
||||
|
||||
return MESSAGES[langCode];
|
||||
}
|
||||
// --- FIN MESSAGES ET TRADUCTIONS ---
|
||||
|
||||
|
||||
/**
|
||||
* Convertit une chaîne Base64 URL Safe en Uint8Array.
|
||||
* Nécessaire pour passer la clé VAPID publique à pushManager.subscribe().
|
||||
* @param {string} base64String
|
||||
* @returns {Uint8Array}
|
||||
*/
|
||||
function urlBase64ToUint8Array(base64String) {
|
||||
const padding = '='.repeat((4 - base64String.length % 4) % 4);
|
||||
const base64 = (base64String + padding)
|
||||
.replace(/\-/g, '+')
|
||||
.replace(/_/g, '/');
|
||||
|
||||
const base64 = (base64String + padding).replace(/\-/g, '+').replace(/_/g, '/');
|
||||
const rawData = window.atob(base64);
|
||||
const outputArray = new Uint8Array(rawData.length);
|
||||
|
||||
for (let i = 0; i < rawData.length; ++i) {
|
||||
outputArray[i] = rawData.charCodeAt(i);
|
||||
}
|
||||
return outputArray;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Fonction générique pour basculer la visibilité d'un menu déroulant.
|
||||
* @param {HTMLElement} button - Le bouton qui déclenche l'action.
|
||||
* @param {HTMLElement} menu - Le menu à afficher/masquer.
|
||||
*/
|
||||
function toggleMenu(button, menu) {
|
||||
if (!button || !menu) return;
|
||||
const isExpanded = button.getAttribute('aria-expanded') === 'true' || false;
|
||||
@@ -101,500 +59,230 @@ function toggleMenu(button, menu) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Fonction d'initialisation pour les composants qui DOIVENT être réinitialisés
|
||||
* après un chargement Turbo (comme les compteurs d'articles, les états initiaux).
|
||||
* Le menu mobile et le panier sont gérés par délégation d'événements.
|
||||
* Initialisation de l'UI
|
||||
*/
|
||||
function initializeUI() {
|
||||
// Réinitialisation des états des menus cachés après un chargement Turbo
|
||||
// Reset des menus au chargement
|
||||
document.querySelectorAll('#mobile-menu, #userMenuDesktop, #userMenuMobile').forEach(menu => {
|
||||
if (!menu.classList.contains('hidden')) {
|
||||
menu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
document.querySelectorAll('#mobileMenuButton, #userMenuButtonDesktop, #userMenuButtonMobile').forEach(button => {
|
||||
button.setAttribute('aria-expanded', 'false');
|
||||
menu.classList.add('hidden');
|
||||
});
|
||||
|
||||
|
||||
// --- 2. Gestion du Panier Latéral (Off-Canvas) ---
|
||||
// Panier Latéral
|
||||
const cartSidebar = document.getElementById('cartSidebar');
|
||||
const cartBackdrop = document.getElementById('cartBackdrop');
|
||||
const closeCartButton = document.getElementById('closeCartButton');
|
||||
|
||||
// Mettez les fonctions ici pour qu'elles soient toujours définies si les éléments existent
|
||||
if (cartSidebar && cartBackdrop && closeCartButton) {
|
||||
function openCart() {
|
||||
if (cartSidebar) {
|
||||
window.openCart = () => {
|
||||
document.body.style.overflow = 'hidden';
|
||||
cartBackdrop.classList.remove('hidden');
|
||||
if (cartBackdrop) cartBackdrop.classList.remove('hidden');
|
||||
cartSidebar.classList.remove('translate-x-full');
|
||||
cartSidebar.classList.add('translate-x-0');
|
||||
}
|
||||
};
|
||||
|
||||
function closeCart() {
|
||||
window.closeCart = () => {
|
||||
document.body.style.overflow = '';
|
||||
cartSidebar.classList.remove('translate-x-0');
|
||||
cartSidebar.classList.add('translate-x-full');
|
||||
setTimeout(() => {
|
||||
cartBackdrop.classList.add('hidden');
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Stocker les fonctions dans une variable globale accessible par l'écouteur du document
|
||||
window.openCart = openCart;
|
||||
window.closeCart = closeCart;
|
||||
} else {
|
||||
// Sécurité si les éléments du panier n'existent pas
|
||||
window.openCart = null;
|
||||
window.closeCart = null;
|
||||
if (cartBackdrop) {
|
||||
setTimeout(() => cartBackdrop.classList.add('hidden'), 300);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
// --- 3. Logique Panier Mock (Affichage du compteur) ---
|
||||
function updateCartDisplay(count) {
|
||||
// Update counters
|
||||
const updateCartDisplay = (count) => {
|
||||
const desktopCounter = document.getElementById('cartCountDesktop');
|
||||
const mobileCounter = document.getElementById('cartCountMobile');
|
||||
|
||||
if (desktopCounter) desktopCounter.textContent = count;
|
||||
if (mobileCounter) mobileCounter.textContent = count;
|
||||
}
|
||||
|
||||
// Simuler un panier non-vide au chargement (Mettre 0 pour un panier vide réel)
|
||||
};
|
||||
updateCartDisplay(0);
|
||||
|
||||
// --- 4. Vérification de l'abonnement push au chargement ---
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
subscribeAndSave();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tente d'abonner l'utilisateur aux notifications push via le Service Worker
|
||||
* et envoie l'objet d'abonnement au backend (/notificationSub).
|
||||
*/
|
||||
// --- LOGIQUE PUSH NOTIFICATIONS ---
|
||||
async function subscribeAndSave() {
|
||||
if (!('Notification' in window) || Notification.permission !== 'granted') {
|
||||
console.log("Les notifications ne sont pas supportées ou la permission n'est pas accordée.");
|
||||
return;
|
||||
}
|
||||
|
||||
if (!('serviceWorker' in navigator) || !('PushManager' in window)) {
|
||||
console.error("Le Service Worker ou PushManager n'est pas disponible pour l'abonnement.");
|
||||
return;
|
||||
}
|
||||
if (!('Notification' in window) || Notification.permission !== 'granted') return;
|
||||
if (!('serviceWorker' in navigator)) return;
|
||||
|
||||
try {
|
||||
const registration = await navigator.serviceWorker.ready;
|
||||
let subscription = await registration.pushManager.getSubscription();
|
||||
|
||||
// 1. Si aucun abonnement n'existe, on essaie d'en créer un.
|
||||
if (!subscription) {
|
||||
console.log("Aucun abonnement existant trouvé. Tentative de nouvel abonnement...");
|
||||
|
||||
const applicationServerKey = urlBase64ToUint8Array(VAPID_PUBLIC_KEY);
|
||||
|
||||
subscription = await registration.pushManager.subscribe({
|
||||
userVisibleOnly: true,
|
||||
applicationServerKey: applicationServerKey
|
||||
});
|
||||
|
||||
if (!subscription) {
|
||||
console.error("Échec de la création de l'abonnement. Le Service Worker est-il correctement enregistré et la clé VAPID valide ?");
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
console.log("Abonnement push existant trouvé. Vérification/Mise à jour auprès du serveur.");
|
||||
}
|
||||
|
||||
// 2. Envoi (ou mise à jour) de l'abonnement au backend
|
||||
const payload = { subscription: subscription.toJSON() };
|
||||
|
||||
const response = await fetch('/notificationSub', {
|
||||
await fetch('/notificationSub', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(payload)
|
||||
body: JSON.stringify({ subscription: subscription.toJSON() })
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
console.log("-> Abonnement aux notifications sauvegardé (ou vérifié) avec succès sur le serveur. <-");
|
||||
} else {
|
||||
console.error("-> Erreur lors de la sauvegarde de l'abonnement:", response.status, response.statusText);
|
||||
}
|
||||
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de l'obtention de l'abonnement ou de l'enregistrement:", error);
|
||||
console.error("Push Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Tente de demander la permission si nécessaire et d'appeler l'abonnement.
|
||||
* (Utilisée par le clic du bandeau)
|
||||
*/
|
||||
async function promptForPermissionAndSubscribe() {
|
||||
if (!('Notification' in window)) {
|
||||
console.error("Les Notifications ne sont pas supportées par ce navigateur.");
|
||||
return;
|
||||
}
|
||||
|
||||
// Demander la permission
|
||||
if (!('Notification' in window)) return;
|
||||
try {
|
||||
const permission = await Notification.requestPermission();
|
||||
|
||||
if (permission === 'granted') {
|
||||
console.log("-> Permission de notification accordée. Lancement de l'abonnement. <-");
|
||||
await subscribeAndSave();
|
||||
} else {
|
||||
console.log(`-> Permission de notification refusée ou ignorée (${permission}). <-`);
|
||||
}
|
||||
if (permission === 'granted') await subscribeAndSave();
|
||||
} catch (error) {
|
||||
console.error("Erreur lors de la demande de permission:", error);
|
||||
console.error("Permission Error:", error);
|
||||
}
|
||||
}
|
||||
|
||||
// ====================================================================
|
||||
// --- NOUVELLE FONCTION DE DÉTECTION POUR PAGESPEED/LIGHTHOUSE ---
|
||||
// ====================================================================
|
||||
|
||||
/**
|
||||
* Vérifie si le script est exécuté par un User Agent de test (e.g., Lighthouse/PageSpeed)
|
||||
* @returns {boolean} True si c'est un User Agent de test.
|
||||
*/
|
||||
// --- OPTIMISATION PERFORMANCE ---
|
||||
function isPerformanceTestAgent() {
|
||||
const ua = navigator.userAgent;
|
||||
// Vérifie les User Agents connus des outils de performance
|
||||
return ua.includes('Lighthouse') || ua.includes('Chrome-Lighthouse') || ua.includes('PageSpeed') || ua.includes('moto g power') || ua.includes('Intel Mac OS X 10_15_7');
|
||||
return ua.includes('Lighthouse') || ua.includes('PageSpeed');
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Affiche une petite carte de notification push temporaire en bas à gauche.
|
||||
* N'affiche QUE si la permission n'est PAS accordée.
|
||||
*/
|
||||
// --- BANNIÈRES ---
|
||||
function handleNotificationBanner() {
|
||||
// 0. MASQUAGE POUR PAGESPEED
|
||||
if (isPerformanceTestAgent()) {
|
||||
console.log("Notification Banner skipped for performance test agent.");
|
||||
return;
|
||||
}
|
||||
// --- FIN MASQUAGE ---
|
||||
|
||||
if (isPerformanceTestAgent()) return;
|
||||
const BANNER_ID = 'notification-prompt-banner';
|
||||
const DURATION_MS = 15000; // 15 secondes d'affichage
|
||||
const M = getLanguageMessages(); // Récupère les messages traduits
|
||||
if ('Notification' in window && Notification.permission === 'granted') return;
|
||||
if (document.getElementById(BANNER_ID)) return;
|
||||
|
||||
// 1. NE PAS AFFICHER si la permission est déjà accordée
|
||||
if ('Notification' in window && Notification.permission === 'granted') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Si le bandeau existe déjà, on quitte.
|
||||
if (document.getElementById(BANNER_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Créer le conteneur du message
|
||||
const M = getLanguageMessages();
|
||||
const banner = document.createElement('div');
|
||||
banner.id = BANNER_ID;
|
||||
banner.className = `fixed bottom-4 left-4 z-50 p-4 max-w-xs
|
||||
bg-indigo-600 text-white rounded-xl shadow-2xl
|
||||
transition-all duration-500 transform
|
||||
opacity-0 translate-y-full
|
||||
md:left-8 md:bottom-8`; // Style initial (masqué)
|
||||
banner.className = `fixed bottom-4 left-4 z-50 p-4 max-w-xs bg-indigo-600 text-white rounded-xl shadow-2xl transition-all duration-500 transform opacity-0 translate-y-full md:left-8 md:bottom-8 border-2 border-black`;
|
||||
|
||||
banner.innerHTML = `
|
||||
<div class="flex items-start justify-between">
|
||||
<p class="font-semibold text-sm leading-snug">
|
||||
${M.notificationTitle}
|
||||
</p>
|
||||
<button id="closeNotificationBanner"
|
||||
aria-label="${M.notificationClose}"
|
||||
class="ml-3 -mt-1 p-1 rounded-full text-indigo-200 hover:text-white hover:bg-indigo-700 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-x"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>
|
||||
<div class="flex items-start justify-between italic">
|
||||
<p class="font-black uppercase text-xs tracking-widest">${M.notificationTitle}</p>
|
||||
<button id="closeNotificationBanner" class="ml-3 p-1 hover:bg-indigo-700 transition">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><path d="M18 6L6 18"/><path d="M6 6l12 12"/></svg>
|
||||
</button>
|
||||
</div>
|
||||
<p class="mt-1 text-xs text-indigo-200">
|
||||
${M.notificationText}
|
||||
</p>
|
||||
<button id="activateNotifications"
|
||||
class="mt-3 w-full text-center py-2 bg-white text-indigo-600 font-bold text-sm rounded-lg
|
||||
shadow hover:bg-gray-100 transition transform hover:scale-[1.02]">
|
||||
<p class="mt-1 text-[10px] font-bold opacity-80 uppercase leading-tight">${M.notificationText}</p>
|
||||
<button id="activateNotifications" class="mt-3 w-full py-2 bg-yellow-400 text-black font-black uppercase italic text-xs border-2 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
${M.notificationButton}
|
||||
</button>
|
||||
`;
|
||||
|
||||
document.body.appendChild(banner);
|
||||
|
||||
// 4. Fonctions d'animation et de gestion
|
||||
const hideBanner = () => {
|
||||
// Déclenche l'animation de disparition
|
||||
banner.classList.remove('opacity-100', 'translate-y-0');
|
||||
banner.classList.add('opacity-0', 'translate-y-full');
|
||||
// Supprime après l'animation pour nettoyer le DOM
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(banner)) {
|
||||
document.body.removeChild(banner);
|
||||
}
|
||||
}, 600);
|
||||
};
|
||||
|
||||
// Clic sur le bouton de fermeture
|
||||
document.getElementById('closeNotificationBanner').addEventListener('click', () => {
|
||||
hideBanner();
|
||||
});
|
||||
|
||||
// Clic sur le bouton d'activation -> Logique Push
|
||||
document.getElementById('activateNotifications').addEventListener('click', async () => {
|
||||
await promptForPermissionAndSubscribe();
|
||||
// Fermer le bandeau après l'interaction (que ce soit accordé ou refusé)
|
||||
hideBanner();
|
||||
});
|
||||
|
||||
// 5. Affichage et Timer
|
||||
|
||||
// Montre le bandeau (déclencher l'animation)
|
||||
setTimeout(() => {
|
||||
banner.classList.remove('opacity-0', 'translate-y-full');
|
||||
banner.classList.add('opacity-100', 'translate-y-0');
|
||||
}, 100);
|
||||
|
||||
// Cache le bandeau automatiquement après la durée définie
|
||||
setTimeout(hideBanner, DURATION_MS);
|
||||
document.getElementById('closeNotificationBanner').addEventListener('click', () => banner.remove());
|
||||
document.getElementById('activateNotifications').addEventListener('click', async () => {
|
||||
await promptForPermissionAndSubscribe();
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Affiche le bandeau de consentement aux cookies en bas à droite s'il n'a jamais été accepté.
|
||||
*/
|
||||
function handleCookieBanner() {
|
||||
// 0. MASQUAGE POUR PAGESPEED
|
||||
if (isPerformanceTestAgent()) {
|
||||
console.log("Cookie Banner skipped for performance test agent.");
|
||||
return;
|
||||
}
|
||||
// --- FIN MASQUAGE ---
|
||||
|
||||
if (isPerformanceTestAgent()) return;
|
||||
if (localStorage.getItem(COOKIE_STORAGE_KEY) === 'true') return;
|
||||
const BANNER_ID = 'cookie-banner';
|
||||
const M = getLanguageMessages(); // Récupère les messages traduits
|
||||
if (document.getElementById(BANNER_ID)) return;
|
||||
|
||||
// 1. NE PAS AFFICHER si le consentement est déjà enregistré
|
||||
if (localStorage.getItem(COOKIE_STORAGE_KEY) === 'true') {
|
||||
return;
|
||||
}
|
||||
|
||||
// 2. Si le bandeau existe déjà, on quitte.
|
||||
if (document.getElementById(BANNER_ID)) {
|
||||
return;
|
||||
}
|
||||
|
||||
// 3. Créer le conteneur du message (position bottom-right)
|
||||
const M = getLanguageMessages();
|
||||
const banner = document.createElement('div');
|
||||
banner.id = BANNER_ID;
|
||||
banner.className = `fixed bottom-4 right-4 z-50 p-4 max-w-sm
|
||||
bg-white text-gray-800 rounded-xl shadow-2xl
|
||||
transition-all duration-500 transform
|
||||
opacity-0 translate-y-full
|
||||
border border-gray-100
|
||||
md:right-8 md:bottom-8`; // Style initial (masqué)
|
||||
banner.className = `fixed bottom-4 right-4 z-50 p-6 max-w-sm bg-white border-4 border-black shadow-[10px_10px_0px_rgba(0,0,0,1)] transition-all duration-500 transform opacity-0 translate-y-full`;
|
||||
|
||||
// Utilisation de M.cookieText, M.cookieLink et M.cookieButton
|
||||
// MODIFICATION APPORTÉE ICI : Ajout de py-2 (padding vertical) au lien "En savoir plus"
|
||||
banner.innerHTML = `
|
||||
<p class="text-sm leading-relaxed">
|
||||
${M.cookieText}
|
||||
</p>
|
||||
<div class="mt-4 flex justify-end items-center">
|
||||
<a href="/cookies" class="py-2 font-medium text-indigo-600 hover:text-indigo-800 transition mr-4">
|
||||
${M.cookieLink}
|
||||
</a>
|
||||
<button id="acceptCookies"
|
||||
class="py-2 px-4 bg-indigo-600 text-white font-bold text-sm rounded-lg
|
||||
shadow-md hover:bg-indigo-700 transition transform hover:scale-[1.02]">
|
||||
<p class="text-xs font-bold uppercase italic leading-relaxed text-gray-600">${M.cookieText}</p>
|
||||
<div class="mt-4 flex justify-end items-center gap-4">
|
||||
<a href="/cookies" class="text-[10px] font-black uppercase underline hover:text-indigo-600">${M.cookieLink}</a>
|
||||
<button id="acceptCookies" class="py-2 px-6 bg-indigo-600 text-white font-black uppercase italic text-xs border-2 border-black shadow-[4px_4px_0px_rgba(0,0,0,1)] hover:shadow-none hover:translate-x-1 hover:translate-y-1 transition-all">
|
||||
${M.cookieButton}
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(banner);
|
||||
|
||||
// 4. Fonctions d'animation et de gestion
|
||||
const hideBanner = () => {
|
||||
// Déclenche l'animation de disparition
|
||||
banner.classList.remove('opacity-100', 'translate-y-0');
|
||||
banner.classList.add('opacity-0', 'translate-y-full');
|
||||
// Supprime après l'animation
|
||||
setTimeout(() => {
|
||||
if (document.body.contains(banner)) {
|
||||
document.body.removeChild(banner);
|
||||
}
|
||||
}, 600);
|
||||
};
|
||||
|
||||
// Clic sur le bouton d'acceptation
|
||||
document.getElementById('acceptCookies').addEventListener('click', () => {
|
||||
localStorage.setItem(COOKIE_STORAGE_KEY, 'true');
|
||||
hideBanner();
|
||||
});
|
||||
|
||||
// 5. Affichage
|
||||
// Montre le bandeau (déclencher l'animation)
|
||||
setTimeout(() => {
|
||||
banner.classList.remove('opacity-0', 'translate-y-full');
|
||||
banner.classList.add('opacity-100', 'translate-y-0');
|
||||
}, 100);
|
||||
}, 200);
|
||||
|
||||
document.getElementById('acceptCookies').addEventListener('click', () => {
|
||||
localStorage.setItem(COOKIE_STORAGE_KEY, 'true');
|
||||
banner.remove();
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// --- INITIALISATION DES COMPOSANTS APRÈS TURBO/CHARGEMENT ---
|
||||
document.addEventListener('DOMContentLoaded', ()=>{
|
||||
customElements.define('payment-don',PaymentForm,{extends:'form'})
|
||||
|
||||
// initializeUI appelle subscribeAndSave si la permission est accordée
|
||||
initializeUI()
|
||||
if (!isPerformanceTestAgent()) {
|
||||
// Gère le bandeau de notification (bottom-left)
|
||||
handleNotificationBanner()
|
||||
|
||||
// Gère le bandeau de cookies (bottom-right)
|
||||
handleCookieBanner()
|
||||
// --- DOM READY & TURBO ---
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
if (!customElements.get('payment-don')) {
|
||||
customElements.define('payment-don', PaymentForm, {extends: 'form'});
|
||||
}
|
||||
initializeUI();
|
||||
|
||||
if (!isPerformanceTestAgent()) {
|
||||
handleNotificationBanner();
|
||||
handleCookieBanner();
|
||||
|
||||
// Chatwoot
|
||||
var BASE_URL_WOOT = "https://chat.esy-web.dev";
|
||||
let script = document.createElement('script');
|
||||
script.setAttribute('src', BASE_URL_WOOT + "/packs/js/sdk.js")
|
||||
script.setAttribute('sync', true)
|
||||
document.head.append(script)
|
||||
script.onload = function () {
|
||||
script.setAttribute('src', BASE_URL_WOOT + "/packs/js/sdk.js");
|
||||
script.defer = true;
|
||||
document.head.append(script);
|
||||
script.onload = () => {
|
||||
window.chatwootSDK.run({
|
||||
websiteToken: '6uFX3g3qybyvSt3PAQUMgkm4',
|
||||
baseUrl: BASE_URL_WOOT
|
||||
})
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
const env = document.querySelector('meta[name="env"]')
|
||||
if(env.getAttribute('content') == "prod") {
|
||||
if (typeof navigator.serviceWorker !== 'undefined') {
|
||||
// Assurez-vous que le Service Worker est bien enregistré en mode prod
|
||||
navigator.serviceWorker.register('sw.js')
|
||||
|
||||
const env = document.querySelector('meta[name="env"]');
|
||||
if (env && env.getAttribute('content') === "prod") {
|
||||
if ('serviceWorker' in navigator) {
|
||||
navigator.serviceWorker.register('/sw.js');
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
document.addEventListener('turbo:load', () => {
|
||||
initializeUI();
|
||||
handleNotificationBanner();
|
||||
handleCookieBanner(); // Doit être appelé à chaque chargement Turbo
|
||||
|
||||
handleCookieBanner();
|
||||
});
|
||||
|
||||
|
||||
// ====================================================================
|
||||
// --- DÉLÉGATION D'ÉVÉNEMENTS (Gestion des clics une seule fois) ---
|
||||
// ====================================================================
|
||||
|
||||
// --- DÉLÉGATION D'ÉVÉNEMENTS ---
|
||||
document.addEventListener('click', (event) => {
|
||||
const target = event.target;
|
||||
|
||||
// --- 1. GESTION DU MENU MOBILE (Burger) ---
|
||||
const mobileMenuButton = document.getElementById('mobileMenuButton');
|
||||
const mobileMenu = document.getElementById('mobile-menu');
|
||||
|
||||
// On vérifie si la cible cliquée est le bouton ou un de ses enfants
|
||||
if (mobileMenuButton && mobileMenu && (target === mobileMenuButton || mobileMenuButton.contains(target))) {
|
||||
event.preventDefault();
|
||||
toggleMenu(mobileMenuButton, mobileMenu);
|
||||
// Menu Mobile
|
||||
const mobileMenuButton = target.closest('#mobileMenuButton');
|
||||
if (mobileMenuButton) {
|
||||
const menu = document.getElementById('mobile-menu');
|
||||
toggleMenu(mobileMenuButton, menu);
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 2. GESTION DU MENU UTILISATEUR (Dropdown) ---
|
||||
const userMenuButtonDesktop = document.getElementById('userMenuButtonDesktop');
|
||||
const userMenuDesktop = document.getElementById('userMenuDesktop');
|
||||
const userMenuButtonMobile = document.getElementById('userMenuButtonMobile');
|
||||
const userMenuMobile = document.getElementById('userMenuMobile');
|
||||
|
||||
// Ouverture/Fermeture du menu utilisateur Desktop
|
||||
if (userMenuButtonDesktop && userMenuDesktop && (target === userMenuButtonDesktop || userMenuButtonDesktop.contains(target))) {
|
||||
event.preventDefault();
|
||||
// S'assurer que les autres menus sont fermés
|
||||
userMenuMobile.classList.add('hidden');
|
||||
toggleMenu(userMenuButtonDesktop, userMenuDesktop);
|
||||
return;
|
||||
}
|
||||
|
||||
// Ouverture/Fermeture du menu utilisateur Mobile
|
||||
if (userMenuButtonMobile && userMenuMobile && (target === userMenuButtonMobile || userMenuButtonMobile.contains(target))) {
|
||||
event.preventDefault();
|
||||
// S'assurer que les autres menus sont fermés
|
||||
userMenuDesktop.classList.add('hidden');
|
||||
toggleMenu(userMenuButtonMobile, userMenuMobile);
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermeture des menus s'il y a un clic en dehors
|
||||
const isClickInsideDesktopMenu = userMenuDesktop && (userMenuDesktop.contains(target) || userMenuButtonDesktop.contains(target));
|
||||
const isClickInsideMobileMenu = userMenuMobile && (userMenuMobile.contains(target) || userMenuButtonMobile.contains(target));
|
||||
|
||||
if (userMenuDesktop && userMenuButtonDesktop && !isClickInsideDesktopMenu) {
|
||||
userMenuDesktop.classList.add('hidden');
|
||||
userMenuButtonDesktop.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
if (userMenuMobile && userMenuButtonMobile && !isClickInsideMobileMenu) {
|
||||
userMenuMobile.classList.add('hidden');
|
||||
userMenuButtonMobile.setAttribute('aria-expanded', 'false');
|
||||
}
|
||||
|
||||
// --- 3. GESTION DE L'OUVERTURE ET FERMETURE DU PANIER ---
|
||||
const openCartDesktop = document.getElementById('openCartDesktop');
|
||||
const openCartMobile = document.getElementById('openCartMobile');
|
||||
const closeCartButton = document.getElementById('closeCartButton');
|
||||
const cartBackdrop = document.getElementById('cartBackdrop');
|
||||
|
||||
// Ouverture (Desktop ou Mobile)
|
||||
if (window.openCart && (
|
||||
(openCartDesktop && (target === openCartDesktop || openCartDesktop.contains(target))) ||
|
||||
(openCartMobile && (target === openCartMobile || openCartMobile.contains(target)))
|
||||
)) {
|
||||
// Panier
|
||||
const openCartBtn = target.closest('#openCartDesktop, #openCartMobile');
|
||||
if (openCartBtn && window.openCart) {
|
||||
event.preventDefault();
|
||||
window.openCart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermeture (Bouton interne)
|
||||
if (window.closeCart && closeCartButton && (target === closeCartButton || closeCartButton.contains(target))) {
|
||||
event.preventDefault();
|
||||
const closeCartBtn = target.closest('#closeCartButton') || target === document.getElementById('cartBackdrop');
|
||||
if (closeCartBtn && window.closeCart) {
|
||||
window.closeCart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermeture (Cliquer sur le fond/backdrop)
|
||||
if (window.closeCart && target === cartBackdrop) {
|
||||
window.closeCart();
|
||||
return;
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
// --- GESTION GLOBALE DE LA TOUCHE ESC (Une seule fois) ---
|
||||
document.addEventListener('keydown', (event) => {
|
||||
const cartSidebar = document.getElementById('cartSidebar');
|
||||
// Fermer le panier
|
||||
if (cartSidebar && window.closeCart && event.key === 'Escape' && !cartSidebar.classList.contains('translate-x-full')) {
|
||||
window.closeCart();
|
||||
return;
|
||||
}
|
||||
|
||||
// Fermer les menus utilisateur
|
||||
const userMenuDesktop = document.getElementById('userMenuDesktop');
|
||||
const userMenuButtonDesktop = document.getElementById('userMenuButtonDesktop');
|
||||
const userMenuMobile = document.getElementById('userMenuMobile');
|
||||
const userMenuButtonMobile = document.getElementById('userMenuButtonMobile');
|
||||
|
||||
if (event.key === 'Escape') {
|
||||
if (userMenuDesktop && !userMenuDesktop.classList.contains('hidden')) {
|
||||
toggleMenu(userMenuButtonDesktop, userMenuDesktop);
|
||||
return;
|
||||
}
|
||||
if (userMenuMobile && !userMenuMobile.classList.contains('hidden')) {
|
||||
toggleMenu(userMenuButtonMobile, userMenuMobile);
|
||||
return;
|
||||
}
|
||||
}
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && window.closeCart) window.closeCart();
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user