- Implemented CartContext for managing cart state, including adding, removing, and updating items. - Added CartDrawer component to display cart items. - Updated AppHeader to include cart item count. - Enhanced welcome page with new layout, improved product image handling, and updated call-to-action buttons. - Refactored homepage data types to accommodate new features and removed unused properties.
103 lines
3.1 KiB
TypeScript
103 lines
3.1 KiB
TypeScript
import { createContext, useCallback, useContext, useEffect, useMemo, useState } from 'react';
|
|
|
|
export type CartItem = {
|
|
productId: number;
|
|
productName: string;
|
|
variantId: number;
|
|
variantName: string;
|
|
priceType: 'retail' | 'wholesale';
|
|
price: number;
|
|
quantity: number;
|
|
imageUrl: string;
|
|
};
|
|
|
|
type CartContextType = {
|
|
items: CartItem[];
|
|
addItem: (item: Omit<CartItem, 'quantity'> & { quantity?: number }) => void;
|
|
removeItem: (variantId: number) => void;
|
|
updateQuantity: (variantId: number, quantity: number) => void;
|
|
clearCart: () => void;
|
|
totalItems: number;
|
|
totalPrice: number;
|
|
};
|
|
|
|
const CartContext = createContext<CartContextType | null>(null);
|
|
|
|
const STORAGE_KEY = 'dst_cart';
|
|
const VISITOR_KEY = 'dst_visitor_id';
|
|
|
|
function getVisitorId(): string {
|
|
let id = localStorage.getItem(VISITOR_KEY);
|
|
if (!id) {
|
|
id = crypto.randomUUID();
|
|
localStorage.setItem(VISITOR_KEY, id);
|
|
}
|
|
return id;
|
|
}
|
|
|
|
function loadCart(): CartItem[] {
|
|
try {
|
|
const raw = localStorage.getItem(`${STORAGE_KEY}_${getVisitorId()}`);
|
|
return raw ? JSON.parse(raw) : [];
|
|
} catch {
|
|
return [];
|
|
}
|
|
}
|
|
|
|
function saveCart(items: CartItem[]): void {
|
|
localStorage.setItem(`${STORAGE_KEY}_${getVisitorId()}`, JSON.stringify(items));
|
|
}
|
|
|
|
export function CartProvider({ children }: { children: React.ReactNode }) {
|
|
const [items, setItems] = useState<CartItem[]>(loadCart);
|
|
|
|
useEffect(() => {
|
|
saveCart(items);
|
|
}, [items]);
|
|
|
|
const addItem = useCallback((item: Omit<CartItem, 'quantity'> & { quantity?: number }) => {
|
|
setItems((prev) => {
|
|
const existing = prev.find((i) => i.variantId === item.variantId);
|
|
if (existing) {
|
|
return prev.map((i) =>
|
|
i.variantId === item.variantId
|
|
? { ...i, quantity: i.quantity + (item.quantity ?? 1) }
|
|
: i,
|
|
);
|
|
}
|
|
return [...prev, { ...item, quantity: item.quantity ?? 1 }];
|
|
});
|
|
}, []);
|
|
|
|
const removeItem = useCallback((variantId: number) => {
|
|
setItems((prev) => prev.filter((i) => i.variantId !== variantId));
|
|
}, []);
|
|
|
|
const updateQuantity = useCallback((variantId: number, quantity: number) => {
|
|
if (quantity <= 0) {
|
|
setItems((prev) => prev.filter((i) => i.variantId !== variantId));
|
|
return;
|
|
}
|
|
setItems((prev) => prev.map((i) => (i.variantId === variantId ? { ...i, quantity } : i)));
|
|
}, []);
|
|
|
|
const clearCart = useCallback(() => {
|
|
setItems([]);
|
|
}, []);
|
|
|
|
const totalItems = useMemo(() => items.reduce((sum, i) => sum + i.quantity, 0), [items]);
|
|
const totalPrice = useMemo(() => items.reduce((sum, i) => sum + i.price * i.quantity, 0), [items]);
|
|
|
|
return (
|
|
<CartContext.Provider value={{ items, addItem, removeItem, updateQuantity, clearCart, totalItems, totalPrice }}>
|
|
{children}
|
|
</CartContext.Provider>
|
|
);
|
|
}
|
|
|
|
export function useCart(): CartContextType {
|
|
const ctx = useContext(CartContext);
|
|
if (!ctx) throw new Error('useCart must be used within CartProvider');
|
|
return ctx;
|
|
}
|