Fix cart quantities doubling on every logout/login and a crash on /cart

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 <noreply@anthropic.com>
This commit is contained in:
Marco
2026-07-30 17:13:54 +00:00
parent 73b4b06eae
commit c6fe65ad99
2 changed files with 20 additions and 5 deletions
+12 -5
View File
@@ -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<void> {
try {
const res = await fetch("/api/account/cart");