"use client"; import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState, type ReactNode, } from "react"; import { useAuth } from "@/lib/auth"; import type { CartItem, CartSnapshot } from "@/lib/cart-types"; import { normalizeCart } from "@/lib/cart-types"; import { clearGuestCart, readGuestCart, writeGuestCart, } from "@/lib/guest-cart"; type ToastState = { id: number; message: string; } | null; export type AddCartItemInput = { storeItemVariantId: string; quantity?: number; slug?: string; name?: string; image?: string | null; price?: number; optionLabel?: string; }; type CartContextValue = { items: CartItem[]; itemCount: number; subtotal: number; total: number; ready: boolean; loading: boolean; addItem: (input: AddCartItemInput) => Promise; removeItem: (itemId: string) => Promise; setQuantity: (itemId: string, quantity: number) => Promise; refresh: () => Promise; toast: ToastState; showToast: (message: string) => void; dismissToast: () => void; }; const CartContext = createContext(null); function emptySnapshot(): CartSnapshot { return { items: [], subtotal: 0, total: 0 }; } function snapshotFromItems(items: CartItem[]): CartSnapshot { const subtotal = items.reduce((sum, item) => sum + item.price * item.quantity, 0); return { items, subtotal, total: subtotal }; } async function cartRequest( path: string, token: string, init?: RequestInit, ): Promise { const res = await fetch(path, { ...init, headers: { "Content-Type": "application/json", Authorization: `Bearer ${token}`, ...(init?.headers as Record | undefined), }, }); const data = await res.json().catch(() => null); if (!res.ok) { throw new Error( (data as { error?: string } | null)?.error ?? "خطا در ارتباط با سبد خرید", ); } return normalizeCart(data); } export function CartProvider({ children }: { children: ReactNode }) { const { isAuthenticated, ready: authReady, getAccessToken } = useAuth(); const [snapshot, setSnapshot] = useState(emptySnapshot()); const [ready, setReady] = useState(false); const [loading, setLoading] = useState(false); const [toast, setToast] = useState(null); const syncedGuestRef = useRef(false); const showToast = useCallback((message: string) => { setToast({ id: Date.now(), message }); }, []); const dismissToast = useCallback(() => setToast(null), []); useEffect(() => { if (!toast) return; const timer = window.setTimeout(() => setToast(null), 2800); return () => window.clearTimeout(timer); }, [toast]); const refresh = useCallback(async () => { const token = getAccessToken(); if (!token) { setSnapshot(snapshotFromItems(readGuestCart())); return; } setLoading(true); try { const next = await cartRequest("/api/cart", token); setSnapshot(next); } catch { setSnapshot(emptySnapshot()); } finally { setLoading(false); } }, [getAccessToken]); // Load guest cart first; after login, push guest lines into the API cart once. useEffect(() => { if (!authReady) return; const bootstrap = async () => { const token = getAccessToken(); if (!token) { syncedGuestRef.current = false; setSnapshot(snapshotFromItems(readGuestCart())); setReady(true); return; } const guestItems = readGuestCart(); if (guestItems.length > 0 && !syncedGuestRef.current) { syncedGuestRef.current = true; setLoading(true); try { for (const item of guestItems) { await cartRequest("/api/cart/items", token, { method: "POST", body: JSON.stringify({ storeItemVariantId: item.storeItemVariantId, quantity: item.quantity, }), }); } clearGuestCart(); } catch { // keep guest cart if sync fails syncedGuestRef.current = false; } finally { setLoading(false); } } await refresh(); setReady(true); }; void bootstrap(); }, [authReady, isAuthenticated, getAccessToken, refresh]); const addItem = useCallback( async (input: AddCartItemInput) => { const quantity = Math.max(1, input.quantity ?? 1); const token = getAccessToken(); if (token) { setLoading(true); try { const next = await cartRequest("/api/cart/items", token, { method: "POST", body: JSON.stringify({ storeItemVariantId: input.storeItemVariantId, quantity, }), }); setSnapshot(next); showToast("به سبد خرید اضافه شد"); return true; } finally { setLoading(false); } } const current = readGuestCart(); const existing = current.find( (item) => item.storeItemVariantId === input.storeItemVariantId, ); const nextItems = existing ? current.map((item) => item.storeItemVariantId === input.storeItemVariantId ? { ...item, quantity: item.quantity + quantity, price: input.price ?? item.price, name: input.name ?? item.name, image: input.image ?? item.image, optionLabel: input.optionLabel ?? item.optionLabel, } : item, ) : [ ...current, { id: input.storeItemVariantId, storeItemVariantId: input.storeItemVariantId, slug: input.slug ?? "", name: input.name ?? "", image: input.image ?? null, price: input.price ?? 0, quantity, optionLabel: input.optionLabel, }, ]; writeGuestCart(nextItems); setSnapshot(snapshotFromItems(nextItems)); showToast("به سبد خرید اضافه شد"); return true; }, [getAccessToken, showToast], ); const removeItem = useCallback( async (itemId: string) => { const token = getAccessToken(); if (token) { setLoading(true); try { const next = await cartRequest(`/api/cart/items/${itemId}`, token, { method: "DELETE", }); setSnapshot(next); } finally { setLoading(false); } return; } const nextItems = readGuestCart().filter( (item) => item.id !== itemId && item.storeItemVariantId !== itemId, ); writeGuestCart(nextItems); setSnapshot(snapshotFromItems(nextItems)); }, [getAccessToken], ); const setQuantity = useCallback( async (itemId: string, quantity: number) => { const token = getAccessToken(); if (token) { setLoading(true); try { const next = quantity < 1 ? await cartRequest(`/api/cart/items/${itemId}`, token, { method: "DELETE", }) : await cartRequest(`/api/cart/items/${itemId}`, token, { method: "PATCH", body: JSON.stringify({ quantity }), }); setSnapshot(next); } finally { setLoading(false); } return; } if (quantity < 1) { const nextItems = readGuestCart().filter( (item) => item.id !== itemId && item.storeItemVariantId !== itemId, ); writeGuestCart(nextItems); setSnapshot(snapshotFromItems(nextItems)); return; } const nextItems = readGuestCart().map((item) => item.id === itemId || item.storeItemVariantId === itemId ? { ...item, quantity } : item, ); writeGuestCart(nextItems); setSnapshot(snapshotFromItems(nextItems)); }, [getAccessToken], ); const itemCount = useMemo( () => snapshot.items.reduce((sum, item) => sum + item.quantity, 0), [snapshot.items], ); const value = useMemo( () => ({ items: snapshot.items, itemCount, subtotal: snapshot.subtotal, total: snapshot.total, ready, loading, addItem, removeItem, setQuantity, refresh, toast, showToast, dismissToast, }), [ snapshot, itemCount, ready, loading, addItem, removeItem, setQuantity, refresh, toast, showToast, dismissToast, ], ); return {children}; } export function useCart() { const ctx = useContext(CartContext); if (!ctx) throw new Error("useCart must be used within CartProvider"); return ctx; }