4
0
forked from crowetic/commerce

Added initial version of useAddItem

This commit is contained in:
Luis Alvarez 2021-02-16 21:14:11 -05:00
parent 75d485d35a
commit c02d7fec62
7 changed files with 145 additions and 96 deletions

View File

@ -1,9 +1,6 @@
import { useCallback } from 'react' import type { MutationHandler } 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, { import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
AddItemInput as UseAddItemInput,
} from '@commerce/cart/use-add-item'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { import type {
AddCartItemBody, AddCartItemBody,
@ -12,55 +9,45 @@ import type {
CartItemBody, CartItemBody,
} from '../types' } from '../types'
import useCart from './use-cart' import useCart from './use-cart'
import { BigcommerceProvider } from '..'
const defaultOpts = { const defaultOpts = {
url: '/api/bigcommerce/cart', url: '/api/bigcommerce/cart',
method: 'POST', method: 'POST',
} }
export type AddItemInput = UseAddItemInput<CartItemBody> export default useAddItem as UseAddItem<BigcommerceProvider, CartItemBody>
export const fetcher: HookFetcher<Cart, AddCartItemBody> = async ( export const handler: MutationHandler<Cart, {}, AddCartItemBody> = {
options, fetchOptions: {
{ item }, url: '/api/bigcommerce/cart',
fetch method: 'GET',
) => { },
if ( async fetcher({ input: { item }, options, fetch }) {
item.quantity && if (
(!Number.isInteger(item.quantity) || item.quantity! < 1) 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', throw new CommerceError({
message: 'The item quantity has to be a valid integer greater than 0',
})
}
const data = await fetch<BigcommerceCart, AddCartItemBody>({
...defaultOpts,
...options,
body: { item },
}) })
}
const data = await fetch<BigcommerceCart, AddCartItemBody>({ return normalizeCart(data)
...defaultOpts, },
...options, useHook() {
body: { item },
})
return normalizeCart(data)
}
export function extendHook(customFetcher: typeof fetcher) {
const useAddItem = () => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartAddItem(defaultOpts, customFetcher)
return useCallback( return async function addItem({ input, fetch }) {
async function addItem(input: AddItemInput) { const data = await fetch({ input })
const data = await fn({ item: input }) await mutate(data, false)
await mutate(data, false) return data
return data }
}, },
[fn, mutate]
)
}
useAddItem.extend = extendHook
return useAddItem
} }
export default extendHook(fetcher)

View File

