Add swell provider folder

This commit is contained in:
Dave Loneragan
2021-03-02 21:05:13 -06:00
parent 394efd9e81
commit 753234dc51
88 changed files with 17268 additions and 0 deletions

View File

@@ -0,0 +1,3 @@
export { default as useCart } from './use-cart'
export { default as useAddItem } from './use-add-item'
export { default as useRemoveItem } from './use-remove-item'

View File

@@ -0,0 +1,58 @@
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 { Cart, CartItemBody } from '../types'
import { checkoutLineItemAddMutation, getCheckoutId } from '../utils'
import { checkoutToCart } from './utils'
import { Mutation, MutationCheckoutLineItemsAddArgs } from '../schema'
import { useCallback } from 'react'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<Cart, {}, CartItemBody> = {
fetchOptions: {
query: checkoutLineItemAddMutation,
},
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 { checkoutLineItemsAdd } = await fetch<
Mutation,
MutationCheckoutLineItemsAddArgs
>({
...options,
variables: {
checkoutId: getCheckoutId(),
lineItems: [
{
variantId: item.variantId,
quantity: item.quantity ?? 1,
},
],
},
})
// TODO: Fix this Cart type here
return checkoutToCart(checkoutLineItemsAdd) as any
},
useHook: ({ fetch }) => () => {
const { mutate } = useCart()
return useCallback(
async function addItem(input) {
const data = await fetch({ input })
await mutate(data, false)
return data
},
[fetch, mutate]
)
},
}

View File

@@ -0,0 +1,59 @@
import { useMemo } from 'react'
import useCommerceCart, {
FetchCartInput,
UseCart,
} from '@commerce/cart/use-cart'
import { Cart } from '../types'
import { SWRHook } from '@commerce/utils/types'
import { checkoutCreate, checkoutToCart } from './utils'
import getCheckoutQuery from '../utils/queries/get-checkout-query'
export default useCommerceCart as UseCart<typeof handler>
export const handler: SWRHook<
Cart | null,
{},
FetchCartInput,
{ isEmpty?: boolean }
> = {
fetchOptions: {
query: getCheckoutQuery,
},
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)
}
// TODO: Fix this type
return checkoutToCart({ checkout } as any)
},
useHook: ({ useData }) => (input) => {
const response = useData({
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
})
return useMemo(
() =>
Object.create(response, {
isEmpty: {
get() {
return (response.data?.lineItems.length ?? 0) <= 0
},
enumerable: true,
},
}),
[response]
)
},
}

View File

@@ -0,0 +1,72 @@
import { useCallback } from 'react'
import type {
MutationHookContext,
HookFetcherContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useRemoveItem, {
RemoveItemInput as RemoveItemInputBase,
UseRemoveItem,
} from '@commerce/cart/use-remove-item'
import useCart from './use-cart'
import { checkoutLineItemRemoveMutation, getCheckoutId } from '../utils'
import { checkoutToCart } from './utils'
import { Cart, LineItem } from '../types'
import { Mutation, MutationCheckoutLineItemsRemoveArgs } from '../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<RemoveItemInputBase>
: RemoveItemInputBase
export default useRemoveItem as UseRemoveItem<typeof handler>
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 { item } = ctx
const { mutate } = useCart()
const removeItem: RemoveItemFn<LineItem> = async (input) => {
const itemId = input?.id ?? item?.id
if (!itemId) {
throw new ValidationError({
message: 'Invalid input used for this operation',
})
}
const data = await fetch({ input: { itemId } })
await mutate(data, false)
return data
}
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
},
}

View File

