Files
ben.de-roo.org/domme-converters/assets/js/ui.js
T

209 lines
8.1 KiB
JavaScript

"use strict";
/**
* Globale UI die op elke pagina aanwezig is: winkelmandje-lade, afrekenmodal
* en toast-meldingen. Deze module praat uitsluitend met CL.store, zodat de
* mandjestoestand consistent blijft tijdens het navigeren.
*/
(function () {
const CL = (window.CL = window.CL || {});
const { formatPrice, escapeHtml } = CL.fmt;
const store = CL.store;
const PRODUCTS = window.PRODUCTS || [];
const findProduct = (id) => PRODUCTS.find((p) => p.id === id);
const els = {};
let toastTimer;
function init() {
els.overlay = document.getElementById("overlay");
els.drawer = document.getElementById("drawer");
els.closeCart = document.getElementById("closeCart");
els.cartItems = document.getElementById("cartItems");
els.cartSub = document.getElementById("cartSub");
els.cartTax = document.getElementById("cartTax");
els.cartTotal = document.getElementById("cartTotal");
els.checkoutBtn = document.getElementById("checkoutBtn");
els.modalWrap = document.getElementById("modalWrap");
els.modalContent = document.getElementById("modalContent");
els.toast = document.getElementById("toast");
els.closeCart.addEventListener("click", closeCart);
els.overlay.addEventListener("click", closeCart);
els.checkoutBtn.addEventListener("click", () => {
if (store.size() === 0) return;
closeCart();
renderCheckoutForm();
openModal();
});
// Aantal aanpassen in de lade (delegatie).
els.cartItems.addEventListener("click", (e) => {
const btn = e.target.closest("[data-step]");
if (btn) store.step(btn.dataset.id, Number(btn.dataset.step));
});
// "Toevoegen" werkt overal: catalogus, productpagina, aanbiedingen.
document.addEventListener("click", (e) => {
const add = e.target.closest("[data-add]");
if (!add) return;
e.preventDefault();
const product = findProduct(add.dataset.add);
if (!product) return;
store.add(product.id);
showToast(product.name + " toegevoegd aan het mandje.");
});
els.modalWrap.addEventListener("click", (e) => {
if (e.target === els.modalWrap) closeModal();
});
document.addEventListener("keydown", (e) => {
if (e.key === "Escape") { closeModal(); closeCart(); }
});
}
// Koppelt knoppen die per route opnieuw worden gerenderd (in de header).
function bindHeader() {
const openCartBtn = document.getElementById("openCart");
if (openCartBtn) openCartBtn.addEventListener("click", openCart);
const toggle = document.getElementById("navToggle");
const links = document.getElementById("navLinks");
if (toggle && links) {
toggle.addEventListener("click", () => {
const open = links.classList.toggle("open");
toggle.setAttribute("aria-expanded", String(open));
});
// Sluit het mobiele menu na een keuze.
links.addEventListener("click", (e) => {
if (e.target.closest("a")) {
links.classList.remove("open");
toggle.setAttribute("aria-expanded", "false");
}
});
}
}
function updateCartCount() {
const { count } = store.totals();
document.querySelectorAll("[data-cart-count]").forEach((el) => (el.textContent = String(count)));
}
function renderCart() {
const { count, subtotal, tax, total } = store.totals();
els.cartSub.innerHTML = formatPrice(subtotal);
els.cartTax.innerHTML = formatPrice(tax);
els.cartTotal.innerHTML = formatPrice(total);
els.checkoutBtn.disabled = count === 0;
const entries = store.entries();
if (entries.length === 0) {
els.cartItems.innerHTML = '<div class="cart-empty">Uw winkelmandje is leeg.</div>';
return;
}
const rows = [];
for (const [id, qty] of entries) {
const p = findProduct(id);
if (!p) continue;
const tile = p.image
? `<img src="${escapeHtml(p.image)}" alt="" onerror="this.remove(); this.parentNode.textContent = this.parentNode.dataset.fallback;" />`
: escapeHtml(p.from);
rows.push(`
<div class="cart-row">
<div class="tile" data-fallback="${escapeHtml(p.from)}" aria-hidden="true">${tile}</div>
<div class="info">
<strong>${escapeHtml(p.name)}</strong>
<span class="unit">${formatPrice(p.price)} per stuk</span>
<div class="qty">
<button data-step="-1" data-id="${p.id}" aria-label="Aantal verlagen">&minus;</button>
<span>${qty}</span>
<button data-step="1" data-id="${p.id}" aria-label="Aantal verhogen">+</button>
</div>
</div>
<span class="line-total">${formatPrice(p.price * qty)}</span>
</div>
`);
}
els.cartItems.innerHTML = rows.join("");
}
function openCart() { els.overlay.classList.add("open"); els.drawer.classList.add("open"); }
function closeCart() { els.overlay.classList.remove("open"); els.drawer.classList.remove("open"); }
function openModal() { els.modalWrap.classList.add("open"); }
function closeModal() { els.modalWrap.classList.remove("open"); }
function showToast(message) {
els.toast.textContent = message;
els.toast.classList.add("show");
clearTimeout(toastTimer);
toastTimer = setTimeout(() => els.toast.classList.remove("show"), 2200);
}
function renderCheckoutForm() {
const { total } = store.totals();
els.modalContent.innerHTML = `
<h2>Afrekenen</h2>
<p class="sub">Dit formulier verwerkt geen gegevens. Ingevoerde waarden worden niet opgeslagen of verzonden.</p>
<form id="payForm" novalidate>
<div class="field">
<label for="f-name">Naam</label>
<input id="f-name" name="name" required autocomplete="name" placeholder="Voor- en achternaam" />
</div>
<div class="field">
<label for="f-email">E-mailadres</label>
<input id="f-email" name="email" type="email" required autocomplete="email" placeholder="naam@voorbeeld.nl" />
</div>
<div class="field">
<label for="f-address">Bezorgadres</label>
<input id="f-address" name="address" required autocomplete="street-address" placeholder="Straat, huisnummer, plaats" />
</div>
<div class="field">
<label for="f-card">Kaartnummer (voer geen echte gegevens in)</label>
<input id="f-card" name="card" inputmode="numeric" required placeholder="0000 0000 0000 0000" />
</div>
<div class="row2">
<div class="field">
<label for="f-exp">Vervaldatum</label>
<input id="f-exp" name="exp" required placeholder="MM/JJ" />
</div>
<div class="field">
<label for="f-cvc">CVC</label>
<input id="f-cvc" name="cvc" inputmode="numeric" required placeholder="123" />
</div>
</div>
<button class="pay-btn" type="submit">Plaats bestelling (${formatPrice(total)})</button>
<p class="pay-note">Demonstratieformulier. Er wordt geen betaling uitgevoerd.</p>
</form>
`;
document.getElementById("payForm").addEventListener("submit", (event) => {
event.preventDefault();
const form = event.currentTarget;
if (!form.checkValidity()) { form.reportValidity(); return; }
renderSuccess();
});
}
function renderSuccess() {
const { count } = store.totals();
const orderId = "CL-" + Math.floor(100000 + Math.random() * 900000);
els.modalContent.innerHTML = `
<div class="success">
<div class="check" aria-hidden="true">&checkmark;</div>
<h2>Bestelling geregistreerd</h2>
<p>${count} fictief artikel(en) zijn vastgelegd in deze demonstratie.</p>
<div class="order-id">Referentie: ${escapeHtml(orderId)}</div>
<p>Verwachte levering: niet van toepassing.</p>
<button class="pay-btn" id="doneBtn" style="margin-top:18px">Verder winkelen</button>
</div>
`;
store.clear();
document.getElementById("doneBtn").addEventListener("click", () => {
closeModal();
showToast("Winkelmandje geleegd.");
});
}
CL.ui = { init, bindHeader, updateCartCount, renderCart, openCart, closeCart, showToast };
})();