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 { 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<CartItemBody>
export default useAddItem as UseAddItem<BigcommerceProvider, CartItemBody>
export const fetcher: HookFetcher<Cart, AddCartItemBody> = 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<Cart, {}, AddCartItemBody> = {
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<BigcommerceCart, AddCartItemBody>({
...defaultOpts,
...options,
body: { item },
})
}
const data = await fetch<BigcommerceCart, AddCartItemBody>({
...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)

View File

@ -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 },

View File

@ -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<CartItemBody> {}
export type GetCartHandlerBody = Core.GetCartHandlerBody
export interface AddCartItemHandlerBody
extends Core.AddCartItemHandlerBody<CartItemBody> {}
export type AddCartItemBody = Core.AddCartItemBody<CartItemBody>
export interface UpdateCartItemBody
extends Core.UpdateCartItemBody<CartItemBody> {}
export type AddCartItemHandlerBody = Core.AddCartItemHandlerBody<CartItemBody>
export interface UpdateCartItemHandlerBody
extends Core.UpdateCartItemHandlerBody<CartItemBody> {}
export type UpdateCartItemBody = Core.UpdateCartItemBody<CartItemBody>
export interface RemoveCartItemBody extends Core.RemoveCartItemBody {}
export type UpdateCartItemHandlerBody = Core.UpdateCartItemHandlerBody<CartItemBody>
export interface RemoveCartItemHandlerBody
extends Core.RemoveCartItemHandlerBody {}
export type RemoveCartItemBody = Core.RemoveCartItemBody
export type RemoveCartItemHandlerBody = Core.RemoveCartItemHandlerBody

View File

@ -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<P extends Provider> = Prop<
Prop<P, 'cart'>,
'useAddItem'
>
// 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,
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<Cart | null, any, FetchCartInput>
useAddItem?: MutationHandler<Cart, any, any>
}
wishlist?: {
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 { 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<T extends CartItemBody> {
export type AddCartItemBody<T extends CartItemBody> = {
item: T
}
// Body expected by the add item to cart operation handler
export interface AddCartItemHandlerBody<T extends CartItemBody>
extends Partial<AddCartItemBody<T>> {
export type AddCartItemHandlerBody<T extends CartItemBody> = Partial<
AddCartItemBody<T>
> & {
cartId?: string
}
// Body used by the update cart item operation
export interface UpdateCartItemBody<T extends CartItemBody> {
export type 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>> {
export type UpdateCartItemHandlerBody<T extends CartItemBody> = Partial<
UpdateCartItemBody<T>
> & {
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<RemoveCartItemBody> {
export type RemoveCartItemHandlerBody = Partial<RemoveCartItemBody> & {
cartId?: string
}

View File

@ -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<Data, FetchInput> }
useCallback(
fn: (context?: {
input?: HookFetchInput | HookSwrInput
swrOptions?: SwrOptions<Data, FetchInput>
}) => Data
): ResponseState<Data>
}): ResponseState<Data> & State
input: Input
}): (context: {
input: FetchInput
fetch: (context: { input: FetchInput }) => Data | Promise<Data>
}) => Data | Promise<Data>
fetchOptions: HookFetcherOptions
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 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'>
>
export type UseHookResponse<H extends HookHandler<any>> = ReturnType<
export type UseHookResponse<H extends HookHandlerType> = ReturnType<
Prop<H, 'useHook'>
>
export type UseHookInput<
H extends HookHandler<any>
H extends HookHandlerType
> = UseHookParameters<H>[0]['input']