mirror of
https://github.com/vercel/commerce.git
synced 2025-06-18 21:21:21 +00:00
Implement useUpdateItem & useRemoveItem
This commit is contained in:
parent
0f7c151805
commit
d0ed97466c
27
framework/vendure/api/fragments/cart.ts
Normal file
27
framework/vendure/api/fragments/cart.ts
Normal 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`
|
@ -1,9 +0,0 @@
|
|||||||
export const categoryTreeItemFragment = /* GraphQL */ `
|
|
||||||
fragment categoryTreeItem on CategoryTreeItem {
|
|
||||||
entityId
|
|
||||||
name
|
|
||||||
path
|
|
||||||
description
|
|
||||||
productCount
|
|
||||||
}
|
|
||||||
`
|
|
@ -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}
|
|
||||||
`
|
|
@ -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; };
|
||||||
|
@ -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,
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
${cartFragment}
|
||||||
|
`
|
||||||
|
|
||||||
export type RemoveItemInput = {
|
export const fetcher: HookFetcher<Cart | null, any> = (
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export const fetcher: HookFetcher<Cart | null, RemoveItemBody> = (
|
|
||||||
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
|
||||||
},
|
},
|
||||||
|
@ -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
|
||||||
|
Loading…
x
Reference in New Issue
Block a user