From c02d7fec62898917580bcf810d3205dcb82619ac Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Tue, 16 Feb 2021 21:14:11 -0500 Subject: [PATCH] Added initial version of useAddItem --- framework/bigcommerce/cart/use-add-item.tsx | 77 +++++++++------------ framework/bigcommerce/provider.ts | 3 +- framework/bigcommerce/types.ts | 27 ++++---- framework/commerce/cart/use-add-item.tsx | 70 +++++++++++++++++-- framework/commerce/index.tsx | 3 +- framework/commerce/types.ts | 34 ++++----- framework/commerce/utils/types.ts | 27 ++++---- 7 files changed, 145 insertions(+), 96 deletions(-) diff --git a/framework/bigcommerce/cart/use-add-item.tsx b/framework/bigcommerce/cart/use-add-item.tsx index c66ee462a..7aec2f9e0 100644 --- a/framework/bigcommerce/cart/use-add-item.tsx +++ b/framework/bigcommerce/cart/use-add-item.tsx @@ -1,9 +1,6 @@ -import { useCallback } from 'react' -import type { HookFetcher } from '@commerce/utils/types' +import type { MutationHandler } from '@commerce/utils/types' import { CommerceError } from '@commerce/utils/errors' -import useCartAddItem, { - AddItemInput as UseAddItemInput, -} from '@commerce/cart/use-add-item' +import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item' import { normalizeCart } from '../lib/normalize' import type { AddCartItemBody, @@ -12,55 +9,45 @@ import type { CartItemBody, } from '../types' import useCart from './use-cart' +import { BigcommerceProvider } from '..' const defaultOpts = { url: '/api/bigcommerce/cart', method: 'POST', } -export type AddItemInput = UseAddItemInput +export default useAddItem as UseAddItem -export const fetcher: HookFetcher = async ( - options, - { item }, - 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', +export const handler: MutationHandler = { + fetchOptions: { + url: '/api/bigcommerce/cart', + method: 'GET', + }, + 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({ + ...defaultOpts, + ...options, + body: { item }, }) - } - const data = await fetch({ - ...defaultOpts, - ...options, - body: { item }, - }) - - return normalizeCart(data) -} - -export function extendHook(customFetcher: typeof fetcher) { - const useAddItem = () => { + return normalizeCart(data) + }, + useHook() { const { mutate } = useCart() - const fn = useCartAddItem(defaultOpts, customFetcher) - return useCallback( - async function addItem(input: AddItemInput) { - const data = await fn({ item: input }) - await mutate(data, false) - return data - }, - [fn, mutate] - ) - } - - useAddItem.extend = extendHook - - return useAddItem + return async function addItem({ input, fetch }) { + const data = await fetch({ input }) + await mutate(data, false) + return data + } + }, } - -export default extendHook(fetcher) diff --git a/framework/bigcommerce/provider.ts b/framework/bigcommerce/provider.ts index ee5630813..08192df37 100644 --- a/framework/bigcommerce/provider.ts +++ b/framework/bigcommerce/provider.ts @@ -1,4 +1,5 @@ import { handler as useCart } from './cart/use-cart' +import { handler as useAddItem } from './cart/use-add-item' import { handler as useWishlist } from './wishlist/use-wishlist' import { handler as useCustomer } from './customer/use-customer' import { handler as useSearch } from './product/use-search' @@ -8,7 +9,7 @@ export const bigcommerceProvider = { locale: 'en-us', cartCookie: 'bc_cartId', fetcher, - cart: { useCart }, + cart: { useCart, useAddItem }, wishlist: { useWishlist }, customer: { useCustomer }, products: { useSearch }, diff --git a/framework/bigcommerce/types.ts b/framework/bigcommerce/types.ts index 90afb425d..16d1ea07a 100644 --- a/framework/bigcommerce/types.ts +++ b/framework/bigcommerce/types.ts @@ -23,11 +23,11 @@ export type BigcommerceCart = { // TODO: add missing fields } -export interface Cart extends Core.Cart { +export type Cart = Core.Cart & { lineItems: LineItem[] } -export interface LineItem extends Core.LineItem {} +export type LineItem = Core.LineItem /** * Cart mutations @@ -38,25 +38,24 @@ export type OptionSelections = { option_value: number | string } -export interface CartItemBody extends Core.CartItemBody { +export type CartItemBody = Core.CartItemBody & { productId: string // The product id is always required for BC optionSelections?: OptionSelections } -export interface GetCartHandlerBody extends Core.GetCartHandlerBody {} +type X = Core.CartItemBody extends CartItemBody ? any : never +type Y = CartItemBody extends Core.CartItemBody ? any : never -export interface AddCartItemBody extends Core.AddCartItemBody {} +export type GetCartHandlerBody = Core.GetCartHandlerBody -export interface AddCartItemHandlerBody - extends Core.AddCartItemHandlerBody {} +export type AddCartItemBody = Core.AddCartItemBody -export interface UpdateCartItemBody - extends Core.UpdateCartItemBody {} +export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody -export interface UpdateCartItemHandlerBody - extends Core.UpdateCartItemHandlerBody {} +export type UpdateCartItemBody = Core.UpdateCartItemBody -export interface RemoveCartItemBody extends Core.RemoveCartItemBody {} +export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody -export interface RemoveCartItemHandlerBody - extends Core.RemoveCartItemHandlerBody {} +export type RemoveCartItemBody = Core.RemoveCartItemBody + +export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody diff --git a/framework/commerce/cart/use-add-item.tsx b/framework/commerce/cart/use-add-item.tsx index 2f6422ab3..0a70ff30d 100644 --- a/framework/commerce/cart/use-add-item.tsx +++ b/framework/commerce/cart/use-add-item.tsx @@ -1,9 +1,69 @@ -import useAction from '../utils/use-action' -import type { CartItemBody } from '../types' +import { useCallback } from 'react' +import type { + Prop, + HookFetcherFn, + UseHookInput, + UseHookResponse, +} from '../utils/types' +import type { Cart, CartItemBody, AddCartItemBody } from '../types' +import { Provider, useCommerce } from '..' +import { BigcommerceProvider } from '@framework' + +export type UseAddItemHandler

= Prop< + Prop, + 'useAddItem' +> // Input expected by the action returned by the `useAddItem` hook -export type AddItemInput = T +export type UseAddItemInput

= UseHookInput< + UseAddItemHandler

+> -const useAddItem = useAction +export type UseAddItemResult

= ReturnType< + UseHookResponse> +> -export default useAddItem +export type UseAddItem

= Partial< + UseAddItemInput

+> extends UseAddItemInput

+ ? (input?: UseAddItemInput

) => (input: Input) => UseAddItemResult

+ : (input: UseAddItemInput

) => (input: Input) => UseAddItemResult

+ +export const fetcher: HookFetcherFn< + Cart, + AddCartItemBody +> = async ({ options, input, fetch }) => { + return fetch({ ...options, body: input }) +} + +type X = UseAddItemResult + +export default function useAddItem

( + input: UseAddItemInput

+) { + const { providerRef, fetcherRef } = useCommerce

() + + const provider = providerRef.current + const opts = provider.cart?.useAddItem + + const fetcherFn = opts?.fetcher ?? fetcher + const useHook = opts?.useHook ?? (() => () => {}) + const fetchFn = provider.fetcher ?? fetcherRef.current + const action = useHook({ input }) + + return useCallback( + function addItem(input: Input) { + return action({ + input, + fetch({ input }) { + return fetcherFn({ + input, + options: opts!.fetchOptions, + fetch: fetchFn, + }) + }, + }) + }, + [input, fetchFn, opts?.fetchOptions] + ) +} diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index d8d882f93..243fba2db 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -6,7 +6,7 @@ import { useMemo, useRef, } from 'react' -import { Fetcher, HookHandler } from './utils/types' +import { Fetcher, HookHandler, MutationHandler } from './utils/types' import type { FetchCartInput } from './cart/use-cart' import type { Cart, Wishlist, Customer, SearchProductsData } from './types' @@ -16,6 +16,7 @@ export type Provider = CommerceConfig & { fetcher: Fetcher cart?: { useCart?: HookHandler + useAddItem?: MutationHandler } wishlist?: { useWishlist?: HookHandler diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index 1f8390535..bf635c9dc 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -2,12 +2,12 @@ import type { Wishlist as BCWishlist } from '@framework/api/wishlist' import type { Customer as BCCustomer } from '@framework/api/customers' import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products' -export interface Discount { +export type Discount = { // The value of the discount, can be an amount or percentage value: number } -export interface LineItem { +export type LineItem = { id: string variantId: string productId: string @@ -19,19 +19,19 @@ export interface LineItem { variant: ProductVariant } -export interface Measurement { +export type Measurement = { value: number unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' } -export interface Image { +export type Image = { url: string altText?: string width?: number height?: number } -export interface ProductVariant { +export type ProductVariant = { id: string // The SKU (stock keeping unit) associated with the product variant. sku: string @@ -66,7 +66,7 @@ export interface ProductVariant { } // Shopping cart, a.k.a Checkout -export interface Cart { +export type Cart = { id: string // ID of the customer to which the cart belongs. customerId?: string @@ -105,47 +105,49 @@ export interface SearchProductsData extends BCSearchProductsData {} */ // Base cart item body used for cart mutations -export interface CartItemBody { +export type CartItemBody = { variantId: string productId?: string quantity?: number } // Body used by the `getCart` operation handler -export interface GetCartHandlerBody { +export type GetCartHandlerBody = { cartId?: string } // Body used by the add item to cart operation -export interface AddCartItemBody { +export type AddCartItemBody = { item: T } // Body expected by the add item to cart operation handler -export interface AddCartItemHandlerBody - extends Partial> { +export type AddCartItemHandlerBody = Partial< + AddCartItemBody +> & { cartId?: string } // Body used by the update cart item operation -export interface UpdateCartItemBody { +export type UpdateCartItemBody = { itemId: string item: T } // Body expected by the update cart item operation handler -export interface UpdateCartItemHandlerBody - extends Partial> { +export type UpdateCartItemHandlerBody = Partial< + UpdateCartItemBody +> & { cartId?: string } // Body used by the remove cart item operation -export interface RemoveCartItemBody { +export type RemoveCartItemBody = { itemId: string } // Body expected by the remove cart item operation handler -export interface RemoveCartItemHandlerBody extends Partial { +export type RemoveCartItemHandlerBody = Partial & { cartId?: string } diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index a06aa0477..1d3adef81 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -82,19 +82,14 @@ export type MutationHandler< // Input expected by the hook Input extends { [k: string]: unknown } = {}, // Input expected before doing a fetch operation - FetchInput extends HookFetchInput = {}, - // Custom state added to the response object of SWR - State = {} + FetchInput extends { [k: string]: unknown } = {} > = { useHook?(context: { - input: Input & { swrOptions?: SwrOptions } - useCallback( - fn: (context?: { - input?: HookFetchInput | HookSwrInput - swrOptions?: SwrOptions - }) => Data - ): ResponseState - }): ResponseState & State + input: Input + }): (context: { + input: FetchInput + fetch: (context: { input: FetchInput }) => Data | Promise + }) => Data | Promise fetchOptions: HookFetcherOptions fetcher?: HookFetcherFn } @@ -110,14 +105,18 @@ export type SwrOptions = ConfigInterface< */ export type Prop = NonNullable -export type UseHookParameters> = Parameters< +export type HookHandlerType = + | HookHandler + | MutationHandler + +export type UseHookParameters = Parameters< Prop > -export type UseHookResponse> = ReturnType< +export type UseHookResponse = ReturnType< Prop > export type UseHookInput< - H extends HookHandler + H extends HookHandlerType > = UseHookParameters[0]['input']