diff --git a/src/public/borne/assets/js/nav.js b/src/public/borne/assets/js/nav.js index 5b431b8..74fd5ea 100644 --- a/src/public/borne/assets/js/nav.js +++ b/src/public/borne/assets/js/nav.js @@ -6,12 +6,38 @@ * element with [data-mode-badge] on the page. * - Sync the cart item count into any element with [data-cart-count]. * - Handle the mode query-string on page load (welcome -> categories handoff). + * - Guard : une page au-dela de l'accueil EXIGE un mode de consommation. Sans + * mode (ex. localStorage vide en cours de session), la borne POSTerait + * service_mode:null et la commande est rejetee en 422. Sur une page profonde + * sans mode, on renvoie vers l'accueil pour que le mode soit (re)choisi. * * Import this module in every page that has a header. */ import { getMode, setMode, getCartCount } from './state.js'; +const VALID_MODES = ['sur-place', 'a-emporter']; + +/** Libelle humain d'un mode ; chaine vide si aucun mode valide (ne ment pas). */ +export function modeLabel(mode) { + if (mode === 'a-emporter') return 'A emporter'; + if (mode === 'sur-place') return 'Sur place'; + return ''; +} + +/** + * Faut-il renvoyer vers l'accueil ? Vrai hors de l'ecran d'accueil quand aucun + * mode de consommation valide n'est memorise. Pur (teste sans DOM). Sans cette + * garde, atteindre une page de commande sans mode mene a service_mode:null -> 422. + * @param {string} pathname window.location.pathname + * @param {string|null} mode mode memorise + * @returns {boolean} + */ +export function needsModeRedirect(pathname, mode) { + const onWelcome = pathname === '/' || pathname.endsWith('/index.html'); + return !onWelcome && !VALID_MODES.includes(mode); +} + /** * Reads ?mode= from the current URL and persists it if present. * Called once on DOMContentLoaded so that the welcome -> categories @@ -20,7 +46,7 @@ import { getMode, setMode, getCartCount } from './state.js'; function syncModeFromURL() { const params = new URLSearchParams(window.location.search); const modeParam = params.get('mode'); - if (modeParam === 'sur-place' || modeParam === 'a-emporter') { + if (VALID_MODES.includes(modeParam)) { setMode(modeParam); } } @@ -29,8 +55,7 @@ function syncModeFromURL() { * Renders the human-readable mode label into every [data-mode-badge] element. */ function renderModeBadge() { - const mode = getMode(); - const label = mode === 'a-emporter' ? 'A emporter' : 'Sur place'; + const label = modeLabel(getMode()); document.querySelectorAll('[data-mode-badge]').forEach(el => { el.textContent = label; }); @@ -48,9 +73,17 @@ export function refreshCartBadge() { }); } -/* Initialise on DOM ready */ -document.addEventListener('DOMContentLoaded', () => { - syncModeFromURL(); - renderModeBadge(); - refreshCartBadge(); -}); +/* Initialise on DOM ready. Garde derriere typeof document pour rester importable + * en test pur (node sans jsdom) : modeLabel/needsModeRedirect n'ont alors aucun effet de bord. */ +if (typeof document !== 'undefined') { + document.addEventListener('DOMContentLoaded', () => { + syncModeFromURL(); + // Mode absent sur une page profonde -> retour accueil (evite le 422 service_mode:null). + if (needsModeRedirect(window.location.pathname, getMode())) { + window.location.replace('index.html'); + return; + } + renderModeBadge(); + refreshCartBadge(); + }); +} diff --git a/src/public/borne/assets/js/page-payment.js b/src/public/borne/assets/js/page-payment.js index 0f68b24..6fc18d5 100644 --- a/src/public/borne/assets/js/page-payment.js +++ b/src/public/borne/assets/js/page-payment.js @@ -60,8 +60,17 @@ async function doSubmit(serviceTag) { function startCheckout() { if (checkingOut) return; + const mode = getMode(); + // Garde finale (defense en profondeur, en plus de la garde nav.js) : sans mode de + // consommation valide (ex. localStorage vide), ne PAS soumettre une commande a + // service_mode null (rejetee en 422 INVALID_SERVICE_MODE). On renvoie a l'accueil + // pour (re)choisir le mode, le panier etant conserve. + if (mode !== 'sur-place' && mode !== 'a-emporter') { + window.location.replace('index.html'); + return; + } checkingOut = true; - if (getMode() === 'sur-place') { + if (mode === 'sur-place') { openChevalet(tag => doSubmit(tag), () => { checkingOut = false; }); } else { doSubmit(''); @@ -176,7 +185,8 @@ document.addEventListener('DOMContentLoaded', () => { return; } if (recap) { - const modeLabel = getMode() === 'a-emporter' ? 'A emporter' : 'Sur place'; + const m = getMode(); + const modeLabel = m === 'a-emporter' ? 'A emporter' : (m === 'sur-place' ? 'Sur place' : ''); recap.innerHTML = `
${escHtml(modeLabel)}
${items.length} article${items.length > 1 ? 's' : ''}
diff --git a/src/public/borne/categories.html b/src/public/borne/categories.html index 70fbe4d..e7bea2e 100644 --- a/src/public/borne/categories.html +++ b/src/public/borne/categories.html @@ -177,6 +177,11 @@ + +