Skip to content

Commit

Permalink
implement storefront pages
Browse files Browse the repository at this point in the history
  • Loading branch information
yinkakun committed Oct 23, 2022
1 parent 293ea77 commit 7dbd8e4
Show file tree
Hide file tree
Showing 26 changed files with 1,285 additions and 3 deletions.
17 changes: 17 additions & 0 deletions storefront/components/animations/fade-in.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { motion } from 'framer-motion';

interface FadeInProps {
children: React.ReactNode;
}

export const FadeIn = ({ children }: FadeInProps) => {
return (
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ duration: 1, ease: 'easeIn' }}
>
{children}
</motion.div>
);
};
44 changes: 44 additions & 0 deletions storefront/components/animations/slide-in-characters.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { motion } from 'framer-motion';
import { Fragment } from 'react';

interface SlideInCharacters {
text: string;
}

export const SlideInCharacters = ({ text }: SlideInCharacters) => {
const characters = text.split('');

return (
<Fragment>
{characters.map((character, index) => {
if (character === ' ') {
return (
<motion.span
key={index}
className="inline-block w-4"
initial={{ x: 100, opacity: 0 }}
whileInView={{ x: 0, opacity: 1 }}
viewport={{ once: true }}
transition={{
duration: 0.1,
delay: 0.5 + index * 0.05,
ease: 'easeOut',
}}
/>
);
}

return (
<motion.span
className="inline-block"
initial={{ x: 100, opacity: 0 }}
whileInView={{ x: 0, opacity: 1 }}
transition={{ duration: 0.5, delay: 0.5 + index * 0.1 }}
>
{character}
</motion.span>
);
})}
</Fragment>
);
};
65 changes: 65 additions & 0 deletions storefront/components/cart/cart-modal.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import { useCart } from '@/context/cart-context';
import { Dialog, Transition } from '@headlessui/react';
import { useEffect, Fragment } from 'react';
import { CartSummary } from './cart-summary';
import { EmptyCart } from './empty-cart';
import { useLenis } from '@/context/lenis-context';

interface CartModalProps {
isOpen: boolean;
closeCart: () => void;
}

export const CartModal = ({ isOpen, closeCart }: CartModalProps) => {
const lenis = useLenis();
const { totalCartItems } = useCart();

useEffect(() => {
if (isOpen) {
lenis?.stop();
}

if (!isOpen) {
lenis?.start();
}
}, [isOpen]);

return (
<Transition appear show={isOpen} as={Fragment}>
<Dialog onClose={closeCart} className="relative z-50" as="div">
<Overlay />
<div className="fixed bottom-0 top-auto h-[500px] w-full lg:right-10 lg:bottom-auto lg:top-10 lg:max-w-sm">
<Transition.Child
as={Fragment}
enterTo="opacity-100"
leaveFrom="opacity-100"
enter="ease duration-500"
leave="ease-in duration-300"
leaveTo="opacity-0 lg:translate-x-10 lg:-translate-y-10 translate-y-10"
enterFrom="opacity-0 lg:translate-x-10 lg:-translate-y-10 translate-y-10"
>
<Dialog.Panel className="h-full w-full rounded-t-2xl border border-white border-opacity-20 bg-noir p-4 lg:rounded-none">
{totalCartItems > 0 ? <CartSummary /> : <EmptyCart />}
</Dialog.Panel>
</Transition.Child>
</div>
</Dialog>
</Transition>
);
};

const Overlay = () => {
return (
<Transition.Child
as={Fragment}
leaveTo="opacity-0"
enterTo="opacity-100"
enterFrom="opacity-0"
leaveFrom="opacity-100"
leave="ease-in duration-300"
enter="linear duration-500"
>
<div className="linear fixed inset-0 bg-white bg-opacity-20 backdrop-blur-sm transition-all duration-500" />
</Transition.Child>
);
};
111 changes: 111 additions & 0 deletions storefront/components/cart/cart-summary.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import Link from 'next/link';
import { Minus, Plus, Crosshair, X } from 'react-feather';
import { useCart } from '@/context/cart-context';
import { LineItem } from '@medusajs/medusa';
import Image from 'next/image';