@ -1,4 +1,5 @@
import { handler as useCart } from './cart/use-cart' 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 useWishlist } from './wishlist/use-wishlist'
import { handler as useCustomer } from './customer/use-customer' import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search' import { handler as useSearch } from './product/use-search'
@ -8,7 +9,7 @@ export const bigcommerceProvider = {
locale: 'en-us', locale: 'en-us',
cartCookie: 'bc_cartId', cartCookie: 'bc_cartId',
fetcher, fetcher,
cart: { useCart }, cart: { useCart, useAddItem },
wishlist: { useWishlist }, wishlist: { useWishlist },
customer: { useCustomer }, customer: { useCustomer },
products: { useSearch }, products: { useSearch },

View File

@ -23,11 +23,11 @@ export type BigcommerceCart = {
// TODO: add missing fields // TODO: add missing fields
} }
export interface Cart extends Core.Cart { export type Cart = Core.Cart & {
lineItems: LineItem[] lineItems: LineItem[]
} }
export interface LineItem extends Core.LineItem {} export type LineItem = Core.LineItem
/** /**
* Cart mutations * Cart mutations
@ -38,25 +38,24 @@ export type OptionSelections = {
option_value: number | string 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 productId: string // The product id is always required for BC
optionSelections?: OptionSelections 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<CartItemBody> {} export type GetCartHandlerBody = Core.GetCartHandlerBody
export interface AddCartItemHandlerBody export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
extends Core.AddCartItemHandlerBody<CartItemBody> {}
export interface UpdateCartItemBody export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
extends Core.UpdateCartItemBody<CartItemBody> {}
export interface UpdateCartItemHandlerBody export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
extends Core.UpdateCartItemHandlerBody<CartItemBody> {}
export interface RemoveCartItemBody extends Core.RemoveCartItemBody {} export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody<CartItemBody>
export interface RemoveCartItemHandlerBody export type RemoveCartItemBody = Core.RemoveCartItemBody
extends Core.RemoveCartItemHandlerBody {}
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody

View File

@ -1,9 +1,69 @@
import useAction from '../utils/use-action' import { useCallback } from 'react'
import type { CartItemBody } from '../types' 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<P extends Provider> = Prop<
Prop<P, 'cart'>,
'useAddItem'
>
// Input expected by the action returned by the `useAddItem` hook // Input expected by the action returned by the `useAddItem` hook
export type AddItemInput<T extends CartItemBody> = T export type UseAddItemInput<P extends Provider> = UseHookInput<
UseAddItemHandler<P>
>
const useAddItem = useAction export type UseAddItemResult<P extends Provider> = ReturnType<
UseHookResponse<UseAddItemHandler<P>>
>
export default useAddItem export type UseAddItem<P extends Provider, Input> = Partial<
UseAddItemInput<P>
> extends UseAddItemInput<P>
? (input?: UseAddItemInput<P>) => (input: Input) => UseAddItemResult<P>
: (input: UseAddItemInput<P>) => (input: Input) => UseAddItemResult<P>
export const fetcher: HookFetcherFn<
Cart,
AddCartItemBody<CartItemBody>
> = async ({ options, input, fetch }) => {
return fetch({ ...options, body: input })
}
type X = UseAddItemResult<BigcommerceProvider>
export default function useAddItem<P extends Provider, Input>(
input: UseAddItemInput<P>
) {
const { providerRef, fetcherRef } = useCommerce<P>()
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]
)
}

View File

@ -6,7 +6,7 @@ import {
useMemo, useMemo,
useRef, useRef,
} from 'react' } from 'react'
import { Fetcher, HookHandler } from './utils/types' import { Fetcher, HookHandler, MutationHandler } from './utils/types'
import type { FetchCartInput } from './cart/use-cart' import type { FetchCartInput } from './cart/use-cart'
import type { Cart, Wishlist, Customer, SearchProductsData } from './types' import type { Cart, Wishlist, Customer, SearchProductsData } from './types'
@ -16,6 +16,7 @@ export type Provider = CommerceConfig & {
fetcher: Fetcher fetcher: Fetcher
cart?: { cart?: {
useCart?: HookHandler<Cart | null, any, FetchCartInput> useCart?: HookHandler<Cart | null, any, FetchCartInput>
useAddItem?: MutationHandler<Cart, any, any>
} }
wishlist?: { wishlist?: {
useWishlist?: HookHandler<Wishlist | null, any, any> useWishlist?: HookHandler<Wishlist | null, any, any>

View File

@ -2,12 +2,12 @@ import type { Wishlist as BCWishlist } from '@framework/api/wishlist'
import type { Customer as BCCustomer } from '@framework/api/customers' import type { Customer as BCCustomer } from '@framework/api/customers'
import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products' 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 // The value of the discount, can be an amount or percentage
value: number value: number
} }
export interface LineItem { export type LineItem = {
id: string id: string
variantId: string variantId: string
productId: string productId: string
@ -19,19 +19,19 @@ export interface LineItem {
variant: ProductVariant variant: ProductVariant
} }
export interface Measurement { export type Measurement = {
value: number value: number
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES' unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
} }
export interface Image { export type Image = {
url: string url: string
altText?: string altText?: string
width?: number width?: number
height?: number height?: number
} }
export interface ProductVariant { export type ProductVariant = {
id: string id: string
// The SKU (stock keeping unit) associated with the product variant. // The SKU (stock keeping unit) associated with the product variant.
sku: string sku: string
@ -66,7 +66,7 @@ export interface ProductVariant {
} }
// Shopping cart, a.k.a Checkout // Shopping cart, a.k.a Checkout
export interface Cart { export type Cart = {
id: string id: string
// ID of the customer to which the cart belongs. // ID of the customer to which the cart belongs.
customerId?: string customerId?: string
@ -105,47 +105,49 @@ export interface SearchProductsData extends BCSearchProductsData {}
*/ */
// Base cart item body used for cart mutations // Base cart item body used for cart mutations
export interface CartItemBody { export type CartItemBody = {
variantId: string variantId: string
productId?: string productId?: string
quantity?: number quantity?: number
} }
// Body used by the `getCart` operation handler // Body used by the `getCart` operation handler
export interface GetCartHandlerBody { export type GetCartHandlerBody = {
cartId?: string cartId?: string
} }
// Body used by the add item to cart operation // Body used by the add item to cart operation
export interface AddCartItemBody<T extends CartItemBody> { export type AddCartItemBody<T extends CartItemBody> = {
item: T item: T
} }
// Body expected by the add item to cart operation handler // Body expected by the add item to cart operation handler
export interface AddCartItemHandlerBody<T extends CartItemBody> export type AddCartItemHandlerBody<T extends CartItemBody> = Partial<
extends Partial<AddCartItemBody<T>> { AddCartItemBody<T>
> & {
cartId?: string cartId?: string
} }
// Body used by the update cart item operation // Body used by the update cart item operation
export interface UpdateCartItemBody<T extends CartItemBody> { export type UpdateCartItemBody<T extends CartItemBody> = {
itemId: string itemId: string
item: T item: T
} }
// Body expected by the update cart item operation handler // Body expected by the update cart item operation handler
export interface UpdateCartItemHandlerBody<T extends CartItemBody> export type UpdateCartItemHandlerBody<T extends CartItemBody> = Partial<
extends Partial<UpdateCartItemBody<T>> { UpdateCartItemBody<T>
> & {
cartId?: string cartId?: string
} }
// Body used by the remove cart item operation // Body used by the remove cart item operation
export interface RemoveCartItemBody { export type RemoveCartItemBody = {
itemId: string itemId: string
} }
// Body expected by the remove cart item operation handler // Body expected by the remove cart item operation handler
export interface RemoveCartItemHandlerBody extends Partial<RemoveCartItemBody> { export type RemoveCartItemHandlerBody = Partial<RemoveCartItemBody> & {
cartId?: string cartId?: string
} }

