chore: add client/src/hooks/use-cart.tsx
This commit is contained in:
parent
12b68031e1
commit
d05c981dae
1 changed files with 91 additions and 0 deletions
91
client/src/hooks/use-cart.tsx
Normal file
91
client/src/hooks/use-cart.tsx
Normal file
|
|
@ -0,0 +1,91 @@
|
||||||
|
import { createContext, useContext, useState, useCallback } from "react";
|
||||||
|
import { useQuery, useMutation } from "@tanstack/react-query";
|
||||||
|
import { queryClient, apiRequest } from "../lib/queryClient";
|
||||||
|
|
||||||
|
interface CartItem {
|
||||||
|
id: number;
|
||||||
|
cartId: number;
|
||||||
|
productId: number;
|
||||||
|
variantId?: number;
|
||||||
|
title: string;
|
||||||
|
variantTitle?: string;
|
||||||
|
price: string;
|
||||||
|
quantity: number;
|
||||||
|
imageUrl?: string;
|
||||||
|
productHandle?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
interface CartContextType {
|
||||||
|
items: CartItem[];
|
||||||
|
isOpen: boolean;
|
||||||
|
openCart: () => void;
|
||||||
|
closeCart: () => void;
|
||||||
|
toggleCart: () => void;
|
||||||
|
addItem: (productId: number, variantId?: number, quantity?: number) => Promise<void>;
|
||||||
|
updateQuantity: (itemId: number, quantity: number) => Promise<void>;
|
||||||
|
removeItem: (itemId: number) => Promise<void>;
|
||||||
|
itemCount: number;
|
||||||
|
subtotal: number;
|
||||||
|
isLoading: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CartContext = createContext<CartContextType | null>(null);
|
||||||
|
|
||||||
|
export function CartProvider({ children }: { children: React.ReactNode }) {
|
||||||
|
const [isOpen, setIsOpen] = useState(false);
|
||||||
|
|
||||||
|
const { data, isLoading } = useQuery<{ cart: any; items: CartItem[] }>({
|
||||||
|
queryKey: ["/api/cart"],
|
||||||
|
queryFn: async () => {
|
||||||
|
const res = await fetch("/api/cart", { credentials: "include" });
|
||||||
|
return res.json();
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const items = data?.items || [];
|
||||||
|
|
||||||
|
const invalidateCart = () => queryClient.invalidateQueries({ queryKey: ["/api/cart"] });
|
||||||
|
|
||||||
|
const addItem = useCallback(async (productId: number, variantId?: number, quantity = 1) => {
|
||||||
|
await apiRequest("POST", "/api/cart/items", { productId, variantId, quantity });
|
||||||
|
await invalidateCart();
|
||||||
|
setIsOpen(true);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const updateQuantity = useCallback(async (itemId: number, quantity: number) => {
|
||||||
|
await apiRequest("PATCH", `/api/cart/items/${itemId}`, { quantity });
|
||||||
|
await invalidateCart();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const removeItem = useCallback(async (itemId: number) => {
|
||||||
|
await apiRequest("DELETE", `/api/cart/items/${itemId}`);
|
||||||
|
await invalidateCart();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const itemCount = items.reduce((sum, i) => sum + i.quantity, 0);
|
||||||
|
const subtotal = items.reduce((sum, i) => sum + parseFloat(i.price) * i.quantity, 0);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<CartContext.Provider value={{
|
||||||
|
items,
|
||||||
|
isOpen,
|
||||||
|
openCart: () => setIsOpen(true),
|
||||||
|
closeCart: () => setIsOpen(false),
|
||||||
|
toggleCart: () => setIsOpen((v) => !v),
|
||||||
|
addItem,
|
||||||
|
updateQuantity,
|
||||||
|
removeItem,
|
||||||
|
itemCount,
|
||||||
|
subtotal,
|
||||||
|
isLoading,
|
||||||
|
}}>
|
||||||
|
{children}
|
||||||
|
</CartContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useCart() {
|
||||||
|
const ctx = useContext(CartContext);
|
||||||
|
if (!ctx) throw new Error("useCart must be used within CartProvider");
|
||||||
|
return ctx;
|
||||||
|
}
|
||||||
Loading…
Add table
Reference in a new issue