209 lines
8.1 KiB
JavaScript
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">−</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">✓</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 };
|
|
})();
|