View File

@ -82,19 +82,14 @@ export type MutationHandler<
// Input expected by the hook // Input expected by the hook
Input extends { [k: string]: unknown } = {}, Input extends { [k: string]: unknown } = {},
// Input expected before doing a fetch operation // Input expected before doing a fetch operation
FetchInput extends HookFetchInput = {}, FetchInput extends { [k: string]: unknown } = {}
// Custom state added to the response object of SWR
State = {}
> = { > = {
useHook?(context: { useHook?(context: {
input: Input & { swrOptions?: SwrOptions<Data, FetchInput> } input: Input
useCallback( }): (context: {
fn: (context?: { input: FetchInput
input?: HookFetchInput | HookSwrInput fetch: (context: { input: FetchInput }) => Data | Promise<Data>
swrOptions?: SwrOptions<Data, FetchInput> }) => Data | Promise<Data>
}) => Data
): ResponseState<Data>
}): ResponseState<Data> & State
fetchOptions: HookFetcherOptions fetchOptions: HookFetcherOptions
fetcher?: HookFetcherFn<Data, FetchInput> fetcher?: HookFetcherFn<Data, FetchInput>
} }
@ -110,14 +105,18 @@ export type SwrOptions<Data, Input = null, Result = any> = ConfigInterface<
*/ */
export type Prop<T, K extends keyof T> = NonNullable<T[K]> export type Prop<T, K extends keyof T> = NonNullable<T[K]>
export type UseHookParameters<H extends HookHandler<any>> = Parameters< export type HookHandlerType =
| HookHandler<any, any, any>
| MutationHandler<any, any, any>
export type UseHookParameters<H extends HookHandlerType> = Parameters<
Prop<H, 'useHook'> Prop<H, 'useHook'>
> >
export type UseHookResponse<H extends HookHandler<any>> = ReturnType< export type UseHookResponse<H extends HookHandlerType> = ReturnType<
Prop<H, 'useHook'> Prop<H, 'useHook'>
> >
export type UseHookInput< export type UseHookInput<
H extends HookHandler<any> H extends HookHandlerType
> = UseHookParameters<H>[0]['input'] > = UseHookParameters<H>[0]['input']