Custom Checkout Progress

This commit is contained in:
Bel Curcio 2021-05-27 14:23:55 -03:00
parent 8fb6c7b206
commit 7c1344a19e
60 changed files with 869 additions and 252 deletions

View File

@ -20,16 +20,20 @@
--blue: #0070f3; --blue: #0070f3;
--violet: #5f3dc4; --violet: #5f3dc4;
--violet-light: #7048e8; --violet-light: #7048e8;
--accents-0: #f8f9fa;
--accents-1: #f1f3f5; --accent-0: #fff;
--accents-2: #e9ecef; --accent-05: #f8f9fa;
--accents-3: #dee2e6; --accent-1: #f1f3f5;
--accents-4: #ced4da; --accent-2: #e9ecef;
--accents-5: #adb5bd; --accent-3: #dee2e6;
--accents-6: #868e96; --accent-4: #ced4da;
--accents-7: #495057; --accent-5: #adb5bd;
--accents-8: #343a40; --accent-6: #868e96;
--accents-9: #212529; --accent-7: #495057;
--accent-8: #343a40;
--accent-9: #212529;
--accent-10: #000;
--font-sans: -apple-system, system-ui, BlinkMacSystemFont, 'Helvetica Neue', --font-sans: -apple-system, system-ui, BlinkMacSystemFont, 'Helvetica Neue',
'Helvetica', sans-serif; 'Helvetica', sans-serif;
} }
@ -48,16 +52,18 @@
--text-primary: white; --text-primary: white;
--text-secondary: black; --text-secondary: black;
--accents-0: #212529; --accent-0: #000;
--accents-1: #343a40; --accent-05: #212529;
--accents-2: #495057; --accent-1: #343a40;
--accents-3: #868e96; --accent-2: #495057;
--accents-4: #adb5bd; --accent-3: #868e96;
--accents-5: #ced4da; --accent-4: #adb5bd;
--accents-6: #dee2e6; --accent-5: #ced4da;
--accents-7: #e9ecef; --accent-6: #dee2e6;
--accents-8: #f1f3f5; --accent-7: #e9ecef;
--accents-9: #f8f9fa; --accent-8: #f1f3f5;
--accent-9: #f8f9fa;
--accent-10: #fff;
} }
*, *,
@ -127,4 +133,3 @@ a {
opacity: 1; opacity: 1;
} }
} }

View File

