mirror of
https://github.com/vercel/commerce.git
synced 2025-07-04 04:01:21 +00:00
Implement cart
This commit is contained in:
parent
7bdefa3f48
commit
72cc34d8c7
77
framework/ordercloud/api/endpoints/cart/add-item.ts
Normal file
77
framework/ordercloud/api/endpoints/cart/add-item.ts
Normal file
@ -0,0 +1,77 @@
|
||||
import type { CartEndpoint } from '.'
|
||||
import type { RawVariant } from '../../../types/product'
|
||||
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||
|
||||
import { serialize } from 'cookie'
|
||||
|
||||
import { formatCart } from '../../utils/cart'
|
||||
|
||||
const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
res,
|
||||
body: { cartId, item },
|
||||
config: { fetch, cartCookie },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Set the quantity if not present
|
||||
if (!item.quantity) item.quantity = 1
|
||||
|
||||
// Create an order if it doesn't exist
|
||||
if (!cartId) {
|
||||
cartId = await fetch('POST', `/orders/Outgoing`, {}).then(
|
||||
(response: { ID: string }) => response.ID
|
||||
)
|
||||
}
|
||||
|
||||
// Set the cart cookie
|
||||
res.setHeader(
|
||||
'Set-Cookie',
|
||||
serialize(cartCookie, cartId, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
})
|
||||
)
|
||||
|
||||
// Store specs
|
||||
let specs: RawVariant['Specs'] = []
|
||||
|
||||
// If a variant is present, fetch its specs
|
||||
if (item.variantId) {
|
||||
specs = await fetch(
|
||||
'GET',
|
||||
`/me/products/${item.productId}/variants/${item.variantId}`
|
||||
).then((res: RawVariant) => res.Specs)
|
||||
}
|
||||
|
||||
// Add the item to the order
|
||||
await fetch('POST', `/orders/Outgoing/${cartId}/lineitems`, {
|
||||
ProductID: item.productId,
|
||||
Quantity: item.quantity,
|
||||
Specs: specs,
|
||||
})
|
||||
|
||||
// Get cart
|
||||
const [cart, lineItems] = await Promise.all([
|
||||
fetch('GET', `/orders/Outgoing/${cartId}`),
|
||||
fetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
|
||||
(response: { Items: OrdercloudLineItem[] }) => response.Items
|
||||
),
|
||||
])
|
||||
|
||||
// Format cart
|
||||
const formattedCart = formatCart(cart, lineItems)
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({ data: formattedCart, errors: [] })
|
||||
}
|
||||
|
||||
export default addItem
|
51
framework/ordercloud/api/endpoints/cart/get-cart.ts
Normal file
51
framework/ordercloud/api/endpoints/cart/get-cart.ts
Normal file
@ -0,0 +1,51 @@
|
||||
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||
import type { CartEndpoint } from '.'
|
||||
|
||||
import { serialize } from 'cookie'
|
||||
|
||||
import { formatCart } from '../../utils/cart'
|
||||
|
||||
// Return current cart info
|
||||
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||
res,
|
||||
body: { cartId },
|
||||
config: { fetch, cartCookie },
|
||||
}) => {
|
||||
if (!cartId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
try {
|
||||
// Get cart
|
||||
const cart = await fetch('GET', `/orders/Outgoing/${cartId}`)
|
||||
|
||||
// Get line items
|
||||
const lineItems = await fetch(
|
||||
'GET',
|
||||
`/orders/Outgoing/${cartId}/lineitems`
|
||||
).then((response: { Items: OrdercloudLineItem[] }) => response.Items)
|
||||
|
||||
// Format cart
|
||||
const formattedCart = formatCart(cart, lineItems)
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({ data: formattedCart, errors: [] })
|
||||
} catch (error) {
|
||||
// Reset cart cookie
|
||||
res.setHeader(
|
||||
'Set-Cookie',
|
||||
serialize(cartCookie, cartId, {
|
||||
maxAge: -1,
|
||||
path: '/',
|
||||
})
|
||||
)
|
||||
|
||||
// Return empty cart
|
||||
res.status(200).json({ data: null, errors: [] })
|
||||
}
|
||||
}
|
||||
|
||||
export default getCart
|
36
framework/ordercloud/api/endpoints/cart/remove-item.ts
Normal file
36
framework/ordercloud/api/endpoints/cart/remove-item.ts
Normal file
@ -0,0 +1,36 @@
|
||||
import type { CartEndpoint } from '.'
|
||||
|
||||
import { formatCart } from '../../utils/cart'
|
||||
import { OrdercloudLineItem } from '../../../types/cart'
|
||||
|
||||
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
res,
|
||||
body: { cartId, itemId },
|
||||
config: { fetch },
|
||||
}) => {
|
||||
if (!cartId || !itemId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Remove the item to the order
|
||||
await fetch('DELETE', `/orders/Outgoing/${cartId}/lineitems/${itemId}`)
|
||||
|
||||
// Get cart
|
||||
const [cart, lineItems] = await Promise.all([
|
||||
fetch('GET', `/orders/Outgoing/${cartId}`),
|
||||
fetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
|
||||
(response: { Items: OrdercloudLineItem[] }) => response.Items
|
||||
),
|
||||
])
|
||||
|
||||
// Format cart
|
||||
const formattedCart = formatCart(cart, lineItems)
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({ data: formattedCart, errors: [] })
|
||||
}
|
||||
|
||||
export default removeItem
|
52
framework/ordercloud/api/endpoints/cart/update-item.ts
Normal file
52
framework/ordercloud/api/endpoints/cart/update-item.ts
Normal file
@ -0,0 +1,52 @@
|
||||
import type { OrdercloudLineItem } from '../../../types/cart'
|
||||
import type { RawVariant } from '../../../types/product'
|
||||
import type { CartEndpoint } from '.'
|
||||
|
||||
import { formatCart } from '../../utils/cart'
|
||||
|
||||
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||
res,
|
||||
body: { cartId, itemId, item },
|
||||
config: { fetch },
|
||||
}) => {
|
||||
if (!cartId || !itemId || !item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Store specs
|
||||
let specs: RawVariant['Specs'] = []
|
||||
|
||||
// If a variant is present, fetch its specs
|
||||
if (item.variantId) {
|
||||
specs = await fetch(
|
||||
'GET',
|
||||
`/me/products/${item.productId}/variants/${item.variantId}`
|
||||
).then((res: RawVariant) => res.Specs)
|
||||
}
|
||||
|
||||
// Add the item to the order
|
||||
await fetch('PATCH', `/orders/Outgoing/${cartId}/lineitems/${itemId}`, {
|
||||
ProductID: item.productId,
|
||||
Quantity: item.quantity,
|
||||
Specs: specs,
|
||||
})
|
||||
|
||||
// Get cart
|
||||
const [cart, lineItems] = await Promise.all([
|
||||
fetch('GET', `/orders/Outgoing/${cartId}`),
|
||||
fetch('GET', `/orders/Outgoing/${cartId}/lineitems`).then(
|
||||
(response: { Items: OrdercloudLineItem[] }) => response.Items
|
||||
),
|
||||
])
|
||||
|
||||
// Format cart
|
||||
const formattedCart = formatCart(cart, lineItems)
|
||||
|
||||
// Return cart and errors
|
||||
res.status(200).json({ data: formattedCart, errors: [] })
|
||||
}
|
||||
|
||||
export default updateItem
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
41
framework/ordercloud/api/utils/cart.ts
Normal file
41
framework/ordercloud/api/utils/cart.ts
Normal file
@ -0,0 +1,41 @@
|
||||
import type { Cart, OrdercloudCart, OrdercloudLineItem } from '../../types/cart'
|
||||
|
||||
export function formatCart(
|
||||
cart: OrdercloudCart,
|
||||
lineItems: OrdercloudLineItem[]
|
||||
): Cart {
|
||||
return {
|
||||
id: cart.ID,
|
||||
customerId: cart.FromUserID,
|
||||
email: cart.FromUser.Email,
|
||||
createdAt: cart.DateCreated,
|
||||
currency: {
|
||||
code: cart.FromUser?.xp?.currency ?? 'USD',
|
||||
},
|
||||
taxesIncluded: cart.TaxCost === 0,
|
||||
lineItems: lineItems.map((lineItem) => ({
|
||||
id: lineItem.ID,
|
||||
variantId: lineItem.Variant ? String(lineItem.Variant.ID) : '',
|
||||
productId: lineItem.ProductID,
|
||||
name: lineItem.Product.Name,
|
||||
quantity: lineItem.Quantity,
|
||||
discounts: [],
|
||||
path: lineItem.ProductID,
|
||||
variant: {
|
||||
id: lineItem.Variant ? String(lineItem.Variant.ID) : '',
|
||||
sku: lineItem.ID,
|
||||
name: lineItem.Product.Name,
|
||||
image: {
|
||||
url: lineItem.Product.xp?.Images?.[0]?.url,
|
||||
},
|
||||
requiresShipping: Boolean(lineItem.ShippingAddress),
|
||||
price: lineItem.UnitPrice,
|
||||
listPrice: lineItem.UnitPrice,
|
||||
},
|
||||
})),
|
||||
lineItemsSubtotalPrice: cart.Subtotal,
|
||||
subtotalPrice: cart.Subtotal,
|
||||
totalPrice: cart.Total,
|
||||
discounts: [],
|
||||
}
|
||||
}
|
@ -1,106 +0,0 @@
|
||||
import type { OrdercloudConfig } from '../index'
|
||||
|
||||
import { FetcherError } from '@commerce/utils/errors'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchRestApi: (
|
||||
getConfig: () => OrdercloudConfig
|
||||
) => <T>(
|
||||
method: string,
|
||||
resource: string,
|
||||
body?: Record<string, unknown>,
|
||||
fetchOptions?: Record<string, any>
|
||||
) => Promise<T> =
|
||||
(getConfig) =>
|
||||
async <T>(
|
||||
method: string,
|
||||
resource: string,
|
||||
body?: Record<string, unknown>,
|
||||
fetchOptions?: Record<string, any>
|
||||
) => {
|
||||
const { commerceUrl } = getConfig()
|
||||
|
||||
async function getToken() {
|
||||
// If not, get a new one and store it
|
||||
const authResponse = await fetch(`${commerceUrl}/oauth/token`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded',
|
||||
Accept: 'application/json',
|
||||
},
|
||||
body: `client_id=${process.env.NEXT_PUBLIC_ORDERCLOUD_CLIENT_ID}&grant_type=client_credentials&client_secret=${process.env.NEXT_PUBLIC_ORDERCLOUD_CLIENT_SECRET}`,
|
||||
})
|
||||
|
||||
// If something failed getting the auth response
|
||||
if (!authResponse.ok) {
|
||||
// Get the body of it
|
||||
const error = await authResponse.json()
|
||||
|
||||
// And return an error
|
||||
throw new FetcherError({
|
||||
errors: [{ message: error.error_description.Code }],
|
||||
status: error.error_description.HttpStatus,
|
||||
})
|
||||
}
|
||||
|
||||
// If everything is fine, store the access token in global.token
|
||||
global.token = await authResponse
|
||||
.json()
|
||||
.then((response) => response.access_token)
|
||||
}
|
||||
|
||||
async function fetchData(retries = 0): Promise<T> {
|
||||
// Do the request with the correct headers
|
||||
const dataResponse = await fetch(`${commerceUrl}/v1${resource}`, {
|
||||
...fetchOptions,
|
||||
method,
|
||||
headers: {
|
||||
...fetchOptions?.headers,
|
||||
accept: 'application/json, text/plain, */*',
|
||||
authorization: `Bearer ${global.token}`,
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined,
|
||||
})
|
||||
|
||||
// If something failed getting the data response
|
||||
if (!dataResponse.ok) {
|
||||
// If token is expired
|
||||
if (dataResponse.status === 401) {
|
||||
// Reset it
|
||||
global.token = null
|
||||
|
||||
// Get a new one
|
||||
await getToken()
|
||||
|
||||
// And if retries left
|
||||
if (retries < 2) {
|
||||
// Refetch
|
||||
return fetchData(retries + 1)
|
||||
}
|
||||
}
|
||||
|
||||
// Get the body of it
|
||||
const error = await dataResponse.json()
|
||||
|
||||
// And return an error
|
||||
throw new FetcherError({
|
||||
errors: [{ message: error.error_description.Code }],
|
||||
status: error.error_description.HttpStatus,
|
||||
})
|
||||
}
|
||||
|
||||
// Return data response
|
||||
return dataResponse.json() as Promise<T>
|
||||
}
|
||||
|
||||
// Check if we have a token stored
|
||||
if (!global.token) {
|
||||
// If not, get a new one and store it
|
||||
await getToken()
|
||||
}
|
||||
|
||||
// Return the data and specify the expected type
|
||||
return fetchData()
|
||||
}
|
||||
|
||||
export default fetchRestApi
|
@ -1,17 +1,48 @@
|
||||
import type { AddItemHook } from '@commerce/types/cart'
|
||||
import type { MutationHook } from '@commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { CommerceError } from '@commerce/utils/errors'
|
||||
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import useCart from './use-cart'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
export const handler: MutationHook<any> = {
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
url: '/api/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
return async function addItem() {
|
||||
return {}
|
||||
}
|
||||
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 data = await fetch({
|
||||
...options,
|
||||
body: { item },
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) =>
|
||||
function useHook() {
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -1,42 +1,33 @@
|
||||
import type { GetCartHook } from '@commerce/types/cart'
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@commerce/utils/types'
|
||||
import useCart, { UseCart } from '@commerce/cart/use-cart'
|
||||
|
||||
export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
export const handler: SWRHook<GetCartHook> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
url: '/api/cart',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher() {
|
||||
return {
|
||||
id: '',
|
||||
createdAt: '',
|
||||
currency: { code: '' },
|
||||
taxesIncluded: '',
|
||||
lineItems: [],
|
||||
lineItemsSubtotalPrice: '',
|
||||
subtotalPrice: 0,
|
||||
totalPrice: 0,
|
||||
}
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
useHook: ({ useData }) =>
|
||||
function useHook(input) {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(
|
||||
{},
|
||||
{
|
||||
isEmpty: {
|
||||
get() {
|
||||
return true
|
||||
},
|
||||
enumerable: true,
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems?.length ?? 0) <= 0
|
||||
},
|
||||
}
|
||||
),
|
||||
[]
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -1,18 +1,60 @@
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
|
||||
|
||||
import useCart from './use-cart'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||
|
||||
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<RemoveItemHook['actionInput']>
|
||||
: RemoveItemHook['actionInput']
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
url: '/api/cart',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
return async function removeItem(input) {
|
||||
return {}
|
||||
async fetcher({
|
||||
input: { itemId },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<RemoveItemHook>) {
|
||||
return await fetch({ ...options, body: { itemId } })
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||
function useHook<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])
|
||||
},
|
||||
}
|
||||
|
@ -1,18 +1,93 @@
|
||||
import type {
|
||||
HookFetcherContext,
|
||||
MutationHookContext,
|
||||
} from '@commerce/utils/types'
|
||||
import type { UpdateItemHook, LineItem } from '@commerce/types/cart'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
|
||||
import { MutationHook } from '@commerce/utils/types'
|
||||
import { ValidationError } from '@commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
|
||||
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useCart from './use-cart'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
: UpdateItemHook['actionInput']
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
url: '/api/cart',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
return async function addItem() {
|
||||
return {}
|
||||
async fetcher({
|
||||
input: { itemId, item },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<UpdateItemHook>) {
|
||||
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',
|
||||
})
|
||||
}
|
||||
|
||||
return await fetch({
|
||||
...options,
|
||||
body: { itemId, item },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||
function useHook<T extends LineItem | undefined = undefined>(
|
||||
ctx: {
|
||||
item?: T
|
||||
wait?: number
|
||||
} = {}
|
||||
) {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCart() as any
|
||||
|
||||
return useCallback(
|
||||
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||
const itemId = input.id ?? item?.id
|
||||
const productId = input.productId ?? item?.productId
|
||||
const variantId = input.productId ?? item?.variantId
|
||||
|
||||
if (!itemId || !productId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: {
|
||||
productId,
|
||||
variantId: variantId || '',
|
||||
quantity: input.quantity,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
await mutate(data, false)
|
||||
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
126
framework/ordercloud/types/cart.ts
Normal file
126
framework/ordercloud/types/cart.ts
Normal file
@ -0,0 +1,126 @@
|
||||
import * as Core from '@commerce/types/cart'
|
||||
|
||||
export * from '@commerce/types/cart'
|
||||
|
||||
export interface OrdercloudCart {
|
||||
ID: string
|
||||
FromUser: {
|
||||
ID: string
|
||||
Username: string
|
||||
Password: null
|
||||
FirstName: string
|
||||
LastName: string
|
||||
Email: string
|
||||
Phone: null
|
||||
TermsAccepted: null
|
||||
Active: true
|
||||
xp: {
|
||||
something: string
|
||||
currency: string
|
||||
}
|
||||
AvailableRoles: null
|
||||
DateCreated: string
|
||||
PasswordLastSetDate: null
|
||||
}
|
||||
FromCompanyID: string
|
||||
ToCompanyID: string
|
||||
FromUserID: string
|
||||
BillingAddressID: null
|
||||
BillingAddress: null
|
||||
ShippingAddressID: null
|
||||
Comments: null
|
||||
LineItemCount: number
|
||||
Status: string
|
||||
DateCreated: string
|
||||
DateSubmitted: null
|
||||
DateApproved: null
|
||||
DateDeclined: null
|
||||
DateCanceled: null
|
||||
DateCompleted: null
|
||||
LastUpdated: string
|
||||
Subtotal: number
|
||||
ShippingCost: number
|
||||
TaxCost: number
|
||||
PromotionDiscount: number
|
||||
Total: number
|
||||
IsSubmitted: false
|
||||
xp: {
|
||||
productId: string
|
||||
variantId: string
|
||||
quantity: 1
|
||||
}
|
||||
}
|
||||
|
||||
export interface OrdercloudLineItem {
|
||||
ID: string
|
||||
ProductID: string
|
||||
Quantity: 1
|
||||
DateAdded: string
|
||||
QuantityShipped: number
|
||||
UnitPrice: number
|
||||
PromotionDiscount: number
|
||||
LineTotal: number
|
||||
LineSubtotal: number
|
||||
CostCenter: null
|
||||
DateNeeded: null
|
||||
ShippingAccount: null
|
||||
ShippingAddressID: null
|
||||
ShipFromAddressID: null
|
||||
Product: {
|
||||
ID: string
|
||||
Name: string
|
||||
Description: string
|
||||
QuantityMultiplier: number
|
||||
ShipWeight: number
|
||||
ShipHeight: null
|
||||
ShipWidth: null
|
||||
ShipLength: null
|
||||
xp: {
|
||||
Images: {
|
||||
url: string
|
||||
}[]
|
||||
}
|
||||
}
|
||||
Variant: null | {
|
||||
ID: string
|
||||
Name: null
|
||||
Description: null
|
||||
ShipWeight: null
|
||||
ShipHeight: null
|
||||
ShipWidth: null
|
||||
ShipLength: null
|
||||
xp: null
|
||||
}
|
||||
ShippingAddress: null
|
||||
ShipFromAddress: null
|
||||
SupplierID: null
|
||||
Specs: []
|
||||
xp: null
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend core cart types
|
||||
*/
|
||||
|
||||
export type Cart = Core.Cart & {
|
||||
lineItems: Core.LineItem[]
|
||||
url?: string
|
||||
}
|
||||
|
||||
export type CartTypes = Core.CartTypes
|
||||
|
||||
export type CartHooks = Core.CartHooks<CartTypes>
|
||||
|
||||
export type GetCartHook = CartHooks['getCart']
|
||||
export type AddItemHook = CartHooks['addItem']
|
||||
export type UpdateItemHook = CartHooks['updateItem']
|
||||
export type RemoveItemHook = CartHooks['removeItem']
|
||||
|
||||
export type CartSchema = Core.CartSchema<CartTypes>
|
||||
|
||||
export type CartHandlers = Core.CartHandlers<CartTypes>
|
||||
|
||||
export type GetCartHandler = CartHandlers['getCart']
|
||||
export type AddItemHandler = CartHandlers['addItem']
|
||||
export type UpdateItemHandler = CartHandlers['updateItem']
|
||||
export type RemoveItemHandler = CartHandlers['removeItem']
|
@ -3,6 +3,8 @@ interface RawVariantSpec {
|
||||
Name: string
|
||||
OptionID: string
|
||||
Value: string
|
||||
PriceMarkupType: string
|
||||
PriceMarkup: string | null
|
||||
}
|
||||
|
||||
export interface RawSpec {
|
||||
|
@ -29,7 +29,7 @@ export function normalize(product: RawProduct): Product {
|
||||
}))
|
||||
: [
|
||||
{
|
||||
id: product.ID,
|
||||
id: '',
|
||||
options: [],
|
||||
},
|
||||
],
|
||||
|
Loading…
x
Reference in New Issue
Block a user