Files
ludikevent_crm/assets/reserve.js
Serreau Jovann 1896f83107 ```
 feat(reservation/flow): Améliore le flux de réservation et ajoute des options.

Cette commit améliore le flux de réservation, ajoute une estimation des
frais de livraison et gère les options de produit et les paiements.
```
2026-02-05 08:18:29 +01:00

311 lines
12 KiB
JavaScript

import './reserve.scss';
import { UtmEvent, UtmAccount } from "./tools/UtmEvent.js";
import { CookieBanner } from "./tools/CookieBanner.js";
import { FlowReserve } from "./tools/FlowReserve.js";
import { FlowDatePicker } from "./tools/FlowDatePicker.js";
import { FlowAddToCart } from "./tools/FlowAddToCart.js";
import { LeafletMap } from "./tools/LeafletMap.js";
import { LocalStorageClear } from "./tools/LocalStorageClear.js";
import * as Turbo from "@hotwired/turbo";
import { onLCP, onINP, onCLS } from 'web-vitals';
import AOS from 'aos';
import 'aos/dist/aos.css';
// --- CONFIGURATION & ÉTAT ---
const CONFIG = {
sentryDsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
vitalsUrl: '/web-vitals'
};
// --- UTILS ---
const isLighthouse = () => {
if (typeof navigator === 'undefined' || !navigator.userAgent) return false;
return ['lighthouse', 'pagespeed', 'headless', 'webdriver'].some(p =>
navigator.userAgent.toLowerCase().includes(p)
);
};
// --- PERFORMANCE (Web Vitals) ---
const sendToAnalytics = (metric) => {
if (isLighthouse()) return;
const body = JSON.stringify({ ...metric, path: window.location.pathname });
if (navigator.sendBeacon) {
navigator.sendBeacon(CONFIG.vitalsUrl, body);
} else {
fetch(CONFIG.vitalsUrl, { body, method: 'POST', keepalive: true });
}
};
const initVitals = () => {
onLCP(sendToAnalytics);
onINP(sendToAnalytics);
onCLS(sendToAnalytics);
};
// --- SERVICES (Sentry) ---
const toggleSentry = async (status) => {
if (isLighthouse()) return;
try {
const Sentry = await import("@sentry/browser");
if (status === 'accepted') {
if (!window.SentryInitialized) {
window.sentryClient = Sentry.init({
dsn: CONFIG.sentryDsn,
tunnel: "/sentry-tunnel",
integrations: [Sentry.browserTracingIntegration()],
tracesSampleRate: 1.0,
});
window.SentryInitialized = true;
} else if (window.sentryClient) {
window.sentryClient.getOptions().enabled = true;
}
} else if (status === 'refused' && window.SentryInitialized) {
if (window.sentryClient) window.sentryClient.getOptions().enabled = false;
}
} catch (e) { console.warn("Sentry error", e); }
};
// --- UI : ACCESSIBILITÉ (Text-to-Speech) ---
const initAccessibility = () => {
if (document.getElementById('accessibility-toggle')) return;
const btn = document.createElement('button');
btn.id = 'accessibility-toggle';
// Positionné en bas à gauche, fond gris ardoise par défaut
btn.className = `
fixed bottom-24 left-8 z-50 p-4
rounded-2xl shadow-xl border-2 border-white/10
transition-all duration-300 hover:scale-110 active:scale-95
`;
const updateBtnStyle = () => {
const isActive = localStorage.getItem('ldk_tts_active') === 'true';
btn.innerHTML = isActive ? '🔊' : '🔇';
btn.classList.toggle('bg-[#f39e36]', isActive); // Orange si actif
btn.classList.toggle('bg-slate-900', !isActive); // Noir si inactif
btn.classList.toggle('text-white', true);
};
updateBtnStyle();
document.body.appendChild(btn);
btn.onclick = () => {
const currentState = localStorage.getItem('ldk_tts_active') === 'true';
localStorage.setItem('ldk_tts_active', !currentState);
updateBtnStyle();
if (currentState) window.speechSynthesis.cancel();
};
// Écouteur global sur le document pour la lecture au clic
document.addEventListener('click', (e) => {
if (localStorage.getItem('ldk_tts_active') !== 'true') return;
const target = e.target.closest('p, h1, h2, h3, h4, span, label, li');
if (target && target.innerText.trim().length > 0) {
window.speechSynthesis.cancel();
const utterance = new SpeechSynthesisUtterance(target.innerText);
utterance.lang = 'fr-FR';
// Feedback visuel Ludik Event
target.classList.add('outline', 'outline-2', 'outline-[#f39e36]', 'outline-offset-2');
setTimeout(() => target.classList.remove('outline', 'outline-2', 'outline-[#f39e36]', 'outline-offset-2'), 1200);
window.speechSynthesis.speak(utterance);
}
});
};
// --- UI : LOADERS & IMAGES ---
const initImageLoader = () => {
const images = document.querySelectorAll('main img:not(.loaded)');
images.forEach(img => {
if (img.complete) {
img.classList.add('loaded');
return;
}
const parent = img.parentElement;
if (!parent) return;
parent.classList.add('relative', 'overflow-hidden', 'bg-slate-50');
const loader = document.createElement('div');
loader.className = 'absolute inset-0 flex items-center justify-center z-10 bg-slate-50 transition-opacity duration-500';
loader.innerHTML = `<div class="w-7 h-7 border-3 border-slate-200 border-t-[#f39e36] rounded-full animate-spin"></div>`;
parent.insertBefore(loader, img);
img.classList.add('opacity-0', 'transition-opacity', 'duration-700');
img.onload = () => {
img.classList.replace('opacity-0', 'opacity-100');
img.classList.add('loaded');
loader.classList.add('opacity-0');
setTimeout(() => loader.remove(), 500);
};
img.onerror = () => { loader.innerHTML = '⚠️'; };
});
};
const initTurboLoader = () => {
if (document.getElementById('turbo-loader')) return;
const loaderEl = document.createElement('div');
loaderEl.id = 'turbo-loader';
loaderEl.className = 'fixed inset-0 z-[9999] flex items-center justify-center bg-white/80 backdrop-blur-sm transition-opacity duration-300 opacity-0 pointer-events-none';
loaderEl.innerHTML = `
<div class="relative flex flex-col items-center gap-4">
<div class="w-16 h-16 border-4 border-[#f39e36] border-t-[#fc0e50] rounded-full animate-spin"></div>
<img src="/provider/images/favicon.webp" class="w-8 h-8 absolute top-4 animate-pulse" alt="LDK">
</div>`;
document.body.appendChild(loaderEl);
document.addEventListener("turbo:click", () => {
loaderEl.classList.replace('opacity-0', 'opacity-100');
loaderEl.classList.remove('pointer-events-none');
});
const hide = () => {
loaderEl.classList.replace('opacity-100', 'opacity-0');
loaderEl.classList.add('pointer-events-none');
};
document.addEventListener("turbo:load", hide);
document.addEventListener("turbo:render", hide);
};
// --- UI : BACK TO TOP ---
const initBackToTop = () => {
if (document.getElementById('back-to-top')) return;
const btn = document.createElement('button');
btn.id = 'back-to-top';
btn.className = `
fixed bottom-24 right-8 z-50 p-4
bg-[#f39e36] text-white rounded-2xl shadow-xl
border-2 border-white/20 opacity-0 translate-y-10
pointer-events-none transition-all duration-500
hover:bg-[#fc0e50] hover:-translate-y-2 group
`;
btn.innerHTML = `
<svg class="w-6 h-6 transform group-hover:animate-bounce" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="4" d="M5 10l7-7m0 0l7 7m-7-7v18"/>
</svg>
`;
document.body.appendChild(btn);
const handleScroll = () => {
const isVisible = window.scrollY > 400;
btn.classList.toggle('opacity-100', isVisible);
btn.classList.toggle('translate-y-0', isVisible);
btn.classList.toggle('opacity-0', !isVisible);
btn.classList.toggle('translate-y-10', !isVisible);
btn.classList.toggle('pointer-events-none', !isVisible);
};
window.addEventListener('scroll', handleScroll, { passive: true });
btn.onclick = () => window.scrollTo({ top: 0, behavior: 'smooth' });
};
// --- LOGIQUE MÉTIER ---
const initMobileMenu = () => {
const btn = document.getElementById('menu-button');
const menu = document.getElementById('mobile-menu');
console.log(btn,menu)
if (!btn || !menu) return;
btn.onclick = () => {
const open = menu.classList.toggle('hidden');
btn.setAttribute('aria-expanded', !open);
};
};
const initCatalogueSearch = () => {
const filters = document.querySelectorAll('.filter-btn');
const products = document.querySelectorAll('.product-item');
const emptyMsg = document.getElementById('empty-msg');
filters.forEach(btn => {
btn.onclick = () => {
const category = btn.dataset.filter.toLowerCase();
let count = 0;
filters.forEach(f => f.classList.remove('bg-slate-900', 'text-black'));
btn.classList.add('bg-slate-900', 'text-black');
products.forEach(item => {
const isVisible = category === 'all' || (item.dataset.category || '').toLowerCase().includes(category);
item.classList.toggle('hidden', !isVisible);
if (isVisible) count++;
});
emptyMsg?.classList.toggle('hidden', count > 0);
};
});
};
const initRegisterLogic = () => {
const siretContainer = document.getElementById('siret-container');
const typeRadios = document.querySelectorAll('input[name="type"]');
if (!siretContainer) return;
const toggle = () => {
const isBiz = document.querySelector('input[name="type"]:checked')?.value === 'buisness';
siretContainer.classList.toggle('hidden', !isBiz);
siretContainer.querySelector('input')?.toggleAttribute('required', isBiz);
};
typeRadios.forEach(r => r.addEventListener('change', toggle));
toggle();
};
// --- INITIALISATION ---
const registerComponents = () => {
const comps = [['utm-event', UtmEvent], ['utm-account', UtmAccount], ['cookie-banner', CookieBanner], ['leaflet-map', LeafletMap], ['local-storage-clear', LocalStorageClear]];
comps.forEach(([name, cl]) => { if (!customElements.get(name)) customElements.define(name, cl); });
if(!customElements.get('flow-reserve'))
customElements.define('flow-reserve',FlowReserve,{extends:'a'})
if(!customElements.get('flow-datepicker'))
customElements.define('flow-datepicker',FlowDatePicker)
if(!customElements.get('flow-add-to-cart'))
customElements.define('flow-add-to-cart',FlowAddToCart)
};
document.addEventListener('DOMContentLoaded', () => {
initVitals();
initTurboLoader();
registerComponents();
const consent = sessionStorage.getItem('ldk_cookie');
if (consent) toggleSentry(consent);
window.addEventListener('cookieAccepted', () => toggleSentry('accepted'));
window.addEventListener('cookieRefused', () => toggleSentry('refused'));
});
document.addEventListener('turbo:load', () => {
initVitals();
initImageLoader();
initBackToTop();
initAccessibility();
initMobileMenu();
initRegisterLogic();
initCatalogueSearch();
// Initialize AOS
AOS.init({
duration: 800,
once: true,
});
const payContainer = document.getElementById('payment-check-container');
if (payContainer?.dataset.autoRedirect) {
setTimeout(() => {
if (document.getElementById('payment-check-container')) {
Turbo.visit(payContainer.dataset.autoRedirect);
}
}, 10000);
}
});
document.addEventListener("turbo:before-cache", () => {
document.querySelectorAll('.product-item').forEach(i => i.classList.remove('hidden'));
document.getElementById('empty-msg')?.classList.add('hidden');
});