Implement useUpdateItem & useRemoveItem

This commit is contained in:
Michael Bromley 2021-01-25 14:32:18 +01:00
parent 0f7c151805
commit d0ed97466c
7 changed files with 71 additions and 215 deletions

View File

@ -0,0 +1,27 @@
export const cartFragment = /* GraphQL */ `
fragment Cart on Order {
id
code
totalQuantity
subTotal
subTotalWithTax
total
totalWithTax
currencyCode
lines {
id
quantity
featuredAsset {
id
preview
}
productVariant {
name
product {
slug
}
productId
}
}
}
`

View File

@ -1,9 +0,0 @@
export const categoryTreeItemFragment = /* GraphQL */ `
fragment categoryTreeItem on CategoryTreeItem {
entityId
name
path
description
productCount
}
`

View File

@ -1,113 +0,0 @@
export const productPrices = /* GraphQL */ `
fragment productPrices on Prices {
price {
value
currencyCode
}
salePrice {
value
currencyCode
}
retailPrice {
value
currencyCode
}
}
`
export const swatchOptionFragment = /* GraphQL */ `
fragment swatchOption on SwatchOptionValue {
isDefault
hexColors
}
`
export const multipleChoiceOptionFragment = /* GraphQL */ `
fragment multipleChoiceOption on MultipleChoiceOption {
values {
edges {
node {
label
...swatchOption
}
}
}
}
${swatchOptionFragment}
`
export const productInfoFragment = /* GraphQL */ `
fragment productInfo on Product {
entityId
name
path
brand {
entityId
}
description
prices {
...productPrices
}
images {
edges {
node {
urlOriginal
altText
isDefault
}
}
}
variants {
edges {
node {
entityId
defaultImage {
urlOriginal
altText
isDefault
}
}
}
}
productOptions {
edges {
node {
__typename
entityId
displayName
...multipleChoiceOption
}
}
}
localeMeta: metafields(namespace: $locale, keys: ["name", "description"])
@include(if: $hasLocale) {
edges {
node {
key
value
}
}
}
}
${productPrices}
${multipleChoiceOptionFragment}
`
export const productConnectionFragment = /* GraphQL */ `
fragment productConnnection on ProductConnection {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
...productInfo
}
}
}
${productInfoFragment}
`

View File

@ -4,28 +4,15 @@ import fetchGraphqlApi from '@framework/api/utils/fetch-graphql-api'
import useCartAddItem from '@commerce/cart/use-add-item' import useCartAddItem from '@commerce/cart/use-add-item'
import useCart from './use-cart' import useCart from './use-cart'
import { useCallback } from 'react' import { useCallback } from 'react'
import { cartFragment } from '../api/fragments/cart'
export const addItemToOrderMutation = /* GraphQL */ ` export const addItemToOrderMutation = /* GraphQL */ `
mutation addItemToOrder($variantId: ID!, $quantity: Int!) { mutation addItemToOrder($variantId: ID!, $quantity: Int!) {
addItemToOrder(productVariantId: $variantId, quantity: $quantity) { addItemToOrder(productVariantId: $variantId, quantity: $quantity) {
... on Order { ... Cart
id
code
totalQuantity
total
totalWithTax
lines {
id
productVariant {
featuredAsset {
id
preview
}
}
}
}
} }
} }
${cartFragment}
` `
export type AddItemInput = { productId?: number; variantId: number; quantity?: number; }; export type AddItemInput = { productId?: number; variantId: number; quantity?: number; };

View File

