From 6acaa2b8fd7704543c45c4d4e9091f7431a9b592 Mon Sep 17 00:00:00 2001 From: Yinka Adedire Date: Sun, 23 Oct 2022 04:25:57 +0100 Subject: [PATCH] implement smooth scroll and cart context --- storefront/context/cart-context.tsx | 143 +++++++++++++++++++++++++++ storefront/context/lenis-context.tsx | 47 +++++++++ storefront/pages/_app.tsx | 22 ++++- 3 files changed, 210 insertions(+), 2 deletions(-) create mode 100644 storefront/context/cart-context.tsx create mode 100644 storefront/context/lenis-context.tsx diff --git a/storefront/context/cart-context.tsx b/storefront/context/cart-context.tsx new file mode 100644 index 0000000..c1c494c --- /dev/null +++ b/storefront/context/cart-context.tsx @@ -0,0 +1,143 @@ +import { Cart } from '@medusajs/medusa'; +import { medusaClient } from '@/lib/medusa-client'; +import { createContext, useContext, useEffect, useState } from 'react'; + +type CartState = Omit | null; + +const initialCartState: CartState = null; + +interface CartContext { + cart: CartState; + setCart: React.Dispatch>; +} + +const CartContext = createContext(null); + +interface CartProviderProps { + children: React.ReactNode; +} + +const CART_ID = 'cart_id'; +const isBrowser = typeof window !== 'undefined'; + +const cartId = isBrowser ? localStorage.getItem(CART_ID) : null; + +export const CartProvider = ({ children }: CartProviderProps) => { + const [cartIsInit, setCartIsInit] = useState(false); + const [cart, setCart] = useState(initialCartState); + + useEffect(() => { + if (cartIsInit) return; + + const initializeCart = async () => { + const cartId = isBrowser && window.localStorage.getItem(CART_ID); + + const setCartId = (cartId: string) => { + if (isBrowser) { + window.localStorage.setItem(CART_ID, cartId); + } + }; + + const DEFAULT_REGION_ID = await ( + await medusaClient.regions.list() + ).regions[0].id; + + if (!cartId) { + const { cart } = await medusaClient.carts.create({ + region_id: DEFAULT_REGION_ID, + }); + + if (!cart || cart.completed_at) { + setCartId(''); + setCart(initialCartState); + } + setCartId(cart.id); + setCart(cart); + } + + if (cartId) { + const { cart } = await medusaClient.carts.retrieve(cartId); + setCart((prev) => ({ ...prev, ...cart })); + } + }; + + initializeCart(); + setCartIsInit(true); + }, [cart, setCart, cartIsInit, setCartIsInit]); + + return ( + + {children} + + ); +}; + +interface AddItemParams { + variantId: string; + quantity: number; +} + +interface UpdateItemParams { + lineId: string; + quantity: number; +} + +export const useCart = () => { + const context = useContext(CartContext); + + if (context === undefined || context === null) { + throw new Error('useCart must be used within a CartProvider'); + } + + const { cart, setCart } = context; + + const addItem = async ({ variantId, quantity }: AddItemParams) => { + if (cartId) { + const { cart } = await medusaClient.carts.lineItems.create(cartId, { + variant_id: variantId, + quantity, + }); + setCart(cart); + } + }; + + const updateItem = async ({ lineId, quantity }: UpdateItemParams) => { + if (cartId) { + const { cart } = await medusaClient.carts.lineItems.update( + cartId, + lineId, + { + quantity, + }, + ); + setCart(cart); + } + }; + + const removeItem = async (lineId: string) => { + if (cartId) { + const { cart } = await medusaClient.carts.lineItems.delete( + cartId, + lineId, + ); + setCart(cart); + } + }; + + const totalCartItems = + cart?.items.reduce((acc, item) => acc + item.quantity, 0) || 0; + + const emptyCart = async () => { + if (cartId) { + } + }; + + return { + cart, + addItem, + updateItem, + removeItem, + emptyCart, + totalCartItems, + }; +}; diff --git a/storefront/context/lenis-context.tsx b/storefront/context/lenis-context.tsx new file mode 100644 index 0000000..2930e76 --- /dev/null +++ b/storefront/context/lenis-context.tsx @@ -0,0 +1,47 @@ +import Lenis from '@studio-freight/lenis'; +import { useContext, createContext, useState } from 'react'; +import { useAnimationFrame } from 'framer-motion'; +import { useIsomorphicLayoutEffect } from '@/hooks/use-layout-effect'; + +const LenisContext = createContext(null); + +interface ProviderProps { + children: React.ReactNode; +} + +export const LenisProvider = ({ children }: ProviderProps) => { + const [lenis, setLenis] = useState(null); + + useIsomorphicLayoutEffect(() => { + const lenis = new Lenis({ + smooth: true, + duration: 1.3, + touchMultiplier: 1.3, + direction: 'vertical', + }); + + setLenis(lenis); + + return () => { + lenis.destroy(); + }; + }, []); + + useAnimationFrame((time) => { + lenis?.raf(time); + }); + + return ( + {children} + ); +}; + +export const useLenis = () => { + const lenis = useContext(LenisContext); + + if (lenis === undefined) { + throw new Error('useLenis must be used within a LenisProvider'); + } + + return lenis; +}; diff --git a/storefront/pages/_app.tsx b/storefront/pages/_app.tsx index a6c0247..2cd653c 100644 --- a/storefront/pages/_app.tsx +++ b/storefront/pages/_app.tsx @@ -1,8 +1,26 @@ import type { AppProps } from 'next/app'; import '@/styles/fonts.css'; -import '@/styles/tailwind.css'; import '@/styles/global.css'; +import '@/styles/tailwind.css'; + +import { Fragment } from 'react'; +import { DefaultSeo } from 'next-seo'; +import { CartProvider } from '@/context/cart-context'; +import { LenisProvider } from '@/context/lenis-context'; + +import { AnimatePresence } from 'framer-motion'; export default function MyApp({ Component, pageProps }: AppProps) { - return ; + return ( + + + + + + + + + + + ); }