From c6fe65ad990d39cd2bd8311a89102bbfd40b0db8 Mon Sep 17 00:00:00 2001 From: Marco Date: Thu, 30 Jul 2026 17:13:54 +0000 Subject: [PATCH] Fix cart quantities doubling on every logout/login and a crash on /cart MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Root cause 1 (count doubling): LogoutButton never cleared the local cart, and mergeServerCartIntoLocal() (called on login) adds server quantities into the existing local ones rather than replacing — since CartSync mirrors local to the server continuously, local and server already held the same quantities at logout time, so every login added them together, doubling the count each cycle. Fix: clear the local cart on logout, so the next login's merge starts from empty (or only genuine guest-session additions) instead of re-adding already-synced quantities. Root cause 2 (page couldn't load): readCart()/getCart() only guarded against JSON.parse syntax errors, not against the parsed value being a non-array — once localStorage held a corrupted (inflated/malformed) value, CartContent.tsx's cart.map() threw uncaught during render. Fix: validate Array.isArray() after parsing, falling back to []. Co-Authored-By: Claude Sonnet 5 --- app/konto/components/LogoutButton.tsx | 8 ++++++++ app/lib/cart.ts | 17 ++++++++++++----- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/app/konto/components/LogoutButton.tsx b/app/konto/components/LogoutButton.tsx index ad5f88d..28985f5 100644 --- a/app/konto/components/LogoutButton.tsx +++ b/app/konto/components/LogoutButton.tsx @@ -2,12 +2,20 @@ import { useRouter } from "next/navigation"; import { dispatchAuthChanged } from "../../lib/auth"; +import { clearCart } from "../../lib/cart"; export function LogoutButton() { const router = useRouter(); async function handleLogout() { await fetch("/api/account/logout", { method: "POST" }); + // The local cart is already mirrored server-side by CartSync, so it's + // safe to clear it here — the next login's mergeServerCartIntoLocal() + // restores it from the server. Without this, the local cart survived + // logout untouched, and mergeServerCartIntoLocal()'s additive merge + // (existing.qty += qty) would add the already-synced server quantities + // on top of it on every login, doubling every logout/login cycle. + clearCart(); dispatchAuthChanged(); router.push("/"); router.refresh(); diff --git a/app/lib/cart.ts b/app/lib/cart.ts index 519189b..18ffcce 100644 --- a/app/lib/cart.ts +++ b/app/lib/cart.ts @@ -18,7 +18,8 @@ function readCart(): CartItem[] { if (typeof window === "undefined") return []; try { const raw = window.localStorage.getItem(CART_KEY); - return raw ? JSON.parse(raw) : []; + const parsed = raw ? JSON.parse(raw) : []; + return Array.isArray(parsed) ? parsed : []; } catch { return []; } @@ -90,7 +91,8 @@ export function getCart(): CartItem[] { if (raw === cachedRaw) return cachedItems; cachedRaw = raw; try { - cachedItems = raw ? JSON.parse(raw) : EMPTY_CART; + const parsed = raw ? JSON.parse(raw) : EMPTY_CART; + cachedItems = Array.isArray(parsed) ? parsed : EMPTY_CART; } catch { cachedItems = EMPTY_CART; } @@ -125,9 +127,14 @@ export function useCart(): CartItem[] { // Called right after a successful login (LoginForm.tsx, CheckoutContent.tsx's // inline login toggle) — folds whatever was saved server-side into the // local cart by quantity (addToCart adds to an existing line rather than -// overwriting it), so items added before logging in aren't lost. CartSync -// then picks up the resulting change and pushes the merged cart back to -// the server on its own, closing the loop without a separate save call here. +// overwriting it), so items added as a guest before logging in aren't lost. +// This only stays correct because LogoutButton.tsx clears the local cart on +// logout — the local cart is always either empty (no guest additions since +// the last logout) or holds only genuinely new guest-session items, never a +// stale copy of what's already in the server cart, so this add never +// double-counts. CartSync then picks up the resulting change and pushes the +// merged cart back to the server on its own, closing the loop without a +// separate save call here. export async function mergeServerCartIntoLocal(): Promise { try { const res = await fetch("/api/account/cart");