4
0
forked from crowetic/commerce

Improved types for operations

This commit is contained in:
Luis Alvarez 2021-01-29 19:24:10 -05:00
parent 172b413521
commit e5f0809070
6 changed files with 54 additions and 34 deletions

View File

@ -1,14 +1,15 @@
import { useCallback } from 'react' 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 { CommerceError } from '@commerce/utils/errors' import { ValidationError } from '@commerce/utils/errors'
import useCartUpdateItem from '@commerce/cart/use-update-item' import useCartUpdateItem from '@commerce/cart/use-update-item'
import { normalizeCart } from '../lib/normalize' import { normalizeCart } from '../lib/normalize'
import type { import type {
Cart,
BigcommerceCart,
UpdateCartItemBody, UpdateCartItemBody,
UpdateCartItemInput, UpdateCartItemInput,
Cart,
BigcommerceCart,
LineItem,
} from '../types' } from '../types'
import { fetcher as removeFetcher } from './use-remove-item' import { fetcher as removeFetcher } from './use-remove-item'
import useCart from './use-cart' import useCart from './use-cart'
@ -29,12 +30,12 @@ export const fetcher: HookFetcher<Cart | null, UpdateCartItemBody> = async (
return removeFetcher(null, { itemId }, fetch) return removeFetcher(null, { itemId }, fetch)
} }
} else if (item.quantity) { } else if (item.quantity) {
throw new CommerceError({ throw new ValidationError({
message: 'The item quantity has to be a valid integer', message: 'The item quantity has to be a valid integer',
}) })
} }
const data = await fetch<BigcommerceCart>({ const data = await fetch<BigcommerceCart, UpdateCartItemBody>({
...defaultOpts, ...defaultOpts,
...options, ...options,
body: { itemId, item }, body: { itemId, item },
@ -44,7 +45,9 @@ export const fetcher: HookFetcher<Cart | null, UpdateCartItemBody> = async (
} }
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
const useUpdateItem = (item?: any) => { const useUpdateItem = <T extends LineItem | undefined = undefined>(
item?: T
) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartUpdateItem<Cart | null, UpdateCartItemBody>( const fn = useCartUpdateItem<Cart | null, UpdateCartItemBody>(
defaultOpts, defaultOpts,
@ -52,26 +55,31 @@ function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
) )
return useCallback( return useCallback(
debounce(async (input: UpdateCartItemInput) => { debounce(
console.log('INPUT', input, { async (
itemId: input.id ?? item?.id, input: T extends LineItem
item: { ? Partial<UpdateCartItemInput>
productId: input.productId ?? item?.product_id, : UpdateCartItemInput
variantId: input.productId ?? item?.variant_id, ) => {
quantity: input.quantity, const itemId = input.id ?? item?.id
}, const productId = input.productId ?? item?.productId
}) const variantId = input.productId ?? item?.variantId
const data = await fn({
itemId: input.id ?? item?.id, if (!itemId || !productId || !variantId) {
item: { throw new ValidationError({
productId: input.productId ?? item?.product_id, message: 'Invalid input used for this operation',
variantId: input.productId ?? item?.variant_id, })
quantity: input.quantity, }
},
}) const data = await fn({
await mutate(data, false) itemId,
return data item: { productId, variantId, quantity: input.quantity },
}, cfg?.wait ?? 500), })
await mutate(data, false)
return data
},
cfg?.wait ?? 500
),
[fn, mutate] [fn, mutate]
) )
} }

View File

@ -1,4 +1,4 @@
import type { Cart as BigcommerceCart } from '../api/cart' import type { Cart, BigcommerceCart, LineItem } from '../types'
import update from './immutability' import update from './immutability'
function normalizeProductOption(productOption: any) { function normalizeProductOption(productOption: any) {
@ -90,6 +90,7 @@ function normalizeLineItem(item: any): LineItem {
return { return {
id: item.id, id: item.id,
variantId: String(item.variant_id), variantId: String(item.variant_id),
productId: String(item.product_id),
name: item.name, name: item.name,
quantity: item.quantity, quantity: item.quantity,
variant: { variant: {

View File

@ -39,6 +39,7 @@ export type OptionSelections = {
} }
export interface CartItemBody extends Core.CartItemBody { export interface CartItemBody extends Core.CartItemBody {
productId: string // The product id is always required for BC
optionSelections?: OptionSelections optionSelections?: OptionSelections
} }
@ -46,7 +47,8 @@ export interface UpdateCartItemBody extends Core.UpdateCartItemBody {
item: CartItemBody item: CartItemBody
} }
export interface UpdateCartItemInput extends Core.UpdateCartItemInput {} export interface UpdateCartItemInput
extends Core.UpdateCartItemInput<CartItemBody> {}
export interface UpdateCartItemHandlerBody export interface UpdateCartItemHandlerBody
extends Core.UpdateCartItemHandlerBody {} extends Core.UpdateCartItemHandlerBody {}

View File

@ -6,6 +6,7 @@ export interface Discount {
export interface LineItem { export interface LineItem {
id: string id: string
variantId: string variantId: string
productId: string
name: string name: string
quantity: number quantity: number
discounts: Discount[] discounts: Discount[]
@ -88,8 +89,8 @@ export interface Cart {
// Base cart item body used for cart mutations // Base cart item body used for cart mutations
export interface CartItemBody { export interface CartItemBody {
productId: string
variantId: string variantId: string
productId?: string
quantity?: number quantity?: number
} }
@ -100,8 +101,8 @@ export interface UpdateCartItemBody {
} }
// Input expected by the `useUpdateItem` hook // Input expected by the `useUpdateItem` hook
export interface UpdateCartItemInput extends Partial<CartItemBody> { export type UpdateCartItemInput<T extends CartItemBody> = T & {
id?: string id: string
} }
// Body expected by the update operation handler // Body expected by the update operation handler

View File

@ -26,6 +26,14 @@ export class CommerceError extends Error {
} }
} }
// Used for errors that come from a bad implementation of the hooks
export class ValidationError extends CommerceError {
constructor(options: ErrorProps) {
super(options)
this.code = 'validation_error'
}
}
export class FetcherError extends CommerceError { export class FetcherError extends CommerceError {
status: number status: number

View File

@ -1,18 +1,18 @@
// Core fetcher added by CommerceProvider // Core fetcher added by CommerceProvider
export type Fetcher<T> = (options: FetcherOptions) => T | Promise<T> export type Fetcher<T> = (options: FetcherOptions) => T | Promise<T>
export type FetcherOptions = { export type FetcherOptions<Body = any> = {
url?: string url?: string
query?: string query?: string
method?: string method?: string
variables?: any variables?: any
body?: any body?: Body
} }
export type HookFetcher<Data, Input = null, Result = any> = ( export type HookFetcher<Data, Input = null, Result = any> = (
options: HookFetcherOptions | null, options: HookFetcherOptions | null,
input: Input, input: Input,
fetch: <T = Result>(options: FetcherOptions) => Promise<T> fetch: <T = Result, Body = any>(options: FetcherOptions<Body>) => Promise<T>
) => Data | Promise<Data> ) => Data | Promise<Data>
export type HookFetcherOptions = { export type HookFetcherOptions = {