"use client"; import { useSyncExternalStore } from "react"; const CART_KEY = "ep_cart"; const CART_EVENT = "ep-cart-updated"; const EMPTY_CART: CartItem[] = []; export type CartItem = { id: string; qty: number }; function readCart(): CartItem[] { if (typeof window === "undefined") return []; try { const raw = window.localStorage.getItem(CART_KEY); return raw ? JSON.parse(raw) : []; } catch { return []; } } function writeCart(items: CartItem[]) { window.localStorage.setItem(CART_KEY, JSON.stringify(items)); window.dispatchEvent(new Event(CART_EVENT)); } export function getCartCount(): number { return readCart().reduce((sum, item) => sum + item.qty, 0); } export function addToCart(id: string, qty = 1) { const items = readCart(); const existing = items.find((i) => i.id === id); if (existing) existing.qty += qty; else items.push({ id, qty }); writeCart(items); } export function removeFromCart(id: string) { writeCart(readCart().filter((i) => i.id !== id)); } // Called by /bestellbestaetigung once it has captured a snapshot of the // cart to display — there's no real order backend here, so "placing an // order" just means the local cart empties out the same way it would // after a real purchase completes. export function clearCart() { writeCart([]); } // qty <= 0 removes the item outright — the cart page's quantity stepper // never lets the visible count go below 1, but this keeps the function // itself safe to call with any integer without a separate remove path. export function setQuantity(id: string, qty: number) { if (qty <= 0) { removeFromCart(id); return; } const items = readCart(); const existing = items.find((i) => i.id === id); if (existing) existing.qty = qty; writeCart(items); } // Cached-by-raw-string snapshot, not a fresh JSON.parse() every call — // useSyncExternalStore (below) requires getSnapshot to return the SAME // reference when the underlying data hasn't actually changed, or React // treats every render as a change. readCart()'s plain JSON.parse would // allocate a new array each call and break that. let cachedRaw: string | null | undefined; let cachedItems: CartItem[] = EMPTY_CART; export function getCart(): CartItem[] { if (typeof window === "undefined") return EMPTY_CART; const raw = window.localStorage.getItem(CART_KEY); if (raw === cachedRaw) return cachedItems; cachedRaw = raw; try { cachedItems = raw ? JSON.parse(raw) : EMPTY_CART; } catch { cachedItems = EMPTY_CART; } return cachedItems; } function subscribe(onStoreChange: () => void) { window.addEventListener(CART_EVENT, onStoreChange); window.addEventListener("storage", onStoreChange); return () => { window.removeEventListener(CART_EVENT, onStoreChange); window.removeEventListener("storage", onStoreChange); }; } // useSyncExternalStore, not useState+useEffect — localStorage is an // external store outside React, and the previous approach (setState // synchronously inside an effect body) causes an extra render and trips // the react-hooks/set-state-in-effect lint rule. This is React's own // recommended pattern for subscribing to exactly this kind of external // store, and is correctly SSR-safe via the third (server snapshot) // argument — 0 / EMPTY_CART until the client hydrates and reads // localStorage for real. export function useCartCount(): number { return useSyncExternalStore(subscribe, getCartCount, () => 0); } export function useCart(): CartItem[] { return useSyncExternalStore(subscribe, getCart, () => EMPTY_CART); }