@ -61,7 +61,7 @@ const ForgotPassword: FC<Props> = () => {
</div> </div>
<span className="pt-3 text-center text-sm"> <span className="pt-3 text-center text-sm">
<span className="text-accents-7">Do you have an account?</span> <span className="text-accent-7">Do you have an account?</span>
{` `} {` `}
<a <a
className="text-accent-9 font-bold hover:underline cursor-pointer" className="text-accent-9 font-bold hover:underline cursor-pointer"

View File

@ -87,7 +87,7 @@ const LoginView: FC<Props> = () => {
Log In Log In
</Button> </Button>
<div className="pt-1 text-center text-sm"> <div className="pt-1 text-center text-sm">
<span className="text-accents-7">Don't have an account?</span> <span className="text-accent-7">Don't have an account?</span>
{` `} {` `}
<a <a
className="text-accent-9 font-bold hover:underline cursor-pointer" className="text-accent-9 font-bold hover:underline cursor-pointer"

View File

@ -76,7 +76,7 @@ const SignUpView: FC<Props> = () => {
<Input placeholder="Last Name" onChange={setLastName} /> <Input placeholder="Last Name" onChange={setLastName} />
<Input type="email" placeholder="Email" onChange={setEmail} /> <Input type="email" placeholder="Email" onChange={setEmail} />
<Input type="password" placeholder="Password" onChange={setPassword} /> <Input type="password" placeholder="Password" onChange={setPassword} />
<span className="text-accents-8"> <span className="text-accent-8">
<span className="inline-block align-middle "> <span className="inline-block align-middle ">
<Info width="15" height="15" /> <Info width="15" height="15" />
</span>{' '} </span>{' '}
@ -97,7 +97,7 @@ const SignUpView: FC<Props> = () => {
</div> </div>
<span className="pt-1 text-center text-sm"> <span className="pt-1 text-center text-sm">
<span className="text-accents-7">Do you have an account?</span> <span className="text-accent-7">Do you have an account?</span>
{` `} {` `}
<a <a
className="text-accent-9 font-bold hover:underline cursor-pointer" className="text-accent-9 font-bold hover:underline cursor-pointer"

View File

@ -1,6 +1,31 @@
.root {
@apply flex flex-col py-4;
}
.root:first-child {
padding-top: 0;
}
.actions {
@apply flex p-1 border-accent-3 border items-center justify-center w-12 text-accent-7;
transition-property: border-color, background, color, transform, box-shadow;
transition-duration: 0.15s;
transition-timing-function: ease;
}
.actions:hover {
@apply bg-accent-1 border-accent-4 text-accent-9;
transition: border-color;
z-index: 10;
}
.actions:focus {
@apply bg-accent-2 outline-none;
}
.quantity { .quantity {
appearance: textfield; appearance: textfield;
@apply w-8 border-accents-2 border mx-3 rounded text-center text-sm text-black; @apply w-8 border-accent-2 border mx-3 rounded text-center text-sm text-black;
} }
.quantity::-webkit-outer-spin-button, .quantity::-webkit-outer-spin-button,

View File

@ -3,7 +3,7 @@ import cn from 'classnames'
import Image from 'next/image' import Image from 'next/image'
import Link from 'next/link' import Link from 'next/link'
import s from './CartItem.module.css' import s from './CartItem.module.css'
import { Trash, Plus, Minus } from '@components/icons' import { Trash, Plus, Minus, Cross } from '@components/icons'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
import type { LineItem } from '@framework/types' import type { LineItem } from '@framework/types'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
@ -20,8 +20,10 @@ type ItemOption = {
const CartItem = ({ const CartItem = ({
item, item,
currencyCode, currencyCode,
noEdit = false,
...rest ...rest
}: { }: {
noEdit?: boolean
item: LineItem item: LineItem
currencyCode: string currencyCode: string
}) => { }) => {
@ -49,6 +51,7 @@ const CartItem = ({
setQuantity(Number(e.target.value)) setQuantity(Number(e.target.value))
} }
} }
const handleBlur = () => { const handleBlur = () => {
const val = Number(quantity) const val = Number(quantity)
@ -56,6 +59,7 @@ const CartItem = ({
updateQuantity(val) updateQuantity(val)
} }
} }
const increaseQuantity = (n = 1) => { const increaseQuantity = (n = 1) => {
const val = Number(quantity) + n const val = Number(quantity) + n
@ -64,6 +68,7 @@ const CartItem = ({
updateQuantity(val) updateQuantity(val)
} }
} }
const handleRemove = async () => { const handleRemove = async () => {
setRemoving(true) setRemoving(true)
@ -87,11 +92,12 @@ const CartItem = ({
return ( return (
<li <li
className={cn('flex flex-row space-x-8 py-8', { className={cn(s.root, {
'opacity-75 pointer-events-none': removing, 'opacity-50 pointer-events-none': removing,
})} })}
{...rest} {...rest}
> >
<div className="flex flex-row space-x-4 py-4">
<div className="w-16 h-16 bg-violet relative overflow-hidden cursor-pointer"> <div className="w-16 h-16 bg-violet relative overflow-hidden cursor-pointer">
<Link href={`/product/${item.path}`}> <Link href={`/product/${item.path}`}>
<Image <Image
@ -108,7 +114,7 @@ const CartItem = ({
<div className="flex-1 flex flex-col text-base"> <div className="flex-1 flex flex-col text-base">
<Link href={`/product/${item.path}`}> <Link href={`/product/${item.path}`}>
<span <span
className="font-bold text-lg cursor-pointer leading-6" className="font-medium cursor-pointer leading-6"
onClick={() => closeSidebarIfPresent()} onClick={() => closeSidebarIfPresent()}
> >
{item.name} {item.name}
@ -117,45 +123,69 @@ const CartItem = ({
{options && options.length > 0 ? ( {options && options.length > 0 ? (
<div className=""> <div className="">
{options.map((option: ItemOption, i: number) => ( {options.map((option: ItemOption, i: number) => (
<span <div
key={`${item.id}-${option.name}`} key={`${item.id}-${option.name}`}
className="text-sm font-semibold text-accents-7" className="text-sm font-semibold text-accent-7 inline-flex items-center justify-center"
> >
{option.name}
{option.name === 'Color' ? (
<span
className="mx-2 rounded-full bg-transparent border w-5 h-5 p-1 text-accent-9 inline-flex items-center justify-center overflow-hidden"
style={{
backgroundColor: `${option.value}`,
}}
></span>
) : (
<span className="mx-2 rounded-full bg-transparent border h-5 p-1 text-accent-9 inline-flex items-center justify-center overflow-hidden">
{option.value} {option.value}
{i === options.length - 1 ? '' : ', '}
</span> </span>
)}
{i === options.length - 1 ? '' : <span className="mr-3" />}
</div>
))} ))}
</div> </div>
) : null} ) : null}
<div className="flex items-center mt-3"> </div>
<button type="button" onClick={() => increaseQuantity(-1)}> <div className="flex flex-col justify-between space-y-2 text-sm">
<Minus width={18} height={18} /> <span>{price}</span>
</div>
</div>
{!noEdit ? (
<div className="flex flex-row h-9">
<button className={s.actions} onClick={handleRemove}>
<Cross width={20} height={20} />
</button> </button>
<label> <label className="w-full border-accent-3 border ml-2">
<input <input
type="number" type="number"
max={99} max={99}
min={0} min={0}
className={s.quantity} className="bg-transparent px-4 w-full h-full focus:outline-none"
value={quantity} value={quantity}
onChange={handleQuantity} onChange={handleQuantity}
onBlur={handleBlur} onBlur={handleBlur}
/> />
</label> </label>
<button type="button" onClick={() => increaseQuantity(1)}> <button
type="button"
onClick={() => increaseQuantity(-1)}
className={s.actions}
style={{ marginLeft: '-1px' }}
>
<Minus width={18} height={18} />
</button>
<button
type="button"
onClick={() => increaseQuantity(1)}
className={cn(s.actions)}
style={{ marginLeft: '-1px' }}
>
<Plus width={18} height={18} /> <Plus width={18} height={18} />
</button> </button>
</div> </div>
</div> ) : (
<div className="flex flex-col justify-between space-y-2 text-base"> <div>x{quantity}</div>
<span>{price}</span> )}
<button
className="flex justify-end outline-none"
onClick={handleRemove}
>
<Trash />
</button>
</div>
</li> </li>
) )
} }

View File

@ -1,5 +1,5 @@
.root { .root {
@apply h-full flex flex-col; @apply h-full flex flex-col relative w-full relative;
} }
.root.empty { .root.empty {

View File

@ -1,8 +1,8 @@
import { FC } from 'react'
import cn from 'classnames' import cn from 'classnames'
import Link from 'next/link' import Link from 'next/link'
import CartItem from '../CartItem' import { FC } from 'react'
import s from './CartSidebarView.module.css' import s from './CartSidebarView.module.css'
import CartItem from '../CartItem'
import { Button } from '@components/ui' import { Button } from '@components/ui'
import { UserNav } from '@components/common' import { UserNav } from '@components/common'
import { useUI } from '@components/ui/context' import { useUI } from '@components/ui/context'
@ -11,7 +11,7 @@ import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
const CartSidebarView: FC = () => { const CartSidebarView: FC = () => {
const { closeSidebar } = useUI() const { closeSidebar, setSidebarView } = useUI()
const { data, isLoading, isEmpty } = useCart() const { data, isLoading, isEmpty } = useCart()
const { price: subTotal } = usePrice( const { price: subTotal } = usePrice(
@ -27,6 +27,7 @@ const CartSidebarView: FC = () => {
} }
) )
const handleClose = () => closeSidebar() const handleClose = () => closeSidebar()
const goToCheckout = () => setSidebarView('CHECKOUT_VIEW')
const error = null const error = null
const success = null const success = null
@ -43,9 +44,12 @@ const CartSidebarView: FC = () => {
<button <button
onClick={handleClose} onClick={handleClose}
aria-label="Close panel" aria-label="Close panel"
className="hover:text-gray-500 transition ease-in-out duration-150" className="hover:text-gray-500 transition ease-in-out duration-150 flex items-center focus:outline-none"
> >
<Cross className="h-6 w-6" /> <Cross className="h-6 w-6" />
<span className="ml-2 text-accent-7 text-xs hover:text-gray-500">
Close
</span>
</button> </button>
</div> </div>
<div className="space-y-1"> <div className="space-y-1">
@ -62,7 +66,7 @@ const CartSidebarView: FC = () => {
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center"> <h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
Your cart is empty Your cart is empty
</h2> </h2>
<p className="text-accents-3 px-10 text-center pt-2"> <p className="text-accent-3 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake. Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
</p> </p>
</div> </div>
@ -90,13 +94,13 @@ const CartSidebarView: FC = () => {
<div className="px-4 sm:px-6 flex-1"> <div className="px-4 sm:px-6 flex-1">
<Link href="/cart"> <Link href="/cart">
<h2 <h2
className="pt-1 pb-4 text-2xl leading-7 font-bold text-base tracking-wide cursor-pointer inline-block" className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block"
onClick={handleClose} onClick={handleClose}
> >
My Cart My Cart
</h2> </h2>
</Link> </Link>
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-3 border-t border-accents-3"> <ul className="py-4 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accent-3 border-accent-3">
{data!.lineItems.map((item: any) => ( {data!.lineItems.map((item: any) => (
<CartItem <CartItem
key={item.id} key={item.id}
@ -107,9 +111,8 @@ const CartSidebarView: FC = () => {
</ul> </ul>
</div> </div>
<div className="flex-shrink-0 px-4 py-5 sm:px-6"> <div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 shadow-outline-normal text-sm">
<div className="border-t border-accents-3"> <ul className="pb-2">
<ul className="py-3">
<li className="flex justify-between py-1"> <li className="flex justify-between py-1">
<span>Subtotal</span> <span>Subtotal</span>
<span>{subTotal}</span> <span>{subTotal}</span>
@ -119,18 +122,30 @@ const CartSidebarView: FC = () => {
<span>Calculated at checkout</span> <span>Calculated at checkout</span>
</li> </li>
<li className="flex justify-between py-1"> <li className="flex justify-between py-1">
<span>Estimated Shipping</span> <span>Shipping</span>
<span className="font-bold tracking-wide">FREE</span> <span className="font-bold tracking-wide">FREE</span>
</li> </li>
</ul> </ul>
<div className="flex justify-between border-t border-accents-3 py-3 font-bold mb-10"> <div className="flex justify-between border-t border-accent-3 py-3 font-bold mb-2">
<span>Total</span> <span>Total</span>
<span>{total}</span> <span>{total}</span>
</div> </div>
</div> <div>
{process.env.COMMERCE_CHECKOUT_ENABLED ? (
<Button
Component="a"
width="100%"
variant="ghost"
onClick={goToCheckout}
>
Continue to Checkout
</Button>
) : (
<Button href="/checkout" Component="a" width="100%"> <Button href="/checkout" Component="a" width="100%">
Proceed to Checkout Proceed to Checkout
</Button> </Button>
)}
</div>
</div> </div>
</> </>
)} )}

View File

@ -0,0 +1,3 @@
.root {
@apply h-full flex flex-col relative w-full relative;
}

View File

@ -0,0 +1,190 @@
import cn from 'classnames'
import Link from 'next/link'
import { FC } from 'react'
import s from './CheckoutSidebarView.module.css'
import CartItem from '@components/cart/CartItem'
import { Button } from '@components/ui'
import { UserNav } from '@components/common'
import { useUI } from '@components/ui/context'
import {
Bag,
Cross,
Check,
MapPin,
CreditCard,
ChevronLeft,
ChevronRight,
} from '@components/icons'
import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price'
const CheckoutSidebarView: FC = () => {
const { closeSidebar, setSidebarView } = useUI()
const { data, isLoading, isEmpty } = useCart()
const { price: subTotal } = usePrice(
data && {
amount: Number(data.subtotalPrice),
currencyCode: data.currency.code,
}
)
const { price: total } = usePrice(
data && {
amount: Number(data.totalPrice),
currencyCode: data.currency.code,
}
)
const handleClose = () => closeSidebar()
const error = null
const success = null
return (
<div
className={cn(s.root, {
[s.empty]: error || success || isLoading || isEmpty,
})}
>
<header className="px-4 pt-6 pb-4 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="h-7 flex items-center">
<button
onClick={() => setSidebarView('CART_VIEW')}
aria-label="Close panel"
className="hover:text-gray-500 transition ease-in-out duration-150 flex items-center focus:outline-none"
>
<ChevronLeft className="h-6 w-6" />
<span className="ml-2 text-accent-7 text-xs hover:text-gray-500">
Back
</span>
</button>
</div>
<div className="space-y-1">
<UserNav />
</div>
</div>
</header>
{isLoading || isEmpty ? (
<div className="flex-1 px-4 flex flex-col justify-center items-center">
<span className="border border-dashed border-primary rounded-full flex items-center justify-center w-16 h-16 p-12 bg-secondary text-secondary">
<Bag className="absolute" />
</span>
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
Your cart is empty
</h2>
<p className="text-accent-3 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
</p>
</div>
) : error ? (
<div className="flex-1 px-4 flex flex-col justify-center items-center">
<span className="border border-white rounded-full flex items-center justify-center w-16 h-16">
<Cross width={24} height={24} />
</span>
<h2 className="pt-6 text-xl font-light text-center">
We couldnt process the purchase. Please check your card information
and try again.
</h2>
</div>
) : success ? (
<div className="flex-1 px-4 flex flex-col justify-center items-center">
<span className="border border-white rounded-full flex items-center justify-center w-16 h-16">
<Check />
</span>
<h2 className="pt-6 text-xl font-light text-center">
Thank you for your order.
</h2>
</div>
) : (
<>
<div className="px-4 sm:px-6 flex-1">
<Link href="/cart">
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
Checkout
</h2>
</Link>
{/* Payment Method */}
{/* Only available with checkout set to true - Meaning that the provider does offer checkout functionality. */}
<div
onClick={() => setSidebarView('PAYMENT_VIEW')}
className="border border-accent-2 px-6 py-5 mb-4 text-center flex items-center cursor-pointer hover:border-accent-4"
>
<div className="flex flex-1 items-center">
<CreditCard className="w-5 flex" />
<span className="ml-5 text-sm text-center font-medium">
Add Payment Method
</span>
{/* <span>VISA #### #### #### 2345</span> */}
</div>
<div>
<ChevronRight />
</div>
</div>
{/* Shipping Address */}
{/* Only available with checkout set to true - Meaning that the provider does offer checkout functionality. */}
<div
onClick={() => setSidebarView('SHIPPING_VIEW')}
className="border border-accent-2 px-6 py-5 mb-4 text-center flex items-center cursor-pointer hover:border-accent-4"
>
<div className="flex flex-1 items-center">
<MapPin className="w-5 flex" />
<span className="ml-5 text-sm text-center font-medium">
Add Shipping Address
</span>
{/* <span>
1046 Kearny Street.<br/>
San Franssisco, California
</span> */}
</div>
<div>
<ChevronRight />
</div>
</div>
<ul className="py-4 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accent-3 border-accent-3">
{data!.lineItems.map((item: any) => (
<CartItem
key={item.id}
item={item}
currencyCode={data!.currency.code}
noEdit
/>
))}
</ul>
</div>
<div className="flex-shrink-0 px-6 py-6 sm:px-6 sticky z-20 bottom-0 w-full right-0 left-0 bg-accent-0 shadow-outline-normal text-sm">
<ul className="pb-2">
<li className="flex justify-between py-1">
<span>Subtotal</span>
<span>{subTotal}</span>
</li>
<li className="flex justify-between py-1">
<span>Taxes</span>
<span>Calculated at checkout</span>
</li>
<li className="flex justify-between py-1">
<span>Shipping</span>
<span className="font-bold tracking-wide">FREE</span>
</li>
</ul>
<div className="flex justify-between border-t border-accent-3 py-3 font-bold mb-2">
<span>Total</span>
<span>{total}</span>
</div>
<div>
<Button href="/checkout" Component="a" width="100%">
Confirm Purchase
</Button>
</div>
</div>
</>
)}
</div>
)
}
export default CheckoutSidebarView

View File

@ -0,0 +1 @@
export { default } from './CheckoutSidebarView'

View File

@ -0,0 +1,21 @@
.root {
@apply h-full flex flex-col relative w-full relative;
}
.fieldset {
@apply flex flex-col my-3;
}
.fieldset .label {
@apply text-accent-7 uppercase text-xs font-medium mb-2;
}
.fieldset .input,
.fieldset .select {
@apply p-2 border border-accent-3 w-full text-sm font-normal;
}
.fieldset .input:focus,
.fieldset .select:focus {
@apply outline-none shadow-outline-normal;
}

View File

@ -0,0 +1,106 @@
import { FC } from 'react'
import s from './PaymentMethodView.module.css'
import { ChevronLeft } from '@components/icons'
import { UserNav } from '@components/common'
import { useUI } from '@components/ui/context'
import Button from '@components/ui/Button'
import cn from 'classnames'
const PaymentMethodView: FC = () => {
const { setSidebarView } = useUI()
return (
<div className={s.root}>
<header className="px-4 pt-6 pb-4 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="h-7 flex items-center">
<button
onClick={() => setSidebarView('CHECKOUT_VIEW')}
aria-label="Close panel"
className="hover:text-gray-500 transition ease-in-out duration-150 flex items-center focus:outline-none"
>
<ChevronLeft className="h-6 w-6" />
<span className="ml-2 text-accent-7 text-xs hover:text-gray-500">
Back
</span>
</button>
</div>
<div className="space-y-1">
<UserNav />
</div>
</div>
</header>
<div className="px-4 sm:px-6 flex-1">
<h2 className="pt-1 pb-6 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
Payment Method
</h2>
<div>
<div className={s.fieldset}>
<label className={s.label}>Cardholder Name</label>
<input className={s.input} />
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-7')}>
<label className={s.label}>Card Number</label>
<input className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-3')}>
<label className={s.label}>Expires</label>
<input className={s.input} placeholder="MM/YY" />
</div>
<div className={cn(s.fieldset, 'col-span-2')}>
<label className={s.label}>CVC</label>
<input className={s.input} />
</div>
</div>
<hr className="border-accent-3 my-6" />
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>First Name</label>
<input className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Last Name</label>
<input className={s.input} />
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Company (Optional)</label>
<input className={s.input} />
</div>
<div className={s.fieldset}>
<label className={s.label}>Street and House Number</label>
<input className={s.input} />
</div>
<div className={s.fieldset}>
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
<input className={s.input} />
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Postal Code</label>
<input className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>City</label>
<input className={s.input} />
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country/Region</label>
<select className={s.select}>
<option>Hong Kong</option>
</select>
</div>
<div className="mt-10 sticky z-20 bottom-0 w-full right-0 left-0 ">
<Button Component="a" width="100%" variant="ghost">
Continue
</Button>
</div>
</div>
</div>
</div>
)
}
export default PaymentMethodView

View File

@ -0,0 +1 @@
export { default } from './PaymentMethodView'

View File

@ -0,0 +1,12 @@
# Checkout
Checkout is only enabled in those providers that specify Custom Checkout to be enabled.
This feature enables:
- Shipping
- Payment
```
COMMERCE_CHECKOUT_ENABLED
```

View File

@ -0,0 +1,25 @@
.root {
@apply h-full flex flex-col relative w-full relative;
}
.fieldset {
@apply flex flex-col my-3;
}
.fieldset .label {
@apply text-accent-7 uppercase text-xs font-medium mb-2;
}
.fieldset .input,
.fieldset .select {
@apply p-2 border border-accent-3 w-full text-sm font-normal;
}
.fieldset .input:focus,
.fieldset .select:focus {
@apply outline-none shadow-outline-normal;
}
.radio {
@apply bg-black;
}

View File

@ -0,0 +1,98 @@
import { FC } from 'react'
import s from './ShippingView.module.css'
import { ChevronLeft } from '@components/icons'
import { UserNav } from '@components/common'
import { useUI } from '@components/ui/context'
import Button from '@components/ui/Button'
import cn from 'classnames'
const PaymentMethodView: FC = () => {
const { setSidebarView } = useUI()
return (
<div className={s.root}>
<header className="px-4 pt-6 pb-4 sm:px-6">
<div className="flex items-start justify-between space-x-3">
<div className="h-7 flex items-center">
<button
onClick={() => setSidebarView('CHECKOUT_VIEW')}
aria-label="Close panel"
className="hover:text-gray-500 transition ease-in-out duration-150 flex items-center focus:outline-none"
>
<ChevronLeft className="h-6 w-6" />
<span className="ml-2 text-accent-7 text-xs hover:text-gray-500">
Back
</span>
</button>
</div>
<div className="space-y-1">
<UserNav />
</div>
</div>
</header>
<div className="px-4 sm:px-6 flex-1">
<h2 className="pt-1 pb-8 text-2xl font-semibold tracking-wide cursor-pointer inline-block">
Shipping
</h2>
<div>
<div className="flex flex-row my-3 items-center">
<input className={s.radio} type="radio" />
<span className="ml-3 text-sm">Same as billing address</span>
</div>
<div className="flex flex-row my-3 items-center">
<input className={s.radio} type="radio" />
<span className="ml-3 text-sm">
Use a different shipping address
</span>
</div>
<hr className="border-accent-3 my-6" />
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>First Name</label>
<input className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Last Name</label>
<input className={s.input} />
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Company (Optional)</label>
<input className={s.input} />
</div>
<div className={s.fieldset}>
<label className={s.label}>Street and House Number</label>
<input className={s.input} />
</div>
<div className={s.fieldset}>
<label className={s.label}>Apartment, Suite, Etc. (Optional)</label>
<input className={s.input} />
</div>
<div className="grid gap-3 grid-flow-row grid-cols-12">
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>Postal Code</label>
<input className={s.input} />
</div>
<div className={cn(s.fieldset, 'col-span-6')}>
<label className={s.label}>City</label>
<input className={s.input} />
</div>
</div>
<div className={s.fieldset}>
<label className={s.label}>Country/Region</label>
<select className={s.select}>
<option>Hong Kong</option>
</select>
</div>
</div>
</div>
<div className="mt-10 sticky z-20 bottom-0 w-full right-0 left-0 p-6">
<Button href="/checkout" Component="a" width="100%" variant="ghost">
Continue
</Button>
</div>
</div>
)
}
export default PaymentMethodView

View File

@ -0,0 +1 @@
export { default } from './ShippingView'

View File

@ -1,9 +1,7 @@
.root { .root {
@apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30 transition-all duration-300 ease-out; @apply text-center p-6 bg-primary text-sm flex-row justify-center items-center font-medium fixed bottom-0 w-full z-30 transition-all duration-300 ease-out;
}
@screen md { @screen md {
.root {
@apply flex text-left; @apply flex text-left;
} }
} }

View File

@ -24,7 +24,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
return ( return (
<footer className={rootClassName}> <footer className={rootClassName}>
<Container> <Container>
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accents-2 py-12 text-primary bg-primary transition-colors duration-150"> <div className="grid grid-cols-1 lg:grid-cols-12 gap-8 border-b border-accent-2 py-12 text-primary bg-primary transition-colors duration-150">
<div className="col-span-1 lg:col-span-2"> <div className="col-span-1 lg:col-span-2">
<Link href="/"> <Link href="/">
<a className="flex flex-initial items-center font-bold md:mr-24"> <a className="flex flex-initial items-center font-bold md:mr-24">
@ -39,15 +39,29 @@ const Footer: FC<Props> = ({ className, pages }) => {
<ul className="flex flex-initial flex-col md:flex-1"> <ul className="flex flex-initial flex-col md:flex-1">
<li className="py-3 md:py-0 md:pb-4"> <li className="py-3 md:py-0 md:pb-4">
<Link href="/"> <Link href="/">
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150"> <a className="text-primary hover:text-accent-6 transition ease-in-out duration-150">
Home Home
</a> </a>
</Link> </Link>
</li> </li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/">
<a className="text-primary hover:text-accent-6 transition ease-in-out duration-150">
Careers
</a>
</Link>
</li>
<li className="py-3 md:py-0 md:pb-4">
<Link href="/blog">
<a className="text-primary hover:text-accent-6 transition ease-in-out duration-150">
Blog
</a>
</Link>
</li>
{sitePages.map((page) => ( {sitePages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4"> <li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}> <Link href={page.url!}>
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150"> <a className="text-primary hover:text-accent-6 transition ease-in-out duration-150">
{page.name} {page.name}
</a> </a>
</Link> </Link>
@ -60,7 +74,7 @@ const Footer: FC<Props> = ({ className, pages }) => {
{legalPages.map((page) => ( {legalPages.map((page) => (
<li key={page.url} className="py-3 md:py-0 md:pb-4"> <li key={page.url} className="py-3 md:py-0 md:pb-4">
<Link href={page.url!}> <Link href={page.url!}>
<a className="text-primary hover:text-accents-6 transition ease-in-out duration-150"> <a className="text-primary hover:text-accent-6 transition ease-in-out duration-150">
{page.name} {page.name}
</a> </a>
</Link> </Link>

View File

@ -28,7 +28,7 @@ const HomeAllProductsGrid: FC<Props> = ({
</Link> </Link>
</li> </li>
{categories.map((cat: any) => ( {categories.map((cat: any) => (
<li key={cat.path} className="py-1 text-accents-8 text-base"> <li key={cat.path} className="py-1 text-accent-8 text-base">
<Link href={getCategoryPath(cat.path)}> <Link href={getCategoryPath(cat.path)}>
<a>{cat.name}</a> <a>{cat.name}</a>
</Link> </Link>
@ -42,7 +42,7 @@ const HomeAllProductsGrid: FC<Props> = ({
</Link> </Link>
</li> </li>
{brands.flatMap(({ node }: any) => ( {brands.flatMap(({ node }: any) => (
<li key={node.path} className="py-1 text-accents-8 text-base"> <li key={node.path} className="py-1 text-accent-8 text-base">
<Link href={getDesignerPath(node.path)}> <Link href={getDesignerPath(node.path)}>
<a>{node.name}</a> <a>{node.name}</a>
</Link> </Link>

View File

@ -3,11 +3,11 @@
} }
.button { .button {
@apply h-10 px-2 rounded-md border border-accents-2 flex items-center justify-center; @apply h-10 px-2 rounded-md border border-accent-2 flex items-center justify-center;
} }
.button:hover { .button:hover {
@apply border-accents-4 shadow-sm; @apply border-accent-4 shadow-sm;
} }
.button:focus { .button:focus {
@ -16,16 +16,14 @@
.dropdownMenu { .dropdownMenu {
@apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full; @apply fixed right-0 top-12 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
}
@screen lg { @screen lg {
.dropdownMenu { @apply absolute border border-accent-1 shadow-lg w-56 h-auto;
@apply absolute border border-accents-1 shadow-lg w-56 h-auto;
} }
} }
@screen md {
.closeButton { .closeButton {
@screen md {
@apply hidden; @apply hidden;
} }
} }
@ -36,7 +34,7 @@
} }
.item:hover { .item:hover {
@apply bg-accents-1; @apply bg-accent-1;
} }
.icon { .icon {

View File

@ -8,6 +8,9 @@ import { Navbar, Footer } from '@components/common'
import { useAcceptCookies } from '@lib/hooks/useAcceptCookies' import { useAcceptCookies } from '@lib/hooks/useAcceptCookies'
import { Sidebar, Button, Modal, LoadingDots } from '@components/ui' import { Sidebar, Button, Modal, LoadingDots } from '@components/ui'
import CartSidebarView from '@components/cart/CartSidebarView' import CartSidebarView from '@components/cart/CartSidebarView'
import CheckoutSidebarView from '@components/checkout/CheckoutSidebarView'
import PaymentMethodView from '@components/checkout/PaymentMethodView'
import ShippingView from '@components/checkout/ShippingView'
import LoginView from '@components/auth/LoginView' import LoginView from '@components/auth/LoginView'
import { CommerceProvider } from '@framework' import { CommerceProvider } from '@framework'
@ -55,6 +58,7 @@ const Layout: FC<Props> = ({
closeSidebar, closeSidebar,
closeModal, closeModal,
modalView, modalView,
sidebarView,
} = useUI() } = useUI()
const { acceptedCookies, onAcceptCookies } = useAcceptCookies() const { acceptedCookies, onAcceptCookies } = useAcceptCookies()
const { locale = 'en-US' } = useRouter() const { locale = 'en-US' } = useRouter()
@ -72,7 +76,10 @@ const Layout: FC<Props> = ({
</Modal> </Modal>
<Sidebar open={displaySidebar} onClose={closeSidebar}> <Sidebar open={displaySidebar} onClose={closeSidebar}>
<CartSidebarView /> {sidebarView === 'CART_VIEW' && <CartSidebarView />}
{sidebarView === 'CHECKOUT_VIEW' && <CheckoutSidebarView />}
{sidebarView === 'PAYMENT_VIEW' && <PaymentMethodView />}
{sidebarView === 'SHIPPING_VIEW' && <ShippingView />}
</Sidebar> </Sidebar>
<FeatureBar <FeatureBar

View File

@ -3,15 +3,15 @@
} }
.link { .link {
@apply inline-flex items-center text-primary leading-6 font-medium transition ease-in-out duration-75 cursor-pointer text-accents-6; @apply inline-flex items-center text-primary leading-6 font-medium transition ease-in-out duration-75 cursor-pointer text-accent-6;
} }
.link:hover { .link:hover {
@apply text-accents-9; @apply text-accent-9;
} }
.link:focus { .link:focus {
@apply outline-none text-accents-8; @apply outline-none text-accent-8;
} }
.logo { .logo {

View File

@ -19,7 +19,7 @@ const Searchbar: FC<Props> = ({ className, id = 'search' }) => {
() => ( () => (
<div <div
className={cn( className={cn(
'relative text-sm bg-accents-1 text-base w-full transition-colors duration-150', 'relative text-sm bg-accent-1 text-base w-full transition-colors duration-150',
className className
)} )}
> >

View File

@ -1,10 +1,8 @@
.dropdownMenu { .dropdownMenu {
@apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full; @apply fixed right-0 mt-2 origin-top-right outline-none bg-primary z-40 w-full h-full;
}
@screen lg { @screen lg {
.dropdownMenu { @apply absolute top-10 border border-accent-1 shadow-lg w-56 h-auto;
@apply absolute top-10 border border-accents-1 shadow-lg w-56 h-auto;
} }
} }
@ -14,11 +12,11 @@
} }
.link:hover { .link:hover {
@apply bg-accents-1; @apply bg-accent-1;
} }
.link.active { .link.active {
@apply font-bold bg-accents-2; @apply font-bold bg-accent-2;
} }
.off { .off {

View File

@ -109,7 +109,7 @@ const DropdownMenu: FC<DropdownMenuProps> = ({ open = false }) => {
</li> </li>
<li> <li>
<a <a
className={cn(s.link, 'border-t border-accents-2 mt-4')} className={cn(s.link, 'border-t border-accent-2 mt-4')}
onClick={() => logout()} onClick={() => logout()}
> >
Logout Logout

View File

@ -10,7 +10,7 @@
@apply mr-6 cursor-pointer relative transition ease-in-out duration-100 flex items-center outline-none text-primary; @apply mr-6 cursor-pointer relative transition ease-in-out duration-100 flex items-center outline-none text-primary;
&:hover { &:hover {
@apply text-accents-6 transition scale-110 duration-100; @apply text-accent-6 transition scale-110 duration-100;
} }
&:last-child { &:last-child {
@ -24,7 +24,7 @@
} }
.bagCount { .bagCount {
@apply border border-accents-1 bg-secondary text-secondary absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs; @apply border border-accent-1 bg-secondary text-secondary absolute rounded-full right-3 top-3 flex items-center justify-center font-bold text-xs;
padding-left: 2.5px; padding-left: 2.5px;
padding-right: 2.5px; padding-right: 2.5px;
min-width: 1.25rem; min-width: 1.25rem;

View File

@ -0,0 +1,20 @@
const ChevronUp = ({ ...props }) => {
return (
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
{...props}
>
<path d="M15 18l-6-6 6-6" />
</svg>
)
}
export default ChevronUp

View File

@ -0,0 +1,20 @@
const ChevronUp = ({ ...props }) => {
return (
<svg
viewBox="0 0 24 24"
width="24"
height="24"
stroke="currentColor"
strokeWidth="1.5"
strokeLinecap="round"
strokeLinejoin="round"
fill="none"
shapeRendering="geometricPrecision"
{...props}
>
<path d="M9 18l6-6-6-6" />
</svg>
)
}
export default ChevronUp

View File

@ -10,6 +10,7 @@ const CreditCard = ({ ...props }) => {
strokeLinejoin="round" strokeLinejoin="round"
fill="none" fill="none"
shapeRendering="geometricPrecision" shapeRendering="geometricPrecision"
{...props}
> >
<rect x="1" y="4" width="22" height="16" rx="2" ry="2" /> <rect x="1" y="4" width="22" height="16" rx="2" ry="2" />
<path d="M1 10h22" /> <path d="M1 10h22" />

View File

@ -9,10 +9,12 @@ export { default as Check } from './Check'
export { default as Sun } from './Sun' export { default as Sun } from './Sun'
export { default as Moon } from './Moon' export { default as Moon } from './Moon'
export { default as Github } from './Github' export { default as Github } from './Github'
export { default as DoubleChevron } from './DoubleChevron'
export { default as RightArrow } from './RightArrow' export { default as RightArrow } from './RightArrow'
export { default as Info } from './Info' export { default as Info } from './Info'
export { default as ChevronUp } from './ChevronUp'
export { default as Vercel } from './Vercel' export { default as Vercel } from './Vercel'
export { default as MapPin } from './MapPin' export { default as MapPin } from './MapPin'
export { default as CreditCard } from './CreditCard' export { default as CreditCard } from './CreditCard'
export { default as ChevronUp } from './ChevronUp'
export { default as ChevronLeft } from './ChevronLeft'
export { default as ChevronRight } from './ChevronRight'
export { default as DoubleChevron } from './DoubleChevron'

View File

@ -81,7 +81,7 @@
.simple { .simple {
& .squareBg { & .squareBg {
@apply bg-accents-0 !important; @apply bg-accent-0 !important;
background-image: url('/bg-products.svg'); background-image: url('/bg-products.svg');
} }

View File

@ -28,6 +28,6 @@
.active { .active {
&.size { &.size {
@apply border-accents-9 border-2; @apply border-accent-9 border-2;
} }
} }

View File

@ -1,9 +1,12 @@
.root { .root {
@apply bg-secondary text-accents-1 cursor-pointer inline-flex px-10 rounded-sm leading-6 transition ease-in-out duration-150 shadow-sm font-semibold text-center justify-center uppercase py-4 border border-transparent items-center; @apply bg-secondary text-accent-1 cursor-pointer inline-flex
px-10 rounded-sm leading-6 transition ease-in-out duration-150
shadow-sm font-semibold text-center justify-center uppercase
py-4 border border-transparent items-center;
} }
.root:hover { .root:hover {
@apply bg-accents-0 text-primary border border-secondary; @apply bg-accent-0 text-primary border border-secondary;
} }
.root:focus { .root:focus {
@ -15,16 +18,20 @@
} }
.loading { .loading {
@apply bg-accents-1 text-accents-3 border-accents-2 cursor-not-allowed; @apply bg-accent-1 text-accent-3 border-accent-2 cursor-not-allowed;
} }
.slim { .slim {
@apply py-2 transform-none normal-case; @apply py-2 transform-none normal-case;
} }
.ghost {
@apply border border-accent-3 bg-accent-0 text-accent-9 text-sm;
}
.disabled, .disabled,
.disabled:hover { .disabled:hover {
@apply text-accents-4 border-accents-2 bg-accents-1 cursor-not-allowed; @apply text-accent-4 border-accent-2 bg-accent-1 cursor-not-allowed;
filter: grayscale(1); filter: grayscale(1);
-webkit-transform: translateZ(0); -webkit-transform: translateZ(0);
-webkit-perspective: 1000; -webkit-perspective: 1000;

View File

@ -12,7 +12,7 @@ import { LoadingDots } from '@components/ui'
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> { export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
href?: string href?: string
className?: string className?: string
variant?: 'flat' | 'slim' variant?: 'flat' | 'slim' | 'ghost'
active?: boolean active?: boolean
type?: 'submit' | 'reset' | 'button' type?: 'submit' | 'reset' | 'button'
Component?: string | JSXElementConstructor<any> Component?: string | JSXElementConstructor<any>
@ -39,6 +39,7 @@ const Button: React.FC<ButtonProps> = forwardRef((props, buttonRef) => {
const rootClassName = cn( const rootClassName = cn(
s.root, s.root,
{ {
[s.ghost]: variant === 'ghost',
[s.slim]: variant === 'slim', [s.slim]: variant === 'slim',
[s.loading]: loading, [s.loading]: loading,
[s.disabled]: disabled, [s.disabled]: disabled,

View File

@ -3,6 +3,10 @@
@apply grid grid-cols-1 gap-0; @apply grid grid-cols-1 gap-0;
min-height: var(--row-height); min-height: var(--row-height);
@screen lg {
@apply grid-cols-3 grid-rows-2;
}
& > * { & > * {
@apply row-span-1 bg-transparent box-border overflow-hidden; @apply row-span-1 bg-transparent box-border overflow-hidden;
height: 500px; height: 500px;
@ -15,17 +19,6 @@
} }
} }
@screen lg {
.root {
@apply grid-cols-3 grid-rows-2;
}
.root & > * {
@apply col-span-1;
height: inherit;
}
}
.default { .default {
& > * { & > * {
@apply bg-transparent; @apply bg-transparent;

View File

@ -1,9 +1,6 @@
.root { .root {
@apply mx-auto grid grid-cols-1 py-32 gap-4; @apply mx-auto grid grid-cols-1 py-32 gap-4;
}
@screen md { @screen md {
.root {
@apply grid-cols-2; @apply grid-cols-2;
} }
} }

View File

@ -21,7 +21,7 @@ const Hero: FC<Props> = ({ headline, description }) => {
<p className="mt-5 text-xl leading-7 text-accent-2 text-white"> <p className="mt-5 text-xl leading-7 text-accent-2 text-white">
{description} {description}
</p> </p>
<Link href="/"> <Link href="/blog">
<a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content"> <a className="text-white pt-3 font-bold hover:underline flex flex-row cursor-pointer w-max-content">
Read it here Read it here
<RightArrow width="20" heigh="20" className="ml-1" /> <RightArrow width="20" heigh="20" className="ml-1" />

View File

@ -1,5 +1,5 @@
.root { .root {
@apply bg-primary py-2 px-6 w-full appearance-none transition duration-150 ease-in-out pr-10 border border-accents-3 text-accents-6; @apply bg-primary py-2 px-6 w-full appearance-none transition duration-150 ease-in-out pr-10 border border-accent-3 text-accent-6;
} }
.root:focus { .root:focus {

View File

@ -2,7 +2,7 @@
@apply inline-flex text-center items-center leading-7; @apply inline-flex text-center items-center leading-7;
& span { & span {
@apply bg-accents-6 rounded-full h-2 w-2; @apply bg-accent-6 rounded-full h-2 w-2;
animation-name: blink; animation-name: blink;
animation-duration: 1.4s; animation-duration: 1.4s;
animation-iteration-count: infinite; animation-iteration-count: infinite;

View File

@ -4,7 +4,7 @@
} }
.modal { .modal {
@apply bg-primary p-12 border border-accents-2 relative; @apply bg-primary p-12 border border-accent-2 relative;
} }
.modal:focus { .modal:focus {

View File

@ -40,7 +40,7 @@ const Sidebar: FC<Props> = ({ children, open = false, onClose }) => {
/> />
<section className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16 outline-none"> <section className="absolute inset-y-0 right-0 pl-10 max-w-full flex sm:pl-16 outline-none">
<div className="h-full md:w-screen md:max-w-md"> <div className="h-full md:w-screen md:max-w-md">
<div className="h-full flex flex-col text-base bg-accents-1 shadow-xl overflow-y-auto"> <div className="h-full flex flex-col text-base bg-accent-0 shadow-xl overflow-y-auto">
{children} {children}
</div> </div>
</div> </div>

View File

@ -2,10 +2,10 @@
@apply block; @apply block;
background-image: linear-gradient( background-image: linear-gradient(
270deg, 270deg,
var(--accents-1), var(--accent-1),
var(--accents-2), var(--accent-2),
var(--accents-2), var(--accent-2),
var(--accents-1) var(--accent-1)
); );
background-size: 400% 100%; background-size: 400% 100%;
animation: loading 8s ease-in-out infinite; animation: loading 8s ease-in-out infinite;
@ -28,10 +28,10 @@
z-index: 100; z-index: 100;
background-image: linear-gradient( background-image: linear-gradient(
270deg, 270deg,
var(--accents-1), var(--accent-1),
var(--accents-2), var(--accent-2),
var(--accents-2), var(--accent-2),
var(--accents-1) var(--accent-1)
); );
background-size: 400% 100%; background-size: 400% 100%;
animation: loading 8s ease-in-out infinite; animation: loading 8s ease-in-out infinite;

View File

@ -1,5 +1,5 @@
.body { .body {
@apply text-lg leading-7 font-medium max-w-6xl mx-auto; @apply text-base leading-7 max-w-6xl mx-auto;
} }
.heading { .heading {
@ -11,5 +11,5 @@
} }
.sectionHeading { .sectionHeading {
@apply pt-1 pb-2 font-semibold leading-7 tracking-wider uppercase border-b border-accents-2 mb-3; @apply pt-1 pb-2 font-semibold leading-7 tracking-wider uppercase border-b border-accent-2 mb-3;
} }

View File

@ -7,6 +7,7 @@ export interface State {
displayModal: boolean displayModal: boolean
displayToast: boolean displayToast: boolean
modalView: string modalView: string
sidebarView: string
toastText: string toastText: string
userAvatar: string userAvatar: string
} }
@ -16,6 +17,7 @@ const initialState = {
displayDropdown: false, displayDropdown: false,
displayModal: false, displayModal: false,
modalView: 'LOGIN_VIEW', modalView: 'LOGIN_VIEW',
sidebarView: 'CART_VIEW',
displayToast: false, displayToast: false,
toastText: '', toastText: '',
userAvatar: '', userAvatar: '',
@ -54,6 +56,10 @@ type Action =
type: 'SET_MODAL_VIEW' type: 'SET_MODAL_VIEW'
view: MODAL_VIEWS view: MODAL_VIEWS
} }
| {
type: 'SET_SIDEBAR_VIEW'
view: SIDEBAR_VIEWS
}
| { | {
type: 'SET_USER_AVATAR' type: 'SET_USER_AVATAR'
value: string value: string
@ -65,6 +71,9 @@ type MODAL_VIEWS =
| 'FORGOT_VIEW' | 'FORGOT_VIEW'
| 'NEW_SHIPPING_ADDRESS' | 'NEW_SHIPPING_ADDRESS'
| 'NEW_PAYMENT_METHOD' | 'NEW_PAYMENT_METHOD'
type SIDEBAR_VIEWS = 'CART_VIEW' | 'CHECKOUT_VIEW' | 'PAYMENT_METHOD_VIEW'
type ToastText = string type ToastText = string
export const UIContext = React.createContext<State | any>(initialState) export const UIContext = React.createContext<State | any>(initialState)
@ -128,6 +137,12 @@ function uiReducer(state: State, action: Action) {
modalView: action.view, modalView: action.view,
} }
} }
case 'SET_SIDEBAR_VIEW': {
return {
...state,
sidebarView: action.view,
}
}
case 'SET_TOAST_TEXT': { case 'SET_TOAST_TEXT': {
return { return {
...state, ...state,
@ -170,6 +185,9 @@ export const UIProvider: FC = (props) => {
const setModalView = (view: MODAL_VIEWS) => const setModalView = (view: MODAL_VIEWS) =>
dispatch({ type: 'SET_MODAL_VIEW', view }) dispatch({ type: 'SET_MODAL_VIEW', view })
const setSidebarView = (view: SIDEBAR_VIEWS) =>
dispatch({ type: 'SET_SIDEBAR_VIEW', view })
const value = useMemo( const value = useMemo(
() => ({ () => ({
...state, ...state,
@ -182,6 +200,7 @@ export const UIProvider: FC = (props) => {
openModal, openModal,
closeModal, closeModal,
setModalView, setModalView,
setSidebarView,
openToast, openToast,
closeToast, closeToast,
setUserAvatar, setUserAvatar,

View File

@ -1,5 +1,5 @@
.root { .root {
@apply grid grid-cols-12 w-full gap-6 px-3 py-6 border-b border-accents-2 transition duration-100 ease-in-out; @apply grid grid-cols-12 w-full gap-6 px-3 py-6 border-b border-accent-2 transition duration-100 ease-in-out;
&:nth-child(3n + 1) { &:nth-child(3n + 1) {
& .productBg { & .productBg {

View File

@ -9,6 +9,7 @@ const fetchGraphqlApi: GraphQLFetcher = async (
{ variables } = {}, { variables } = {},
fetchOptions fetchOptions
) => { ) => {
try {
const res = await fetch(API_URL, { const res = await fetch(API_URL, {
...fetchOptions, ...fetchOptions,
method: 'POST', method: 'POST',
@ -30,5 +31,15 @@ const fetchGraphqlApi: GraphQLFetcher = async (
} }
return { data, res } return { data, res }
} catch (err) {
throw getError(
[
{
message: `${err} \n Most likely related to an unexpected output. e.g the store might be protected with password or not available.`,
},
],
500
)
}
} }
export default fetchGraphqlApi export default fetchGraphqlApi

View File

@ -10,15 +10,11 @@ export const handler: SWRHook<Customer | null> = {
query: getCustomerQuery, query: getCustomerQuery,
}, },
async fetcher({ options, fetch }) { async fetcher({ options, fetch }) {
const customerAccessToken = getCustomerToken() const data = await fetch<any | null>({
if (customerAccessToken) {
const data = await fetch({
...options, ...options,
variables: { customerAccessToken: getCustomerToken() }, variables: { customerAccessToken: getCustomerToken() },
}) })
return data.customer return data.customer ?? null
}
return null
}, },
useHook: ({ useData }) => (input) => { useHook: ({ useData }) => (input) => {
return useData({ return useData({

View File

@ -1,4 +1,6 @@
import { Product } from '@commerce/types'
import { getConfig, ShopifyConfig } from '../api' import { getConfig, ShopifyConfig } from '../api'
import fetchAllProducts from '../api/utils/fetch-all-products'
import { ProductEdge } from '../schema' import { ProductEdge } from '../schema'
import getAllProductsPathsQuery from '../utils/queries/get-all-products-paths-query' import getAllProductsPathsQuery from '../utils/queries/get-all-products-paths-query'
@ -19,22 +21,21 @@ const getAllProductPaths = async (options?: {
config?: ShopifyConfig config?: ShopifyConfig
preview?: boolean preview?: boolean
}): Promise<ReturnType> => { }): Promise<ReturnType> => {
let { config, variables = { first: 100, sortKey: 'BEST_SELLING' } } = let { config, variables = { first: 250 } } = options ?? {}
options ?? {}
config = getConfig(config) config = getConfig(config)
const { data } = await config.fetch(getAllProductsPathsQuery, { const products = await fetchAllProducts({
config,
query: getAllProductsPathsQuery,
variables, variables,
}) })
return { return {
products: data.products?.edges?.map( products: products?.map(({ node: { handle } }: ProductEdge) => ({
({ node: { handle } }: ProductEdge) => ({
node: { node: {
path: `/${handle}`, path: `/${handle}`,
}, },
}) })),
),
} }
} }

View File

@ -27,9 +27,10 @@ const getAllProducts = async (options: {
{ variables } { variables }
) )
const products = data.products?.edges?.map(({ node: p }: ProductEdge) => const products =
data.products?.edges?.map(({ node: p }: ProductEdge) =>
normalizeProduct(p) normalizeProduct(p)
) ) ?? []
return { return {
products, products,

View File

@ -21,6 +21,7 @@ const getProduct = async (options: {
const { data }: GraphQLFetcherResult = await config.fetch(getProductQuery, { const { data }: GraphQLFetcherResult = await config.fetch(getProductQuery, {
variables, variables,
}) })
const { productByHandle } = data const { productByHandle } = data
return { return {

View File

@ -1,6 +1,6 @@
import { FetcherError } from '@commerce/utils/errors' import { FetcherError } from '@commerce/utils/errors'
export function getError(errors: any[], status: number) { export function getError(errors: any[] | null, status: number) {
errors = errors ?? [{ message: 'Failed to fetch Shopify API' }] errors = errors ?? [{ message: 'Failed to fetch Shopify API' }]
return new FetcherError({ errors, status }) return new FetcherError({ errors, status })
} }

View File

@ -68,6 +68,7 @@ const normalizeProductVariants = ({ edges }: ProductVariantConnection) => {
name, name,
values: [value], values: [value],
}) })
return options return options
}), }),
} }
@ -154,12 +155,6 @@ function normalizeLineItem({
discounts: [], discounts: [],
options: options:
// By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095 // By default Shopify adds a default variant with default names, we're removing it. https://community.shopify.com/c/Shopify-APIs-SDKs/Adding-new-product-variant-is-automatically-adding-quot-Default/td-p/358095
variant?.title == 'Default Title' variant?.title == 'Default Title' ? [] : variant?.selectedOptions,
? []
: [
{
value: variant?.title,
},
],
} }
} }

View File

@ -29,6 +29,10 @@ export const checkoutDetailsFragment = `
id id
sku sku
title title
selectedOptions {
name
value
}
image { image {
originalSrc originalSrc
altText altText

View File

@ -48,7 +48,7 @@ export default function Cart() {
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center"> <h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
Your cart is empty Your cart is empty
</h2> </h2>
<p className="text-accents-6 px-10 text-center pt-2"> <p className="text-accent-6 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake. Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
</p> </p>
</div> </div>
@ -75,7 +75,7 @@ export default function Cart() {
<div className="px-4 sm:px-6 flex-1"> <div className="px-4 sm:px-6 flex-1">
<Text variant="pageHeading">My Cart</Text> <Text variant="pageHeading">My Cart</Text>
<Text variant="sectionHeading">Review your Order</Text> <Text variant="sectionHeading">Review your Order</Text>
<ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accents-2 border-b border-accents-2"> <ul className="py-6 space-y-6 sm:py-0 sm:space-y-0 sm:divide-y sm:divide-accent-2 border-b border-accent-2">
{data!.lineItems.map((item) => ( {data!.lineItems.map((item) => (
<CartItem <CartItem
key={item.id} key={item.id}
@ -93,7 +93,7 @@ export default function Cart() {
{[1, 2, 3, 4, 5, 6].map((x) => ( {[1, 2, 3, 4, 5, 6].map((x) => (
<div <div
key={x} key={x}
className="border border-accents-3 w-full h-24 bg-accents-2 bg-opacity-50 transform cursor-pointer hover:scale-110 duration-75" className="border border-accent-3 w-full h-24 bg-accent-2 bg-opacity-50 transform cursor-pointer hover:scale-110 duration-75"
/> />
))} ))}
</div> </div>
@ -107,7 +107,7 @@ export default function Cart() {
<> <>
{/* Shipping Address */} {/* Shipping Address */}
{/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */} {/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */}
<div className="rounded-md border border-accents-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accents-4"> <div className="rounded-md border border-accent-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accent-4">
<div className="mr-5"> <div className="mr-5">
<MapPin /> <MapPin />
</div> </div>
@ -121,7 +121,7 @@ export default function Cart() {
</div> </div>
{/* Payment Method */} {/* Payment Method */}
{/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */} {/* Only available with customCheckout set to true - Meaning that the provider does offer checkout functionality. */}
<div className="rounded-md border border-accents-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accents-4"> <div className="rounded-md border border-accent-2 px-6 py-6 mb-4 text-center flex items-center justify-center cursor-pointer hover:border-accent-4">
<div className="mr-5"> <div className="mr-5">
<CreditCard /> <CreditCard />
</div> </div>
@ -132,7 +132,7 @@ export default function Cart() {
</div> </div>
</> </>
)} )}
<div className="border-t border-accents-2"> <div className="border-t border-accent-2">
<ul className="py-3"> <ul className="py-3">
<li className="flex justify-between py-1"> <li className="flex justify-between py-1">
<span>Subtotal</span> <span>Subtotal</span>
@ -147,7 +147,7 @@ export default function Cart() {
<span className="font-bold tracking-wide">FREE</span> <span className="font-bold tracking-wide">FREE</span>
</li> </li>
</ul> </ul>
<div className="flex justify-between border-t border-accents-2 py-3 font-bold mb-10"> <div className="flex justify-between border-t border-accent-2 py-3 font-bold mb-10">
<span>Total</span> <span>Total</span>
<span>{total}</span> <span>{total}</span>
</div> </div>

View File

@ -27,7 +27,7 @@ export default function Orders() {
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center"> <h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
No orders found No orders found
</h2> </h2>
<p className="text-accents-6 px-10 text-center pt-2"> <p className="text-accent-6 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake. Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
</p> </p>
</div> </div>

View File

@ -48,7 +48,7 @@ export default function Wishlist() {
<h2 className="pt-6 text-2xl font-bold tracking-wide text-center"> <h2 className="pt-6 text-2xl font-bold tracking-wide text-center">
Your wishlist is empty Your wishlist is empty
</h2> </h2>
<p className="text-accents-6 px-10 text-center pt-2"> <p className="text-accent-6 px-10 text-center pt-2">
Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake. Biscuit oat cake wafer icing ice cream tiramisu pudding cupcake.
</p> </p>
</div> </div>

View File

@ -27,16 +27,16 @@ module.exports = {
hover: 'var(--hover)', hover: 'var(--hover)',
'hover-1': 'var(--hover-1)', 'hover-1': 'var(--hover-1)',
'hover-2': 'var(--hover-2)', 'hover-2': 'var(--hover-2)',
'accents-0': 'var(--accents-0)', 'accent-0': 'var(--accent-0)',
'accents-1': 'var(--accents-1)', 'accent-1': 'var(--accent-1)',
'accents-2': 'var(--accents-2)', 'accent-2': 'var(--accent-2)',
'accents-3': 'var(--accents-3)', 'accent-3': 'var(--accent-3)',
'accents-4': 'var(--accents-4)', 'accent-4': 'var(--accent-4)',
'accents-5': 'var(--accents-5)', 'accent-5': 'var(--accent-5)',
'accents-6': 'var(--accents-6)', 'accent-6': 'var(--accent-6)',
'accents-7': 'var(--accents-7)', 'accent-7': 'var(--accent-7)',
'accents-8': 'var(--accents-8)', 'accent-8': 'var(--accent-8)',
'accents-9': 'var(--accents-9)', 'accent-9': 'var(--accent-9)',
violet: 'var(--violet)', violet: 'var(--violet)',
'violet-light': 'var(--violet-light)', 'violet-light': 'var(--violet-light)',
pink: 'var(--pink)', pink: 'var(--pink)',
@ -51,7 +51,7 @@ module.exports = {
secondary: 'var(--text-secondary)', secondary: 'var(--text-secondary)',
}, },
boxShadow: { boxShadow: {
'outline-normal': '0 0 0 2px var(--accents-2)', 'outline-normal': '0 0 0 2px var(--accent-2)',
magical: magical:
'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px', 'rgba(0, 0, 0, 0.02) 0px 30px 30px, rgba(0, 0, 0, 0.03) 0px 0px 8px, rgba(0, 0, 0, 0.05) 0px 1px 0px',
}, },

View File

@ -22,8 +22,8 @@
"@components/*": ["components/*"], "@components/*": ["components/*"],
"@commerce": ["framework/commerce"], "@commerce": ["framework/commerce"],
"@commerce/*": ["framework/commerce/*"], "@commerce/*": ["framework/commerce/*"],
"@framework": ["framework/bigcommerce"], "@framework": ["framework/shopify"],
"@framework/*": ["framework/bigcommerce/*"] "@framework/*": ["framework/shopify/*"]
} }
}, },
"include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"], "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],