84 lines
2.8 KiB
TypeScript
84 lines
2.8 KiB
TypeScript
'use client';
|
|
|
|
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
|
|
export type CartItem = { productId: string; slug: string; name: string; variant?: string; price: number; quantity: number };
|
|
|
|
type CartContextValue = {
|
|
items: CartItem[];
|
|
count: number;
|
|
total: number;
|
|
add: (item: Omit<CartItem, 'quantity'>, quantity?: number) => void;
|
|
setQuantity: (productId: string, variant: string | undefined, quantity: number) => void;
|
|
remove: (productId: string, variant: string | undefined) => void;
|
|
clear: () => void;
|
|
};
|
|
|
|
const CartContext = createContext<CartContextValue | null>(null);
|
|
const STORAGE_KEY = 'w4c_cart';
|
|
|
|
export function CartProvider({ children }: { children: React.ReactNode }) {
|
|
const [items, setItems] = useState<CartItem[]>([]);
|
|
const [ready, setReady] = useState(false);
|
|
|
|
useEffect(() => {
|
|
try {
|
|
const raw = localStorage.getItem(STORAGE_KEY);
|
|
if (raw) setItems(JSON.parse(raw) as CartItem[]);
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
setReady(true);
|
|
}, []);
|
|
|
|
useEffect(() => {
|
|
if (ready) {
|
|
try {
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(items));
|
|
} catch {
|
|
/* ignore */
|
|
}
|
|
}
|
|
}, [items, ready]);
|
|
|
|
const add = useCallback((item: Omit<CartItem, 'quantity'>, quantity = 1) => {
|
|
setItems((prev) => {
|
|
const idx = prev.findIndex((i) => i.productId === item.productId && i.variant === item.variant);
|
|
if (idx >= 0) {
|
|
const next = [...prev];
|
|
next[idx] = { ...next[idx], quantity: Math.min(99, next[idx].quantity + quantity) };
|
|
return next;
|
|
}
|
|
return [...prev, { ...item, quantity }];
|
|
});
|
|
}, []);
|
|
|
|
const setQuantity = useCallback((productId: string, variant: string | undefined, quantity: number) => {
|
|
setItems((prev) =>
|
|
quantity <= 0
|
|
? prev.filter((i) => !(i.productId === productId && i.variant === variant))
|
|
: prev.map((i) => (i.productId === productId && i.variant === variant ? { ...i, quantity: Math.min(99, quantity) } : i)),
|
|
);
|
|
}, []);
|
|
|
|
const remove = useCallback((productId: string, variant: string | undefined) => {
|
|
setItems((prev) => prev.filter((i) => !(i.productId === productId && i.variant === variant)));
|
|
}, []);
|
|
|
|
const clear = useCallback(() => setItems([]), []);
|
|
|
|
const value = useMemo<CartContextValue>(() => {
|
|
const count = items.reduce((n, i) => n + i.quantity, 0);
|
|
const total = items.reduce((n, i) => n + i.price * i.quantity, 0);
|
|
return { items, count, total, add, setQuantity, remove, clear };
|
|
}, [items, add, setQuantity, remove, clear]);
|
|
|
|
return <CartContext.Provider value={value}>{children}</CartContext.Provider>;
|
|
}
|
|
|
|
export function useCart(): CartContextValue {
|
|
const ctx = useContext(CartContext);
|
|
if (!ctx) throw new Error('useCart must be used within a CartProvider');
|
|
return ctx;
|
|
}
|