diff --git a/packages/opencommerce/src/cart/use-add-item.tsx b/packages/opencommerce/src/cart/use-add-item.tsx index 2be6e0aaa..684b90794 100644 --- a/packages/opencommerce/src/cart/use-add-item.tsx +++ b/packages/opencommerce/src/cart/use-add-item.tsx @@ -1,17 +1,42 @@ +import { useCallback } from 'react' import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item' +import type { AddItemHook } from '@vercel/commerce/types/cart' +import { CommerceError } from '@vercel/commerce/utils/errors' import { MutationHook } from '@vercel/commerce/utils/types' +import useCart from './use-cart' export default useAddItem as UseAddItem -export const handler: MutationHook = { + +export const handler: MutationHook = { fetchOptions: { - query: '', + url: '/api/cart', + method: 'POST', + }, + async fetcher({ input: item, options, fetch }) { + if ( + item.quantity && + (!Number.isInteger(item.quantity) || item.quantity! < 1) + ) { + throw new CommerceError({ + message: 'The item quantity has to be a valid integer greater than 0', + }) + } + + const data = await fetch({ ...options, body: { item } }) + return data }, - async fetcher({ input, options, fetch }) {}, useHook: ({ fetch }) => () => { - return async function addItem() { - return {} - } + const { mutate } = useCart() + + return useCallback( + async function addItem(input) { + const data = await fetch({ input }) + await mutate(data, false) + return data + }, + [fetch, mutate] + ) }, } diff --git a/packages/opencommerce/src/cart/use-cart.tsx b/packages/opencommerce/src/cart/use-cart.tsx index 8f92de3c9..3041488cd 100644 --- a/packages/opencommerce/src/cart/use-cart.tsx +++ b/packages/opencommerce/src/cart/use-cart.tsx @@ -1,42 +1,32 @@ import { useMemo } from 'react' import { SWRHook } from '@vercel/commerce/utils/types' import useCart, { UseCart } from '@vercel/commerce/cart/use-cart' +import type { GetCartHook } from '@vercel/commerce/types/cart' export default useCart as UseCart -export const handler: SWRHook = { +export const handler: SWRHook = { fetchOptions: { - query: '', - }, - async fetcher() { - return { - id: '', - createdAt: '', - currency: { code: '' }, - taxesIncluded: '', - lineItems: [], - lineItemsSubtotalPrice: '', - subtotalPrice: 0, - totalPrice: 0, - } + url: '/api/cart', + method: 'GET', }, useHook: ({ useData }) => (input) => { + const response = useData({ + swrOptions: { revalidateOnFocus: false, ...input?.swrOptions }, + }) return useMemo( () => - Object.create( - {}, - { - isEmpty: { - get() { - return true - }, - enumerable: true, + Object.create(response, { + isEmpty: { + get() { + return (response.data?.lineItems.length ?? 0) <= 0 }, - } - ), - [] + enumerable: true, + }, + }), + [response] ) }, } diff --git a/packages/opencommerce/src/cart/use-remove-item.tsx b/packages/opencommerce/src/cart/use-remove-item.tsx index 92d52c997..f9efd276d 100644 --- a/packages/opencommerce/src/cart/use-remove-item.tsx +++ b/packages/opencommerce/src/cart/use-remove-item.tsx @@ -1,20 +1,59 @@ -import { MutationHook } from '@vercel/commerce/utils/types' +import { useCallback } from 'react' +import { + HookFetcherContext, + MutationHookContext, +} from '@vercel/commerce/utils/types' import useRemoveItem, { UseRemoveItem, } from '@vercel/commerce/cart/use-remove-item' +import type { + LineItem, + RemoveItemHook, + Cart, +} from '@vercel/commerce/types/cart' +import { ValidationError } from '@vercel/commerce/utils/errors' +import useCart from './use-cart' + +export type RemoveItemActionInput = T extends LineItem + ? Partial + : RemoveItemHook['actionInput'] + +export type RemoveItemFn = T extends LineItem + ? (input?: RemoveItemActionInput) => Promise + : (input: RemoveItemActionInput) => Promise export default useRemoveItem as UseRemoveItem -export const handler: MutationHook = { +export const handler = { fetchOptions: { - query: '', + url: '/api/cart', + method: 'DELETE', + }, + async fetcher({ + input: { itemId }, + options, + fetch, + }: HookFetcherContext) { + return await fetch({ ...options, body: { itemId } }) }, - async fetcher({ input, options, fetch }) {}, useHook: - ({ fetch }) => - () => { - return async function removeItem(input) { - return {} + ({ fetch }: MutationHookContext) => + (ctx: { item?: T } = {}) => { + const { item } = ctx + const { mutate } = useCart() + const removeItem: RemoveItemFn = async (input) => { + const itemId = input?.id ?? item?.id + if (!itemId) { + throw new ValidationError({ + message: 'Invalid input used for this operation', + }) + } + + const data = await fetch({ input: { itemId } }) + await mutate(data, false) + return data } + + return useCallback(removeItem, [fetch, mutate]) }, } diff --git a/packages/opencommerce/src/cart/use-update-item.tsx b/packages/opencommerce/src/cart/use-update-item.tsx index 950f422e1..566208b0b 100644 --- a/packages/opencommerce/src/cart/use-update-item.tsx +++ b/packages/opencommerce/src/cart/use-update-item.tsx @@ -1,20 +1,76 @@ -import { MutationHook } from '@vercel/commerce/utils/types' +import { useCallback } from 'react' +import debounce from 'lodash.debounce' +import { MutationHook, MutationHookContext } from '@vercel/commerce/utils/types' import useUpdateItem, { UseUpdateItem, } from '@vercel/commerce/cart/use-update-item' +import type { LineItem, UpdateItemHook } from '@vercel/commerce/types/cart' +import { ValidationError } from '@vercel/commerce/utils/errors' +import { handler as removeItemHandler } from './use-remove-item' +import useCart from './use-cart' -export default useUpdateItem as UseUpdateItem +export type UpdateItemActionInput = T extends LineItem + ? Partial + : UpdateItemHook['actionInput'] -export const handler: MutationHook = { +export default useUpdateItem as UseUpdateItem + +export const handler: MutationHook = { fetchOptions: { - query: '', + query: '/api/cart', + method: 'PUT', }, - async fetcher({ input, options, fetch }) {}, - useHook: - ({ fetch }) => - () => { - return async function addItem() { - return {} + async fetcher({ input: { itemId, item }, options, fetch }) { + if (Number.isInteger(item.quantity)) { + // Also allow the update hook to remove an item if the quantity is lower than 1 + if (item.quantity! < 1) { + return removeItemHandler.fetcher({ + options: removeItemHandler.fetchOptions, + input: { itemId }, + fetch, + }) } + } else if (item.quantity) { + throw new ValidationError({ + message: 'The item quantity has to be a valid integer', + }) + } + + return await fetch({ + ...options, + body: { itemId, item }, + }) + }, + useHook: + ({ fetch }: MutationHookContext) => + ( + ctx: { item?: T; wait?: number } = {} + ) => { + const { item, wait } = ctx + const { mutate } = useCart() + + return useCallback( + debounce(async (input: UpdateItemActionInput) => { + const itemId = input.id ?? item?.id + const productId = input.productId ?? item?.productId + const variantId = input.productId ?? item?.variantId + + if (!itemId || !productId || !variantId) { + throw new ValidationError({ + message: 'Invalid input used for this operation', + }) + } + + const data = await fetch({ + input: { + itemId, + item: { productId, variantId, quantity: input.quantity }, + }, + }) + await mutate(data, false) + return data + }, wait ?? 500), + [fetch, mutate] + ) }, }