Files
fieldwork/dist/js/cart.js
T
jzzliu 73fcc0548c Rework storefront into modules and tokenised styles
Split the 100-line script into catalog, cart, format, dom and main modules.
Money is integer cents, the bag is capped and sanitised and syncs across
tabs, and the view (category, sort) lives in the URL. Templates escape every
interpolation.

Rewrite the minified stylesheet as cascade layers on design tokens. Fixes
heading tracking that collapsed word gaps, a 2.6:1 focus ring on the hero,
1.3:1 control borders, misaligned cart quantities, uneven product cards and
undersized touch targets. Adds a favicon and a resolvable dialog label.
2026-11-07 21:18:38 -07:00

65 lines
2.1 KiB
JavaScript

/**
* Shopping bag logic. A cart is a plain `{ [productId]: quantity }` object.
*
* Everything here is pure (no DOM, no globals) except load/save, which take the storage object
* as an argument so they can be tested with a fake, including one that throws.
*/
export const MAX_QTY = 9;
export const STORAGE_KEY = "fieldwork-bag-v1"; // format unchanged, so existing bags carry over
const clamp = (n) => Math.min(MAX_QTY, Math.max(0, n));
/** Keep only known products with whole-number quantities, clamped to 1..MAX_QTY. */
export function sanitizeCart(raw, validIds) {
const clean = {};
if (raw === null || typeof raw !== "object" || Array.isArray(raw)) return clean;
for (const id of validIds) {
const qty = raw[id];
if (Number.isSafeInteger(qty) && qty > 0) clean[id] = clamp(qty);
}
return clean;
}
/** Return a new cart with `delta` added to one line. Lines at 0 are removed. */
export function changeQty(cart, id, delta) {
const next = { ...cart };
const qty = clamp((cart[id] || 0) + delta);
if (qty === 0) delete next[id];
else next[id] = qty;
return next;
}
export const cartCount = (cart) => Object.values(cart).reduce((sum, qty) => sum + qty, 0);
/** Lines in catalogue order: [{ product, qty, total }] with totals in cents. */
export function cartLines(cart, products) {
return products
.filter((product) => cart[product.id])
.map((product) => ({
product,
qty: cart[product.id],
total: product.price * cart[product.id],
}));
}
export const cartSubtotal = (cart, products) =>
cartLines(cart, products).reduce((sum, line) => sum + line.total, 0);
export function loadCart(storage, validIds) {
try {
return sanitizeCart(JSON.parse(storage.getItem(STORAGE_KEY)), validIds);
} catch {
return {}; // storage blocked, empty, or corrupted JSON
}
}
export function saveCart(storage, cart) {
try {
if (cartCount(cart) === 0) storage.removeItem(STORAGE_KEY);
else storage.setItem(STORAGE_KEY, JSON.stringify(cart));
} catch {
/* private mode or quota exceeded: the bag still works for this visit */
}
}