export const CartSummary = () => {
const { cart } = useCart();

return (
<div className="flex h-full w-full flex-col gap-4">
<div className="track w-full border-b border-white border-opacity-20 pb-2 text-center font-alt-sans text-sm uppercase opacity-80">
<h3>Shopping Cart</h3>
</div>

<div className="scrollbar-hide flex grow flex-col gap-4 overflow-y-auto">
{cart?.items.map((item) => {
return <CartItem key={item.id} item={item} />;
})}
</div>

<div className="mt-auto w-full">
<div className="flex w-full items-center justify-between py-2 font-alt-sans text-sm uppercase">
<span>Total</span>
<span>${((cart?.total || 0) / 100).toFixed(2)}</span>
</div>
<Link href="/checkout">
<a className="flex w-full items-center justify-center border border-white border-opacity-50 p-2 text-center text-xs uppercase duration-300 hover:border-monster-green-300 hover:text-monster-green-300">
<span>Continue To Checkout</span>
</a>
</Link>
</div>
</div>
);
};

interface CartItemProps {
item: LineItem;
}

const CartItem = ({ item }: CartItemProps) => {
const { addItem, removeItem, updateItem } = useCart();
const itemQuantity = item.quantity;

const handleRemoveItem = () => {
removeItem(item.id);
};

const decreaseQuantity = () => {
if (itemQuantity > 1) {
updateItem({
lineId: item.id,
quantity: itemQuantity - 1,
});
} else {
removeItem(item.id);
}
};

const increaseQuantity = () => {
updateItem({
lineId: item.id,
quantity: itemQuantity + 1,
});
};

return (
<div>
<div className="flex items-center justify-between gap-4 font-alt-sans text-white text-opacity-90">
<div className="flex h-[80px] w-[80px] shrink-0 items-center justify-center border-white border-opacity-20 bg-white bg-opacity-10">
<Image src={item.thumbnail || ''} width={20} height={50} />
</div>

<div className="flex grow flex-col gap-2 text-sm">
<div className="text-sm">{item.title}</div>
<div className="flex items-center gap-4 text-xs">
<div>${(item.unit_price / 100).toFixed(2)}</div>

<div className="flex divide-x divide-white divide-opacity-20 border border-white border-opacity-20">
<button
onClick={decreaseQuantity}
className="flex items-center justify-center p-1"
>
<Minus size={16} className="" />
</button>
<div className="flex items-center justify-center p-1 px-2">
<span> {item.quantity}</span>
</div>
<button
onClick={increaseQuantity}
className="flex items-center justify-center p-1"
>
<Plus size={16} />
</button>
</div>
</div>
</div>

<div>
<button
onClick={handleRemoveItem}
className="text-xs uppercase underline hover:text-monster-green-300"
>
<span className="sr-only">Remove Item</span>
<X size={24} strokeWidth={1} />
</button>
</div>
</div>
</div>
);
};
14 changes: 14 additions & 0 deletions storefront/components/cart/empty-cart.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import Link from 'next/link';

export const EmptyCart = () => {
return (
<div className="flex h-full w-full flex-col items-center justify-center gap-4 border border-white border-opacity-10">
<p className="text-lg uppercase">Your cart is empty 👀</p>
<Link href="/shop">
<a className="border border-white border-opacity-70 py-2 px-4 text-center text-sm uppercase duration-300 hover:border-monster-green-300 hover:text-monster-green-300">
Explore the Shop
</a>
</Link>
</div>
);
};
26 changes: 26 additions & 0 deletions storefront/components/cart/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { CartModal } from './cart-modal';
import { useCart } from '@/context/cart-context';
import { useState, useEffect, useRef, Fragment } from 'react';

