4
0
forked from crowetic/commerce

Merge branch 'agnostic' of github.com:vercel/commerce into agnostic

This commit is contained in:
okbel 2021-02-04 11:05:06 -03:00
commit 6cacfec8c7
28 changed files with 270 additions and 188 deletions

View File

@ -90,8 +90,8 @@ Our commitment to Open Source can be found [here](https://vercel.com/oss).
5. Duplicate `.env.template` and rename it to `.env.local`. 5. Duplicate `.env.template` and rename it to `.env.local`.
6. Add proper store values to `.env.local`. 6. Add proper store values to `.env.local`.
7. Run `yarn dev` to build and watch for code changes 7. Run `yarn dev` to build and watch for code changes
8. The development branch is `development` (this is the branch pull requests should be made against). 8. The development branch is `canary` (this is the branch pull requests should be made against).
On a release, `develop` branch is rebased into `master`. On a release, `canary` branch is rebased into `master`.
## Framework ## Framework

View File

@ -102,8 +102,6 @@ a {
} }
.animated { .animated {
-webkit-animation-duration: 1s;
animation-duration: 1s;
-webkit-animation-duration: 1s; -webkit-animation-duration: 1s;
animation-duration: 1s; animation-duration: 1s;
-webkit-animation-fill-mode: both; -webkit-animation-fill-mode: both;

View File

@ -10,7 +10,14 @@ import usePrice from '@framework/product/use-price'
import useUpdateItem from '@framework/cart/use-update-item' import useUpdateItem from '@framework/cart/use-update-item'
import useRemoveItem from '@framework/cart/use-remove-item' import useRemoveItem from '@framework/cart/use-remove-item'
const Item = ({ type ItemOption = {
name: string
nameId: number
value: string
valueId: number
}
const CartItem = ({
item, item,
currencyCode, currencyCode,
...rest ...rest
@ -63,7 +70,7 @@ const Item = ({
try { try {
// If this action succeeds then there's no need to do `setRemoving(true)` // If this action succeeds then there's no need to do `setRemoving(true)`
// because the component will be removed from the view // because the component will be removed from the view
await removeItem({ id: String(item.id) }) await removeItem(item)
} catch (error) { } catch (error) {
setRemoving(false) setRemoving(false)
} }
@ -96,14 +103,26 @@ const Item = ({
<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 mb-5 text-lg cursor-pointer" className="font-bold text-lg cursor-pointer leading-6"
onClick={() => closeSidebarIfPresent()} onClick={() => closeSidebarIfPresent()}
> >
{item.name} {item.name}
</span> </span>
</Link> </Link>
{item.options && item.options.length > 0 ? (
<div className="flex items-center"> <div className="">
{item.options.map((option: ItemOption, i: number) => (
<span
key={`${item.id}-${option.name}`}
className="text-sm font-semibold text-accents-7"
>
{option.value}
{i === item.options.length - 1 ? '' : ', '}
</span>
))}
</div>
) : null}
<div className="flex items-center mt-3">
<button type="button" onClick={() => increaseQuantity(-1)}> <button type="button" onClick={() => increaseQuantity(-1)}>
<Minus width={18} height={18} /> <Minus width={18} height={18} />
</button> </button>
@ -136,4 +155,4 @@ const Item = ({
) )
} }
export default Item export default CartItem

View File

@ -1,4 +1,3 @@
import cn from 'classnames'
import { FC, useState, useMemo, useRef, useEffect } from 'react' import { FC, useState, useMemo, useRef, useEffect } from 'react'
import { getRandomPairOfColors } from '@lib/colors' import { getRandomPairOfColors } from '@lib/colors'

View File

@ -29,7 +29,7 @@
} }
.item { .item {
@apply flex cursor-pointer px-6 py-3 flex transition ease-in-out duration-150 text-primary leading-6 font-medium items-center; @apply flex cursor-pointer px-6 py-3 transition ease-in-out duration-150 text-primary leading-6 font-medium items-center;
text-transform: capitalize; text-transform: capitalize;
} }

View File

@ -62,16 +62,16 @@ const Layout: FC<Props> = ({ children, pageProps }) => {
<main className="fit">{children}</main> <main className="fit">{children}</main>
<Footer pages={pageProps.pages} /> <Footer pages={pageProps.pages} />
<Sidebar open={displaySidebar} onClose={closeSidebar}>
<CartSidebarView />
</Sidebar>
<Modal open={displayModal} onClose={closeModal}> <Modal open={displayModal} onClose={closeModal}>
{modalView === 'LOGIN_VIEW' && <LoginView />} {modalView === 'LOGIN_VIEW' && <LoginView />}
{modalView === 'SIGNUP_VIEW' && <SignUpView />} {modalView === 'SIGNUP_VIEW' && <SignUpView />}
{modalView === 'FORGOT_VIEW' && <ForgotPassword />} {modalView === 'FORGOT_VIEW' && <ForgotPassword />}
</Modal> </Modal>
<Sidebar open={displaySidebar} onClose={closeSidebar}>
<CartSidebarView />
</Sidebar>
<FeatureBar <FeatureBar
title="This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy." title="This site uses cookies to improve your experience. By clicking, you agree to our Privacy Policy."
hide={acceptedCookies} hide={acceptedCookies}

View File

@ -12,76 +12,74 @@ interface Props {
imgProps?: Omit<ImageProps, 'src'> imgProps?: Omit<ImageProps, 'src'>
} }
const placeholderImg = '/product-img-placeholder.svg'
const ProductCard: FC<Props> = ({ const ProductCard: FC<Props> = ({
className, className,
product, product,
variant, variant,
imgProps, imgProps,
...props ...props
}) => { }) => (
return ( <Link href={`/product/${product.slug}`} {...props}>
<Link href={`/product/${product.slug}`} {...props}> <a className={cn(s.root, { [s.simple]: variant === 'simple' }, className)}>
<a {variant === 'slim' ? (
className={cn(s.root, { [s.simple]: variant === 'simple' }, className)} <div className="relative overflow-hidden box-border">
> <div className="absolute inset-0 flex items-center justify-end mr-8 z-20">
{variant === 'slim' ? ( <span className="bg-black text-white inline-block p-3 font-bold text-xl break-words">
<div className="relative overflow-hidden box-border"> {product.name}
<div className="absolute inset-0 flex items-center justify-end mr-8 z-20"> </span>
<span className="bg-black text-white inline-block p-3 font-bold text-xl break-words"> </div>
{product.name} {product?.images && (
<Image
quality="85"
src={product.images[0].url || placeholderImg}
alt={product.name || 'Product Image'}
height={320}
width={320}
layout="fixed"
{...imgProps}
/>
)}
</div>
) : (
<>
<div className={s.squareBg} />
<div className="flex flex-row justify-between box-border w-full z-20 absolute">
<div className="absolute top-0 left-0 pr-16 max-w-full">
<h3 className={s.productTitle}>
<span>{product.name}</span>
</h3>
<span className={s.productPrice}>
{product.price.value}
&nbsp;
{product.price.currencyCode}
</span> </span>
</div> </div>
{product?.images && ( {/* <WishlistButton
<Image
quality="85"
alt={product.name}
src={product.images[0].url!}
height={320}
width={320}
layout="fixed"
{...imgProps}
/>
)}
</div>
) : (
<>
<div className={s.squareBg} />
<div className="flex flex-row justify-between box-border w-full z-20 absolute">
<div className="absolute top-0 left-0 pr-16 max-w-full">
<h3 className={s.productTitle}>
<span>{product.name}</span>
</h3>
<span className={s.productPrice}>
{product.price.value}
&nbsp;
{product.price.currencyCode}
</span>
</div>
{/* <WishlistButton
className={s.wishlistButton} className={s.wishlistButton}
productId={product.id} productId={product.id}
variant={product.variants[0]} variant={product.variants[0]}
/> */} /> */}
</div> </div>
<div className={s.imageContainer}> <div className={s.imageContainer}>
{product?.images && ( {product?.images && (
<Image <Image
alt={product.name} alt={product.name || 'Product Image'}
className={s.productImage} className={s.productImage}
src={product.images[0].url} src={product.images[0].url || placeholderImg}
height={540} height={540}
width={540} width={540}
quality="85" quality="85"
layout="responsive" layout="responsive"
{...imgProps} {...imgProps}
/> />
)} )}
</div> </div>
</> </>
)} )}
</a> </a>
</Link> </Link>
) )
}
export default ProductCard export default ProductCard

View File

@ -40,10 +40,8 @@ const ProductView: FC<Props> = ({ product }) => {
setLoading(true) setLoading(true)
try { try {
await addItem({ await addItem({
productId: Number(product.id), productId: product.id,
variantId: variant variantId: variant ? variant.id : product.variants[0].id,
? Number(variant.id)
: Number(product.variants[0].id),
}) })
openSidebar() openSidebar()
setLoading(false) setLoading(false)

View File

@ -90,6 +90,7 @@ function uiReducer(state: State, action: Action) {
return { return {
...state, ...state,
displayModal: true, displayModal: true,
displaySidebar: false,
} }
} }
case 'CLOSE_MODAL': { case 'CLOSE_MODAL': {

View File

@ -25,8 +25,14 @@ const addItem: CartHandlers['addItem'] = async ({
}), }),
} }
const { data } = cartId const { data } = cartId
? await config.storeApiFetch(`/v3/carts/${cartId}/items`, options) ? await config.storeApiFetch(
: await config.storeApiFetch('/v3/carts', options) `/v3/carts/${cartId}/items?include=line_items.physical_items.options`,
options
)
: await config.storeApiFetch(
'/v3/carts?include=line_items.physical_items.options',
options
)
// Create or update the cart cookie // Create or update the cart cookie
res.setHeader( res.setHeader(

View File

@ -1,6 +1,7 @@
import type { BigcommerceCart } from '../../../types'
import { BigcommerceApiError } from '../../utils/errors' import { BigcommerceApiError } from '../../utils/errors'
import getCartCookie from '../../utils/get-cart-cookie' import getCartCookie from '../../utils/get-cart-cookie'
import type { Cart, CartHandlers } from '..' import type { CartHandlers } from '../'
// Return current cart info // Return current cart info
const getCart: CartHandlers['getCart'] = async ({ const getCart: CartHandlers['getCart'] = async ({
@ -8,11 +9,13 @@ const getCart: CartHandlers['getCart'] = async ({
body: { cartId }, body: { cartId },
config, config,
}) => { }) => {
let result: { data?: Cart } = {} let result: { data?: BigcommerceCart } = {}
if (cartId) { if (cartId) {
try { try {
result = await config.storeApiFetch(`/v3/carts/${cartId}`) result = await config.storeApiFetch(
`/v3/carts/${cartId}?include=line_items.physical_items.options`
)
} catch (error) { } catch (error) {
if (error instanceof BigcommerceApiError && error.status === 404) { if (error instanceof BigcommerceApiError && error.status === 404) {
// Remove the cookie if it exists but the cart wasn't found // Remove the cookie if it exists but the cart wasn't found

View File

@ -14,7 +14,7 @@ const removeItem: CartHandlers['removeItem'] = async ({
} }
const result = await config.storeApiFetch<{ data: any } | null>( const result = await config.storeApiFetch<{ data: any } | null>(
`/v3/carts/${cartId}/items/${itemId}`, `/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
{ method: 'DELETE' } { method: 'DELETE' }
) )
const data = result?.data ?? null const data = result?.data ?? null

View File

@ -14,11 +14,8 @@ const updateItem: CartHandlers['updateItem'] = async ({
}) })
} }
console.log('ITEM', item)
console.log('AFTER', parseCartItem(item))
const { data } = await config.storeApiFetch( const { data } = await config.storeApiFetch(
`/v3/carts/${cartId}/items/${itemId}`, `/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
{ {
method: 'PUT', method: 'PUT',
body: JSON.stringify({ body: JSON.stringify({

View File

@ -8,38 +8,25 @@ import getCart from './handlers/get-cart'
import addItem from './handlers/add-item' import addItem from './handlers/add-item'
import updateItem from './handlers/update-item' import updateItem from './handlers/update-item'
import removeItem from './handlers/remove-item' import removeItem from './handlers/remove-item'
import type { Cart, UpdateCartItemHandlerBody } from '../../types' import type {
BigcommerceCart,
type OptionSelections = { GetCartHandlerBody,
option_id: Number AddCartItemHandlerBody,
option_value: Number | String UpdateCartItemHandlerBody,
} RemoveCartItemHandlerBody,
} from '../../types'
export type ItemBody = {
productId: number
variantId: number
quantity?: number
optionSelections?: OptionSelections
}
export type AddItemBody = { item: ItemBody }
export type RemoveItemBody = { itemId: string }
export type CartHandlers = { export type CartHandlers = {
getCart: BigcommerceHandler<Cart, { cartId?: string }> getCart: BigcommerceHandler<BigcommerceCart, GetCartHandlerBody>
addItem: BigcommerceHandler<Cart, { cartId?: string } & Partial<AddItemBody>> addItem: BigcommerceHandler<BigcommerceCart, AddCartItemHandlerBody>
updateItem: BigcommerceHandler<Cart, UpdateCartItemHandlerBody> updateItem: BigcommerceHandler<BigcommerceCart, UpdateCartItemHandlerBody>
removeItem: BigcommerceHandler< removeItem: BigcommerceHandler<BigcommerceCart, RemoveCartItemHandlerBody>
Cart,
{ cartId?: string } & Partial<RemoveItemBody>
>
} }
const METHODS = ['GET', 'POST', 'PUT', 'DELETE'] const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
// TODO: a complete implementation should have schema validation for `req.body` // TODO: a complete implementation should have schema validation for `req.body`
const cartApi: BigcommerceApiHandler<Cart, CartHandlers> = async ( const cartApi: BigcommerceApiHandler<BigcommerceCart, CartHandlers> = async (
req, req,
res, res,
config, config,

View File

@ -1,13 +1,16 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import type { HookFetcher } from '@commerce/utils/types' import type { HookFetcher } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors' import { CommerceError } from '@commerce/utils/errors'
import useCartAddItem from '@commerce/cart/use-add-item' import useCartAddItem, {
AddItemInput as UseAddItemInput,
} from '@commerce/cart/use-add-item'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { import type {
ItemBody, AddCartItemBody,
AddItemBody, Cart,
Cart as BigcommerceCart, BigcommerceCart,
} from '../api/cart' CartItemBody,
} from '../types'
import useCart from './use-cart' import useCart from './use-cart'
const defaultOpts = { const defaultOpts = {
@ -15,9 +18,9 @@ const defaultOpts = {
method: 'POST', method: 'POST',
} }
export type AddItemInput = ItemBody export type AddItemInput = UseAddItemInput<CartItemBody>
export const fetcher: HookFetcher<Cart, AddItemBody> = async ( export const fetcher: HookFetcher<Cart, AddCartItemBody> = async (
options, options,
{ item }, { item },
fetch fetch
@ -31,7 +34,7 @@ export const fetcher: HookFetcher<Cart, AddItemBody> = async (
}) })
} }
const data = await fetch<BigcommerceCart>({ const data = await fetch<BigcommerceCart, AddCartItemBody>({
...defaultOpts, ...defaultOpts,
...options, ...options,
body: { item }, body: { item },

View File

@ -3,7 +3,7 @@ import type { SwrOptions } from '@commerce/utils/use-data'
import useResponse from '@commerce/utils/use-response' import useResponse from '@commerce/utils/use-response'
import useCommerceCart, { CartInput } from '@commerce/cart/use-cart' import useCommerceCart, { CartInput } from '@commerce/cart/use-cart'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { Cart as BigcommerceCart } from '../api/cart' import type { Cart, BigcommerceCart } from '../types'
const defaultOpts = { const defaultOpts = {
url: '/api/bigcommerce/cart', url: '/api/bigcommerce/cart',

View File

@ -1,8 +1,16 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { HookFetcher } from '@commerce/utils/types' import { HookFetcher } from '@commerce/utils/types'
import useCartRemoveItem from '@commerce/cart/use-remove-item' import { ValidationError } from '@commerce/utils/errors'
import useCartRemoveItem, {
RemoveItemInput as UseRemoveItemInput,
} from '@commerce/cart/use-remove-item'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { RemoveItemBody, Cart as BigcommerceCart } from '../api/cart' import type {
RemoveCartItemBody,
Cart,
BigcommerceCart,
LineItem,
} from '../types'
import useCart from './use-cart' import useCart from './use-cart'
const defaultOpts = { const defaultOpts = {
@ -10,11 +18,15 @@ const defaultOpts = {
method: 'DELETE', method: 'DELETE',
} }
export type RemoveItemInput = { export type RemoveItemFn<T = any> = T extends LineItem
id: string ? (input?: RemoveItemInput<T>) => Promise<Cart | null>
} : (input: RemoveItemInput<T>) => Promise<Cart | null>
export const fetcher: HookFetcher<Cart | null, RemoveItemBody> = async ( export type RemoveItemInput<T = any> = T extends LineItem
? Partial<UseRemoveItemInput>
: UseRemoveItemInput
export const fetcher: HookFetcher<Cart | null, RemoveCartItemBody> = async (
options, options,
{ itemId }, { itemId },
fetch fetch
@ -28,21 +40,29 @@ export const fetcher: HookFetcher<Cart | null, RemoveItemBody> = async (
} }
export function extendHook(customFetcher: typeof fetcher) { export function extendHook(customFetcher: typeof fetcher) {
const useRemoveItem = (item?: any) => { const useRemoveItem = <T extends LineItem | undefined = undefined>(
item?: T
) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartRemoveItem<Cart | null, RemoveItemBody>( const fn = useCartRemoveItem<Cart | null, RemoveCartItemBody>(
defaultOpts, defaultOpts,
customFetcher customFetcher
) )
const removeItem: RemoveItemFn<LineItem> = async (input) => {
const itemId = input?.id ?? item?.id
return useCallback( if (!itemId) {
async function removeItem(input: RemoveItemInput) { throw new ValidationError({
const data = await fn({ itemId: input.id ?? item?.id }) message: 'Invalid input used for this operation',
await mutate(data, false) })
return data }
},
[fn, mutate] const data = await fn({ itemId })
) await mutate(data, false)
return data
}
return useCallback(removeItem as RemoveItemFn<T>, [fn, mutate])
} }
useRemoveItem.extend = extendHook useRemoveItem.extend = extendHook

View File

@ -2,11 +2,12 @@ import { useCallback } from 'react'
import debounce from 'lodash.debounce' import debounce from 'lodash.debounce'
import type { HookFetcher } from '@commerce/utils/types' import type { HookFetcher } from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useCartUpdateItem from '@commerce/cart/use-update-item' import useCartUpdateItem, {
UpdateItemInput as UseUpdateItemInput,
} from '@commerce/cart/use-update-item'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { import type {
UpdateCartItemBody, UpdateCartItemBody,
UpdateCartItemInput,
Cart, Cart,
BigcommerceCart, BigcommerceCart,
LineItem, LineItem,
@ -19,6 +20,10 @@ const defaultOpts = {
method: 'PUT', method: 'PUT',
} }
export type UpdateItemInput<T = any> = T extends LineItem
? Partial<UseUpdateItemInput<LineItem>>
: UseUpdateItemInput<LineItem>
export const fetcher: HookFetcher<Cart | null, UpdateCartItemBody> = async ( export const fetcher: HookFetcher<Cart | null, UpdateCartItemBody> = async (
options, options,
{ itemId, item }, { itemId, item },
@ -55,31 +60,24 @@ function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
) )
return useCallback( return useCallback(
debounce( debounce(async (input: UpdateItemInput<T>) => {
async ( const itemId = input.id ?? item?.id
input: T extends LineItem const productId = input.productId ?? item?.productId
? Partial<UpdateCartItemInput> const variantId = input.productId ?? item?.variantId
: UpdateCartItemInput
) => {
const itemId = input.id ?? item?.id
const productId = input.productId ?? item?.productId
const variantId = input.productId ?? item?.variantId
if (!itemId || !productId || !variantId) { if (!itemId || !productId || !variantId) {
throw new ValidationError({ throw new ValidationError({
message: 'Invalid input used for this operation', message: 'Invalid input used for this operation',
})
}
const data = await fn({
itemId,
item: { productId, variantId, quantity: input.quantity },
}) })
await mutate(data, false) }
return data
}, const data = await fn({
cfg?.wait ?? 500 itemId,
), item: { productId, variantId, quantity: input.quantity },
})
await mutate(data, false)
return data
}, cfg?.wait ?? 500),
[fn, mutate] [fn, mutate]
) )
} }

View File

@ -26,8 +26,7 @@ export const fetcher: HookFetcher<SearchProductsData, SearchProductsInput> = (
if (search) url.searchParams.set('search', search) if (search) url.searchParams.set('search', search)
if (Number.isInteger(categoryId)) if (Number.isInteger(categoryId))
url.searchParams.set('category', String(categoryId)) url.searchParams.set('category', String(categoryId))
if (Number.isInteger(categoryId)) if (Number.isInteger(brandId)) url.searchParams.set('brand', String(brandId))
url.searchParams.set('brand', String(brandId))
if (sort) url.searchParams.set('sort', sort) if (sort) url.searchParams.set('sort', sort)
return fetch({ return fetch({

View File

@ -43,12 +43,20 @@ export interface CartItemBody extends Core.CartItemBody {
optionSelections?: OptionSelections optionSelections?: OptionSelections
} }
export interface UpdateCartItemBody extends Core.UpdateCartItemBody { export interface GetCartHandlerBody extends Core.GetCartHandlerBody {}
item: CartItemBody
}
export interface UpdateCartItemInput export interface AddCartItemBody extends Core.AddCartItemBody<CartItemBody> {}
extends Core.UpdateCartItemInput<CartItemBody> {}
export interface AddCartItemHandlerBody
extends Core.AddCartItemHandlerBody<CartItemBody> {}
export interface UpdateCartItemBody
extends Core.UpdateCartItemBody<CartItemBody> {}
export interface UpdateCartItemHandlerBody export interface UpdateCartItemHandlerBody
extends Core.UpdateCartItemHandlerBody {} extends Core.UpdateCartItemHandlerBody<CartItemBody> {}
export interface RemoveCartItemBody extends Core.RemoveCartItemBody {}
export interface RemoveCartItemHandlerBody
extends Core.RemoveCartItemHandlerBody {}

View File

@ -1,4 +1,8 @@
import useAction from '../utils/use-action' import useAction from '../utils/use-action'
import type { CartItemBody } from '../types'
// Input expected by the action returned by the `useAddItem` hook
export type AddItemInput<T extends CartItemBody> = T
const useAddItem = useAction const useAddItem = useAction

View File

@ -6,6 +6,7 @@ import { useCommerce } from '..'
export type CartResponse<Data> = ResponseState<Data> & { isEmpty?: boolean } export type CartResponse<Data> = ResponseState<Data> & { isEmpty?: boolean }
// Input expected by the `useCart` hook
export type CartInput = { export type CartInput = {
cartId?: Cart['id'] cartId?: Cart['id']
} }

View File

@ -1,5 +1,10 @@
import useAction from '../utils/use-action' import useAction from '../utils/use-action'
// Input expected by the action returned by the `useRemoveItem` hook
export interface RemoveItemInput {
id: string
}
const useRemoveItem = useAction const useRemoveItem = useAction
export default useRemoveItem export default useRemoveItem

View File

@ -1,4 +1,10 @@
import useAction from '../utils/use-action' import useAction from '../utils/use-action'
import type { CartItemBody } from '../types'
// Input expected by the action returned by the `useUpdateItem` hook
export type UpdateItemInput<T extends CartItemBody> = T & {
id: string
}
const useUpdateItem = useAction const useUpdateItem = useAction

View File

@ -87,6 +87,10 @@ export interface Cart {
discounts?: Discount[] discounts?: Discount[]
} }
/**
* Cart mutations
*/
// Base cart item body used for cart mutations // Base cart item body used for cart mutations
export interface CartItemBody { export interface CartItemBody {
variantId: string variantId: string
@ -94,18 +98,40 @@ export interface CartItemBody {
quantity?: number quantity?: number
} }
// Body by the update operation // Body used by the `getCart` operation handler
export interface UpdateCartItemBody { export interface GetCartHandlerBody {
itemId: string cartId?: string
item: CartItemBody }
}
// Body used by the add item to cart operation
// Input expected by the `useUpdateItem` hook export interface AddCartItemBody<T extends CartItemBody> {
export type UpdateCartItemInput<T extends CartItemBody> = T & { item: T
id: string }
}
// Body expected by the add item to cart operation handler
// Body expected by the update operation handler export interface AddCartItemHandlerBody<T extends CartItemBody>
export interface UpdateCartItemHandlerBody extends Partial<UpdateCartItemBody> { extends Partial<AddCartItemBody<T>> {
cartId?: string
}
// Body used by the update cart item operation
export interface UpdateCartItemBody<T extends CartItemBody> {
itemId: string
item: T
}
// Body expected by the update cart item operation handler
export interface UpdateCartItemHandlerBody<T extends CartItemBody>
extends Partial<UpdateCartItemBody<T>> {
cartId?: string
}
// Body used by the remove cart item operation
export interface RemoveCartItemBody {
itemId: string
}
// Body expected by the remove cart item operation handler
export interface RemoveCartItemHandlerBody extends Partial<RemoveCartItemBody> {
cartId?: string cartId?: string
} }

View File

@ -65,7 +65,7 @@ export default function Pages({
page, page,
}: InferGetStaticPropsType<typeof getStaticProps>) { }: InferGetStaticPropsType<typeof getStaticProps>) {
return ( return (
<div className="max-w-2xl mx-auto py-20"> <div className="max-w-2xl mx-8 sm:mx-auto py-20">
{page?.body && <Text html={page.body} />} {page?.body && <Text html={page.body} />}
</div> </div>
) )

View File

@ -4,10 +4,9 @@ import getAllPages from '@framework/common/get-all-pages'
import useCart from '@framework/cart/use-cart' import useCart from '@framework/cart/use-cart'
import usePrice from '@framework/product/use-price' import usePrice from '@framework/product/use-price'
import { Layout } from '@components/common' import { Layout } from '@components/common'
import { Button } from '@components/ui' import { Button, Text } from '@components/ui'
import { Bag, Cross, Check } from '@components/icons' import { Bag, Cross, Check } from '@components/icons'
import { CartItem } from '@components/cart' import { CartItem } from '@components/cart'
import { Text } from '@components/ui'
export async function getStaticProps({ export async function getStaticProps({
preview, preview,

View File

@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 800 800">
<defs/>
<g fill="none" fill-rule="nonzero">
<path fill="#EAEAEA" d="M0 0h800v800H0z"/>
<path fill="#FFF" d="M366.333 365.833c0 5.695-1.993 10.535-5.979 14.521-3.986 3.986-8.826 5.98-14.52 5.98-5.695 0-10.535-1.994-14.522-5.98-3.986-3.986-5.979-8.826-5.979-14.52 0-5.695 1.993-10.535 5.98-14.522 3.986-3.986 8.826-5.979 14.52-5.979 5.695 0 10.535 1.993 14.521 5.98 3.986 3.986 5.98 8.826 5.98 14.52zm109.334 41v47.834H325.333v-20.5L359.5 400l17.083 17.083 54.667-54.666 44.417 44.416zm10.25-75.166H315.083c-.925 0-1.726.338-2.402 1.014-.676.676-1.014 1.477-1.014 2.402v129.834c0 .925.338 1.726 1.014 2.402.676.676 1.477 1.014 2.402 1.014h170.834c.925 0 1.726-.338 2.402-1.014.676-.676 1.014-1.477 1.014-2.402V335.083c0-.925-.338-1.726-1.014-2.402-.676-.676-1.477-1.014-2.402-1.014zM503 335.083v129.834c0 4.698-1.673 8.72-5.018 12.065-3.346 3.345-7.367 5.018-12.065 5.018H315.083c-4.698 0-8.72-1.673-12.065-5.018-3.345-3.346-5.018-7.367-5.018-12.065V335.083c0-4.698 1.673-8.72 5.018-12.065 3.346-3.345 7.367-5.018 12.065-5.018h170.834c4.698 0 8.72 1.673 12.065 5.018 3.345 3.346 5.018 7.367 5.018 12.065z"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB