| (function () { |
| const THEME_KEY = "corner-cafe-theme"; |
| const SERVICE_KEY = "corner-cafe-order-service"; |
| const MODE_KEY = "corner-cafe-order-mode"; |
| const CART_KEY = "corner-cafe-cart"; |
|
|
| const serviceLabels = { |
| delivery: "Delivery", |
| collection: "Collection", |
| "sitting-in": "Sitting in", |
| }; |
|
|
| const modeLabels = { |
| menu: "Browse menu", |
| }; |
|
|
| const root = document.documentElement; |
| const metaTheme = document.getElementById("meta-theme-color"); |
| const toggle = document.querySelector("[data-theme-toggle]"); |
|
|
| const applyTheme = (theme) => { |
| const next = theme === "light" ? "light" : "dark"; |
| root.setAttribute("data-theme", next); |
| if (metaTheme) metaTheme.setAttribute("content", next === "light" ? "#f6f3ee" : "#070707"); |
| if (toggle) { |
| toggle.setAttribute( |
| "aria-label", |
| next === "light" ? "Switch to dark theme" : "Switch to light theme" |
| ); |
| } |
| }; |
|
|
| try { |
| applyTheme(localStorage.getItem(THEME_KEY) || "dark"); |
| } catch (e) { |
| applyTheme("dark"); |
| } |
|
|
| toggle?.addEventListener("click", () => { |
| const next = root.getAttribute("data-theme") === "light" ? "dark" : "light"; |
| applyTheme(next); |
| try { |
| localStorage.setItem(THEME_KEY, next); |
| } catch (e) { |
| |
| } |
| }); |
|
|
| const params = new URLSearchParams(window.location.search); |
|
|
| const money = (n) => `£${Number(n).toFixed(2)}`; |
| const priceText = (item) => item.priceDisplay || money(item.price); |
|
|
| const readCart = () => { |
| try { |
| const raw = sessionStorage.getItem(CART_KEY); |
| const parsed = raw ? JSON.parse(raw) : []; |
| return Array.isArray(parsed) ? parsed : []; |
| } catch (e) { |
| return []; |
| } |
| }; |
|
|
| const writeCart = (next) => { |
| try { |
| sessionStorage.setItem(CART_KEY, JSON.stringify(next)); |
| } catch (e) { |
| |
| } |
| }; |
|
|
| let cart = readCart(); |
| let menuRenderList = null; |
|
|
| const cartCount = () => cart.reduce((n, line) => n + line.qty, 0); |
| const cartTotal = () => cart.reduce((n, line) => n + line.qty * line.price, 0); |
| const qtyOf = (id) => cart.find((line) => line.id === id)?.qty || 0; |
|
|
| const setQty = (item, qty) => { |
| const next = Math.max(0, Math.floor(qty)); |
| const idx = cart.findIndex((line) => line.id === item.id); |
| if (next <= 0) { |
| if (idx >= 0) cart.splice(idx, 1); |
| } else if (idx >= 0) { |
| cart[idx].qty = next; |
| } else { |
| cart.push({ |
| id: item.id, |
| name: item.name, |
| price: item.price, |
| qty: next, |
| section: item.section || "", |
| }); |
| } |
| writeCart(cart); |
| renderCartChrome(); |
| if (typeof menuRenderList === "function") menuRenderList(); |
| }; |
|
|
| const addToCart = (item) => setQty(item, qtyOf(item.id) + 1); |
| const removeOne = (item) => setQty(item, qtyOf(item.id) - 1); |
|
|
| const clearCart = () => { |
| cart = []; |
| writeCart(cart); |
| renderCartChrome(); |
| if (typeof menuRenderList === "function") menuRenderList(); |
| }; |
|
|
| const pageFile = (location.pathname.split("/").pop() || "").toLowerCase(); |
| const onOrderFlowPage = pageFile === "order.html" || pageFile === "checkout.html"; |
| let suppressUnloadWarn = false; |
| let orderPlaced = false; |
|
|
| const clearPayRefs = () => { |
| try { |
| const keys = []; |
| for (let i = 0; i < sessionStorage.length; i += 1) { |
| const key = sessionStorage.key(i); |
| if (key && key.startsWith("corner-cafe-pay-ref")) keys.push(key); |
| } |
| keys.forEach((key) => sessionStorage.removeItem(key)); |
| } catch (e) { |
| |
| } |
| }; |
|
|
| const abandonBasket = () => { |
| cart = []; |
| writeCart(cart); |
| clearPayRefs(); |
| renderCartChrome(); |
| if (typeof menuRenderList === "function") menuRenderList(); |
| }; |
|
|
| const isSameOriginFlowUrl = (href) => { |
| try { |
| const url = new URL(href, location.href); |
| if (url.origin !== location.origin) return false; |
| const file = (url.pathname.split("/").pop() || "").toLowerCase(); |
| return file === "order.html" || file === "checkout.html"; |
| } catch (e) { |
| return false; |
| } |
| }; |
|
|
| const ensureLeaveDialog = () => { |
| let dlg = document.querySelector("[data-leave-dialog]"); |
| if (dlg) return dlg; |
| dlg = document.createElement("dialog"); |
| dlg.className = "leave-dialog"; |
| dlg.setAttribute("data-leave-dialog", ""); |
| dlg.innerHTML = ` |
| <form method="dialog" class="leave-dialog__inner"> |
| <h2 class="leave-dialog__title">Leave this order?</h2> |
| <p class="leave-dialog__copy">If you go back or leave now, your selected menu items will be cleared and you’ll need to start again.</p> |
| <div class="leave-dialog__actions"> |
| <button type="submit" class="leave-dialog__stay" value="stay">Keep ordering</button> |
| <button type="submit" class="leave-dialog__leave" value="leave">Leave & clear basket</button> |
| </div> |
| </form> |
| `; |
| document.body.appendChild(dlg); |
| return dlg; |
| }; |
|
|
| const confirmLeaveOrder = () => |
| new Promise((resolve) => { |
| const dlg = ensureLeaveDialog(); |
| const onClose = () => { |
| dlg.removeEventListener("close", onClose); |
| resolve(dlg.returnValue === "leave"); |
| }; |
| dlg.addEventListener("close", onClose); |
| if (typeof dlg.showModal === "function") dlg.showModal(); |
| else resolve(window.confirm("Leave this order? Your basket will be cleared.")); |
| }); |
|
|
| const leaveOrderTo = async (href) => { |
| if (cartCount() <= 0 || orderPlaced) { |
| suppressUnloadWarn = true; |
| window.location.href = href; |
| return; |
| } |
| const leave = await confirmLeaveOrder(); |
| if (!leave) return; |
| suppressUnloadWarn = true; |
| abandonBasket(); |
| window.location.href = href; |
| }; |
|
|
| |
| if (!onOrderFlowPage && cart.length) { |
| cart = []; |
| writeCart(cart); |
| clearPayRefs(); |
| } |
|
|
| if (onOrderFlowPage) { |
| window.addEventListener("beforeunload", (e) => { |
| if (suppressUnloadWarn || orderPlaced || cartCount() <= 0) return; |
| e.preventDefault(); |
| e.returnValue = ""; |
| }); |
|
|
| history.pushState({ ccLeaveGuard: true }, "", location.href); |
| window.addEventListener("popstate", () => { |
| if (orderPlaced) return; |
|
|
| if (pageFile === "checkout.html") { |
| const service = params.get("service") || sessionStorage.getItem(SERVICE_KEY) || ""; |
| suppressUnloadWarn = true; |
| window.location.replace( |
| serviceLabels[service] |
| ? `order.html?service=${encodeURIComponent(service)}&mode=menu` |
| : "./" |
| ); |
| return; |
| } |
|
|
| history.pushState({ ccLeaveGuard: true }, "", location.href); |
| if (cartCount() <= 0) { |
| suppressUnloadWarn = true; |
| window.location.replace("./"); |
| return; |
| } |
|
|
| confirmLeaveOrder().then((leave) => { |
| if (!leave) return; |
| suppressUnloadWarn = true; |
| abandonBasket(); |
| window.location.replace("./"); |
| }); |
| }); |
|
|
| document.addEventListener( |
| "click", |
| (e) => { |
| const link = e.target.closest?.("a[href]"); |
| if (!link || link.hasAttribute("download") || link.target === "_blank") return; |
| const href = link.getAttribute("href"); |
| if (!href || href.startsWith("#") || href.startsWith("javascript:")) return; |
| if (isSameOriginFlowUrl(href)) { |
| suppressUnloadWarn = true; |
| return; |
| } |
| if (cartCount() <= 0 || orderPlaced) return; |
| e.preventDefault(); |
| e.stopPropagation(); |
| leaveOrderTo(link.href); |
| }, |
| true |
| ); |
| } |
|
|
| const qtyControlsHtml = (item, qty) => { |
| if (qty <= 0) { |
| return `<button type="button" class="qty-add" data-add="${item.id}" aria-label="Add ${item.name}">Add</button>`; |
| } |
| return ` |
| <div class="qty-stepper" role="group" aria-label="Quantity for ${item.name}"> |
| <button type="button" class="qty-btn" data-dec="${item.id}" aria-label="Remove one">−</button> |
| <span class="qty-val" aria-live="polite">${qty}</span> |
| <button type="button" class="qty-btn" data-inc="${item.id}" aria-label="Add one">+</button> |
| </div> |
| `; |
| }; |
|
|
| const bindQtyControls = (rootEl, findItem) => { |
| rootEl.querySelectorAll("[data-add]").forEach((btn) => { |
| btn.addEventListener("click", (e) => { |
| e.stopPropagation(); |
| const item = findItem(btn.getAttribute("data-add")); |
| if (item) addToCart(item); |
| }); |
| }); |
| rootEl.querySelectorAll("[data-inc]").forEach((btn) => { |
| btn.addEventListener("click", (e) => { |
| e.stopPropagation(); |
| const item = findItem(btn.getAttribute("data-inc")); |
| if (item) addToCart(item); |
| }); |
| }); |
| rootEl.querySelectorAll("[data-dec]").forEach((btn) => { |
| btn.addEventListener("click", (e) => { |
| e.stopPropagation(); |
| const item = findItem(btn.getAttribute("data-dec")); |
| if (item) removeOne(item); |
| }); |
| }); |
| }; |
|
|
| const renderCartChrome = () => { |
| const bar = document.querySelector("[data-cart-bar]"); |
| const countEl = document.querySelector("[data-cart-count]"); |
| const totalEl = document.querySelector("[data-cart-total]"); |
| const linesEl = document.querySelector("[data-cart-lines]"); |
| const dialogTotal = document.querySelector("[data-cart-dialog-total]"); |
| if (!bar) return; |
|
|
| const count = cartCount(); |
| if (countEl) countEl.textContent = count === 1 ? "1 item" : `${count} items`; |
| if (totalEl) totalEl.textContent = money(cartTotal()); |
| if (dialogTotal) dialogTotal.textContent = money(cartTotal()); |
| bar.hidden = count === 0; |
|
|
| if (linesEl) { |
| linesEl.innerHTML = ""; |
| const menu = window.CornerCafeMenu || []; |
| cart.forEach((line) => { |
| const item = menu.find((m) => m.id === line.id) || line; |
| const li = document.createElement("li"); |
| li.className = "cart-line cart-line--edit"; |
| li.innerHTML = ` |
| <span class="cart-line__copy"> |
| <span class="cart-line__name">${line.name}</span> |
| <span class="cart-line__price">${money(line.qty * line.price)}</span> |
| </span> |
| ${qtyControlsHtml(item, line.qty)} |
| `; |
| bindQtyControls(li, (id) => menu.find((m) => m.id === id) || (id === line.id ? line : null)); |
| linesEl.appendChild(li); |
| }); |
| } |
| }; |
|
|
| document.querySelector("[data-cart-review]")?.addEventListener("click", () => { |
| document.querySelector("[data-cart-dialog]")?.showModal(); |
| }); |
|
|
| document.querySelector("[data-cart-clear]")?.addEventListener("click", () => { |
| clearCart(); |
| document.querySelector("[data-cart-dialog]")?.close(); |
| }); |
|
|
| const goCheckout = () => { |
| if (cartCount() <= 0) return; |
| const service = params.get("service") || sessionStorage.getItem(SERVICE_KEY) || ""; |
| const mode = params.get("mode") || sessionStorage.getItem(MODE_KEY) || "menu"; |
| if (!serviceLabels[service]) { |
| window.location.replace("./"); |
| return; |
| } |
| document.querySelector("[data-cart-dialog]")?.close(); |
| suppressUnloadWarn = true; |
| window.location.href = `checkout.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`; |
| }; |
|
|
| document.querySelectorAll("[data-cart-checkout]").forEach((btn) => { |
| btn.addEventListener("click", (e) => { |
| e.preventDefault(); |
| goCheckout(); |
| }); |
| }); |
|
|
| const pathLinks = document.querySelectorAll("[data-path-link]"); |
| if (pathLinks.length) { |
| const service = params.get("service") || ""; |
| if (!serviceLabels[service]) { |
| window.location.replace("./"); |
| return; |
| } |
| try { |
| sessionStorage.setItem(SERVICE_KEY, service); |
| } catch (e) { |
| |
| } |
|
|
| const serviceEl = document.querySelector("[data-order-service]"); |
| if (serviceEl) serviceEl.textContent = serviceLabels[service]; |
|
|
| pathLinks.forEach((link) => { |
| link.href = `order.html?service=${encodeURIComponent(service)}&mode=menu`; |
| }); |
| } |
|
|
| const serviceEl = document.querySelector("[data-order-service]"); |
| const panelMenu = document.querySelector("[data-panel-menu]"); |
|
|
| if (serviceEl && panelMenu) { |
| const service = params.get("service") || ""; |
| let mode = params.get("mode") || "menu"; |
| if (!modeLabels[mode]) mode = "menu"; |
|
|
| if (!serviceLabels[service]) { |
| window.location.replace("./"); |
| return; |
| } |
|
|
| try { |
| sessionStorage.setItem(SERVICE_KEY, service); |
| sessionStorage.setItem(MODE_KEY, mode); |
| } catch (e) { |
| |
| } |
|
|
| serviceEl.textContent = serviceLabels[service]; |
|
|
| const back = document.querySelector("[data-back-path]"); |
| if (back) back.href = "./"; |
|
|
| panelMenu.hidden = false; |
| initMenuPanel(); |
| renderCartChrome(); |
| } |
|
|
| function initMenuPanel() { |
| const menu = window.CornerCafeMenu || []; |
| const filters = document.querySelector("[data-menu-filters]"); |
| const list = document.querySelector("[data-menu-list]"); |
| if (!filters || !list) return; |
|
|
| const sections = ["Breakfast", "Lunch", "Tea", "Drinks", "Indian", "Kebab", "Pizza"].filter((s) => |
| menu.some((item) => item.section === s) |
| ); |
| let active = sections[0] || "Breakfast"; |
| const hoursEl = document.querySelector("[data-section-hours]"); |
|
|
| const syncSectionHours = () => { |
| if (!hoursEl) return; |
| const h = window.SmOS_CC_Hours; |
| if (active === "Indian" && h?.indian) { |
| hoursEl.hidden = false; |
| hoursEl.innerHTML = `Indian cuisine <strong>${h.indian.display}</strong> · online cut-off <strong>${h.indian.orderCutoff}</strong>.`; |
| } else if (h?.cafe && ["Breakfast", "Lunch", "Tea", "Drinks"].includes(active)) { |
| hoursEl.hidden = false; |
| hoursEl.innerHTML = `Cafe <strong>${h.cafe.display}</strong> · online cut-off <strong>${h.cafe.orderCutoff}</strong>.`; |
| } else { |
| hoursEl.hidden = true; |
| hoursEl.textContent = ""; |
| } |
| }; |
|
|
| const findItem = (id) => menu.find((m) => m.id === id); |
|
|
| const renderFilters = () => { |
| filters.innerHTML = ""; |
| sections.forEach((section) => { |
| const btn = document.createElement("button"); |
| btn.type = "button"; |
| btn.className = "menu-filter" + (section === active ? " is-active" : ""); |
| btn.textContent = section; |
| btn.setAttribute("role", "tab"); |
| btn.setAttribute("aria-selected", section === active ? "true" : "false"); |
| btn.addEventListener("click", () => { |
| active = section; |
| renderFilters(); |
| renderList(); |
| syncSectionHours(); |
| }); |
| filters.appendChild(btn); |
| }); |
| }; |
|
|
| const renderList = () => { |
| list.innerHTML = ""; |
| const items = menu.filter((item) => item.section === active); |
| let lastGroup = ""; |
| items.forEach((item) => { |
| const group = item.group || ""; |
| if (group && group !== lastGroup) { |
| const h = document.createElement("h3"); |
| h.className = "menu-group"; |
| h.textContent = group; |
| list.appendChild(h); |
| lastGroup = group; |
| } |
| const row = document.createElement("div"); |
| row.className = "menu-item" + (qtyOf(item.id) > 0 ? " is-in-basket" : ""); |
| const qty = qtyOf(item.id); |
| row.innerHTML = ` |
| <span class="menu-item__copy"> |
| <span class="menu-item__name">${item.name}</span> |
| ${item.desc ? `<span class="menu-item__desc">${item.desc}</span>` : ""} |
| </span> |
| <span class="menu-item__actions"> |
| <span class="menu-item__price">${priceText(item)}</span> |
| ${qtyControlsHtml(item, qty)} |
| </span> |
| `; |
| bindQtyControls(row, findItem); |
| list.appendChild(row); |
| }); |
| }; |
|
|
| menuRenderList = renderList; |
| renderFilters(); |
| renderList(); |
| syncSectionHours(); |
| } |
|
|
| function tokenize(text) { |
| return String(text || "") |
| .toLowerCase() |
| .replace(/[^a-z0-9\s&']/g, " ") |
| .split(/\s+/) |
| .filter((t) => t.length > 1 && !["and", "the", "with", "for", "please", "want", "like", "some", "a", "an", "of", "my", "me"].includes(t)); |
| } |
|
|
| function scoreItem(item, tokens) { |
| const hay = `${item.name} ${item.desc} ${(item.tags || []).join(" ")}`.toLowerCase(); |
| let score = 0; |
| tokens.forEach((token) => { |
| if (hay.includes(token)) score += token.length > 4 ? 3 : 2; |
| (item.tags || []).forEach((tag) => { |
| if (tag === token) score += 4; |
| else if (tag.startsWith(token) || token.startsWith(tag)) score += 2; |
| }); |
| if (item.name.toLowerCase().includes(token)) score += 2; |
| }); |
| return score; |
| } |
|
|
| function matchOrder(query) { |
| const menu = window.CornerCafeMenu || []; |
| const tokens = tokenize(query); |
| if (!tokens.length) return []; |
|
|
| const chunks = String(query) |
| .split(/,| and |&|\+| then | also /i) |
| .map((c) => c.trim()) |
| .filter(Boolean); |
|
|
| const results = []; |
| const used = new Set(); |
|
|
| const pushBest = (chunkTokens) => { |
| let best = null; |
| let bestScore = 0; |
| menu.forEach((item) => { |
| if (used.has(item.id)) return; |
| const s = scoreItem(item, chunkTokens); |
| if (s > bestScore) { |
| bestScore = s; |
| best = item; |
| } |
| }); |
| if (best && bestScore >= 3) { |
| used.add(best.id); |
| results.push({ item: best, score: bestScore }); |
| } |
| }; |
|
|
| if (chunks.length > 1) { |
| chunks.forEach((chunk) => pushBest(tokenize(chunk))); |
| } |
|
|
| if (!results.length) { |
| menu |
| .map((item) => ({ item, score: scoreItem(item, tokens) })) |
| .filter((r) => r.score >= 3) |
| .sort((a, b) => b.score - a.score) |
| .slice(0, 8) |
| .forEach((r) => results.push(r)); |
| } |
|
|
| return results; |
| } |
|
|
| function initKnowPanel() { |
| const form = document.querySelector("[data-know-form]"); |
| const results = document.querySelector("[data-know-results]"); |
| if (!form || !results) return; |
|
|
| const findItem = (id) => (window.CornerCafeMenu || []).find((m) => m.id === id); |
|
|
| const renderMatches = (matches) => { |
| results.hidden = false; |
| results.innerHTML = ""; |
| const status = document.createElement("p"); |
| status.className = "know-status"; |
|
|
| if (!matches.length) { |
| status.textContent = "No clear match — try a dish name from the menu."; |
| results.appendChild(status); |
| return; |
| } |
|
|
| status.textContent = `${matches.length} match${matches.length === 1 ? "" : "es"}`; |
| results.appendChild(status); |
|
|
| const list = document.createElement("div"); |
| list.className = "know-match-list"; |
| matches.forEach(({ item }) => { |
| const row = document.createElement("div"); |
| row.className = "know-match"; |
| const qty = qtyOf(item.id); |
| row.innerHTML = ` |
| <span class="know-match__copy"> |
| <span class="know-match__name">${item.name}</span> |
| <span class="know-match__meta">${item.section}${item.desc ? ` · ${item.desc}` : ""} · ${priceText(item)}</span> |
| </span> |
| ${qtyControlsHtml(item, qty)} |
| `; |
| bindQtyControls(row, findItem); |
| list.appendChild(row); |
| }); |
| results.appendChild(list); |
| }; |
|
|
| form.addEventListener("submit", (event) => { |
| event.preventDefault(); |
| const input = form.querySelector("[name=want]"); |
| const query = (input?.value || "").trim(); |
| renderMatches(matchOrder(query)); |
| }); |
| } |
|
|
| function orderReference(service) { |
| const prefixes = { |
| delivery: "DEL", |
| collection: "COL", |
| "sitting-in": "SIT", |
| }; |
| const prefix = prefixes[service] || "ORD"; |
| const storageKey = `corner-cafe-pay-ref-${service || "order"}`; |
| let ref = ""; |
| try { |
| ref = sessionStorage.getItem(storageKey) || ""; |
| } catch (e) { |
| |
| } |
| |
| if (ref && !ref.startsWith(`${prefix}-`)) ref = ""; |
| if (!ref) { |
| const stamp = Date.now().toString(36).toUpperCase().slice(-5); |
| const rand = Math.floor(Math.random() * 90 + 10); |
| ref = `${prefix}-${stamp}${rand}`; |
| try { |
| sessionStorage.setItem(storageKey, ref); |
| } catch (e) { |
| |
| } |
| } |
| return ref; |
| } |
|
|
| function clearOrderReference(service) { |
| try { |
| sessionStorage.removeItem(`corner-cafe-pay-ref-${service || "order"}`); |
| sessionStorage.removeItem("corner-cafe-pay-ref"); |
| } catch (e) { |
| |
| } |
| } |
|
|
| function loadSitTablesFromSession() { |
| try { |
| const raw = sessionStorage.getItem("corner-cafe-reservation"); |
| if (!raw) return []; |
| const res = JSON.parse(raw); |
| return Array.isArray(res.tables) ? res.tables : []; |
| } catch (e) { |
| return []; |
| } |
| } |
|
|
| async function fillTableSelect(selectEl, preferredIds) { |
| if (!selectEl) return; |
| const cfg = window.SmOS_CC_Reservations; |
| const tables = Array.isArray(cfg?.tables) ? cfg.tables : []; |
| const pref = new Set((preferredIds || []).map(String)); |
| let occupied = new Set(); |
| if (window.SmOS_API?.occupiedTables) { |
| try { |
| occupied = new Set(await window.SmOS_API.occupiedTables()); |
| } catch (e) { |
| occupied = new Set(); |
| } |
| } |
| selectEl.innerHTML = `<option value="">Select table…</option>`; |
| tables.forEach((t) => { |
| const opt = document.createElement("option"); |
| opt.value = t.id; |
| const taken = occupied.has(String(t.id)) && !pref.has(String(t.id)); |
| opt.textContent = `Table ${t.label}${t.seats ? ` · ${t.seats} seats` : ""}${ |
| taken ? " · occupied" : "" |
| }`; |
| opt.disabled = taken; |
| if (pref.has(String(t.id))) opt.selected = true; |
| selectEl.appendChild(opt); |
| }); |
| if (!tables.length) { |
| selectEl.innerHTML = ""; |
| const opt = document.createElement("option"); |
| opt.value = ""; |
| opt.textContent = "Enter table below"; |
| selectEl.appendChild(opt); |
| } |
| |
| if (pref.size && !selectEl.value) { |
| const first = [...pref][0]; |
| selectEl.value = first; |
| } |
| } |
|
|
| function initAddressLookup(root) { |
| const postcodeEl = root.querySelector("[data-addr-postcode]"); |
| const findBtn = root.querySelector("[data-addr-find]"); |
| const listEl = root.querySelector("[data-addr-list]"); |
| const statusEl = root.querySelector("[data-addr-status]"); |
| const valueEl = root.querySelector("[data-addr-value]"); |
| const manualBtn = root.querySelector("[data-addr-manual]"); |
| if (!postcodeEl || !findBtn || !listEl || !valueEl) return; |
|
|
| let debounceTimer = null; |
| let lastQuery = ""; |
|
|
| const setStatus = (msg, show = true) => { |
| if (!statusEl) return; |
| statusEl.textContent = msg || ""; |
| statusEl.hidden = !show || !msg; |
| }; |
|
|
| const showManual = (focus) => { |
| valueEl.hidden = false; |
| valueEl.required = true; |
| if (manualBtn) manualBtn.hidden = true; |
| if (focus) valueEl.focus(); |
| }; |
|
|
| const renderList = (addresses) => { |
| listEl.innerHTML = ""; |
| if (!addresses.length) { |
| listEl.hidden = true; |
| return; |
| } |
| addresses.forEach((addr, idx) => { |
| const li = document.createElement("li"); |
| li.className = "addr-lookup__item"; |
| const btn = document.createElement("button"); |
| btn.type = "button"; |
| btn.className = "addr-lookup__option"; |
| btn.setAttribute("role", "option"); |
| btn.textContent = addr.label || ""; |
| btn.addEventListener("click", () => { |
| valueEl.value = addr.label || ""; |
| valueEl.hidden = false; |
| valueEl.required = true; |
| listEl.hidden = true; |
| listEl.querySelectorAll(".addr-lookup__option").forEach((el) => { |
| el.removeAttribute("aria-selected"); |
| }); |
| btn.setAttribute("aria-selected", "true"); |
| setStatus("Address selected. You can edit it below if needed.", true); |
| if (manualBtn) manualBtn.hidden = true; |
| }); |
| li.appendChild(btn); |
| listEl.appendChild(li); |
| if (idx === 0) btn.focus({ preventScroll: true }); |
| }); |
| listEl.hidden = false; |
| }; |
|
|
| const runLookup = async () => { |
| const postcode = String(postcodeEl.value || "").trim(); |
| if (!postcode) { |
| setStatus("Enter a postcode to find addresses."); |
| return; |
| } |
| if (postcode === lastQuery && !listEl.hidden) return; |
| lastQuery = postcode; |
| findBtn.disabled = true; |
| setStatus("Looking up addresses…"); |
| listEl.hidden = true; |
| listEl.innerHTML = ""; |
| try { |
| if (!window.SmOS_API?.lookupAddresses) { |
| throw new Error("Address lookup unavailable. Enter the address manually."); |
| } |
| const data = await window.SmOS_API.lookupAddresses(postcode); |
| const addresses = Array.isArray(data.addresses) ? data.addresses : []; |
| if (!addresses.length) { |
| setStatus("No addresses found. Enter the address manually."); |
| showManual(false); |
| if (manualBtn) manualBtn.hidden = true; |
| return; |
| } |
| const demoNote = data.demo |
| ? " Demo mode — add IDEAL_POSTCODES_API_KEY for live UK postcodes." |
| : ""; |
| setStatus(`${addresses.length} address${addresses.length === 1 ? "" : "es"} found. Select one.${demoNote}`); |
| renderList(addresses); |
| if (manualBtn) { |
| manualBtn.hidden = false; |
| manualBtn.textContent = "Enter address manually"; |
| } |
| valueEl.hidden = true; |
| valueEl.value = ""; |
| valueEl.required = false; |
| } catch (err) { |
| setStatus(err?.message || "Address lookup failed. Enter the address manually."); |
| showManual(false); |
| if (manualBtn) { |
| manualBtn.hidden = false; |
| manualBtn.textContent = "Enter address manually"; |
| } |
| } finally { |
| findBtn.disabled = false; |
| } |
| }; |
|
|
| findBtn.addEventListener("click", () => { |
| clearTimeout(debounceTimer); |
| runLookup(); |
| }); |
|
|
| postcodeEl.addEventListener("keydown", (e) => { |
| if (e.key === "Enter") { |
| e.preventDefault(); |
| clearTimeout(debounceTimer); |
| runLookup(); |
| } |
| }); |
|
|
| postcodeEl.addEventListener("input", () => { |
| clearTimeout(debounceTimer); |
| const raw = String(postcodeEl.value || "").trim(); |
| const compact = raw.replace(/\s+/g, ""); |
| if (compact.length < 5) return; |
| if (!/^[A-Z]{1,2}\d[A-Z\d]?\d[A-Z]{2}$/i.test(compact)) return; |
| debounceTimer = setTimeout(runLookup, 450); |
| }); |
|
|
| manualBtn?.addEventListener("click", () => { |
| listEl.hidden = true; |
| showManual(true); |
| setStatus("Type your full delivery address below.", true); |
| }); |
| } |
|
|
| async function initCheckout() { |
| const rootEl = document.querySelector("[data-checkout]"); |
| if (!rootEl) return; |
|
|
| if (window.SmOS_CC_PaymentsReady) { |
| try { |
| await window.SmOS_CC_PaymentsReady; |
| } catch (e) { |
| |
| } |
| } |
|
|
| const mollieReturn = params.get("mollie_return") === "1"; |
| const molliePaymentId = (params.get("payment_id") || "").trim(); |
| if (mollieReturn && molliePaymentId) { |
| suppressUnloadWarn = true; |
| rootEl.querySelectorAll("[data-checkout-hide-on-done]").forEach((el) => { |
| el.hidden = true; |
| }); |
| const done = document.querySelector("[data-checkout-done]"); |
| const lead = document.querySelector("[data-done-lead]"); |
| const donePay = document.querySelector("[data-done-pay]"); |
| if (done) done.hidden = false; |
| if (lead) lead.textContent = "Confirming Mollie payment…"; |
| if (donePay) { |
| donePay.innerHTML = `<p class="pay-detail__summary">Please wait while we confirm your payment.</p>`; |
| } |
|
|
| let pendingMeta = {}; |
| try { |
| pendingMeta = JSON.parse(sessionStorage.getItem("corner-cafe-mollie-pending") || "{}") || {}; |
| } catch (_) { |
| pendingMeta = {}; |
| } |
|
|
| const showMollieResult = (status, booking) => { |
| const ref = booking?.ref || pendingMeta.ref || "—"; |
| const name = booking?.customer_name || pendingMeta.name || ""; |
| const total = booking?.total != null ? money(booking.total) : money(pendingMeta.total || 0); |
| const svc = booking?.service || pendingMeta.service || "order"; |
| const svcLabel = serviceLabels[svc] || svc; |
| if (status === "paid" && booking) { |
| orderPlaced = true; |
| clearCart(); |
| clearOrderReference(svc); |
| try { |
| sessionStorage.removeItem("corner-cafe-mollie-pending"); |
| } catch (_) {} |
| if (lead) { |
| lead.textContent = `Thanks ${name || "there"}. ${ref} · ${svcLabel} · ${total} · paid via Mollie.`; |
| } |
| if (donePay) { |
| donePay.innerHTML = `<p class="pay-detail__summary">Payment confirmed. We’re preparing your ${String(svcLabel).toLowerCase()} order <strong>${ref}</strong>.</p>`; |
| } |
| const receiptBox = document.querySelector("[data-done-receipt]"); |
| if (receiptBox) receiptBox.hidden = false; |
| const historyOrder = { |
| ref, |
| service: svc, |
| payment: "mollie", |
| paymentName: booking.payment || "Mollie", |
| paymentStatus: "paid", |
| total: booking.total, |
| items: booking.items || [], |
| name, |
| phone: booking.phone || pendingMeta.phone || "", |
| email: booking.email || pendingMeta.email || "", |
| confirmVia: pendingMeta.confirmVia || "none", |
| wantReceipt: Boolean(pendingMeta.wantReceipt), |
| address: booking.address || "", |
| notes: booking.notes || "", |
| placedAt: booking.created_at || new Date().toISOString(), |
| }; |
| try { |
| const history = JSON.parse(sessionStorage.getItem("corner-cafe-orders") || "[]"); |
| history.unshift(historyOrder); |
| sessionStorage.setItem("corner-cafe-orders", JSON.stringify(history.slice(0, 20))); |
| } catch (_) {} |
| const receiptBtn = document.querySelector("[data-download-receipt]"); |
| if (receiptBtn && window.SmOSReceipt?.openPrint) { |
| receiptBtn.onclick = () => { |
| window.SmOSReceipt.openPrint({ |
| ...historyOrder, |
| customer_name: name, |
| payment_status: "paid", |
| payment: historyOrder.paymentName, |
| }); |
| }; |
| } |
| return; |
| } |
| if (lead) { |
| lead.textContent = |
| status === "canceled" || status === "expired" || status === "failed" |
| ? `Payment ${status}. Order ${ref} was not placed.` |
| : `Payment status: ${status || "open"}. Order ${ref} is not confirmed yet.`; |
| } |
| if (donePay) { |
| donePay.innerHTML = `<p class="pay-detail__summary">If you completed payment, wait a moment and refresh — or return to checkout and try again.</p> |
| <p class="pay-hint"><a href="checkout.html?service=${encodeURIComponent(pendingMeta.service || "collection")}&mode=menu">Back to checkout</a></p>`; |
| } |
| }; |
|
|
| try { |
| if (!window.SmOS_API?.mollieStatus) throw new Error("Mollie status API unavailable"); |
| let result = await window.SmOS_API.mollieStatus(molliePaymentId); |
| |
| for (let i = 0; i < 4 && result && !result.finalized && result.mollie_status === "open"; i += 1) { |
| await new Promise((r) => setTimeout(r, 900)); |
| result = await window.SmOS_API.mollieStatus(molliePaymentId); |
| } |
| showMollieResult(result?.mollie_status, result?.booking); |
| } catch (e) { |
| if (lead) lead.textContent = e.message || "Could not confirm Mollie payment."; |
| if (donePay) { |
| donePay.innerHTML = `<p class="pay-detail__summary"><a href="./">Back to start</a></p>`; |
| } |
| } |
| return; |
| } |
|
|
| const service = params.get("service") || ""; |
| const mode = params.get("mode") || "menu"; |
| if (!serviceLabels[service] || cartCount() <= 0) { |
| window.location.replace(serviceLabels[service] ? `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}` : "./"); |
| return; |
| } |
|
|
| try { |
| sessionStorage.setItem(SERVICE_KEY, service); |
| sessionStorage.setItem(MODE_KEY, mode); |
| } catch (e) { |
| |
| } |
|
|
| const serviceEl = document.querySelector("[data-order-service]"); |
| if (serviceEl) serviceEl.textContent = serviceLabels[service]; |
|
|
| const back = document.querySelector("[data-back-order]"); |
| if (back) back.href = `order.html?service=${encodeURIComponent(service)}&mode=${encodeURIComponent(mode)}`; |
|
|
| const addressField = document.querySelector("[data-field-address]"); |
| if (addressField) { |
| const needsAddress = service === "delivery"; |
| addressField.hidden = !needsAddress; |
| addressField.toggleAttribute("hidden", !needsAddress); |
| const ta = addressField.querySelector("[data-addr-value], textarea[name=address]"); |
| if (ta) { |
| ta.required = false; |
| ta.disabled = !needsAddress; |
| if (!needsAddress) { |
| ta.value = ""; |
| ta.required = false; |
| } |
| } |
| if (needsAddress) initAddressLookup(addressField); |
| } |
|
|
| const tableField = document.querySelector("[data-field-table]"); |
| const tableSelect = document.querySelector("[name=table_id]"); |
| const reservedTables = service === "sitting-in" ? loadSitTablesFromSession() : []; |
| if (tableField) { |
| const needsTable = service === "sitting-in"; |
| tableField.hidden = !needsTable; |
| tableField.toggleAttribute("hidden", !needsTable); |
| if (tableSelect) { |
| tableSelect.required = needsTable; |
| tableSelect.disabled = !needsTable; |
| if (needsTable) { |
| await fillTableSelect( |
| tableSelect, |
| reservedTables.map((t) => t.id || t.label) |
| ); |
| } else { |
| tableSelect.value = ""; |
| } |
| } |
| } |
|
|
| const linesEl = document.querySelector("[data-checkout-lines]"); |
| const totalEl = document.querySelector("[data-checkout-total]"); |
| const refEl = document.querySelector("[data-checkout-ref]"); |
| const ref = orderReference(service); |
| if (refEl) refEl.textContent = ref; |
| if (totalEl) totalEl.textContent = money(cartTotal()); |
| if (linesEl) { |
| linesEl.innerHTML = ""; |
| cart.forEach((line) => { |
| const li = document.createElement("li"); |
| li.className = "checkout-line"; |
| li.innerHTML = `<span>${line.qty}× ${line.name}</span><span>${money(line.qty * line.price)}</span>`; |
| linesEl.appendChild(li); |
| }); |
| } |
|
|
| const cfg = window.SmOS_CC_Payments || { options: [] }; |
| const optionsRoot = document.querySelector("[data-pay-options]"); |
| const detailEl = document.querySelector("[data-pay-detail]"); |
| const confirmBox = document.querySelector("[data-pay-confirm]"); |
| const confirmCheck = document.querySelector("[data-pay-confirmed]"); |
| const placeBtn = document.querySelector("[data-place-order]"); |
| const placeHint = document.querySelector("[data-place-hint]"); |
| let selectedPay = null; |
|
|
| const requiresPrepaid = () => { |
| if (service === "delivery" || service === "collection") return true; |
| if (!selectedPay) return false; |
| return Boolean(selectedPay.requiresPaidConfirm); |
| }; |
|
|
| const isMollie = () => selectedPay && (selectedPay.id === "mollie" || selectedPay.provider === "mollie"); |
|
|
| const available = (cfg.options || []).filter((opt) => |
| (opt.availableFor || []).includes(service) |
| ); |
|
|
| const heroLead = document.querySelector("[data-checkout-lead]") || document.querySelector(".app-hero__lead"); |
| const payBlock = document.querySelector("[data-checkout-pay]"); |
| const payTitle = document.querySelector("[data-checkout-pay-title]"); |
| const payIntro = document.querySelector("[data-checkout-pay-intro]"); |
| if (heroLead) { |
| if (service === "delivery" || service === "collection") { |
| heroLead.textContent = "Pay first — order only places after payment is confirmed."; |
| } else if (service === "sitting-in") { |
| heroLead.textContent = |
| "Add your details and table — you’re welcome to settle at the till before or after your meal."; |
| } |
| } |
|
|
| if (service === "sitting-in" && payBlock) { |
| payBlock.hidden = true; |
| } else if (payTitle) { |
| payTitle.textContent = "Payment"; |
| } |
| if (payIntro) { |
| if (service === "delivery" || service === "collection") { |
| payIntro.hidden = false; |
| payIntro.textContent = cfg.mollieEnabled |
| ? cfg.mollieMode === "test" |
| ? "Pay by card, Apple Pay, or Google Pay (test mode)." |
| : "Pay by card, Apple Pay, or Google Pay." |
| : "Card payment is not available yet. Sit-in orders can still be placed and paid at the cafe."; |
| } else { |
| payIntro.hidden = true; |
| } |
| } |
|
|
| const syncConfirmVia = () => { |
| const via = |
| document.querySelector('input[name="confirm_via"]:checked')?.value || "none"; |
| const emailEl = document.querySelector("[data-confirm-email]"); |
| const phoneEl = document.querySelector("[data-confirm-phone]"); |
| const wantReceipt = Boolean(document.querySelector("[data-want-receipt]")?.checked); |
| const receiptHint = document.querySelector("[data-receipt-contact-hint]"); |
| if (emailEl) { |
| const on = via === "email"; |
| emailEl.required = on; |
| emailEl.disabled = !on; |
| emailEl.hidden = !on; |
| if (!on) emailEl.value = ""; |
| } |
| if (phoneEl) { |
| const on = via === "phone"; |
| phoneEl.required = on; |
| phoneEl.disabled = !on; |
| phoneEl.hidden = !on; |
| if (!on) phoneEl.value = ""; |
| } |
| if (receiptHint) { |
| receiptHint.hidden = !wantReceipt || via === "email" || via === "phone"; |
| } |
| }; |
| document.querySelectorAll("[data-confirm-via-opt]").forEach((input) => { |
| input.addEventListener("change", syncConfirmVia); |
| }); |
| document.querySelector("[data-want-receipt]")?.addEventListener("change", syncConfirmVia); |
| syncConfirmVia(); |
|
|
| const paymentReady = () => { |
| if (!selectedPay) return false; |
| if (isMollie()) return Boolean(cfg.mollieEnabled); |
| if (!requiresPrepaid()) return true; |
| return Boolean(confirmCheck && confirmCheck.checked); |
| }; |
|
|
| const syncPlaceButton = () => { |
| const mollie = isMollie(); |
| const ready = paymentReady() && cartCount() > 0; |
| if (placeBtn) { |
| placeBtn.disabled = !ready; |
| placeBtn.textContent = mollie ? "Pay now" : "Place order"; |
| } |
| if (placeHint) { |
| if (mollie && !cfg.mollieEnabled) { |
| placeHint.hidden = false; |
| placeHint.textContent = "Card payment is not available yet."; |
| } else { |
| placeHint.hidden = true; |
| placeHint.textContent = ""; |
| } |
| } |
| if (confirmBox) confirmBox.hidden = true; |
| }; |
|
|
| const showDetail = (opt) => { |
| if (!detailEl) return; |
| detailEl.hidden = false; |
| detailEl.innerHTML = `<p class="pay-detail__summary">${opt.summary}</p>`; |
| syncPlaceButton(); |
| }; |
|
|
| if (optionsRoot) { |
| optionsRoot.innerHTML = ""; |
| if (service === "sitting-in") { |
| selectedPay = available[0] || { |
| id: "cash-sit-in", |
| name: "Pay at the cafe", |
| availableFor: ["sitting-in"], |
| }; |
| if (detailEl) detailEl.hidden = true; |
| syncPlaceButton(); |
| } else { |
| available.forEach((opt, idx) => { |
| const label = document.createElement("label"); |
| label.className = "pay-option"; |
| label.innerHTML = ` |
| <input type="radio" name="pay" value="${opt.id}" ${idx === 0 ? "checked" : ""}> |
| <span class="pay-option__body"> |
| <span class="pay-option__name">${opt.name}</span> |
| <span class="pay-option__fee">${opt.feeNote}</span> |
| </span> |
| `; |
| const input = label.querySelector("input"); |
| input.addEventListener("change", () => { |
| if (!input.checked) return; |
| selectedPay = opt; |
| if (confirmCheck) confirmCheck.checked = false; |
| showDetail(opt); |
| }); |
| optionsRoot.appendChild(label); |
| }); |
|
|
| if (available[0]) { |
| selectedPay = available[0]; |
| showDetail(available[0]); |
| } else { |
| syncPlaceButton(); |
| } |
| } |
| } |
|
|
| confirmCheck?.addEventListener("change", syncPlaceButton); |
| syncPlaceButton(); |
|
|
| document.querySelector("[data-checkout-form]")?.addEventListener("submit", async (event) => { |
| event.preventDefault(); |
| if (!selectedPay || cartCount() <= 0) return; |
| const molliePay = isMollie(); |
| if (!molliePay && requiresPrepaid() && !(confirmCheck && confirmCheck.checked)) { |
| if (placeHint) placeHint.hidden = false; |
| return; |
| } |
| const form = event.target; |
| const data = new FormData(form); |
| const tableId = String(data.get("table_id") || "").trim(); |
| const cfgTables = window.SmOS_CC_Reservations?.tables || []; |
| const tableMeta = cfgTables.find((t) => String(t.id) === tableId); |
| const tables = |
| service === "sitting-in" && tableId |
| ? [ |
| { |
| id: tableId, |
| label: tableMeta?.label || tableId, |
| seats: tableMeta?.seats || null, |
| }, |
| ] |
| : reservedTables.length |
| ? reservedTables |
| : null; |
|
|
| const prepaid = requiresPrepaid() && !molliePay; |
| const paymentStatus = molliePay ? "awaiting" : prepaid ? "paid" : "unpaid"; |
| const confirmVia = String(data.get("confirm_via") || "none"); |
| const email = String(data.get("email") || "").trim(); |
| const phone = String(data.get("phone") || "").trim(); |
| if (confirmVia === "email" && !email) { |
| alert("Enter an email address for confirmation, or choose No confirmation."); |
| return; |
| } |
| if (confirmVia === "phone" && !phone) { |
| alert("Enter a phone number for confirmation, or choose No confirmation."); |
| return; |
| } |
| const wantReceipt = Boolean(data.get("want_receipt")); |
| if (wantReceipt && confirmVia !== "email" && confirmVia !== "phone") { |
| alert("To receive a receipt, choose Email or Phone and enter your contact details."); |
| document.querySelector("[data-receipt-contact-hint]")?.removeAttribute("hidden"); |
| return; |
| } |
| if (wantReceipt && confirmVia === "email" && !email) { |
| alert("Enter an email address to receive your receipt."); |
| return; |
| } |
| if (wantReceipt && confirmVia === "phone" && !phone) { |
| alert("Enter a phone number to receive your receipt."); |
| return; |
| } |
| const deliveryAddress = String(data.get("address") || "").trim(); |
| if (service === "delivery" && !deliveryAddress) { |
| alert("Find your postcode and select a delivery address, or enter one manually."); |
| document.querySelector("[data-addr-postcode]")?.focus(); |
| return; |
| } |
| const order = { |
| ref, |
| service, |
| mode, |
| payment: selectedPay.id, |
| paymentName: selectedPay.name, |
| paymentStatus, |
| paymentConfirmed: prepaid, |
| total: cartTotal(), |
| items: cart.slice(), |
| name: String(data.get("name") || "").trim(), |
| phone: confirmVia === "phone" ? phone : phone || "", |
| email: confirmVia === "email" ? email : email || "", |
| confirmVia, |
| wantReceipt, |
| address: service === "delivery" ? deliveryAddress : "", |
| notes: String(data.get("notes") || "").trim(), |
| tableId: tableId || null, |
| tables, |
| placedAt: new Date().toISOString(), |
| }; |
|
|
| if (placeBtn) placeBtn.disabled = true; |
|
|
| if (molliePay) { |
| if (!window.SmOS_API?.createMolliePayment) { |
| if (placeBtn) placeBtn.disabled = false; |
| alert("Mollie checkout is unavailable."); |
| return; |
| } |
| try { |
| const created = await window.SmOS_API.createMolliePayment({ |
| kind: "order", |
| service, |
| ref: order.ref, |
| customer_name: order.name, |
| phone: order.phone || null, |
| email: order.email || null, |
| confirm_via: order.confirmVia === "none" ? null : order.confirmVia, |
| address: order.address || null, |
| notes: order.notes || null, |
| total: order.total, |
| items: order.items, |
| tables: order.tables, |
| payload: order, |
| }); |
| if (!created?.checkout_url) throw new Error("No Mollie checkout URL returned."); |
| try { |
| sessionStorage.setItem( |
| "corner-cafe-mollie-pending", |
| JSON.stringify({ |
| paymentId: created.payment_id, |
| ref: order.ref, |
| service, |
| total: order.total, |
| name: order.name, |
| wantReceipt: order.wantReceipt, |
| confirmVia: order.confirmVia, |
| email: order.email, |
| phone: order.phone, |
| }) |
| ); |
| } catch (_) {} |
| suppressUnloadWarn = true; |
| location.href = created.checkout_url; |
| return; |
| } catch (e) { |
| if (placeBtn) placeBtn.disabled = false; |
| alert(e.message || "Could not start Mollie checkout."); |
| return; |
| } |
| } |
|
|
| if (window.SmOS_API?.postBooking) { |
| try { |
| await window.SmOS_API.postBooking({ |
| kind: "order", |
| service, |
| ref: order.ref, |
| customer_name: order.name, |
| phone: order.phone || null, |
| email: order.email || null, |
| confirm_via: order.confirmVia === "none" ? null : order.confirmVia, |
| payment: order.paymentName, |
| payment_status: paymentStatus, |
| total: order.total, |
| address: order.address || null, |
| notes: order.notes || null, |
| items: order.items, |
| tables: order.tables, |
| payload: order, |
| }); |
| } catch (e) { |
| if (placeBtn) placeBtn.disabled = false; |
| alert(e.message || "Could not place order. Confirm payment and try again."); |
| return; |
| } |
| } |
|
|
| try { |
| const history = JSON.parse(sessionStorage.getItem("corner-cafe-orders") || "[]"); |
| history.unshift(order); |
| sessionStorage.setItem("corner-cafe-orders", JSON.stringify(history.slice(0, 20))); |
| clearOrderReference(service); |
| } catch (e) { |
| |
| } |
|
|
| orderPlaced = true; |
| suppressUnloadWarn = true; |
| clearCart(); |
|
|
| rootEl.querySelectorAll("[data-checkout-hide-on-done]").forEach((el) => { |
| el.hidden = true; |
| }); |
|
|
| const done = document.querySelector("[data-checkout-done]"); |
| const lead = document.querySelector("[data-done-lead]"); |
| const donePay = document.querySelector("[data-done-pay]"); |
| if (done) done.hidden = false; |
| if (lead) { |
| lead.textContent = prepaid |
| ? `Thanks ${order.name}. ${order.ref} · ${serviceLabels[service]} · ${money(order.total)} · paid & confirmed.` |
| : `Thanks ${order.name}. Order ${order.ref} · ${serviceLabels[service]} · ${money(order.total)} · ${selectedPay.name}.`; |
| } |
| if (donePay) { |
| if (prepaid) { |
| donePay.innerHTML = `<p class="pay-detail__summary">Payment confirmed. We’re preparing your ${serviceLabels[service].toLowerCase()} order <strong>${order.ref}</strong>.</p>`; |
| } else if (service === "sitting-in") { |
| donePay.innerHTML = `<p class="pay-detail__summary">Please settle <strong>${money(order.total)}</strong> at the till whenever you’re ready — before or after your meal. Quote <strong>${order.ref}</strong>${order.tableId ? ` · table ${order.tableId}` : ""}.</p>`; |
| } else { |
| donePay.innerHTML = `<p class="pay-detail__summary">Pay ${money(order.total)} at the cafe. Quote <strong>${order.ref}</strong>.</p>`; |
| } |
| } |
|
|
| const receiptBox = document.querySelector("[data-done-receipt]"); |
| const receiptNote = document.querySelector("[data-receipt-send-note]"); |
| const receiptBtn = document.querySelector("[data-download-receipt]"); |
| if (receiptBox) { |
| const showReceipt = Boolean(order.wantReceipt) || prepaid || service === "sitting-in"; |
| receiptBox.hidden = !showReceipt; |
| if (receiptNote) { |
| if (order.wantReceipt && order.confirmVia === "email" && order.email) { |
| receiptNote.hidden = false; |
| receiptNote.textContent = `Receipt copy requested by email to ${order.email} (sent when messaging is connected).`; |
| } else if (order.wantReceipt && order.confirmVia === "phone" && order.phone) { |
| receiptNote.hidden = false; |
| receiptNote.textContent = `Receipt copy requested by phone to ${order.phone} (sent when messaging is connected).`; |
| } else if (order.wantReceipt) { |
| receiptNote.hidden = false; |
| receiptNote.textContent = |
| "You asked for a receipt — download or print it below. Email/SMS delivery can be connected later."; |
| } else if (service === "sitting-in") { |
| receiptNote.hidden = false; |
| receiptNote.textContent = |
| "A till receipt can also be printed by your waiter after you pay."; |
| } else { |
| receiptNote.hidden = true; |
| } |
| } |
| if (receiptBtn) { |
| receiptBtn.onclick = () => { |
| if (window.SmOSReceipt?.openPrint) { |
| window.SmOSReceipt.openPrint({ |
| ...order, |
| customer_name: order.name, |
| payment_status: order.paymentStatus, |
| payment: order.paymentName, |
| }); |
| } else { |
| alert("Receipt helper not loaded."); |
| } |
| }; |
| } |
| } |
|
|
| if (heroLead) { |
| heroLead.textContent = prepaid |
| ? "Order placed — paid and confirmed." |
| : service === "sitting-in" |
| ? "Order placed — settle at the till when you’re ready." |
| : "Order placed."; |
| } |
| }); |
| } |
|
|
| initCheckout(); |
| })(); |
|
|