@ -6,35 +6,15 @@ import useResponse from '@commerce/utils/use-response'
import useAction from '@commerce/utils/use-action' import useAction from '@commerce/utils/use-action'
import { useCallback } from 'react' import { useCallback } from 'react'
import { normalizeCart } from '../../bigcommerce/lib/normalize' import { normalizeCart } from '../../bigcommerce/lib/normalize'
import { cartFragment } from '../api/fragments/cart'
export const getCartQuery = /* GraphQL */ ` export const getCartQuery = /* GraphQL */ `
query activeOrder { query activeOrder {
activeOrder { activeOrder {
id ...Cart
code
totalQuantity
subTotal
subTotalWithTax
total
totalWithTax
currencyCode
lines {
id
quantity
featuredAsset {
id
preview
}
productVariant {
name
product {
slug
}
productId
}
}
} }
} }
${cartFragment}
` `
export const fetcher: HookFetcher<any | null> = ( export const fetcher: HookFetcher<any | null> = (
@ -50,17 +30,17 @@ export function extendHook(
swrOptions?: SwrOptions<any | null> swrOptions?: SwrOptions<any | null>
) { ) {
const useCart = () => { const useCart = () => {
const response = useData({}, [], customFetcher, swrOptions) const response = useData({ query: getCartQuery }, [], customFetcher, swrOptions)
const res = useResponse(response, { const res = useResponse(response, {
normalizer: (data => { normalizer: (data => {
const order = data?.activeOrder; const order = data?.activeOrder || data?.addItemToOrder || data?.adjustOrderLine || data?.removeOrderLine;
console.log({ order });
return (order ? { return (order ? {
id: order.id, id: order.id,
currency: { code: order.currencyCode }, currency: { code: order.currencyCode },
subTotal: order.subTotalWithTax / 100, subTotal: order.subTotalWithTax / 100,
total: order.totalWithTax / 100, total: order.totalWithTax / 100,
items: order.lines?.map(l => ({ items: order.lines?.map(l => ({
id: l.id,
name: l.productVariant.name, name: l.productVariant.name,
quantity: l.quantity, quantity: l.quantity,
url: l.productVariant.product.slug, url: l.productVariant.product.slug,

View File

@ -1,41 +1,41 @@
import { useCallback } from 'react' import { useCallback } from 'react'
import { HookFetcher } from '@commerce/utils/types' import { HookFetcher } from '@commerce/utils/types'
import useCartRemoveItem from '@commerce/cart/use-remove-item' import useCartRemoveItem from '@commerce/cart/use-remove-item'
import type { RemoveItemBody } from '../api/cart'
import useCart, { Cart } from './use-cart' import useCart, { Cart } from './use-cart'
import { cartFragment } from '@framework/api/fragments/cart'
const defaultOpts = { export const removeOrderLineMutation = /* GraphQL */ `
url: '/api/bigcommerce/cart', mutation removeOrderLine($orderLineId: ID!) {
method: 'DELETE', removeOrderLine(orderLineId: $orderLineId) {
...Cart
} }
export type RemoveItemInput = {
id: string
} }
${cartFragment}
`
export const fetcher: HookFetcher<Cart | null, RemoveItemBody> = ( export const fetcher: HookFetcher<Cart | null, any> = (
options, options,
{ itemId }, { lineId },
fetch fetch
) => { ) => {
return fetch({ return fetch({
...defaultOpts,
...options, ...options,
body: { itemId }, query: removeOrderLineMutation,
variables: { orderLineId: lineId },
}) })
} }
export function extendHook(customFetcher: typeof fetcher) { export function extendHook(customFetcher: typeof fetcher) {
const useRemoveItem = (item?: any) => { const useRemoveItem = (item?: any) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartRemoveItem<Cart | null, RemoveItemBody>( const fn = useCartRemoveItem<Cart | null, any>(
defaultOpts, {},
customFetcher customFetcher
) )
return useCallback( return useCallback(
async function removeItem(input: RemoveItemInput) { async function removeItem(input: any) {
const data = await fn({ itemId: input.id ?? item?.id }) const data = await fn({ lineId: input.id })
await mutate(data, false) await mutate(data, false)
return data return data
}, },

View File

@ -1,59 +1,43 @@
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 useCartUpdateItem from '@commerce/cart/use-update-item' import useCartUpdateItem from '@commerce/cart/use-update-item'
import type { ItemBody, UpdateItemBody } from '../api/cart'
import { fetcher as removeFetcher } from './use-remove-item'
import useCart, { Cart } from './use-cart' import useCart, { Cart } from './use-cart'
import { cartFragment } from '@framework/api/fragments/cart'
const defaultOpts = { export const adjustOrderLineMutation = /* GraphQL */ `
url: '/api/bigcommerce/cart', mutation adjustOrderLine($orderLineId: ID!, $quantity: Int!) {
method: 'PUT', adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {
...Cart
} }
}
export type UpdateItemInput = Partial<{ id: string } & ItemBody> ${cartFragment}
`
export const fetcher: HookFetcher<Cart | null, UpdateItemBody> = ( export const fetcher: HookFetcher<Cart | null, any> = (
options, options,
{ itemId, item }, { lineId, quantity },
fetch 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 }, fetch)
}
} else if (item.quantity) {
throw new CommerceError({
message: 'The item quantity has to be a valid integer',
})
}
return fetch({ return fetch({
...defaultOpts,
...options, ...options,
body: { itemId, item }, query: adjustOrderLineMutation,
variables: { orderLineId: lineId, quantity },
}) })
} }
function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) { function extendHook(customFetcher: typeof fetcher, cfg?: { wait?: number }) {
const useUpdateItem = (item?: any) => { const useUpdateItem = (item?: any) => {
const { mutate } = useCart() const { mutate } = useCart()
const fn = useCartUpdateItem<Cart | null, UpdateItemBody>( const fn = useCartUpdateItem<Cart | null, any>(
defaultOpts, {},
customFetcher customFetcher
) )
return useCallback( return useCallback(
debounce(async (input: UpdateItemInput) => { debounce(async (input: any) => {
const data = await fn({ const data = await fn({
itemId: input.id ?? item?.id, lineId: item.id,
item: {
productId: input.productId ?? item?.product_id,
variantId: input.productId ?? item?.variant_id,
quantity: input.quantity, quantity: input.quantity,
},
}) })
await mutate(data, false) await mutate(data, false)
return data return data