cart-client.ts raw
1 // single definition of the cart item shape - imported by every page script
2 // that touches the cart. price_cents is authoritative naming; the server
3 // re-prices from the catalog at checkout regardless.
4
5 export interface CartItem {
6 slug: string;
7 name: string;
8 name_en: string;
9 price_cents: number;
10 image: string;
11 unit_size: number;
12 unit: string;
13 stock: number;
14 qty: number;
15 }
16
17 const KEY = 'cart';
18
19 export function getCart(): CartItem[] {
20 try {
21 const raw = JSON.parse(localStorage.getItem(KEY) || '[]');
22 if (!Array.isArray(raw)) return [];
23 return raw.filter(it => it && typeof it.slug === 'string' && Number.isFinite(it.price_cents) && Number.isInteger(it.qty) && it.qty > 0).map(it => ({
24 ...it,
25 unit_size: it.unit_size || 100,
26 unit: it.unit || 'g',
27 stock: it.stock || 99,
28 image: resolveImage(it.image || ''),
29 name_en: it.name_en || it.name || '',
30 }));
31 } catch {
32 return [];
33 }
34 }
35
36 function save(cart: CartItem[]) {
37 localStorage.setItem(KEY, JSON.stringify(cart));
38 window.dispatchEvent(new CustomEvent('cart-updated'));
39 }
40
41 function resolveImage(raw: string): string {
42 try {
43 const arr = JSON.parse(raw);
44 return arr?.[0] || raw || '/images/logo.png';
45 } catch { return raw || '/images/logo.png'; }
46 }
47
48 export function addToCart(item: Omit<CartItem, 'qty'>, qty = 1) {
49 const cart = getCart();
50 const existing = cart.find(it => it.slug === item.slug);
51 const resolved = { ...item, unit_size: item.unit_size || 100, unit: item.unit || 'g', stock: item.stock || 99, image: resolveImage(item.image) };
52 const limit = resolved.stock;
53 if (existing) {
54 existing.qty = Math.min(limit, existing.qty + qty);
55 } else {
56 cart.push({ ...resolved, qty: Math.min(limit, Math.max(1, qty)) });
57 }
58 save(cart);
59 }
60
61 export function setQty(index: number, qty: number) {
62 const cart = getCart();
63 if (!cart[index]) return;
64 const limit = cart[index].stock;
65 const newQty = Math.min(limit, Math.max(0, qty));
66 if (newQty < 1) {
67 cart.splice(index, 1);
68 } else {
69 cart[index].qty = newQty;
70 }
71 save(cart);
72 }
73
74 export function removeItem(index: number) {
75 const cart = getCart();
76 cart.splice(index, 1);
77 save(cart);
78 }
79
80 export function clearCart() {
81 save([]);
82 }
83
84 export function cartCount(): number {
85 return getCart().reduce((sum, it) => sum + it.qty, 0);
86 }
87
88 export function cartSubtotalCents(): number {
89 return getCart().reduce((sum, it) => sum + it.price_cents * it.qty, 0);
90 }
91
92 export function formatLv(cents: number): string {
93 return (cents / 100).toFixed(2) + ' EUR';
94 }
95
96 export function escapeHtml(s: string): string {
97 return s.replace(/[&<>"']/g, c => ({ '&': '&', '<': '<', '>': '>', '"': '"', "'": ''' }[c]!));
98 }
99