Vendure provider (#223)

* Minimal list/detail views working with Vendure

* Implement useCart/useAddItem

* Implement useUpdateItem & useRemoveItem

* Implement useSearch

* Add operations codegen, tidy up

* Dummy checkout page

* Implement auth/customer hooks

* Use env var for Shop API url

* Add some documentation

* Improve error handling

* Optimize preview image size

* Fix accidental change

* Update Vendure provider to latest changes

* Vendure provider: split out gql operations, remove unused files

* Update Vendure provider readme

* Add local next.config to Vendure provider, update docs

* Update to use demo server

* Fix build errors

* Use proxy for vendure api

* Simplify instructions in Vendure readme

* Refactor Vendure checkout api handler

* Improve image quality
This commit is contained in:
Michael Bromley
2021-05-27 23:06:56 +02:00
committed by GitHub
parent 8fb6c7b206
commit da4371090d
71 changed files with 8593 additions and 51 deletions

View File

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

View File

@@ -0,0 +1,52 @@
import { Cart, CartItemBody } from '@commerce/types'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import { CommerceError } from '@commerce/utils/errors'
import { MutationHook } from '@commerce/utils/types'
import { useCallback } from 'react'
import useCart from './use-cart'
import { AddItemToOrderMutation } from '../schema'
import { normalizeCart } from '../lib/normalize'
import { addItemToOrderMutation } from '../lib/mutations/add-item-to-order-mutation'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<Cart, {}, CartItemBody> = {
fetchOptions: {
query: addItemToOrderMutation,
},
async fetcher({ input, options, fetch }) {
if (
input.quantity &&
(!Number.isInteger(input.quantity) || input.quantity! < 1)
) {
throw new CommerceError({
message: 'The item quantity has to be a valid integer greater than 0',
})
}
const { addItemToOrder } = await fetch<AddItemToOrderMutation>({
...options,
variables: {
quantity: input.quantity || 1,
variantId: input.variantId,
},
})
if (addItemToOrder.__typename === 'Order') {
return normalizeCart(addItemToOrder)
}
throw new CommerceError(addItemToOrder)
},
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,13 @@
import useAddItem from './use-add-item'
import useRemoveItem from './use-remove-item'
import useUpdateItem from './use-update-item'
// This hook is probably not going to be used, but it's here
// to show how a commerce should be structuring it
export default function useCartActions() {
const addItem = useAddItem()
const updateItem = useUpdateItem()
const removeItem = useRemoveItem()
return { addItem, updateItem, removeItem }
}

View File

@@ -0,0 +1,49 @@
import { Cart } from '@commerce/types'
import { SWRHook } from '@commerce/utils/types'
import useCart, { FetchCartInput, UseCart } from '@commerce/cart/use-cart'
import { ActiveOrderQuery, CartFragment } from '../schema'
import { normalizeCart } from '../lib/normalize'
import { useMemo } from 'react'
import { getCartQuery } from '../lib/queries/get-cart-query'
export type CartResult = {
activeOrder?: CartFragment
addItemToOrder?: CartFragment
adjustOrderLine?: CartFragment
removeOrderLine?: CartFragment
}
export default useCart as UseCart<typeof handler>
export const handler: SWRHook<
Cart | null,
{},
FetchCartInput,
{ isEmpty?: boolean }
> = {
fetchOptions: {
query: getCartQuery,
},
async fetcher({ input: { cartId }, options, fetch }) {
const { activeOrder } = await fetch<ActiveOrderQuery>(options)
return activeOrder ? normalizeCart(activeOrder) : null
},
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,48 @@
import { useCallback } from 'react'
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
import { CommerceError } from '@commerce/utils/errors'
import useCart from './use-cart'
import {
RemoveOrderLineMutation,
RemoveOrderLineMutationVariables,
} from '../schema'
import { Cart, LineItem, RemoveCartItemBody } from '@commerce/types'
import { normalizeCart } from '../lib/normalize'
import { removeOrderLineMutation } from '../lib/mutations/remove-order-line-mutation'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler = {
fetchOptions: {
query: removeOrderLineMutation,
},
async fetcher({ input, options, fetch }: HookFetcherContext<LineItem>) {
const variables: RemoveOrderLineMutationVariables = {
orderLineId: input.id,
}
const { removeOrderLine } = await fetch<RemoveOrderLineMutation>({
...options,
variables,
})
if (removeOrderLine.__typename === 'Order') {
return normalizeCart(removeOrderLine)
}
throw new CommerceError(removeOrderLine)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, RemoveCartItemBody>) => (ctx = {}) => {
const { mutate } = useCart()
return useCallback(
async function removeItem(input) {
const data = await fetch({ input })
await mutate(data, false)
return data
},
[fetch, mutate]
)
},
}

View File

@@ -0,0 +1,78 @@
import { useCallback } from 'react'
import { HookFetcherContext, MutationHookContext } from '@commerce/utils/types'
import { CommerceError, ValidationError } from '@commerce/utils/errors'
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
import {
Cart,
CartItemBody,
LineItem,
UpdateCartItemBody,
} from '@commerce/types'
import useCart from './use-cart'
import {
AdjustOrderLineMutation,
AdjustOrderLineMutationVariables,
} from '../schema'
import { normalizeCart } from '../lib/normalize'
import { adjustOrderLineMutation } from '../lib/mutations/adjust-order-line-mutation'
export default useUpdateItem as UseUpdateItem<typeof handler>
export const handler = {
fetchOptions: {
query: adjustOrderLineMutation,
},
async fetcher(context: HookFetcherContext<UpdateCartItemBody<CartItemBody>>) {
const { input, options, fetch } = context
const variables: AdjustOrderLineMutationVariables = {
quantity: input.item.quantity || 1,
orderLineId: input.itemId,
}
const { adjustOrderLine } = await fetch<AdjustOrderLineMutation>({
...options,
variables,
})
if (adjustOrderLine.__typename === 'Order') {
return normalizeCart(adjustOrderLine)
}
throw new CommerceError(adjustOrderLine)
},
useHook: ({
fetch,
}: MutationHookContext<Cart | null, UpdateCartItemBody<CartItemBody>>) => (
ctx: {
item?: LineItem
wait?: number
} = {}
) => {
const { item } = ctx
const { mutate } = useCart()
return useCallback(
async function addItem(input: Partial<CartItemBody>) {
const itemId = 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
},
[fetch, mutate]
)
},
}