2026-01-19 21:08:04 +01:00
|
|
|
import './reserve.scss';
|
2026-01-23 09:25:11 +01:00
|
|
|
import { UtmEvent, UtmAccount } from "./tools/UtmEvent.js";
|
2026-01-27 20:24:02 +01:00
|
|
|
import { CookieBanner } from "./tools/CookieBanner.js";
|
2026-01-22 22:17:32 +01:00
|
|
|
import * as Turbo from "@hotwired/turbo";
|
2026-01-28 12:39:30 +01:00
|
|
|
import { onLCP, onINP, onCLS } from 'web-vitals';
|
2026-01-22 22:17:32 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- CONFIGURATION & ÉTAT ---
|
|
|
|
|
const CONFIG = {
|
|
|
|
|
sentryDsn: "https://803814be6540031b1c37bf92ba9c0f79@sentry.esy-web.dev/24",
|
|
|
|
|
vitalsUrl: '/reservation/web-vitals'
|
|
|
|
|
};
|
|
|
|
|
|
|
|
|
|
// --- UTILS ---
|
2026-01-22 22:53:05 +01:00
|
|
|
const isLighthouse = () => {
|
2026-01-23 09:25:11 +01:00
|
|
|
if (typeof navigator === 'undefined' || !navigator.userAgent) return false;
|
2026-01-28 12:39:30 +01:00
|
|
|
return ['lighthouse', 'pagespeed', 'headless', 'webdriver'].some(p =>
|
|
|
|
|
navigator.userAgent.toLowerCase().includes(p)
|
|
|
|
|
);
|
2026-01-22 22:53:05 +01:00
|
|
|
};
|
2026-01-22 22:17:32 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- PERFORMANCE (Web Vitals) ---
|
|
|
|
|
const sendToAnalytics = (metric) => {
|
2026-01-27 20:24:02 +01:00
|
|
|
if (isLighthouse()) return;
|
2026-01-28 12:39:30 +01:00
|
|
|
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);
|
|
|
|
|
};
|
2026-01-27 20:24:02 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- SERVICES (Sentry) ---
|
|
|
|
|
const toggleSentry = async (status) => {
|
|
|
|
|
if (isLighthouse()) return;
|
2026-01-27 20:24:02 +01:00
|
|
|
try {
|
|
|
|
|
const Sentry = await import("@sentry/browser");
|
|
|
|
|
if (status === 'accepted') {
|
|
|
|
|
if (!window.SentryInitialized) {
|
|
|
|
|
window.sentryClient = Sentry.init({
|
2026-01-28 12:39:30 +01:00
|
|
|
dsn: CONFIG.sentryDsn,
|
2026-01-27 20:24:02 +01:00
|
|
|
tunnel: "/sentry-tunnel",
|
|
|
|
|
integrations: [Sentry.browserTracingIntegration()],
|
|
|
|
|
tracesSampleRate: 1.0,
|
|
|
|
|
});
|
|
|
|
|
window.SentryInitialized = true;
|
2026-01-28 12:39:30 +01:00
|
|
|
} else if (window.sentryClient) {
|
|
|
|
|
window.sentryClient.getOptions().enabled = true;
|
2026-01-27 20:24:02 +01:00
|
|
|
}
|
2026-01-28 12:39:30 +01:00
|
|
|
} else if (status === 'refused' && window.SentryInitialized) {
|
2026-01-27 20:24:02 +01:00
|
|
|
if (window.sentryClient) window.sentryClient.getOptions().enabled = false;
|
2026-01-22 22:17:32 +01:00
|
|
|
}
|
2026-01-28 12:39:30 +01:00
|
|
|
} catch (e) { console.warn("Sentry error", e); }
|
2026-01-22 22:17:32 +01:00
|
|
|
};
|
2026-01-19 21:08:04 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- UI : LOADERS & IMAGES ---
|
2026-01-27 20:44:41 +01:00
|
|
|
const initImageLoader = () => {
|
2026-01-28 12:39:30 +01:00
|
|
|
const images = document.querySelectorAll('main img:not(.loaded)');
|
2026-01-27 20:44:41 +01:00
|
|
|
images.forEach(img => {
|
|
|
|
|
if (img.complete) {
|
|
|
|
|
img.classList.add('loaded');
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
const parent = img.parentElement;
|
|
|
|
|
if (!parent) return;
|
2026-01-28 12:39:30 +01:00
|
|
|
parent.classList.add('relative', 'overflow-hidden', 'bg-slate-50');
|
2026-01-27 20:44:41 +01:00
|
|
|
|
|
|
|
|
const loader = document.createElement('div');
|
2026-01-28 12:39:30 +01:00
|
|
|
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>`;
|
2026-01-27 20:44:41 +01:00
|
|
|
|
|
|
|
|
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);
|
|
|
|
|
};
|
2026-01-28 12:39:30 +01:00
|
|
|
img.onerror = () => { loader.innerHTML = '⚠️'; };
|
2026-01-27 20:44:41 +01:00
|
|
|
});
|
|
|
|
|
};
|
2026-01-23 09:25:11 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
const initTurboLoader = () => {
|
|
|
|
|
if (document.getElementById('turbo-loader')) return;
|
2026-01-27 20:24:02 +01:00
|
|
|
const loaderEl = document.createElement('div');
|
|
|
|
|
loaderEl.id = 'turbo-loader';
|
2026-01-28 12:39:30 +01:00
|
|
|
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';
|
2026-01-27 20:24:02 +01:00
|
|
|
loaderEl.innerHTML = `
|
2026-01-28 12:39:30 +01:00
|
|
|
<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>`;
|
2026-01-27 20:24:02 +01:00
|
|
|
document.body.appendChild(loaderEl);
|
2026-01-23 09:25:11 +01:00
|
|
|
|
|
|
|
|
document.addEventListener("turbo:click", () => {
|
|
|
|
|
loaderEl.classList.replace('opacity-0', 'opacity-100');
|
|
|
|
|
loaderEl.classList.remove('pointer-events-none');
|
|
|
|
|
});
|
2026-01-20 13:51:23 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
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';
|
|
|
|
|
|
|
|
|
|
// Modification :
|
|
|
|
|
// 1. bottom-24 au lieu de bottom-8 pour laisser de la place aux cookies
|
|
|
|
|
// 2. bg-[#f39e36] pour respecter ta couleur orange
|
|
|
|
|
// 3. hover:bg-[#fc0e50] pour le rappel rose au survol
|
|
|
|
|
btn.className = `
|
|
|
|
|
fixed bottom-24 right-8 z-50 p-4
|
|
|
|
|
bg-[#f39e36] text-white rounded-2xl shadow-[0_10px_25px_-5px_rgba(243,158,54,0.4)]
|
|
|
|
|
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;
|
|
|
|
|
if (isVisible) {
|
|
|
|
|
btn.classList.replace('opacity-0', 'opacity-100');
|
|
|
|
|
btn.classList.replace('translate-y-10', 'translate-y-0');
|
|
|
|
|
btn.classList.remove('pointer-events-none');
|
|
|
|
|
} else {
|
|
|
|
|
btn.classList.replace('opacity-100', 'opacity-0');
|
|
|
|
|
btn.classList.replace('translate-y-0', 'translate-y-10');
|
|
|
|
|
btn.classList.add('pointer-events-none');
|
|
|
|
|
}
|
2026-01-20 13:51:23 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
window.addEventListener('scroll', handleScroll, { passive: true });
|
|
|
|
|
|
|
|
|
|
btn.onclick = () => {
|
|
|
|
|
window.scrollTo({ top: 0, behavior: 'smooth' });
|
|
|
|
|
};
|
2026-01-20 13:51:23 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- LOGIQUE MÉTIER ---
|
2026-01-19 21:08:04 +01:00
|
|
|
const initMobileMenu = () => {
|
|
|
|
|
const btn = document.getElementById('menu-button');
|
|
|
|
|
const menu = document.getElementById('mobile-menu');
|
2026-01-28 12:39:30 +01:00
|
|
|
if (!btn || !menu) return;
|
|
|
|
|
btn.onclick = () => {
|
|
|
|
|
const open = menu.classList.toggle('hidden');
|
|
|
|
|
btn.setAttribute('aria-expanded', !open);
|
|
|
|
|
};
|
2026-01-19 21:08:04 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-20 13:51:23 +01:00
|
|
|
const initCatalogueSearch = () => {
|
|
|
|
|
const filters = document.querySelectorAll('.filter-btn');
|
|
|
|
|
const products = document.querySelectorAll('.product-item');
|
|
|
|
|
const emptyMsg = document.getElementById('empty-msg');
|
2026-01-28 12:39:30 +01:00
|
|
|
|
2026-01-20 13:51:23 +01:00
|
|
|
filters.forEach(btn => {
|
|
|
|
|
btn.onclick = () => {
|
2026-01-28 12:39:30 +01:00
|
|
|
const category = btn.dataset.filter.toLowerCase();
|
2026-01-20 13:51:23 +01:00
|
|
|
let count = 0;
|
2026-01-28 12:39:30 +01:00
|
|
|
filters.forEach(f => f.classList.remove('bg-slate-900', 'text-white'));
|
|
|
|
|
btn.classList.add('bg-slate-900', 'text-white');
|
|
|
|
|
|
2026-01-20 13:51:23 +01:00
|
|
|
products.forEach(item => {
|
2026-01-28 12:39:30 +01:00
|
|
|
const isVisible = category === 'all' || (item.dataset.category || '').toLowerCase().includes(category);
|
|
|
|
|
item.classList.toggle('hidden', !isVisible);
|
2026-01-23 09:25:11 +01:00
|
|
|
if (isVisible) count++;
|
2026-01-20 13:51:23 +01:00
|
|
|
});
|
2026-01-28 12:39:30 +01:00
|
|
|
emptyMsg?.classList.toggle('hidden', count > 0);
|
2026-01-20 13:51:23 +01:00
|
|
|
};
|
|
|
|
|
});
|
|
|
|
|
};
|
|
|
|
|
|
2026-01-23 09:25:11 +01:00
|
|
|
const initRegisterLogic = () => {
|
|
|
|
|
const siretContainer = document.getElementById('siret-container');
|
|
|
|
|
const typeRadios = document.querySelectorAll('input[name="type"]');
|
2026-01-28 12:39:30 +01:00
|
|
|
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);
|
2026-01-23 09:25:11 +01:00
|
|
|
};
|
2026-01-28 12:39:30 +01:00
|
|
|
typeRadios.forEach(r => r.addEventListener('change', toggle));
|
|
|
|
|
toggle();
|
2026-01-23 09:25:11 +01:00
|
|
|
};
|
2026-01-27 20:24:02 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
// --- INITIALISATION ---
|
|
|
|
|
const registerComponents = () => {
|
|
|
|
|
const comps = [['utm-event', UtmEvent], ['utm-account', UtmAccount], ['cookie-banner', CookieBanner]];
|
|
|
|
|
comps.forEach(([name, cl]) => { if (!customElements.get(name)) customElements.define(name, cl); });
|
2026-01-27 23:36:11 +01:00
|
|
|
};
|
|
|
|
|
|
2026-01-19 21:08:04 +01:00
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
2026-01-28 12:39:30 +01:00
|
|
|
initVitals();
|
|
|
|
|
initTurboLoader();
|
|
|
|
|
registerComponents();
|
2026-01-27 23:36:11 +01:00
|
|
|
|
2026-01-28 12:39:30 +01:00
|
|
|
const consent = sessionStorage.getItem('ldk_cookie');
|
|
|
|
|
if (consent) toggleSentry(consent);
|
2026-01-27 20:24:02 +01:00
|
|
|
|
|
|
|
|
window.addEventListener('cookieAccepted', () => toggleSentry('accepted'));
|
|
|
|
|
window.addEventListener('cookieRefused', () => toggleSentry('refused'));
|
2026-01-19 21:08:04 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.addEventListener('turbo:load', () => {
|
2026-01-28 12:39:30 +01:00
|
|
|
initVitals();
|
|
|
|
|
initImageLoader();
|
|
|
|
|
initBackToTop();
|
2026-01-19 21:08:04 +01:00
|
|
|
initMobileMenu();
|
2026-01-23 09:25:11 +01:00
|
|
|
initRegisterLogic();
|
2026-01-28 12:39:30 +01:00
|
|
|
initCatalogueSearch();
|
|
|
|
|
|
|
|
|
|
const payContainer = document.getElementById('payment-check-container');
|
|
|
|
|
if (payContainer?.dataset.autoRedirect) {
|
|
|
|
|
setTimeout(() => {
|
|
|
|
|
if (document.getElementById('payment-check-container')) {
|
|
|
|
|
Turbo.visit(payContainer.dataset.autoRedirect);
|
|
|
|
|
}
|
|
|
|
|
}, 10000);
|
|
|
|
|
}
|
2026-01-20 13:51:23 +01:00
|
|
|
});
|
|
|
|
|
|
|
|
|
|
document.addEventListener("turbo:before-cache", () => {
|
2026-01-28 12:39:30 +01:00
|
|
|
document.querySelectorAll('.product-item').forEach(i => i.classList.remove('hidden'));
|
|
|
|
|
document.getElementById('empty-msg')?.classList.add('hidden');
|
2026-01-19 21:08:04 +01:00
|
|
|
});
|