mirror of
https://github.com/vercel/commerce.git
synced 2025-05-17 15:06:59 +00:00
Add dynamic API endpoints
This commit is contained in:
parent
11609a9e71
commit
17788b1eb0
@ -9,12 +9,6 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
body: { cartId, item },
|
||||
config,
|
||||
}) => {
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
if (!item.quantity) item.quantity = 1
|
||||
|
||||
const options = {
|
||||
@ -41,7 +35,7 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
'Set-Cookie',
|
||||
getCartCookie(config.cartCookie, data.id, config.cartCookieMaxAge)
|
||||
)
|
||||
res.status(200).json({ data: normalizeCart(data) })
|
||||
res.status(200).json({ data: data ? normalizeCart(data) : null })
|
||||
}
|
||||
|
||||
export default addItem
|
||||
|
@ -19,6 +19,7 @@ export const handlers: CartEndpoint['handlers'] = {
|
||||
}
|
||||
|
||||
const cartApi = createEndpoint<CartAPI>({
|
||||
/* @ts-ignore */
|
||||
handler: cartEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
@ -7,13 +7,6 @@ const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
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' }
|
||||
@ -28,7 +21,8 @@ const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
: // Remove the cart cookie if the cart was removed (empty items)
|
||||
getCartCookie(config.cartCookie)
|
||||
)
|
||||
res.status(200).json({ data: data && normalizeCart(data) })
|
||||
|
||||
res.status(200).json({ data: data ? normalizeCart(data) : null })
|
||||
}
|
||||
|
||||
export default removeItem
|
||||
|
@ -8,14 +8,7 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||
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(
|
||||
const { data } = await config.storeApiFetch<{ data?: any }>(
|
||||
`/v3/carts/${cartId}/items/${itemId}?include=line_items.physical_items.options`,
|
||||
{
|
||||
method: 'PUT',
|
||||
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
27
packages/bigcommerce/src/api/endpoints/index.ts
Normal file
27
packages/bigcommerce/src/api/endpoints/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import type { BigcommerceAPI, Provider } from '..'
|
||||
|
||||
import createEndpoints from '@vercel/commerce/api/endpoints'
|
||||
|
||||
import cart from './cart'
|
||||
import login from './login'
|
||||
import logout from './logout'
|
||||
import signup from './signup'
|
||||
import checkout from './checkout'
|
||||
import customer from './customer'
|
||||
import wishlist from './wishlist'
|
||||
import products from './catalog/products'
|
||||
|
||||
const endpoints = {
|
||||
cart,
|
||||
login,
|
||||
logout,
|
||||
signup,
|
||||
checkout,
|
||||
wishlist,
|
||||
customer,
|
||||
'catalog/products': products,
|
||||
}
|
||||
|
||||
export default function bigcommerceAPI(commerce: BigcommerceAPI) {
|
||||
return createEndpoints<Provider>(commerce, endpoints)
|
||||
}
|
@ -9,7 +9,7 @@ export default useLogin as UseLogin<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LoginHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/login',
|
||||
url: '/api/commerce/login',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: { email, password }, options, fetch }) {
|
||||
|
@ -8,7 +8,7 @@ export default useLogout as UseLogout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LogoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/logout',
|
||||
url: '/api/commerce/logout',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook:
|
||||
|
@ -9,7 +9,7 @@ export default useSignup as UseSignup<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SignupHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/signup',
|
||||
url: '/api/commerce/signup',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -9,7 +9,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -7,7 +7,7 @@ export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCartHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook:
|
||||
|
@ -4,8 +4,14 @@ import type {
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@vercel/commerce/types/cart'
|
||||
import useRemoveItem, {
|
||||
UseRemoveItem,
|
||||
} from '@vercel/commerce/cart/use-remove-item'
|
||||
import type {
|
||||
Cart,
|
||||
LineItem,
|
||||
RemoveItemHook,
|
||||
} from '@vercel/commerce/types/cart'
|
||||
import useCart from './use-cart'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
@ -20,7 +26,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -5,7 +5,9 @@ import type {
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
|
||||
import useUpdateItem, {
|
||||
UseUpdateItem,
|
||||
} from '@vercel/commerce/cart/use-update-item'
|
||||
import type { LineItem, UpdateItemHook } from '@vercel/commerce/types/cart'
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useCart from './use-cart'
|
||||
@ -18,7 +20,7 @@ export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -6,7 +6,7 @@ export default useCustomer as UseCustomer<typeof handler>
|
||||
|
||||
export const handler: SWRHook<CustomerHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer',
|
||||
url: '/api/commerce/customer',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
|
@ -14,7 +14,7 @@ export type SearchProductsInput = {
|
||||
|
||||
export const handler: SWRHook<SearchProductsHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/catalog/products',
|
||||
url: '/api/commerce/catalog/products',
|
||||
method: 'GET',
|
||||
},
|
||||
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||
|
@ -10,7 +10,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'POST',
|
||||
},
|
||||
useHook:
|
||||
|
@ -12,7 +12,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<RemoveItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'DELETE',
|
||||
},
|
||||
useHook:
|
||||
|
@ -10,7 +10,7 @@ import type { GetWishlistHook } from '../types/wishlist'
|
||||
export default useWishlist as UseWishlist<typeof handler>
|
||||
export const handler: SWRHook<GetWishlistHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||
|
@ -69,7 +69,10 @@ Then, open [/site/.env.template](/site/.env.template) and add the provider name
|
||||
Using BigCommerce as an example. The first thing to do is export a `CommerceProvider` component that includes a `provider` object with all the handlers that can be used for hooks:
|
||||
|
||||
```tsx
|
||||
import { getCommerceProvider, useCommerce as useCoreCommerce } from '@vercel/commerce'
|
||||
import {
|
||||
getCommerceProvider,
|
||||
useCommerce as useCoreCommerce,
|
||||
} from '@vercel/commerce'
|
||||
import { bigcommerceProvider, BigcommerceProvider } from './provider'
|
||||
|
||||
export { bigcommerceProvider }
|
||||
@ -135,7 +138,7 @@ export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCartHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook:
|
||||
@ -175,7 +178,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
@ -213,25 +216,26 @@ export const handler: MutationHook<AddItemHook> = {
|
||||
```
|
||||
|
||||
## Showing progress and features
|
||||
|
||||
When creating a PR for a new provider, include this list in the PR description and mark the progress as you push so we can organize the code review. Not all points are required (but advised) so make sure to keep the list up to date.
|
||||
|
||||
**Status**
|
||||
|
||||
* [ ] CommerceProvider
|
||||
* [ ] Schema & TS types
|
||||
* [ ] API Operations - Get all collections
|
||||
* [ ] API Operations - Get all pages
|
||||
* [ ] API Operations - Get all products
|
||||
* [ ] API Operations - Get page
|
||||
* [ ] API Operations - Get product
|
||||
* [ ] API Operations - Get Shop Info (categories and vendors working — `vendors` query still a WIP PR on Reaction)
|
||||
* [ ] Hook - Add Item
|
||||
* [ ] Hook - Remove Item
|
||||
* [ ] Hook - Update Item
|
||||
* [ ] Hook - Get Cart (account-tied carts working, anonymous carts working, cart reconciliation working)
|
||||
* [ ] Auth (based on a WIP PR on Reaction - still need to implement refresh tokens)
|
||||
* [ ] Customer information
|
||||
* [ ] Product attributes - Size, Colors
|
||||
* [ ] Custom checkout
|
||||
* [ ] Typing (in progress)
|
||||
* [ ] Tests
|
||||
- [ ] CommerceProvider
|
||||
- [ ] Schema & TS types
|
||||
- [ ] API Operations - Get all collections
|
||||
- [ ] API Operations - Get all pages
|
||||
- [ ] API Operations - Get all products
|
||||
- [ ] API Operations - Get page
|
||||
- [ ] API Operations - Get product
|
||||
- [ ] API Operations - Get Shop Info (categories and vendors working — `vendors` query still a WIP PR on Reaction)
|
||||
- [ ] Hook - Add Item
|
||||
- [ ] Hook - Remove Item
|
||||
- [ ] Hook - Update Item
|
||||
- [ ] Hook - Get Cart (account-tied carts working, anonymous carts working, cart reconciliation working)
|
||||
- [ ] Auth (based on a WIP PR on Reaction - still need to implement refresh tokens)
|
||||
- [ ] Customer information
|
||||
- [ ] Product attributes - Size, Colors
|
||||
- [ ] Custom checkout
|
||||
- [ ] Typing (in progress)
|
||||
- [ ] Tests
|
||||
|
@ -1,60 +1,54 @@
|
||||
import type { CartSchema } from '../../types/cart'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { CartSchema } from '../../types/cart'
|
||||
|
||||
const cartEndpoint: GetAPISchema<any, CartSchema<any>>['endpoint']['handler'] =
|
||||
async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getCart'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
import {
|
||||
getCartBodySchema,
|
||||
addItemBodySchema,
|
||||
updateItemBodySchema,
|
||||
removeItemBodySchema,
|
||||
} from '../../schemas/cart'
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
const cartEndpoint: GetAPISchema<
|
||||
any,
|
||||
CartSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
try {
|
||||
// Return current cart info
|
||||
if (req.method === 'GET') {
|
||||
const body = { cartId }
|
||||
return await handlers['getCart']({ ...ctx, body })
|
||||
}
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getCart'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
|
||||
// Create or add an item to the cart
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
// Update item in cart
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the cart
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
// Return current cart info
|
||||
if (req.method === 'GET') {
|
||||
const body = getCartBodySchema.parse({ cartId })
|
||||
return handlers['getCart']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to the cart
|
||||
if (req.method === 'POST') {
|
||||
const body = addItemBodySchema.parse({ ...req.body, cartId })
|
||||
return handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in cart
|
||||
if (req.method === 'PUT') {
|
||||
const body = updateItemBodySchema.parse({ ...req.body, cartId })
|
||||
return handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the cart
|
||||
if (req.method === 'DELETE') {
|
||||
const body = removeItemBodySchema.parse({ ...req.body, cartId })
|
||||
return handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
}
|
||||
|
||||
export default cartEndpoint
|
||||
|
@ -1,31 +1,26 @@
|
||||
import type { ProductsSchema } from '../../../types/product'
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import { searchProductBodySchema } from '../../../schemas/product'
|
||||
import type { GetAPISchema } from '../..'
|
||||
import type { ProductsSchema } from '../../../types/product'
|
||||
|
||||
import validateHandlers from '../../utils/validate-handlers'
|
||||
|
||||
const productsEndpoint: GetAPISchema<
|
||||
any,
|
||||
ProductsSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (!isAllowedOperation(req, res, { GET: handlers['getProducts'] })) {
|
||||
return
|
||||
}
|
||||
validateHandlers(req, res, { GET: handlers['getProducts'] })
|
||||
|
||||
try {
|
||||
const body = req.query
|
||||
return await handlers['getProducts']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const body = searchProductBodySchema.parse({
|
||||
search: req.query.search,
|
||||
categoryId: req.query.categoryId,
|
||||
brandId: req.query.brandId,
|
||||
sort: req.query.sort,
|
||||
locale: req.query.locale,
|
||||
})
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
return handlers['getProducts']({ ...ctx, body })
|
||||
}
|
||||
|
||||
export default productsEndpoint
|
||||
|
@ -1,48 +1,32 @@
|
||||
import type { CheckoutSchema } from '../../types/checkout'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { CheckoutSchema } from '../../types/checkout'
|
||||
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
const checkoutEndpoint: GetAPISchema<
|
||||
any,
|
||||
CheckoutSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getCheckout'],
|
||||
POST: handlers['submitCheckout'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getCheckout'],
|
||||
POST: handlers['submitCheckout'],
|
||||
})
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
// Create checkout
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['getCheckout']({ ...ctx, body })
|
||||
}
|
||||
// Create checkout
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['getCheckout']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create checkout
|
||||
if (req.method === 'POST' && handlers['submitCheckout']) {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['submitCheckout']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
// Create checkout
|
||||
if (req.method === 'POST' && handlers['submitCheckout']) {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['submitCheckout']({ ...ctx, body })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,64 +1,47 @@
|
||||
import type { CustomerAddressSchema } from '../../../types/customer/address'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import validateHandlers from '../../utils/validate-handlers'
|
||||
|
||||
const customerShippingEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerAddressSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getAddresses'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getAddresses'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
const { cookies } = req
|
||||
|
||||
// Cart id might be usefull for anonymous shopping
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
// Return customer addresses
|
||||
if (req.method === 'GET') {
|
||||
const body = { cartId }
|
||||
return await handlers['getAddresses']({ ...ctx, body })
|
||||
}
|
||||
// Return customer addresses
|
||||
if (req.method === 'GET') {
|
||||
const body = { cartId }
|
||||
return handlers['getAddresses']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to customer addresses list
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
// Create or add an item to customer addresses list
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in customer addresses list
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
// Update item in customer addresses list
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from customer addresses list
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
// Remove an item from customer addresses list
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,64 +1,47 @@
|
||||
import type { CustomerCardSchema } from '../../../types/customer/card'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import validateHandlers from '../../utils/validate-handlers'
|
||||
|
||||
const customerCardEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerCardSchema
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getCards'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getCards'],
|
||||
POST: handlers['addItem'],
|
||||
PUT: handlers['updateItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
const { cookies } = req
|
||||
|
||||
// Cart id might be usefull for anonymous shopping
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
// Create or add a card
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body }
|
||||
return await handlers['getCards']({ ...ctx, body })
|
||||
}
|
||||
// Create or add a card
|
||||
if (req.method === 'GET') {
|
||||
const body = { ...req.body }
|
||||
return handlers['getCards']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Create or add an item to customer cards
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
// Create or add an item to customer cards
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Update item in customer cards
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
// Update item in customer cards
|
||||
if (req.method === 'PUT') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['updateItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from customer cards
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
// Remove an item from customer cards
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,36 +1,20 @@
|
||||
import type { CustomerSchema } from '../../../types/customer'
|
||||
import type { GetAPISchema } from '../..'
|
||||
|
||||
import { CommerceAPIError } from '../../utils/errors'
|
||||
import isAllowedOperation from '../../utils/is-allowed-operation'
|
||||
import validateHandlers from '../../utils/validate-handlers'
|
||||
|
||||
const customerEndpoint: GetAPISchema<
|
||||
any,
|
||||
CustomerSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
CustomerSchema
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getLoggedInCustomer'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getLoggedInCustomer'],
|
||||
})
|
||||
|
||||
try {
|
||||
const body = null
|
||||
return await handlers['getLoggedInCustomer']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
const body = null
|
||||
return handlers['getLoggedInCustomer']({ ...ctx, body })
|
||||
}
|
||||
|
||||
export default customerEndpoint
|
||||
|
72
packages/commerce/src/api/endpoints/index.ts
Normal file
72
packages/commerce/src/api/endpoints/index.ts
Normal file
@ -0,0 +1,72 @@
|
||||
import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next'
|
||||
import type { APIProvider, CommerceAPI } from '..'
|
||||
|
||||
import { getErrorResponse } from '../utils/errors'
|
||||
|
||||
/**
|
||||
* Handles the catch-all api endpoint for the Commerce API.
|
||||
* @param {CommerceAPI} commerce The Commerce API instance.
|
||||
* @param endpoints An object containing the handlers for each endpoint.
|
||||
*/
|
||||
export default function createEndpoints<P extends APIProvider>(
|
||||
commerce: CommerceAPI<P>,
|
||||
endpoints: {
|
||||
[key: string]: (commerce: CommerceAPI<P>) => NextApiHandler
|
||||
}
|
||||
) {
|
||||
const paths = Object.keys(endpoints)
|
||||
|
||||
const handlers = paths.reduce<Record<string, NextApiHandler>>(
|
||||
(acc, path) =>
|
||||
Object.assign(acc, {
|
||||
[path]: endpoints[path](commerce),
|
||||
}),
|
||||
{}
|
||||
)
|
||||
|
||||
return async (req: NextApiRequest, res: NextApiResponse) => {
|
||||
try {
|
||||
if (!req.query.commerce) {
|
||||
throw new Error(
|
||||
'Invalid configuration. Please make sure that the /pages/api/commerce/[[...commerce]].ts route is configured correctly, and it passes the commerce instance.'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the url path
|
||||
*/
|
||||
const path = Array.isArray(req.query.commerce)
|
||||
? req.query.commerce.join('/')
|
||||
: req.query.commerce
|
||||
|
||||
/**
|
||||
* Check if the handler for this path exists and return a 404 if it doesn't
|
||||
*/
|
||||
if (!paths.includes(path)) {
|
||||
throw new Error(
|
||||
`Endpoint handler not implemented. Please use one of the available api endpoints: ${paths.join(
|
||||
', '
|
||||
)}`
|
||||
)
|
||||
}
|
||||
|
||||
const data = await handlers[path](req, res)
|
||||
|
||||
/**
|
||||
* If the handler returns a value but the response hasn't been sent yet, send it
|
||||
*/
|
||||
if (!res.headersSent) {
|
||||
res.status(200).json({
|
||||
data,
|
||||
})
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
const { status, data, errors } = getErrorResponse(error)
|
||||
res.status(status).json({
|
||||
data,
|
||||
errors,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
@ -1,36 +1,20 @@
|
||||
import type { LoginSchema } from '../../types/login'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { LoginSchema } from '../../types/login'
|
||||
|
||||
const loginEndpoint: GetAPISchema<
|
||||
any,
|
||||
LoginSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
const loginEndpoint: GetAPISchema<any, LoginSchema>['endpoint']['handler'] = (
|
||||
ctx
|
||||
) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
POST: handlers['login'],
|
||||
GET: handlers['login'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
validateHandlers(req, res, {
|
||||
POST: handlers['login'],
|
||||
GET: handlers['login'],
|
||||
})
|
||||
|
||||
try {
|
||||
const body = req.body ?? {}
|
||||
return await handlers['login']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
const body = req.body ?? {}
|
||||
return handlers['login']({ ...ctx, body })
|
||||
}
|
||||
|
||||
export default loginEndpoint
|
||||
|
@ -1,35 +1,20 @@
|
||||
import type { LogoutSchema } from '../../types/logout'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { LogoutSchema } from '../../types/logout'
|
||||
|
||||
const logoutEndpoint: GetAPISchema<any, LogoutSchema>['endpoint']['handler'] =
|
||||
async (ctx) => {
|
||||
const { req, res, handlers } = ctx
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['logout'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
const logoutEndpoint: GetAPISchema<any, LogoutSchema>['endpoint']['handler'] = (
|
||||
ctx
|
||||
) => {
|
||||
const { req, res, handlers } = ctx
|
||||
|
||||
try {
|
||||
const redirectTo = req.query.redirect_to
|
||||
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['logout'],
|
||||
})
|
||||
const redirectTo = req.query.redirect_to
|
||||
const body = typeof redirectTo === 'string' ? { redirectTo } : {}
|
||||
|
||||
return await handlers['logout']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
return handlers['logout']({ ...ctx, body })
|
||||
}
|
||||
|
||||
export default logoutEndpoint
|
||||
|
@ -1,36 +1,21 @@
|
||||
import type { SignupSchema } from '../../types/signup'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { SignupSchema } from '../../types/signup'
|
||||
|
||||
const signupEndpoint: GetAPISchema<any, SignupSchema>['endpoint']['handler'] =
|
||||
async (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
POST: handlers['signup'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
const signupEndpoint: GetAPISchema<any, SignupSchema>['endpoint']['handler'] = (
|
||||
ctx
|
||||
) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
validateHandlers(req, res, {
|
||||
POST: handlers['signup'],
|
||||
})
|
||||
const { cookies } = req
|
||||
const cartId = cookies[config.cartCookie]
|
||||
|
||||
try {
|
||||
const body = { ...req.body, cartId }
|
||||
return await handlers['signup']({ ...ctx, body })
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
}
|
||||
}
|
||||
const body = { ...req.body, cartId }
|
||||
return handlers['signup']({ ...ctx, body })
|
||||
}
|
||||
|
||||
export default signupEndpoint
|
||||
|
@ -1,57 +1,42 @@
|
||||
import type { WishlistSchema } from '../../types/wishlist'
|
||||
import { CommerceAPIError } from '../utils/errors'
|
||||
import isAllowedOperation from '../utils/is-allowed-operation'
|
||||
import type { GetAPISchema } from '..'
|
||||
import type { WishlistSchema } from '../../types/wishlist'
|
||||
|
||||
import validateHandlers from '../utils/validate-handlers'
|
||||
|
||||
const wishlistEndpoint: GetAPISchema<
|
||||
any,
|
||||
WishlistSchema<any>
|
||||
>['endpoint']['handler'] = async (ctx) => {
|
||||
WishlistSchema
|
||||
>['endpoint']['handler'] = (ctx) => {
|
||||
const { req, res, handlers, config } = ctx
|
||||
|
||||
if (
|
||||
!isAllowedOperation(req, res, {
|
||||
GET: handlers['getWishlist'],
|
||||
POST: handlers['addItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
) {
|
||||
return
|
||||
}
|
||||
validateHandlers(req, res, {
|
||||
GET: handlers['getWishlist'],
|
||||
POST: handlers['addItem'],
|
||||
DELETE: handlers['removeItem'],
|
||||
})
|
||||
|
||||
const { cookies } = req
|
||||
const customerToken = cookies[config.customerCookie]
|
||||
|
||||
try {
|
||||
// Return current wishlist info
|
||||
if (req.method === 'GET') {
|
||||
const body = {
|
||||
customerToken,
|
||||
includeProducts: req.query.products === '1',
|
||||
}
|
||||
return await handlers['getWishlist']({ ...ctx, body })
|
||||
// Return current wishlist info
|
||||
if (req.method === 'GET') {
|
||||
const body = {
|
||||
customerToken,
|
||||
includeProducts: !!req.query.products,
|
||||
}
|
||||
return handlers['getWishlist']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Add an item to the wishlist
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return await handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
// Add an item to the wishlist
|
||||
if (req.method === 'POST') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return handlers['addItem']({ ...ctx, body })
|
||||
}
|
||||
|
||||
// Remove an item from the wishlist
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return await handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
|
||||
const message =
|
||||
error instanceof CommerceAPIError
|
||||
? 'An unexpected error ocurred with the Commerce API'
|
||||
: 'An unexpected error ocurred'
|
||||
|
||||
res.status(500).json({ data: null, errors: [{ message }] })
|
||||
// Remove an item from the wishlist
|
||||
if (req.method === 'DELETE') {
|
||||
const body = { ...req.body, customerToken }
|
||||
return handlers['removeItem']({ ...ctx, body })
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,7 @@
|
||||
import { ZodError } from 'zod'
|
||||
|
||||
import type { Response } from '@vercel/fetch'
|
||||
import { CommerceError } from '../../utils/errors'
|
||||
|
||||
export class CommerceAPIError extends Error {
|
||||
status: number
|
||||
@ -20,3 +23,51 @@ export class CommerceNetworkError extends Error {
|
||||
this.name = 'CommerceNetworkError'
|
||||
}
|
||||
}
|
||||
|
||||
export const getErrorResponse = (error: unknown) => {
|
||||
if (error instanceof CommerceAPIError) {
|
||||
return {
|
||||
status: error.status || 500,
|
||||
data: error.data || null,
|
||||
errors: [
|
||||
{ message: 'An unexpected error ocurred with the Commerce API' },
|
||||
],
|
||||
}
|
||||
}
|
||||
|
||||
if (error instanceof ZodError) {
|
||||
return {
|
||||
status: 400,
|
||||
data: null,
|
||||
errors: error.issues,
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
status: 500,
|
||||
data: null,
|
||||
errors: [{ message: 'An unexpected error ocurred' }],
|
||||
}
|
||||
}
|
||||
|
||||
export const getOperationError = (operation: string, error: unknown) => {
|
||||
if (error instanceof ZodError) {
|
||||
return new CommerceError({
|
||||
code: 'SCHEMA_VALIDATION_ERROR',
|
||||
message:
|
||||
`The ${operation} operation returned invalid data and has ${
|
||||
error.issues.length
|
||||
} parse ${error.issues.length === 1 ? 'error' : 'errors'}: \n` +
|
||||
error.issues
|
||||
.map(
|
||||
(e, index) =>
|
||||
`Error #${index + 1} ${
|
||||
e.path.length > 0 ? `Path: ${e.path.join('.')}, ` : ''
|
||||
}Code: ${e.code}, Message: ${e.message}`
|
||||
)
|
||||
.join('\n'),
|
||||
})
|
||||
}
|
||||
|
||||
return error
|
||||
}
|
||||
|
@ -2,7 +2,11 @@ import type { NextApiRequest, NextApiResponse } from 'next'
|
||||
import isAllowedMethod, { HTTP_METHODS } from './is-allowed-method'
|
||||
import { APIHandler } from './types'
|
||||
|
||||
export default function isAllowedOperation(
|
||||
/**
|
||||
* Checks if the request method is allowed
|
||||
* @throws Error if the method is not allowed
|
||||
*/
|
||||
export default function validateHandlers(
|
||||
req: NextApiRequest,
|
||||
res: NextApiResponse,
|
||||
allowedOperations: { [k in HTTP_METHODS]?: APIHandler<any, any> }
|
||||
@ -15,5 +19,7 @@ export default function isAllowedOperation(
|
||||
return arr
|
||||
}, [])
|
||||
|
||||
return isAllowedMethod(req, res, allowedMethods)
|
||||
if (!isAllowedMethod(req, res, allowedMethods)) {
|
||||
throw new Error(`Method ${req.method} Not Allowed for this url: ${req.url}`)
|
||||
}
|
||||
}
|
27
packages/commerce/src/schemas/cart.ts
Normal file
27
packages/commerce/src/schemas/cart.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const getCartBodySchema = z.object({
|
||||
cartId: z.string(),
|
||||
})
|
||||
|
||||
export const cartItemBodySchema = z.object({
|
||||
variantId: z.string(),
|
||||
productId: z.string().optional(),
|
||||
quantity: z.number().min(1).optional(),
|
||||
})
|
||||
|
||||
export const addItemBodySchema = z.object({
|
||||
cartId: z.string().optional(),
|
||||
item: cartItemBodySchema,
|
||||
})
|
||||
|
||||
export const updateItemBodySchema = z.object({
|
||||
cartId: z.string(),
|
||||
itemId: z.string(),
|
||||
item: cartItemBodySchema,
|
||||
})
|
||||
|
||||
export const removeItemBodySchema = z.object({
|
||||
cartId: z.string(),
|
||||
itemId: z.string(),
|
||||
})
|
18
packages/commerce/src/schemas/page.ts
Normal file
18
packages/commerce/src/schemas/page.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const pageSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
url: z.string().startsWith('/').optional(),
|
||||
body: z.string(),
|
||||
is_visible: z.boolean().optional(),
|
||||
sort_order: z.number().optional(),
|
||||
})
|
||||
|
||||
export const pagesPathsSchema = z.array(
|
||||
z.object({
|
||||
page: z.object({
|
||||
path: z.string().startsWith('/'),
|
||||
}),
|
||||
})
|
||||
)
|
60
packages/commerce/src/schemas/product.ts
Normal file
60
packages/commerce/src/schemas/product.ts
Normal file
@ -0,0 +1,60 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const productPriceSchema = z.object({
|
||||
value: z.number(),
|
||||
currencyCode: z.string().max(3).optional(),
|
||||
retailPrice: z.number().optional(),
|
||||
})
|
||||
|
||||
export const productOptionSchema = z.object({
|
||||
id: z.string(),
|
||||
displayName: z.string(),
|
||||
values: z.array(
|
||||
z.object({
|
||||
label: z.string(),
|
||||
hexColors: z.array(z.string()).optional(),
|
||||
})
|
||||
),
|
||||
})
|
||||
|
||||
export const productImageSchema = z.object({
|
||||
url: z.string().url().or(z.string().startsWith('/')),
|
||||
alt: z.string().optional(),
|
||||
width: z.number().optional(),
|
||||
height: z.number().optional(),
|
||||
})
|
||||
|
||||
export const productVariantSchema = z.object({
|
||||
id: z.string(),
|
||||
sku: z.string().nullish(),
|
||||
name: z.string().optional(),
|
||||
options: z.array(productOptionSchema),
|
||||
image: productImageSchema.optional(),
|
||||
})
|
||||
|
||||
export const productSchema = z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
description: z.string(),
|
||||
descriptionHtml: z.string().optional(),
|
||||
sku: z.string().nullish(),
|
||||
slug: z.string(),
|
||||
path: z.string().startsWith('/'),
|
||||
images: z.array(productImageSchema),
|
||||
variants: z.array(productVariantSchema),
|
||||
price: productPriceSchema,
|
||||
options: z.array(productOptionSchema),
|
||||
vendor: z.string().optional(),
|
||||
})
|
||||
|
||||
export const productsPathsSchema = z.array(
|
||||
z.object({ path: z.string().startsWith('/') })
|
||||
)
|
||||
|
||||
export const searchProductBodySchema = z.object({
|
||||
search: z.string().optional(),
|
||||
categoryId: z.string().optional(),
|
||||
brandId: z.string().optional(),
|
||||
sort: z.string().optional(),
|
||||
locale: z.string().optional(),
|
||||
})
|
18
packages/commerce/src/schemas/site.ts
Normal file
18
packages/commerce/src/schemas/site.ts
Normal file
@ -0,0 +1,18 @@
|
||||
import { z } from 'zod'
|
||||
|
||||
export const siteInfoSchema = z.object({
|
||||
categories: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
path: z.string().startsWith('/'),
|
||||
})
|
||||
),
|
||||
brands: z.array(
|
||||
z.object({
|
||||
id: z.string(),
|
||||
name: z.string(),
|
||||
path: z.string().startsWith('/'),
|
||||
})
|
||||
),
|
||||
})
|
@ -158,11 +158,11 @@ export type CartHandlers<T extends CartTypes = CartTypes> = {
|
||||
}
|
||||
|
||||
export type GetCartHandler<T extends CartTypes = CartTypes> = GetCartHook<T> & {
|
||||
body: { cartId?: string }
|
||||
body: { cartId: string }
|
||||
}
|
||||
|
||||
export type AddItemHandler<T extends CartTypes = CartTypes> = AddItemHook<T> & {
|
||||
body: { cartId: string }
|
||||
body: { cartId?: string }
|
||||
}
|
||||
|
||||
export type UpdateItemHandler<T extends CartTypes = CartTypes> =
|
||||
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
16
packages/commercejs/src/api/endpoints/index.ts
Normal file
16
packages/commercejs/src/api/endpoints/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import type { CommercejsAPI } from '..'
|
||||
|
||||
import handleEndpoints from '@vercel/commerce/api/endpoints'
|
||||
|
||||
import login from './login'
|
||||
import checkout from './checkout'
|
||||
|
||||
const endpoints = {
|
||||
login,
|
||||
checkout,
|
||||
}
|
||||
|
||||
const handler = (commerce: CommercejsAPI) =>
|
||||
handleEndpoints(commerce, endpoints)
|
||||
|
||||
export default handler
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -11,7 +11,7 @@ export default useSubmitCheckout as UseSubmitCheckout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SubmitCheckoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/checkout',
|
||||
url: '/api/commerce/checkout',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
26
packages/kibocommerce/src/api/endpoints/index.ts
Normal file
26
packages/kibocommerce/src/api/endpoints/index.ts
Normal file
@ -0,0 +1,26 @@
|
||||
import type { KiboCommerceAPI } from '..'
|
||||
|
||||
import handleEndpoints from '@vercel/commerce/api/endpoints'
|
||||
|
||||
import cart from './cart'
|
||||
import login from './login'
|
||||
import logout from './logout'
|
||||
import signup from './signup'
|
||||
import customer from './customer'
|
||||
import wishlist from './wishlist'
|
||||
import products from './catalog/products'
|
||||
|
||||
const endpoints = {
|
||||
cart,
|
||||
login,
|
||||
logout,
|
||||
signup,
|
||||
wishlist,
|
||||
customer,
|
||||
'catalog/products': products,
|
||||
}
|
||||
|
||||
const handler = (commerce: KiboCommerceAPI) =>
|
||||
handleEndpoints(commerce, endpoints)
|
||||
|
||||
export default handler
|
@ -10,7 +10,7 @@ export default useLogin as UseLogin<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LoginHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/login',
|
||||
url: '/api/commerce/login',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: { email, password }, options, fetch }) {
|
||||
|
@ -9,7 +9,7 @@ export default useLogout as UseLogout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LogoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/logout',
|
||||
url: '/api/commerce/logout',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ fetch }) => () => {
|
||||
|
@ -9,7 +9,7 @@ export default useSignup as UseSignup<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SignupHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/signup',
|
||||
url: '/api/commerce/signup',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -9,7 +9,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
@ -29,16 +29,18 @@ export const handler: MutationHook<AddItemHook> = {
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) => () => {
|
||||
const { mutate } = useCart()
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -7,27 +7,29 @@ export default useCart as UseCart<typeof handler>
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
method: 'GET',
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
return await fetch({ ...options })
|
||||
},
|
||||
useHook: ({ useData }) => (input) => {
|
||||
const response = useData({
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
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
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.lineItems.length ?? 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -4,8 +4,14 @@ import type {
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@vercel/commerce/types/cart'
|
||||
import useRemoveItem, {
|
||||
UseRemoveItem,
|
||||
} from '@vercel/commerce/cart/use-remove-item'
|
||||
import type {
|
||||
Cart,
|
||||
LineItem,
|
||||
RemoveItemHook,
|
||||
} from '@vercel/commerce/types/cart'
|
||||
import useCart from './use-cart'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
@ -20,7 +26,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
@ -30,27 +36,25 @@ export const handler = {
|
||||
}: HookFetcherContext<RemoveItemHook>) {
|
||||
return await fetch({ ...options, body: { itemId } })
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<RemoveItemHook>) => <
|
||||
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
|
||||
useHook:
|
||||
({ fetch }: MutationHookContext<RemoveItemHook>) =>
|
||||
<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',
|
||||
})
|
||||
if (!itemId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({ input: { itemId } })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}
|
||||
|
||||
const data = await fetch({ input: { itemId } })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}
|
||||
|
||||
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||
},
|
||||
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||
},
|
||||
}
|
||||
|
@ -5,7 +5,9 @@ import type {
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
|
||||
import useUpdateItem, {
|
||||
UseUpdateItem,
|
||||
} from '@vercel/commerce/cart/use-update-item'
|
||||
import type { LineItem, UpdateItemHook } from '@vercel/commerce/types/cart'
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useCart from './use-cart'
|
||||
@ -18,7 +20,7 @@ export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
@ -46,39 +48,39 @@ export const handler = {
|
||||
body: { itemId, item },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) => <
|
||||
T extends LineItem | undefined = undefined
|
||||
>(
|
||||
ctx: {
|
||||
item?: T
|
||||
wait?: number
|
||||
} = {}
|
||||
) => {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCart() as any
|
||||
useHook:
|
||||
({ fetch }: MutationHookContext<UpdateItemHook>) =>
|
||||
<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
|
||||
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 || !variantId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
if (!itemId || !productId || !variantId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: { productId, variantId, quantity: input.quantity },
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: { productId, variantId, quantity: input.quantity },
|
||||
},
|
||||
})
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -6,7 +6,7 @@ export default useCustomer as UseCustomer<typeof handler>
|
||||
|
||||
export const handler: SWRHook<CustomerHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer',
|
||||
url: '/api/commerce/customer',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
|
@ -5,7 +5,7 @@ export default useSearch as UseSearch<typeof handler>
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
method: 'GET',
|
||||
url: '/api/catalog/products',
|
||||
url: '/api/commerce/catalog/products',
|
||||
},
|
||||
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||
// Use a dummy base as we only care about the relative path
|
||||
@ -23,15 +23,17 @@ export const handler: SWRHook<any> = {
|
||||
method: options.method,
|
||||
})
|
||||
},
|
||||
useHook: ({ useData }) => (input) => {
|
||||
return useData({
|
||||
input: [
|
||||
['search', input.search],
|
||||
['categoryId', input.categoryId],
|
||||
['brandId', input.brandId],
|
||||
['sort', input.sort],
|
||||
],
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
return useData({
|
||||
input: [
|
||||
['search', input.search],
|
||||
['categoryId', input.categoryId],
|
||||
['brandId', input.brandId],
|
||||
['sort', input.sort],
|
||||
],
|
||||
swrOptions: { revalidateOnFocus: false, ...input?.swrOptions },
|
||||
})
|
||||
},
|
||||
}
|
||||
|
@ -10,7 +10,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'POST',
|
||||
},
|
||||
useHook:
|
||||
|
@ -12,7 +12,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<RemoveItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'DELETE',
|
||||
},
|
||||
useHook:
|
||||
|
@ -1,6 +1,8 @@
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useWishlist, { UseWishlist } from '@vercel/commerce/wishlist/use-wishlist'
|
||||
import useWishlist, {
|
||||
UseWishlist,
|
||||
} from '@vercel/commerce/wishlist/use-wishlist'
|
||||
import type { GetWishlistHook } from '@vercel/commerce/types/wishlist'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
|
||||
@ -8,45 +10,47 @@ export default useWishlist as UseWishlist<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/wishlist',
|
||||
url: '/api/commerce/wishlist',
|
||||
method: 'GET',
|
||||
},
|
||||
fetcher({ input: { customerId, includeProducts}, options, fetch }) {
|
||||
fetcher({ input: { customerId, includeProducts }, options, fetch }) {
|
||||
if (!customerId) return null
|
||||
// Use a dummy base as we only care about the relative path
|
||||
const url = new URL(options.url!, 'http://a')
|
||||
|
||||
if (includeProducts) url.searchParams.set('products', '1')
|
||||
if(customerId) url.searchParams.set('customerId', customerId)
|
||||
if (customerId) url.searchParams.set('customerId', customerId)
|
||||
|
||||
return fetch({
|
||||
url: url.pathname + url.search,
|
||||
method: options.method,
|
||||
})
|
||||
},
|
||||
useHook: ({ useData }) => (input) => {
|
||||
const { data: customer } = useCustomer()
|
||||
const response = useData({
|
||||
input: [
|
||||
['customerId', customer?.id],
|
||||
['includeProducts', input?.includeProducts],
|
||||
],
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input?.swrOptions,
|
||||
},
|
||||
})
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.items?.length || 0) <= 0
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
const { data: customer } = useCustomer()
|
||||
const response = useData({
|
||||
input: [
|
||||
['customerId', customer?.id],
|
||||
['includeProducts', input?.includeProducts],
|
||||
],
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input?.swrOptions,
|
||||
},
|
||||
})
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(response, {
|
||||
isEmpty: {
|
||||
get() {
|
||||
return (response.data?.items?.length || 0) <= 0
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -11,14 +11,6 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
body: { cartId, item },
|
||||
config: { restBuyerFetch, cartCookie, tokenCookie },
|
||||
}) => {
|
||||
// Return an error if no item is present
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
// Store token
|
||||
let token
|
||||
|
||||
@ -46,7 +38,7 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
path: '/',
|
||||
sameSite: 'lax',
|
||||
}),
|
||||
serialize(cartCookie, cartId, {
|
||||
serialize(cartCookie, cartId!, {
|
||||
maxAge: 60 * 60 * 24 * 30,
|
||||
expires: new Date(Date.now() + 60 * 60 * 24 * 30 * 1000),
|
||||
secure: process.env.NODE_ENV === 'production',
|
||||
|
@ -1,5 +1,5 @@
|
||||
import type { CartSchema } from '../../../types/cart'
|
||||
import type { OrdercloudAPI } from '../..'
|
||||
import type { CartSchema } from '@vercel/commerce/types/cart'
|
||||
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import cartEndpoint from '@vercel/commerce/api/endpoints/cart'
|
||||
@ -9,9 +9,8 @@ import addItem from './add-item'
|
||||
import updateItem from './update-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
export type CartAPI = GetAPISchema<OrdercloudAPI, CartSchema>
|
||||
|
||||
export type CartEndpoint = CartAPI['endpoint']
|
||||
export type CartAPI = GetAPISchema<OrdercloudAPI, CartSchema>
|
||||
|
||||
export const handlers: CartEndpoint['handlers'] = {
|
||||
getCart,
|
||||
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
22
packages/ordercloud/src/api/endpoints/index.ts
Normal file
22
packages/ordercloud/src/api/endpoints/index.ts
Normal file
@ -0,0 +1,22 @@
|
||||
import type { OrdercloudAPI } from '..'
|
||||
|
||||
import handleEndpoints from '@vercel/commerce/api/endpoints'
|
||||
|
||||
import cart from './cart'
|
||||
import checkout from './checkout'
|
||||
import products from './catalog/products'
|
||||
import customerCard from './customer/card'
|
||||
import customerAddress from './customer/address'
|
||||
|
||||
const endpoints = {
|
||||
cart,
|
||||
checkout,
|
||||
'customer/card': customerCard,
|
||||
'customer/address': customerAddress,
|
||||
'catalog/products': products,
|
||||
}
|
||||
|
||||
const handler = (commerce: OrdercloudAPI) =>
|
||||
handleEndpoints(commerce, endpoints)
|
||||
|
||||
export default handler
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -1 +0,0 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@ -10,7 +10,7 @@ export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -8,7 +8,7 @@ export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCartHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
|
@ -2,12 +2,18 @@ import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import type { Cart, LineItem, RemoveItemHook } from '@vercel/commerce/types/cart'
|
||||
import type {
|
||||
Cart,
|
||||
LineItem,
|
||||
RemoveItemHook,
|
||||
} from '@vercel/commerce/types/cart'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useRemoveItem, { UseRemoveItem } from '@vercel/commerce/cart/use-remove-item'
|
||||
import useRemoveItem, {
|
||||
UseRemoveItem,
|
||||
} from '@vercel/commerce/cart/use-remove-item'
|
||||
|
||||
import useCart from './use-cart'
|
||||
|
||||
@ -23,7 +29,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -9,7 +9,9 @@ import debounce from 'lodash.debounce'
|
||||
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
import useUpdateItem, { UseUpdateItem } from '@vercel/commerce/cart/use-update-item'
|
||||
import useUpdateItem, {
|
||||
UseUpdateItem,
|
||||
} from '@vercel/commerce/cart/use-update-item'
|
||||
|
||||
import { handler as removeItemHandler } from './use-remove-item'
|
||||
import useCart from './use-cart'
|
||||
@ -22,7 +24,7 @@ export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
url: '/api/commerce/cart',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -9,7 +9,7 @@ export default useCheckout as UseCheckout<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCheckoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/checkout',
|
||||
url: '/api/commerce/checkout',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
|
@ -10,7 +10,7 @@ export default useSubmitCheckout as UseSubmitCheckout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SubmitCheckoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/checkout',
|
||||
url: '/api/commerce/checkout',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -2,14 +2,16 @@ import type { AddItemHook } from '@vercel/commerce/types/customer/address'
|
||||
import type { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/address/use-add-item'
|
||||
import useAddItem, {
|
||||
UseAddItem,
|
||||
} from '@vercel/commerce/customer/address/use-add-item'
|
||||
import useAddresses from './use-addresses'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
url: '/api/commerce/customer/address',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -10,7 +10,7 @@ export default useAddresses as UseAddresses<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetAddressesHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
url: '/api/commerce/customer/address',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
|
@ -2,7 +2,10 @@ import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import type { Address, RemoveItemHook } from '@vercel/commerce/types/customer/address'
|
||||
import type {
|
||||
Address,
|
||||
RemoveItemHook,
|
||||
} from '@vercel/commerce/types/customer/address'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
@ -25,7 +28,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
url: '/api/commerce/customer/address',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -2,7 +2,10 @@ import type {
|
||||
HookFetcherContext,
|
||||
MutationHookContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import type { UpdateItemHook, Address } from '@vercel/commerce/types/customer/address'
|
||||
import type {
|
||||
UpdateItemHook,
|
||||
Address,
|
||||
} from '@vercel/commerce/types/customer/address'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
|
||||
@ -21,7 +24,7 @@ export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/address',
|
||||
url: '/api/commerce/customer/address',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -2,14 +2,16 @@ import type { AddItemHook } from '@vercel/commerce/types/customer/card'
|
||||
import type { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/card/use-add-item'
|
||||
import useAddItem, {
|
||||
UseAddItem,
|
||||
} from '@vercel/commerce/customer/card/use-add-item'
|
||||
import useCards from './use-cards'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
url: '/api/commerce/customer/card',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
|
@ -8,7 +8,7 @@ export default useCard as UseCards<typeof handler>
|
||||
|
||||
export const handler: SWRHook<GetCardsHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
url: '/api/commerce/customer/card',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ useData }) =>
|
||||
|
@ -25,7 +25,7 @@ export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
url: '/api/commerce/customer/card',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -21,7 +21,7 @@ export default useUpdateItem as UseUpdateItem<any>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer/card',
|
||||
url: '/api/commerce/customer/card',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
|
@ -5,7 +5,7 @@ export default useSearch as UseSearch<typeof handler>
|
||||
|
||||
export const handler: SWRHook<SearchProductsHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/catalog/products',
|
||||
url: '/api/commerce/catalog/products',
|
||||
method: 'GET',
|
||||
},
|
||||
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||
|
@ -1 +0,0 @@
|
||||
export default function (_commerce: any) {}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user