From d05c981daee8e450399268dcce5129f58c6f08f3 Mon Sep 17 00:00:00 2001 From: notshop Date: Sun, 26 Apr 2026 16:35:02 +0000 Subject: [PATCH] chore: add client/src/hooks/use-cart.tsx --- client/src/hooks/use-cart.tsx | 91 +++++++++++++++++++++++++++++++++++ 1 file changed, 91 insertions(+) create mode 100644 client/src/hooks/use-cart.tsx diff --git a/client/src/hooks/use-cart.tsx b/client/src/hooks/use-cart.tsx new file mode 100644 index 0000000..45dd261 --- /dev/null +++ b/client/src/hooks/use-cart.tsx @@ -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; + updateQuantity: (itemId: number, quantity: number) => Promise; + removeItem: (itemId: number) => Promise; + itemCount: number; + subtotal: number; + isLoading: boolean; +} + +const CartContext = createContext(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 ( + setIsOpen(true), + closeCart: () => setIsOpen(false), + toggleCart: () => setIsOpen((v) => !v), + addItem, + updateQuantity, + removeItem, + itemCount, + subtotal, + isLoading, + }}> + {children} + + ); +} + +export function useCart() { + const ctx = useContext(CartContext); + if (!ctx) throw new Error("useCart must be used within CartProvider"); + return ctx; +}