Moved auth & cart hooks + several fixes

This commit is contained in:
cond0r
2021-02-22 14:06:34 +02:00
parent 005fe9d6c9
commit 528d7556a8
53 changed files with 447 additions and 331 deletions

View File

@@ -1,22 +1,20 @@
import type { MutationHandler } from '@commerce/utils/types'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import useCart from './use-cart'
import { ShopifyProvider } from '..'
import { Cart, AddCartItemBody, CartItemBody } from '../types'
import { Cart, CartItemBody } from '../types'
import { checkoutLineItemAddMutation, getCheckoutId } from '../utils'
import { checkoutToCart } from './utils'
import { Mutation } from '../schema'
import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema'
import { useCallback } from 'react'
export default useAddItem as UseAddItem<ShopifyProvider, CartItemBody>
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHandler<Cart, {}, AddCartItemBody> = {
export const handler: MutationHook<Cart, {}, CartItemBody> = {
fetchOptions: {
query: checkoutLineItemAddMutation,
},
async fetcher({ input, options, fetch }) {
const item = input?.item ?? input
async fetcher({ input: item, options, fetch }) {
if (
item.quantity &&
(!Number.isInteger(item.quantity) || item.quantity! < 1)
@@ -26,27 +24,34 @@ export const handler: MutationHandler<Cart, {}, AddCartItemBody> = {
})
}
const { checkoutLineItemsAdd }: Mutation = await fetch<any, any>({
const { checkoutLineItemsAdd } = await fetch<
Mutation,
MutationCheckoutLineItemsAddArgs
>({
...options,
variables: {
checkoutId: getCheckoutId(),
lineItems: [
{
variantId: item.variantId,
quantity: item.quantity ?? 1,
},
],
checkoutId: getCheckoutId(),
},
})
return checkoutToCart(checkoutLineItemsAdd)
},
useHook() {
useHook: ({ fetch }) => () => {
const { mutate } = useCart()
return async function addItem({ input, fetch }) {
const data = await fetch({ input })
await mutate(data, false)
return data
}
return useCallback(
async function addItem(input) {
const data = await fetch({ input })
await mutate(data, false)
return data
},
[fetch, mutate]
)
},
}

View File

@@ -7,14 +7,13 @@ import useCommerceCart, {
} from '@commerce/cart/use-cart'
import { Cart } from '@commerce/types'
import { HookHandler } from '@commerce/utils/types'
import fetcher from './utils/fetcher'
import getCheckoutQuery from '@framework/utils/queries/get-checkout-query'
import { SWRHook } from '@commerce/utils/types'
import { checkoutCreate, checkoutToCart } from './utils'
import getCheckoutQuery from '../utils/queries/get-checkout-query'
export default useCommerceCart as UseCart<ShopifyProvider>
export const handler: HookHandler<
export const handler: SWRHook<
Cart | null,
{},
FetchCartInput,
@@ -23,10 +22,27 @@ export const handler: HookHandler<
fetchOptions: {
query: getCheckoutQuery,
},
fetcher,
useHook({ input, useData }) {
async fetcher({ input: { cartId: checkoutId }, options, fetch }) {
let checkout
if (checkoutId) {
const data = await fetch({
...options,
variables: {
checkoutId,
},
})
checkout = data.node
}
if (checkout?.completedAt || !checkoutId) {
checkout = await checkoutCreate(fetch)
}
return checkoutToCart({ checkout })
},
useHook: ({ useData }) => (input) => {
const response = useData({
swrOptions: { revalidateOnFocus: false, ...input.swrOptions },
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
})
return useMemo(
() =>

View File

@@ -1,48 +1,61 @@
import { useCallback } from 'react'
import { HookFetcher } from '@commerce/utils/types'
import type {
MutationHookContext,
HookFetcherContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useCartRemoveItem, {
RemoveItemInput as UseRemoveItemInput,
import useRemoveItem, {
RemoveItemInput as RemoveItemInputBase,
UseRemoveItem,
} from '@commerce/cart/use-remove-item'
import useCart from './use-cart'
import type { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
import { checkoutLineItemRemoveMutation } from '@framework/utils/mutations'
import getCheckoutId from '@framework/utils/get-checkout-id'
import { checkoutLineItemRemoveMutation, getCheckoutId } from '@framework/utils'
import { checkoutToCart } from './utils'
const defaultOpts = {
query: checkoutLineItemRemoveMutation,
}
import { Cart, LineItem } from '@framework/types'
import {
Mutation,
MutationCheckoutLineItemsRemoveArgs,
} from '@framework/schema'
import { RemoveCartItemBody } from '@commerce/types'
export type RemoveItemFn<T = any> = T extends LineItem
? (input?: RemoveItemInput<T>) => Promise<Cart | null>
: (input: RemoveItemInput<T>) => Promise<Cart | null>
export type RemoveItemInput<T = any> = T extends LineItem
? Partial<UseRemoveItemInput>
: UseRemoveItemInput
? Partial<RemoveItemInputBase>
: RemoveItemInputBase
export const fetcher: HookFetcher<Cart | null, any> = async (
options,
{ itemId, checkoutId },
fetch
) => {
const data = await fetch<any>({
...defaultOpts,
...options,
variables: { lineItemIds: [itemId], checkoutId },
})
return checkoutToCart(data.checkoutLineItemsRemove)
}
export default useRemoveItem as UseRemoveItem<typeof handler>
export function extendHook(customFetcher: typeof fetcher) {
const useRemoveItem = <T extends LineItem | undefined = undefined>(
item?: T
export const handler = {
fetchOptions: {
query: checkoutLineItemRemoveMutation,
},
async fetcher({
input: { itemId },
options,
fetch,
}: HookFetcherContext<RemoveCartItemBody>) {
const data = await fetch<Mutation, MutationCheckoutLineItemsRemoveArgs>({
...options,
variables: { checkoutId: getCheckoutId(), lineItemIds: [itemId] },
})
return checkoutToCart(data.checkoutLineItemsRemove)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: { item?: T } = {}
) => {
const { mutate, data: cart } = useCart()
const fn = useCartRemoveItem<Cart | null, any>(defaultOpts, customFetcher)
const { item } = ctx
const { mutate } = useCart()
const removeItem: RemoveItemFn<LineItem> = async (input) => {
const itemId = input?.id ?? item?.id
@@ -52,21 +65,11 @@ export function extendHook(customFetcher: typeof fetcher) {
})
}
const data = await fn({
checkoutId: getCheckoutId(cart?.id),
itemId,
})
const data = await fetch({ input: { itemId } })
await mutate(data, false)
return data
}
return useCallback(removeItem as RemoveItemFn<T>, [fn, mutate])
}
useRemoveItem.extend = extendHook
return useRemoveItem
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
},
}
export default extendHook(fetcher)

View File

@@ -1,85 +1,110 @@
import { useCallback } from 'react'
import debounce from 'lodash.debounce'
import type { HookFetcher } from '@commerce/utils/types'
import type {
HookFetcherContext,
MutationHookContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useCartUpdateItem, {
UpdateItemInput as UseUpdateItemInput,
import useUpdateItem, {
UpdateItemInput as UpdateItemInputBase,
UseUpdateItem,
} from '@commerce/cart/use-update-item'
import { fetcher as removeFetcher } from './use-remove-item'
import useCart from './use-cart'
import type { Cart, LineItem, UpdateCartItemBody } from '@commerce/types'
import { handler as removeItemHandler } from './use-remove-item'
import type { Cart, LineItem, UpdateCartItemBody } from '../types'
import { checkoutToCart } from './utils'
import checkoutLineItemUpdateMutation from '@framework/utils/mutations/checkout-line-item-update'
import getCheckoutId from '@framework/utils/get-checkout-id'
const defaultOpts = {
query: checkoutLineItemUpdateMutation,
}
import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils'
import {
Mutation,
MutationCheckoutLineItemsUpdateArgs,
} from '@framework/schema'
export type UpdateItemInput<T = any> = T extends LineItem
? Partial<UseUpdateItemInput<LineItem>>
: UseUpdateItemInput<LineItem>
? Partial<UpdateItemInputBase<LineItem>>
: UpdateItemInputBase<LineItem>
export const fetcher: HookFetcher<Cart | null, any> = async (
options,
{ item, checkoutId },
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 removeFetcher(null, { itemId: item.id, checkoutId }, fetch)
export default useUpdateItem as UseUpdateItem<typeof handler>
export const handler = {
fetchOptions: {
query: checkoutLineItemUpdateMutation,
},
async fetcher({
input: { itemId, item },
options,
fetch,
}: HookFetcherContext<UpdateCartItemBody>) {
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',
})
}
} else if (item.quantity) {
throw new ValidationError({
message: 'The item quantity has to be a valid integer',
const { checkoutLineItemsUpdate } = await fetch<
Mutation,
MutationCheckoutLineItemsUpdateArgs
>({
...options,
variables: {
checkoutId: getCheckoutId(),
lineItems: [
{
id: itemId,
quantity: item.quantity,
},
],
},
})
}
const data = await fetch<any, any>({
...defaultOpts,
...options,
variables: { checkoutId, lineItems: [item] },
})
return checkoutToCart(data.checkoutLineItemsUpdate)
}
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
const useUpdateItem = <T extends LineItem | undefined = undefined>(
item?: T
return checkoutToCart(checkoutLineItemsUpdate)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: {
item?: T
wait?: number
} = {}
) => {
const { mutate, data: cart } = useCart()
const fn = useCartUpdateItem<Cart | null, any>(defaultOpts, customFetcher)
const { item } = ctx
const { mutate } = useCart() as any
return useCallback(
debounce(async (input: UpdateItemInput<T>) => {
const itemId = input.id ?? item?.id
const productId = input.productId ?? item?.productId
const variantId = input.productId ?? item?.variantId
if (!itemId || !variantId) {
if (!itemId || !productId || !variantId) {
throw new ValidationError({
message: 'Invalid input used for this operation',
})
}
const data = await fn({
item: { id: itemId, variantId, quantity: input.quantity },
checkoutId: getCheckoutId(cart?.id),
const data = await fetch({
input: {
item: {
productId,
variantId,
quantity: input.quantity,
},
itemId,
},
})
await mutate(data, false)
return data
}, cfg?.wait ?? 500),
[fn, mutate]
}, ctx.wait ?? 500),
[fetch, mutate]
)
}
useUpdateItem.extend = extendHook
return useUpdateItem
},
}
export default extendHook(fetcher)

View File

@@ -11,7 +11,7 @@ export const checkoutCreate = async (fetch: any) => {
query: checkoutCreateMutation,
})
const checkout = data?.checkoutCreate?.checkout
const checkout = data.checkoutCreate?.checkout
const checkoutId = checkout?.id
if (checkoutId) {

View File

@@ -3,6 +3,7 @@ import { CommerceError, ValidationError } from '@commerce/utils/errors'
import {
CheckoutLineItemsAddPayload,
CheckoutLineItemsRemovePayload,
CheckoutLineItemsUpdatePayload,
Maybe,
} from '@framework/schema'
@@ -11,9 +12,10 @@ import { normalizeCart } from '@framework/utils'
export type CheckoutPayload =
| CheckoutLineItemsAddPayload
| CheckoutLineItemsUpdatePayload
| CheckoutLineItemsRemovePayload
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload || !checkoutPayload?.checkout) {
if (!checkoutPayload) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
@@ -28,6 +30,12 @@ const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
})
}
if (!checkout) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
return normalizeCart(checkout)
}

View File

@@ -17,7 +17,7 @@ const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
checkoutId,
},
})
checkout = data?.node
checkout = data.node
}
if (checkout?.completedAt || !checkoutId) {

View File

@@ -1,3 +1,2 @@
export { default as checkoutToCart } from './checkout-to-cart'
export { default as checkoutCreate } from './checkout-create'
export { default as fetcher } from './fetcher'