mirror of
https://github.com/vercel/commerce.git
synced 2025-06-19 05:31:22 +00:00
Adding types, fixes and other updates
This commit is contained in:
parent
e6df8dfb40
commit
b2fbaab5d5
@ -1,8 +1,9 @@
|
|||||||
import { GetAPISchema } from '@commerce/api'
|
import type { GetAPISchema } from '@commerce/api'
|
||||||
|
import type { AddItemOperation } from '@commerce/types'
|
||||||
import getCart from './get-cart'
|
import getCart from './get-cart'
|
||||||
import addItem from './add-item'
|
import addItem from './add-item'
|
||||||
import updateItem from './handlers/update-item'
|
import updateItem from './update-item'
|
||||||
import removeItem from './handlers/remove-item'
|
import removeItem from './remove-item'
|
||||||
import type {
|
import type {
|
||||||
GetCartHandlerBody,
|
GetCartHandlerBody,
|
||||||
AddCartItemHandlerBody,
|
AddCartItemHandlerBody,
|
||||||
@ -18,14 +19,10 @@ export type CartAPI = GetAPISchema<
|
|||||||
endpoint: {
|
endpoint: {
|
||||||
options: {}
|
options: {}
|
||||||
operations: {
|
operations: {
|
||||||
getCart: {
|
getCart: { data: Cart | null; body: GetCartHandlerBody }
|
||||||
data: Cart | null
|
addItem: { data: Cart; body: AddItemOperation['body'] }
|
||||||
body: GetCartHandlerBody
|
updateItem: { data: Cart; body: UpdateCartItemHandlerBody }
|
||||||
options: { yay: string }
|
removeItem: { data: Cart; body: RemoveCartItemHandlerBody }
|
||||||
}
|
|
||||||
addItem: { data: Cart; body: AddCartItemHandlerBody; options: {} }
|
|
||||||
updateItem: { data: Cart; body: UpdateCartItemHandlerBody; options: {} }
|
|
||||||
removeItem: { data: Cart; body: RemoveCartItemHandlerBody; options: {} }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -33,4 +30,4 @@ export type CartAPI = GetAPISchema<
|
|||||||
|
|
||||||
export type CartEndpoint = CartAPI['endpoint']
|
export type CartEndpoint = CartAPI['endpoint']
|
||||||
|
|
||||||
export const operations = { getCart, addItem }
|
export const operations = { getCart, addItem, updateItem, removeItem }
|
||||||
|
34
framework/bigcommerce/api/cart/remove-item.ts
Normal file
34
framework/bigcommerce/api/cart/remove-item.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import { normalizeCart } from '@framework/lib/normalize'
|
||||||
|
import getCartCookie from '../utils/get-cart-cookie'
|
||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
|
const removeItem: CartEndpoint['operations']['removeItem'] = async ({
|
||||||
|
res,
|
||||||
|
body: { cartId, itemId },
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
if (!cartId || !itemId) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Invalid request' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const result = await config.storeApiFetch<{ data: any } | null>(
|
||||||
|
`/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
|
||||||
|
{ method: 'DELETE' }
|
||||||
|
)
|
||||||
|
const data = result?.data ?? null
|
||||||
|
|
||||||
|
res.setHeader(
|
||||||
|
'Set-Cookie',
|
||||||
|
data
|
||||||
|
? // Update the cart cookie
|
||||||
|
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
|
||||||
|
: // Remove the cart cookie if the cart was removed (empty items)
|
||||||
|
getCartCookie(config.cartCookie)
|
||||||
|
)
|
||||||
|
res.status(200).json({ data: normalizeCart(data) })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default removeItem
|
36
framework/bigcommerce/api/cart/update-item.ts
Normal file
36
framework/bigcommerce/api/cart/update-item.ts
Normal file
@ -0,0 +1,36 @@
|
|||||||
|
import { normalizeCart } from '@framework/lib/normalize'
|
||||||
|
import { parseCartItem } from '../utils/parse-item'
|
||||||
|
import getCartCookie from '../utils/get-cart-cookie'
|
||||||
|
import type { CartEndpoint } from '.'
|
||||||
|
|
||||||
|
const updateItem: CartEndpoint['operations']['updateItem'] = async ({
|
||||||
|
res,
|
||||||
|
body: { cartId, itemId, item },
|
||||||
|
config,
|
||||||
|
}) => {
|
||||||
|
if (!cartId || !itemId || !item) {
|
||||||
|
return res.status(400).json({
|
||||||
|
data: null,
|
||||||
|
errors: [{ message: 'Invalid request' }],
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const { data } = await config.storeApiFetch(
|
||||||
|
`/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
|
||||||
|
{
|
||||||
|
method: 'PUT',
|
||||||
|
body: JSON.stringify({
|
||||||
|
line_item: parseCartItem(item),
|
||||||
|
}),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// Update the cart cookie
|
||||||
|
res.setHeader(
|
||||||
|
'Set-Cookie',
|
||||||
|
getCartCookie(config.cartCookie, cartId, config.cartCookieMaxAge)
|
||||||
|
)
|
||||||
|
res.status(200).json({ data: normalizeCart(data) })
|
||||||
|
}
|
||||||
|
|
||||||
|
export default updateItem
|
@ -1,3 +1,4 @@
|
|||||||
|
import type { NextApiHandler } from 'next'
|
||||||
import type { RequestInit } from '@vercel/fetch'
|
import type { RequestInit } from '@vercel/fetch'
|
||||||
import {
|
import {
|
||||||
CommerceAPI as CoreCommerceAPI,
|
CommerceAPI as CoreCommerceAPI,
|
||||||
@ -6,6 +7,8 @@ import {
|
|||||||
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
import fetchGraphqlApi from './utils/fetch-graphql-api'
|
||||||
import fetchStoreApi from './utils/fetch-store-api'
|
import fetchStoreApi from './utils/fetch-store-api'
|
||||||
|
|
||||||
|
import type { CartAPI } from './cart'
|
||||||
|
|
||||||
export interface BigcommerceConfig extends CommerceAPIConfig {
|
export interface BigcommerceConfig extends CommerceAPIConfig {
|
||||||
// Indicates if the returned metadata with translations should be applied to the
|
// Indicates if the returned metadata with translations should be applied to the
|
||||||
// data or returned as it is
|
// data or returned as it is
|
||||||
@ -104,11 +107,20 @@ export const provider = {
|
|||||||
|
|
||||||
export type Provider = typeof provider
|
export type Provider = typeof provider
|
||||||
|
|
||||||
export class CommerceAPI<
|
export type APIs = CartAPI
|
||||||
P extends Provider = Provider
|
|
||||||
> extends CoreCommerceAPI<P> {
|
export class CommerceAPI extends CoreCommerceAPI<Provider> {
|
||||||
constructor(readonly provider: P = provider) {
|
constructor(customProvider: Provider = provider) {
|
||||||
super(provider)
|
super(customProvider)
|
||||||
|
}
|
||||||
|
|
||||||
|
endpoint<E extends APIs>(
|
||||||
|
context: E['endpoint'] & {
|
||||||
|
config?: Provider['config']
|
||||||
|
options?: E['schema']['endpoint']['options']
|
||||||
|
}
|
||||||
|
): NextApiHandler {
|
||||||
|
return this.endpoint(context)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,6 @@ export type BigcommerceCart = {
|
|||||||
|
|
||||||
export type Cart = Core.Cart & {
|
export type Cart = Core.Cart & {
|
||||||
lineItems: LineItem[]
|
lineItems: LineItem[]
|
||||||
core: string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export type LineItem = Core.LineItem
|
export type LineItem = Core.LineItem
|
||||||
|
@ -1,19 +1,7 @@
|
|||||||
import type { NextApiHandler } from 'next'
|
import type { NextApiHandler } from 'next'
|
||||||
import type { RequestInit, Response } from '@vercel/fetch'
|
import type { RequestInit, Response } from '@vercel/fetch'
|
||||||
import type { APIEndpoint, APIHandler } from './utils/types'
|
import type { APIEndpoint, APIHandler } from './utils/types'
|
||||||
import type { Cart } from '../types'
|
import type { CartSchema } from '../types'
|
||||||
|
|
||||||
export type CartSchema = {
|
|
||||||
endpoint: {
|
|
||||||
options: {}
|
|
||||||
operations: {
|
|
||||||
getCart: { data?: Cart | null; body?: any }
|
|
||||||
addItem: { data?: Cart; body?: any }
|
|
||||||
updateItem: { data?: Cart; body?: any }
|
|
||||||
removeItem: { data?: Cart; body?: any }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export type APISchemas = CartSchema
|
export type APISchemas = CartSchema
|
||||||
|
|
||||||
@ -78,10 +66,10 @@ export class CommerceAPI<P extends APIProvider = APIProvider> {
|
|||||||
Object.assign(this.provider.config, newConfig)
|
Object.assign(this.provider.config, newConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
endpoint<E extends GetAPISchema<this>>(
|
endpoint<T extends GetAPISchema<any, any>>(
|
||||||
context: E['endpoint'] & {
|
context: T['endpoint'] & {
|
||||||
config?: P['config']
|
config?: P['config']
|
||||||
options?: E['schema']['endpoint']['options']
|
options?: T['schema']['endpoint']['options']
|
||||||
}
|
}
|
||||||
): NextApiHandler {
|
): NextApiHandler {
|
||||||
const commerce = this
|
const commerce = this
|
||||||
@ -93,7 +81,7 @@ export class CommerceAPI<P extends APIProvider = APIProvider> {
|
|||||||
res,
|
res,
|
||||||
commerce,
|
commerce,
|
||||||
config: cfg,
|
config: cfg,
|
||||||
handlers: context.operations,
|
operations: context.operations,
|
||||||
options: context.options ?? {},
|
options: context.options ?? {},
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ export type APIHandlerContext<
|
|||||||
res: NextApiResponse<APIResponse<Data>>
|
res: NextApiResponse<APIResponse<Data>>
|
||||||
commerce: C
|
commerce: C
|
||||||
config: C['provider']['config']
|
config: C['provider']['config']
|
||||||
handlers: H
|
operations: H
|
||||||
/**
|
/**
|
||||||
* Custom configs that may be used by a particular handler
|
* Custom configs that may be used by a particular handler
|
||||||
*/
|
*/
|
||||||
|
147
framework/commerce/types/cart.ts
Normal file
147
framework/commerce/types/cart.ts
Normal file
@ -0,0 +1,147 @@
|
|||||||
|
import type { Discount, Measurement, Image } from './common'
|
||||||
|
|
||||||
|
export type LineItem = {
|
||||||
|
id: string
|
||||||
|
variantId: string
|
||||||
|
productId: string
|
||||||
|
name: string
|
||||||
|
quantity: number
|
||||||
|
discounts: Discount[]
|
||||||
|
// A human-friendly unique string automatically generated from the product’s name
|
||||||
|
path: string
|
||||||
|
variant: ProductVariant
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProductVariant = {
|
||||||
|
id: string
|
||||||
|
// The SKU (stock keeping unit) associated with the product variant.
|
||||||
|
sku: string
|
||||||
|
// The product variant’s title, or the product's name.
|
||||||
|
name: string
|
||||||
|
// Whether a customer needs to provide a shipping address when placing
|
||||||
|
// an order for the product variant.
|
||||||
|
requiresShipping: boolean
|
||||||
|
// The product variant’s price after all discounts are applied.
|
||||||
|
price: number
|
||||||
|
// Product variant’s price, as quoted by the manufacturer/distributor.
|
||||||
|
listPrice: number
|
||||||
|
// Image associated with the product variant. Falls back to the product image
|
||||||
|
// if no image is available.
|
||||||
|
image?: Image
|
||||||
|
// Indicates whether this product variant is in stock.
|
||||||
|
isInStock?: boolean
|
||||||
|
// Indicates if the product variant is available for sale.
|
||||||
|
availableForSale?: boolean
|
||||||
|
// The variant's weight. If a weight was not explicitly specified on the
|
||||||
|
// variant this will be the product's weight.
|
||||||
|
weight?: Measurement
|
||||||
|
// The variant's height. If a height was not explicitly specified on the
|
||||||
|
// variant, this will be the product's height.
|
||||||
|
height?: Measurement
|
||||||
|
// The variant's width. If a width was not explicitly specified on the
|
||||||
|
// variant, this will be the product's width.
|
||||||
|
width?: Measurement
|
||||||
|
// The variant's depth. If a depth was not explicitly specified on the
|
||||||
|
// variant, this will be the product's depth.
|
||||||
|
depth?: Measurement
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shopping cart, a.k.a Checkout
|
||||||
|
export type Cart = {
|
||||||
|
id: string
|
||||||
|
// ID of the customer to which the cart belongs.
|
||||||
|
customerId?: string
|
||||||
|
// The email assigned to this cart
|
||||||
|
email?: string
|
||||||
|
// The date and time when the cart was created.
|
||||||
|
createdAt: string
|
||||||
|
// The currency used for this cart
|
||||||
|
currency: { code: string }
|
||||||
|
// Specifies if taxes are included in the line items.
|
||||||
|
taxesIncluded: boolean
|
||||||
|
lineItems: LineItem[]
|
||||||
|
// The sum of all the prices of all the items in the cart.
|
||||||
|
// Duties, taxes, shipping and discounts excluded.
|
||||||
|
lineItemsSubtotalPrice: number
|
||||||
|
// Price of the cart before duties, shipping and taxes.
|
||||||
|
subtotalPrice: number
|
||||||
|
// The sum of all the prices of all the items in the cart.
|
||||||
|
// Duties, taxes and discounts included.
|
||||||
|
totalPrice: number
|
||||||
|
// Discounts that have been applied on the cart.
|
||||||
|
discounts?: Discount[]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base cart item body used for cart mutations
|
||||||
|
*/
|
||||||
|
export type CartItemBody = {
|
||||||
|
variantId: string
|
||||||
|
productId?: string
|
||||||
|
quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Hooks schema
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type CartHooks = {
|
||||||
|
getCart: GetCartHook
|
||||||
|
addItem: AddItemHook
|
||||||
|
updateItem: UpdateItemHook
|
||||||
|
remoteItem: RemoveItemHook
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCartHook = {
|
||||||
|
data: Cart | null
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddItemHook = {
|
||||||
|
data: Cart
|
||||||
|
body: { item: CartItemBody }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateItemHook = {
|
||||||
|
data: Cart
|
||||||
|
body: { itemId: string; item: CartItemBody }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RemoveItemHook = {
|
||||||
|
data: Cart | null
|
||||||
|
body: { itemId: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* API Schema
|
||||||
|
*/
|
||||||
|
|
||||||
|
export type CartSchema = {
|
||||||
|
endpoint: {
|
||||||
|
options: {}
|
||||||
|
operations: CartOperations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export type CartOperations = {
|
||||||
|
getCart: GetCartOperation
|
||||||
|
addItem: AddItemOperation
|
||||||
|
updateItem: UpdateItemOperation
|
||||||
|
removeItem: RemoveItemOperation
|
||||||
|
}
|
||||||
|
|
||||||
|
export type GetCartOperation = {
|
||||||
|
data: Cart | null
|
||||||
|
body: { cartId?: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AddItemOperation = AddItemHook & {
|
||||||
|
body: { cartId: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type UpdateItemOperation = UpdateItemHook & {
|
||||||
|
body: { cartId: string }
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RemoveItemOperation = RemoveItemHook & {
|
||||||
|
body: { cartId: string }
|
||||||
|
}
|
16
framework/commerce/types/common.ts
Normal file
16
framework/commerce/types/common.ts
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
export type Discount = {
|
||||||
|
// The value of the discount, can be an amount or percentage
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Measurement = {
|
||||||
|
value: number
|
||||||
|
unit: 'KILOGRAMS' | 'GRAMS' | 'POUNDS' | 'OUNCES'
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Image = {
|
||||||
|
url: string
|
||||||
|
altText?: string
|
||||||
|
width?: number
|
||||||
|
height?: number
|
||||||
|
}
|
130
framework/commerce/types/index.ts
Normal file
130
framework/commerce/types/index.ts
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
import type { Wishlist as BCWishlist } from '../../bigcommerce/api/wishlist'
|
||||||
|
import type { Customer as BCCustomer } from '../../bigcommerce/api/customers'
|
||||||
|
import type { SearchProductsData as BCSearchProductsData } from '../../bigcommerce/api/catalog/products'
|
||||||
|
|
||||||
|
export * from './cart'
|
||||||
|
export * from './common'
|
||||||
|
|
||||||
|
// TODO: Properly define this type
|
||||||
|
export interface Wishlist extends BCWishlist {}
|
||||||
|
|
||||||
|
// TODO: Properly define this type
|
||||||
|
export interface Customer extends BCCustomer {}
|
||||||
|
|
||||||
|
// TODO: Properly define this type
|
||||||
|
export interface SearchProductsData extends BCSearchProductsData {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cart mutations
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Base cart item body used for cart mutations
|
||||||
|
export type CartItemBody = {
|
||||||
|
variantId: string
|
||||||
|
productId?: string
|
||||||
|
quantity?: number
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body used by the `getCart` operation handler
|
||||||
|
export type GetCartHandlerBody = {
|
||||||
|
cartId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body used by the add item to cart operation
|
||||||
|
export type AddCartItemBody<T extends CartItemBody> = {
|
||||||
|
item: T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body expected by the add item to cart operation handler
|
||||||
|
export type AddCartItemHandlerBody<T extends CartItemBody> = Partial<
|
||||||
|
AddCartItemBody<T>
|
||||||
|
> & {
|
||||||
|
cartId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body used by the update cart item operation
|
||||||
|
export type UpdateCartItemBody<T extends CartItemBody> = {
|
||||||
|
itemId: string
|
||||||
|
item: T
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body expected by the update cart item operation handler
|
||||||
|
export type UpdateCartItemHandlerBody<T extends CartItemBody> = Partial<
|
||||||
|
UpdateCartItemBody<T>
|
||||||
|
> & {
|
||||||
|
cartId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body used by the remove cart item operation
|
||||||
|
export type RemoveCartItemBody = {
|
||||||
|
itemId: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Body expected by the remove cart item operation handler
|
||||||
|
export type RemoveCartItemHandlerBody = Partial<RemoveCartItemBody> & {
|
||||||
|
cartId?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporal types
|
||||||
|
*/
|
||||||
|
|
||||||
|
interface Entity {
|
||||||
|
id: string | number
|
||||||
|
[prop: string]: any
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product2 {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
sku?: string
|
||||||
|
slug?: string
|
||||||
|
path?: string
|
||||||
|
images: ProductImage[]
|
||||||
|
variants: ProductVariant2[]
|
||||||
|
price: ProductPrice
|
||||||
|
options: ProductOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface Product extends Entity {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
slug?: string
|
||||||
|
path?: string
|
||||||
|
images: ProductImage[]
|
||||||
|
variants: ProductVariant2[]
|
||||||
|
price: ProductPrice
|
||||||
|
options: ProductOption[]
|
||||||
|
sku?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductOption extends Entity {
|
||||||
|
displayName: string
|
||||||
|
values: ProductOptionValues[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductOptionValues {
|
||||||
|
label: string
|
||||||
|
hexColors?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductImage {
|
||||||
|
url: string
|
||||||
|
alt?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductVariant2 {
|
||||||
|
id: string | number
|
||||||
|
options: ProductOption[]
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ProductPrice {
|
||||||
|
value: number
|
||||||
|
currencyCode: 'USD' | 'ARS' | string | undefined
|
||||||
|
retailPrice?: number
|
||||||
|
salePrice?: number
|
||||||
|
listPrice?: number
|
||||||
|
extendedSalePrice?: number
|
||||||
|
extendedListPrice?: number
|
||||||
|
}
|
@ -1,5 +1,8 @@
|
|||||||
import cart from '@commerce/api/endpoints/cart'
|
import cart from '@commerce/api/endpoints/cart'
|
||||||
import { operations } from '@framework/api/cart'
|
import { CartAPI, operations } from '@framework/api/cart'
|
||||||
import commerce from '@lib/api/commerce'
|
import commerce from '@lib/api/commerce'
|
||||||
|
|
||||||
export default commerce.endpoint({ handler: cart, operations })
|
export default commerce.endpoint({
|
||||||
|
handler: cart as CartAPI['endpoint']['handler'],
|
||||||
|
operations,
|
||||||
|
})
|
||||||
|
Loading…
x
Reference in New Issue
Block a user