@@ -0,0 +1,107 @@
import { useCallback } from 'react'
import debounce from 'lodash.debounce'
import type {
HookFetcherContext,
MutationHookContext,
} from '@commerce/utils/types'
import { ValidationError } from '@commerce/utils/errors'
import useUpdateItem, {
UpdateItemInput as UpdateItemInputBase,
UseUpdateItem,
} from '@commerce/cart/use-update-item'
import useCart from './use-cart'
import { handler as removeItemHandler } from './use-remove-item'
import type { Cart, LineItem, UpdateCartItemBody } from '../types'
import { checkoutToCart } from './utils'
import { getCheckoutId, checkoutLineItemUpdateMutation } from '../utils'
import { Mutation, MutationCheckoutLineItemsUpdateArgs } from '../schema'
export type UpdateItemInput<T = any> = T extends LineItem
? Partial<UpdateItemInputBase<LineItem>>
: UpdateItemInputBase<LineItem>
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',
})
}
const { checkoutLineItemsUpdate } = await fetch<
Mutation,
MutationCheckoutLineItemsUpdateArgs
>({
...options,
variables: {
checkoutId: getCheckoutId(),
lineItems: [
{
id: itemId,
quantity: item.quantity,
},
],
},
})
return checkoutToCart(checkoutLineItemsUpdate)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, UpdateCartItemBody>) => <
T extends LineItem | undefined = undefined
>(
ctx: {
item?: T
wait?: number
} = {}
) => {
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 || !productId || !variantId) {
throw new ValidationError({
message: 'Invalid input used for this operation',
})
}
const data = await fetch({
input: {
item: {
productId,
variantId,
quantity: input.quantity,
},
itemId,
},
})
await mutate(data, false)
return data
}, ctx.wait ?? 500),
[fetch, mutate]
)
},
}

View File

@@ -0,0 +1,29 @@
import {
SHOPIFY_CHECKOUT_ID_COOKIE,
SHOPIFY_CHECKOUT_URL_COOKIE,
SHOPIFY_COOKIE_EXPIRE,
} from '../../const'
import checkoutCreateMutation from '../../utils/mutations/checkout-create'
import Cookies from 'js-cookie'
export const checkoutCreate = async (fetch: any) => {
const data = await fetch({
query: checkoutCreateMutation,
})
const checkout = data.checkoutCreate?.checkout
const checkoutId = checkout?.id
if (checkoutId) {
const options = {
expires: SHOPIFY_COOKIE_EXPIRE,
}
Cookies.set(SHOPIFY_CHECKOUT_ID_COOKIE, checkoutId, options)
Cookies.set(SHOPIFY_CHECKOUT_URL_COOKIE, checkout?.webUrl, options)
}
return checkout
}
export default checkoutCreate

View File

@@ -0,0 +1,42 @@
import { Cart } from '../../types'
import { CommerceError, ValidationError } from '@commerce/utils/errors'
import {
CheckoutLineItemsAddPayload,
CheckoutLineItemsRemovePayload,
CheckoutLineItemsUpdatePayload,
Maybe,
} from '../../schema'
import { normalizeCart } from '../../utils'
export type CheckoutPayload =
| CheckoutLineItemsAddPayload
| CheckoutLineItemsUpdatePayload
| CheckoutLineItemsRemovePayload
const checkoutToCart = (checkoutPayload?: Maybe<CheckoutPayload>): Cart => {
if (!checkoutPayload) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
const checkout = checkoutPayload?.checkout
const userErrors = checkoutPayload?.userErrors
if (userErrors && userErrors.length) {
throw new ValidationError({
message: userErrors[0].message,
})
}
if (!checkout) {
throw new CommerceError({
message: 'Invalid response from Shopify',
})
}
return normalizeCart(checkout)
}
export default checkoutToCart

View File

@@ -0,0 +1,31 @@
import { HookFetcherFn } from '@commerce/utils/types'
import { Cart } from '@commerce/types'
import { checkoutCreate, checkoutToCart } from '.'
import { FetchCartInput } from '@commerce/cart/use-cart'
const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
options,
input: { cartId: checkoutId },
fetch,
}) => {
let checkout
if (checkoutId) {
const data = await fetch({
...options,
variables: {
checkoutId,
},
})
checkout = data.node
}
if (checkout?.completedAt || !checkoutId) {
checkout = await checkoutCreate(fetch)
}
// TODO: Fix this type
return checkoutToCart({ checkout } as any)
}
export default fetcher

View File

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