export const CartButton = () => {
const { totalCartItems } = useCart();
const [isOpen, setIsOpen] = useState(false);

const openCart = () => setIsOpen(true);

const closeCart = () => setIsOpen(false);

return (
<Fragment>
<button
onClick={openCart}
className={`font-alt-sans text-xs uppercase tracking-widest opacity-80 ${
isOpen && 'pointer-events-none'
}`}
>
Cart [{totalCartItems}]
</button>
<CartModal isOpen={isOpen} closeCart={closeCart} />
</Fragment>
);
};
4 changes: 4 additions & 0 deletions storefront/components/collections/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import { ProductCard } from './product-card';
import { NavBar } from './nav-bar';

export { ProductCard, NavBar };
47 changes: 47 additions & 0 deletions storefront/components/collections/nav-bar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { ProductCollection } from '@medusajs/medusa';
import Link from 'next/link';
import { useRouter } from 'next/router';

interface NavBarProps {
collections: ProductCollection[];
}

export const NavBar = ({ collections }: NavBarProps) => {
const router = useRouter();

const currentCollection = router.asPath.split('/')[3];
const isCollectionPage = router.asPath.includes('/collections/');

return (
<div className="md:scrollbar-hide overflow-scroll">
<div className="flex w-fit items-center divide-x divide-white divide-opacity-10 border border-white border-opacity-10 font-alt-sans text-sm uppercase">
<Link href="/shop">
<a
className={`whitespace-nowrap px-4 py-1 ${
!isCollectionPage && 'bg-white bg-opacity-10'
}`}
>
All
</a>
</Link>
{collections.map((collection) => {
return (
<Link
href={`/shop/collections/${collection.handle}`}
key={collection.id}
>
<a
className={`whitespace-nowrap px-4 py-1 ${
currentCollection === collection.handle &&
'bg-white bg-opacity-10'
}`}
>
{collection.title}
</a>
</Link>
);
})}
</div>
</div>
);
};
33 changes: 33 additions & 0 deletions storefront/components/collections/product-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import Image from 'next/image';
import Link from 'next/link';
import { Product as MedusaProduct } from '@medusajs/medusa';
import { formatPrice } from '@/utils/format-price';

interface ProductCardProps {
product: MedusaProduct;
}

export const ProductCard = ({ product }: ProductCardProps) => {
const { title, thumbnail, handle, variants } = product;
const price = variants[0]?.prices[0];

return (
<Link href={`/shop/${handle}`} key={handle}>
<a className="group flex items-center justify-center bg-lotion bg-opacity-5 p-4 pt-6 font-alt-sans">
<div className="flex flex-col gap-4">
<div className="flex items-center justify-center py-3">
<div className="pointer-events-none w-28 border-opacity-20 duration-500 ease-in-out group-hover:scale-105">
<Image src={thumbnail || ''} width={300} height={700} />
</div>
</div>
<div className="flex grow flex-col items-center gap-2 pt-3 text-center font-alt-sans text-sm duration-500 group-hover:text-monster-green-300 group-hover:opacity-100">
<div className="capitalize lg:text-lg">{title}</div>
<div className="mt-auto w-fit border border-lotion border-opacity-10 bg-white bg-opacity-5 p-2 px-4 font-alt-sans text-xs backdrop-blur-md">
{formatPrice(price)}
</div>
</div>
</div>
</a>
</Link>
);
};
14 changes: 14 additions & 0 deletions storefront/components/layout/container.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
interface ContainerProps {
children: React.ReactNode;
className?: string;
}

export const Container = ({ children, className }: ContainerProps) => {
return (
<div
className={`mx-auto w-full max-w-screen-2xl px-4 xl:px-8 ${className}`}
>
{children}
</div>
);
};
Loading

0 comments on commit 7dbd8e4

Please sign in to comment.