diff --git a/packages/nextjs/components/CartContext.tsx b/packages/nextjs/components/CartContext.tsx index 95789727..1a06fd89 100644 --- a/packages/nextjs/components/CartContext.tsx +++ b/packages/nextjs/components/CartContext.tsx @@ -1,56 +1,9 @@ import * as React from "react"; import { q } from "groqd"; -import { useReducer } from "react"; +import { CartItem, CartUpdate, CartProvider as SharedCartProvider } from "shared-ui"; import { runQuery } from "utils/sanityClient"; -export type CartItem = { - _id: string; - qty: number; - variantInfo: CartItemVariant; -}; - -const initialValue = { - isFetchingCartItems: false, - cartItems: [] as CartItem[], - cartItemsErrorIds: [] as string[] | undefined, - cartTotal: 0, - totalCartPrice: 0, - updateCart: (() => null) as (productId: string, quantity: number) => void, - clearCart: (() => null) as () => void, - updateCartFromApi: (() => null) as () => void, -}; - -type CartContextValue = typeof initialValue; - -export const CartContext = React.createContext(initialValue); -export const useCart = () => React.useContext(CartContext); - -type State = { - cartItems: CartItem[]; - cartItemsErrorIds: string[]; - fetching: boolean; -}; - -type Action = - | { type: "loading" } - | { type: "success"; cartItems: CartItem[]; cartItemsErrorIds: string[] } - | { type: "clear" }; - -const defaultState: State = { cartItems: [], cartItemsErrorIds: [], fetching: false }; - -const cartReducer = (state: State, action: Action): State => { - switch (action.type) { - case "loading": - return { ...state, cartItemsErrorIds: [], fetching: true }; - case "success": - return { ...state, cartItems: action.cartItems, cartItemsErrorIds: action.cartItemsErrorIds, fetching: false }; - case "clear": - return defaultState; - } -}; - export const CartProvider = ({ children }: React.PropsWithChildren) => { - const [{ cartItems, cartItemsErrorIds, fetching }, dispatch] = useReducer(cartReducer, defaultState); const retrieveCartItems = React.useCallback(async (cart: Record) => { const cartEntries = Object.entries(cart); @@ -72,20 +25,28 @@ export const CartProvider = ({ children }: React.PropsWithChildren) => { const res = await throttle(getResults, 1000); const errorRetrievingIds: string[] = []; - const formattedItems = cartEntries.reduce((acc, [variantId, quantity]) => { + const results = cartEntries.reduce((acc, [variantId, quantity]) => { const productInfo = res.find((variant) => variant._id === variantId); if (productInfo) { - return [...acc, { _id: variantId, qty: quantity, variantInfo: productInfo }]; + return [ + ...acc, + { + _id: variantId, + quantity, + name: productInfo.name, + price: productInfo.price, + }, + ]; } errorRetrievingIds.push(variantId); return acc; }, []); - dispatch({ type: "success", cartItems: formattedItems, cartItemsErrorIds: errorRetrievingIds }); - } else { - dispatch({ type: "clear" }); + return { results, errors: errorRetrievingIds }; } + + return { results: [], errors: [] }; }, []); const updateCartFromApi = React.useCallback(async () => { @@ -93,15 +54,11 @@ export const CartProvider = ({ children }: React.PropsWithChildren) => { credentials: "same-origin", }).then((res) => res.json()); - retrieveCartItems(data); + return retrieveCartItems(data); }, [retrieveCartItems]); - React.useEffect(() => { - updateCartFromApi(); - }, [updateCartFromApi]); - const updateCart = React.useCallback( - async (variantId: string, quantity: number) => { + async ({ _id: variantId, quantity }: CartUpdate) => { if (!variantId) { return; } @@ -135,36 +92,15 @@ export const CartProvider = ({ children }: React.PropsWithChildren) => { "Content-Type": "application/json", }, }).then((res) => res.json()); - - dispatch({ type: "clear" }); } catch (error) { console.error(error); } }; - // Calculates the total quantity of all items in the cart - const cartTotal = React.useMemo(() => cartItems.reduce((acc, { qty }) => acc + qty, 0), [cartItems]); - - const totalCartPrice = React.useMemo( - () => cartItems.reduce((acc, { qty, variantInfo }) => acc + qty * variantInfo.price, 0), - [cartItems] - ); - return ( - + {children} - + ); }; @@ -172,10 +108,3 @@ const wait = (ms: number) => new Promise((res) => setTimeout(res, ms)); const throttle = (action: () => Promise, ms: number): Promise => Promise.all([action(), wait(ms)]).then((res) => res[0]); - -type CartItemVariant = { - _id: string; - name: string; - msrp: number; - price: number; -}; diff --git a/packages/nextjs/components/Header/Header.tsx b/packages/nextjs/components/Header/Header.tsx index 50f6f6ca..c1a2a358 100644 --- a/packages/nextjs/components/Header/Header.tsx +++ b/packages/nextjs/components/Header/Header.tsx @@ -1,9 +1,8 @@ import * as React from "react"; import Link from "next/link"; import classNames from "classnames"; -import { useCart } from "components/CartContext"; +import { Cart, CartContent, Button, useCart } from "shared-ui"; import { Search } from "components/Search"; -import { Cart } from "./Cart"; import { MobileHeaderItems } from "./MobileHeaderItems"; import { NAV_ITEMS } from "./NavItems"; import { MobileNavMenu } from "./MobileNavMenu"; @@ -12,9 +11,7 @@ import { Logo } from "./Logo"; export const Header = () => { const [isNavOpen, setIsNavOpen] = React.useState(false); - - const [isCartOpen, setIsCartOpen] = React.useState(false); - const { cartTotal, isFetchingCartItems } = useCart(); + const { toggleCartOpen } = useCart(); const onMobileNavClick = () => setIsNavOpen((prev) => !prev); const onMobileNavClose = () => setIsNavOpen(false); @@ -23,10 +20,6 @@ export const Header = () => { document.body.classList[isNavOpen ? "add" : "remove"]("overflow-hidden"); }, [isNavOpen]); - React.useEffect(() => { - document.body.classList[isCartOpen ? "add" : "remove"]("overflow-hidden", "sm:overflow-auto"); - }, [isCartOpen]); - return (