Skip to content

Commit

Permalink
implement smooth scroll and cart context
Browse files Browse the repository at this point in the history
  • Loading branch information
yinkakun committed Oct 23, 2022
1 parent 56876be commit 6acaa2b
Show file tree
Hide file tree
Showing 3 changed files with 210 additions and 2 deletions.
143 changes: 143 additions & 0 deletions storefront/context/cart-context.tsx
Original file line number Diff line number Diff line change
@@ -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<Cart, 'refundable_amount' | 'refunded_total'> | null;

const initialCartState: CartState = null;

interface CartContext {
cart: CartState;
setCart: React.Dispatch<React.SetStateAction<CartState>>;
}

const CartContext = createContext<CartContext | null>(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<CartState>(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 (
<CartContext.Provider value={{ cart, setCart }}>
{children}
</CartContext.Provider>
);
};

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,
};
};
47 changes: 47 additions & 0 deletions storefront/context/lenis-context.tsx
Original file line number Diff line number Diff line change
@@ -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<Lenis | null>(null);

interface ProviderProps {
children: React.ReactNode;
}

export const LenisProvider = ({ children }: ProviderProps) => {
const [lenis, setLenis] = useState<Lenis | null>(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 (
<LenisContext.Provider value={lenis}>{children}</LenisContext.Provider>
);
};

export const useLenis = () => {
const lenis = useContext(LenisContext);

if (lenis === undefined) {
throw new Error('useLenis must be used within a LenisProvider');
}

return lenis;
};
22 changes: 20 additions & 2 deletions storefront/pages/_app.tsx
Original file line number Diff line number Diff line change
@@ -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 <Component {...pageProps} />;
return (
<Fragment>
<DefaultSeo titleTemplate="%s | Monster" defaultTitle="Monster" />
<LenisProvider>
<CartProvider>
<AnimatePresence mode="wait">
<Component {...pageProps} />
</AnimatePresence>
</CartProvider>
</LenisProvider>
</Fragment>
);
}

0 comments on commit 6acaa2b

Please sign in to comment.