mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Dynamic API routes (#836)
* Add dynamic API endpoints * Add missing dependency * Update api handlers * Updates * Fix build errors * Update package.json * Add checkout endpoint parser & update errors * Update tsconfig.json * Update cart.ts * Update parser * Update errors.ts * Update errors.ts * Move to Edge runtime * Revert to local * Fix switchable runtimes * Make nodejs default runtime * Update pnpm-lock.yaml * Update handlers * Fix build errors * Change headers
This commit is contained in:
@@ -50,9 +50,7 @@
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/commerce": "workspace:*",
|
||||
"@vercel/fetch": "^6.2.0",
|
||||
"lodash.debounce": "^4.0.8",
|
||||
"node-fetch": "^2.6.7"
|
||||
"lodash.debounce": "^4.0.8"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12",
|
||||
|
@@ -53,28 +53,19 @@ const buildAddToCartVariables = ({
|
||||
|
||||
const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, item },
|
||||
body: { item },
|
||||
config,
|
||||
}) => {
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
if (!item.quantity) item.quantity = 1
|
||||
|
||||
const productResponse = await config.fetch(getProductQuery, {
|
||||
variables: { productCode: item?.productId },
|
||||
})
|
||||
|
||||
const cookieHandler = new CookieHandler(config, req, res)
|
||||
const cookieHandler = new CookieHandler(config, req)
|
||||
let accessToken = null
|
||||
|
||||
if (!cookieHandler.getAccessToken()) {
|
||||
let anonymousShopperTokenResponse = await cookieHandler.getAnonymousToken()
|
||||
accessToken = anonymousShopperTokenResponse.accessToken;
|
||||
accessToken = anonymousShopperTokenResponse.accessToken
|
||||
} else {
|
||||
accessToken = cookieHandler.getAccessToken()
|
||||
}
|
||||
@@ -95,7 +86,8 @@ const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
|
||||
return { data: normalizeCart(currentCart) }
|
||||
}
|
||||
|
||||
export default addItem
|
||||
|
@@ -6,17 +6,17 @@ import { getCartQuery } from '../../queries/get-cart-query'
|
||||
|
||||
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId },
|
||||
config,
|
||||
}) => {
|
||||
let currentCart: Cart = {}
|
||||
let headers
|
||||
try {
|
||||
const cookieHandler = new CookieHandler(config, req, res)
|
||||
const cookieHandler = new CookieHandler(config, req)
|
||||
let accessToken = null
|
||||
|
||||
if (!cookieHandler.getAccessToken()) {
|
||||
let anonymousShopperTokenResponse = await cookieHandler.getAnonymousToken()
|
||||
let anonymousShopperTokenResponse =
|
||||
await cookieHandler.getAnonymousToken()
|
||||
const response = anonymousShopperTokenResponse.response
|
||||
accessToken = anonymousShopperTokenResponse.accessToken
|
||||
cookieHandler.setAnonymousShopperCookie(response)
|
||||
@@ -30,12 +30,14 @@ const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
headers = cookieHandler.headers
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
res.status(200).json({
|
||||
|
||||
return {
|
||||
data: currentCart ? normalizeCart(currentCart) : null,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export default getCart
|
||||
|
@@ -1,8 +1,8 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import cartEndpoint from '@vercel/commerce/api/endpoints/cart'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import getCart from './get-cart';
|
||||
import addItem from './add-item';
|
||||
import getCart from './get-cart'
|
||||
import addItem from './add-item'
|
||||
import updateItem from './update-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
|
@@ -5,17 +5,10 @@ import { getCartQuery } from '../../../api/queries/get-cart-query'
|
||||
|
||||
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, itemId },
|
||||
body: { itemId },
|
||||
config,
|
||||
}) => {
|
||||
if (!itemId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
const encodedToken = req.cookies.get(config.customerCookie)
|
||||
const token = encodedToken
|
||||
? Buffer.from(encodedToken, 'base64').toString('ascii')
|
||||
: null
|
||||
@@ -39,7 +32,10 @@ const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
|
||||
return {
|
||||
data: normalizeCart(currentCart),
|
||||
}
|
||||
}
|
||||
|
||||
export default removeItem
|
||||
|
@@ -5,17 +5,10 @@ import updateCartItemQuantityMutation from '../../../api/mutations/updateCartIte
|
||||
|
||||
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, itemId, item },
|
||||
body: { itemId, item },
|
||||
config,
|
||||
}) => {
|
||||
if (!itemId || !item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
const encodedToken = req.cookies.get(config.cartCookie)
|
||||
const token = encodedToken
|
||||
? Buffer.from(encodedToken, 'base64').toString('ascii')
|
||||
: null
|
||||
@@ -39,7 +32,8 @@ const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
|
||||
return { data: normalizeCart(currentCart) }
|
||||
}
|
||||
|
||||
export default updateItem
|
||||
|
@@ -2,16 +2,15 @@ import { Product } from '@vercel/commerce/types/product'
|
||||
import { ProductsEndpoint } from '.'
|
||||
import productSearchQuery from '../../../queries/product-search-query'
|
||||
import { buildProductSearchVars } from '../../../../lib/product-search-vars'
|
||||
import {normalizeProduct} from '../../../../lib/normalize'
|
||||
import { normalizeProduct } from '../../../../lib/normalize'
|
||||
|
||||
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
|
||||
res,
|
||||
body: { search, categoryId, brandId, sort },
|
||||
config,
|
||||
}) => {
|
||||
const pageSize = 100;
|
||||
const filters = {};
|
||||
const startIndex = 0;
|
||||
const pageSize = 100
|
||||
const filters = {}
|
||||
const startIndex = 0
|
||||
const variables = buildProductSearchVars({
|
||||
categoryCode: categoryId,
|
||||
pageSize,
|
||||
@@ -20,12 +19,14 @@ const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
|
||||
filters,
|
||||
startIndex,
|
||||
})
|
||||
const {data} = await config.fetch(productSearchQuery, { variables });
|
||||
const found = data?.products?.items?.length > 0 ? true : false;
|
||||
let productsResponse= data?.products?.items.map((item: any) =>normalizeProduct(item,config));
|
||||
const products: Product[] = found ? productsResponse : [];
|
||||
const { data } = await config.fetch(productSearchQuery, { variables })
|
||||
const found = data?.products?.items?.length > 0 ? true : false
|
||||
let productsResponse = data?.products?.items.map((item: any) =>
|
||||
normalizeProduct(item, config)
|
||||
)
|
||||
const products: Product[] = found ? productsResponse : []
|
||||
|
||||
res.status(200).json({ data: { products, found } });
|
||||
return { data: { products, found } }
|
||||
}
|
||||
|
||||
export default getProducts
|
||||
|
@@ -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 {}
|
@@ -2,35 +2,32 @@ import CookieHandler from '../../../api/utils/cookie-handler'
|
||||
import type { CustomerEndpoint } from '.'
|
||||
import { getCustomerAccountQuery } from '../../queries/get-customer-account-query'
|
||||
import { normalizeCustomer } from '../../../lib/normalize'
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
|
||||
const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] = async ({
|
||||
req,
|
||||
res,
|
||||
config,
|
||||
}) => {
|
||||
const cookieHandler = new CookieHandler(config, req, res)
|
||||
let accessToken = cookieHandler.getAccessToken();
|
||||
const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] =
|
||||
async ({ req, config }) => {
|
||||
const cookieHandler = new CookieHandler(config, req)
|
||||
let accessToken = cookieHandler.getAccessToken()
|
||||
|
||||
if (!cookieHandler.isShopperCookieAnonymous()) {
|
||||
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
|
||||
headers: {
|
||||
'x-vol-user-claims': accessToken,
|
||||
},
|
||||
})
|
||||
|
||||
const customer = normalizeCustomer(data?.customerAccount)
|
||||
|
||||
if (!customer.id) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Customer not found', code: 'not_found' }],
|
||||
if (!cookieHandler.isShopperCookieAnonymous()) {
|
||||
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
|
||||
headers: {
|
||||
'x-vol-user-claims': accessToken,
|
||||
},
|
||||
})
|
||||
|
||||
const customer = normalizeCustomer(data?.customerAccount)
|
||||
|
||||
if (!customer.id) {
|
||||
throw new CommerceAPIError('Customer not found', {
|
||||
status: 404,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: { customer } }
|
||||
}
|
||||
|
||||
return res.status(200).json({ data: { customer } })
|
||||
return { data: null }
|
||||
}
|
||||
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
|
||||
export default getLoggedInCustomer
|
||||
|
25
packages/kibocommerce/src/api/endpoints/index.ts
Normal file
25
packages/kibocommerce/src/api/endpoints/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
import type { KiboCommerceAPI } 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 customer from './customer'
|
||||
import wishlist from './wishlist'
|
||||
import products from './catalog/products'
|
||||
|
||||
const endpoints = {
|
||||
cart,
|
||||
login,
|
||||
logout,
|
||||
signup,
|
||||
wishlist,
|
||||
customer,
|
||||
'catalog/products': products,
|
||||
}
|
||||
|
||||
export default function kiboCommerceAPI(commerce: KiboCommerceAPI) {
|
||||
return createEndpoints(commerce, endpoints)
|
||||
}
|
@@ -1,66 +1,53 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { LoginEndpoint } from '.'
|
||||
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
|
||||
import { loginMutation } from '../../mutations/login-mutation'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie';
|
||||
import { setCookies } from '../../../lib/set-cookie'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie'
|
||||
import { getCookieExpirationDate } from '../../../lib/get-cookie-expiration-date'
|
||||
|
||||
const invalidCredentials = /invalid credentials/i
|
||||
|
||||
const login: LoginEndpoint['handlers']['login'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { email, password },
|
||||
config,
|
||||
commerce,
|
||||
}) => {
|
||||
|
||||
if (!(email && password)) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
let response;
|
||||
let response
|
||||
try {
|
||||
|
||||
const variables = { loginInput : { username: email, password }};
|
||||
response = await config.fetch(loginMutation, { variables })
|
||||
const { account: token } = response.data;
|
||||
const variables = { loginInput: { username: email, password } }
|
||||
response = await config.fetch(loginMutation, { variables })
|
||||
const { account: token } = response.data
|
||||
|
||||
// Set Cookie
|
||||
const cookieExpirationDate = getCookieExpirationDate(config.customerCookieMaxAgeInDays)
|
||||
const cookieExpirationDate = getCookieExpirationDate(
|
||||
config.customerCookieMaxAgeInDays
|
||||
)
|
||||
|
||||
const authCookie = prepareSetCookie(
|
||||
config.customerCookie,
|
||||
JSON.stringify(token),
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate }: {},
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate } : {}
|
||||
)
|
||||
setCookies(res, [authCookie])
|
||||
|
||||
return { data: null, headers: { 'Set-Cookie': authCookie } }
|
||||
} catch (error) {
|
||||
// Check if the email and password didn't match an existing account
|
||||
if (
|
||||
error instanceof FetcherError &&
|
||||
invalidCredentials.test(error.message)
|
||||
) {
|
||||
return res.status(401).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Cannot find an account that matches the provided credentials',
|
||||
code: 'invalid_credentials',
|
||||
},
|
||||
],
|
||||
})
|
||||
throw new CommerceAPIError(
|
||||
'Cannot find an account that matches the provided credentials',
|
||||
{
|
||||
status: 401,
|
||||
code: 'invalid_credentials',
|
||||
}
|
||||
)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
res.status(200).json({ data: response })
|
||||
}
|
||||
|
||||
export default login
|
||||
|
@@ -1,22 +1,22 @@
|
||||
import type { LogoutEndpoint } from '.'
|
||||
import {prepareSetCookie} from '../../../lib/prepare-set-cookie';
|
||||
import {setCookies} from '../../../lib/set-cookie'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie'
|
||||
|
||||
const logout: LogoutEndpoint['handlers']['logout'] = async ({
|
||||
res,
|
||||
body: { redirectTo },
|
||||
config,
|
||||
}) => {
|
||||
// Remove the cookie
|
||||
const authCookie = prepareSetCookie(config.customerCookie,'',{ maxAge: -1, path: '/' })
|
||||
setCookies(res, [authCookie])
|
||||
const authCookie = prepareSetCookie(config.customerCookie, '', {
|
||||
maxAge: -1,
|
||||
path: '/',
|
||||
})
|
||||
|
||||
const headers = {
|
||||
'Set-Cookie': authCookie,
|
||||
}
|
||||
|
||||
// Only allow redirects to a relative URL
|
||||
if (redirectTo?.startsWith('/')) {
|
||||
res.redirect(redirectTo)
|
||||
} else {
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
return redirectTo?.startsWith('/') ? { redirectTo, headers } : { headers }
|
||||
}
|
||||
|
||||
export default logout
|
||||
|
@@ -1,91 +1,89 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { SignupEndpoint } from '.'
|
||||
import { registerUserMutation, registerUserLoginMutation } from '../../mutations/signup-mutation'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie';
|
||||
import { setCookies } from '../../../lib/set-cookie'
|
||||
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
|
||||
import {
|
||||
registerUserMutation,
|
||||
registerUserLoginMutation,
|
||||
} from '../../mutations/signup-mutation'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie'
|
||||
import { getCookieExpirationDate } from '../../../lib/get-cookie-expiration-date'
|
||||
|
||||
const invalidCredentials = /invalid credentials/i
|
||||
|
||||
const signup: SignupEndpoint['handlers']['signup'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { email, password, firstName, lastName },
|
||||
config,
|
||||
commerce,
|
||||
}) => {
|
||||
|
||||
if (!(email && password)) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
let response;
|
||||
let response
|
||||
try {
|
||||
|
||||
// Register user
|
||||
const registerUserVariables = {
|
||||
customerAccountInput: {
|
||||
emailAddress: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
acceptsMarketing: true,
|
||||
id: 0
|
||||
}
|
||||
emailAddress: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
acceptsMarketing: true,
|
||||
id: 0,
|
||||
},
|
||||
}
|
||||
|
||||
const registerUserResponse = await config.fetch(registerUserMutation, { variables: registerUserVariables})
|
||||
const accountId = registerUserResponse.data?.account?.id;
|
||||
const registerUserResponse = await config.fetch(registerUserMutation, {
|
||||
variables: registerUserVariables,
|
||||
})
|
||||
const accountId = registerUserResponse.data?.account?.id
|
||||
|
||||
// Login user
|
||||
const registerUserLoginVairables = {
|
||||
accountId: accountId,
|
||||
customerLoginInfoInput: {
|
||||
emailAddress: email,
|
||||
username: email,
|
||||
password: password,
|
||||
isImport: false
|
||||
}
|
||||
emailAddress: email,
|
||||
username: email,
|
||||
password: password,
|
||||
isImport: false,
|
||||
},
|
||||
}
|
||||
|
||||
response = await config.fetch(registerUserLoginMutation, { variables: registerUserLoginVairables})
|
||||
const { account: token } = response.data;
|
||||
response = await config.fetch(registerUserLoginMutation, {
|
||||
variables: registerUserLoginVairables,
|
||||
})
|
||||
const { account: token } = response.data
|
||||
|
||||
// Set Cookie
|
||||
const cookieExpirationDate = getCookieExpirationDate(config.customerCookieMaxAgeInDays)
|
||||
const cookieExpirationDate = getCookieExpirationDate(
|
||||
config.customerCookieMaxAgeInDays
|
||||
)
|
||||
|
||||
const authCookie = prepareSetCookie(
|
||||
config.customerCookie,
|
||||
JSON.stringify(token),
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate }: {},
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate } : {}
|
||||
)
|
||||
|
||||
setCookies(res, [authCookie])
|
||||
|
||||
return {
|
||||
data: response,
|
||||
headers: {
|
||||
'Set-Cookie': authCookie,
|
||||
},
|
||||
}
|
||||
} catch (error) {
|
||||
// Check if the email and password didn't match an existing account
|
||||
if (
|
||||
error instanceof FetcherError &&
|
||||
invalidCredentials.test(error.message)
|
||||
) {
|
||||
return res.status(401).json({
|
||||
data: null,
|
||||
errors: [
|
||||
{
|
||||
message:
|
||||
'Cannot find an account that matches the provided credentials',
|
||||
code: 'invalid_credentials',
|
||||
},
|
||||
],
|
||||
})
|
||||
throw new CommerceAPIError(
|
||||
'Cannot find an account that matches the provided credentials',
|
||||
{
|
||||
status: 401,
|
||||
code: 'invalid_credentials',
|
||||
}
|
||||
)
|
||||
} else {
|
||||
throw error
|
||||
}
|
||||
|
||||
throw error
|
||||
}
|
||||
|
||||
res.status(200).json({ data: response })
|
||||
}
|
||||
|
||||
export default signup
|
||||
|
@@ -1,17 +1,20 @@
|
||||
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import type { WishlistEndpoint } from '.'
|
||||
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
|
||||
import { normalizeWishlistItem } from '../../../lib/normalize'
|
||||
import { getProductQuery } from '../../../api/queries/get-product-query'
|
||||
import addItemToWishlistMutation from '../../mutations/addItemToWishlist-mutation'
|
||||
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import createWishlist from '../../mutations/create-wishlist-mutation'
|
||||
import addItemToWishlistMutation from '../../mutations/addItemToWishlist-mutation'
|
||||
|
||||
// Return wishlist info
|
||||
const buildAddToWishlistVariables = ({
|
||||
productId,
|
||||
variantId,
|
||||
productResponse,
|
||||
wishlist
|
||||
wishlist,
|
||||
}: {
|
||||
productId: string
|
||||
variantId: string
|
||||
@@ -23,7 +26,7 @@ const buildAddToWishlistVariables = ({
|
||||
const selectedOptions = product.variations?.find(
|
||||
(v: any) => v.productCode === variantId
|
||||
).options
|
||||
const quantity=1
|
||||
const quantity = 1
|
||||
let options: any[] = []
|
||||
selectedOptions?.forEach((each: any) => {
|
||||
product?.options
|
||||
@@ -47,53 +50,50 @@ const buildAddToWishlistVariables = ({
|
||||
productCode: productId,
|
||||
variationProductCode: variantId ? variantId : null,
|
||||
options,
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
||||
res,
|
||||
body: { customerToken, item },
|
||||
config,
|
||||
commerce,
|
||||
}) => {
|
||||
const token = customerToken ? Buffer.from(customerToken, 'base64').toString('ascii'): null;
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null;
|
||||
const token = customerToken
|
||||
? Buffer.from(customerToken, 'base64').toString('ascii')
|
||||
: null
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||
let result: { data?: any } = {}
|
||||
let wishlist: any
|
||||
|
||||
if (!item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Missing item' }],
|
||||
})
|
||||
}
|
||||
|
||||
const customerId = customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName= config.defaultWishlistName
|
||||
const customerId =
|
||||
customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName = config.defaultWishlistName
|
||||
|
||||
if (!customerId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
throw new CommerceAPIError('Customer not found', { status: 404 })
|
||||
}
|
||||
|
||||
const wishlistResponse = await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
if(Object.keys(wishlist).length === 0) {
|
||||
const createWishlistResponse= await config.fetch(createWishlist, {variables: {
|
||||
wishlistInput: {
|
||||
customerAccountId: customerId,
|
||||
name: wishlistName
|
||||
}
|
||||
}
|
||||
}, {headers: { 'x-vol-user-claims': accessToken } })
|
||||
wishlist= createWishlistResponse?.data?.createWishlist
|
||||
wishlist = wishlistResponse?.wishlist
|
||||
if (Object.keys(wishlist).length === 0) {
|
||||
const createWishlistResponse = await config.fetch(
|
||||
createWishlist,
|
||||
{
|
||||
variables: {
|
||||
wishlistInput: {
|
||||
customerAccountId: customerId,
|
||||
name: wishlistName,
|
||||
},
|
||||
},
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
wishlist = createWishlistResponse?.data?.createWishlist
|
||||
}
|
||||
|
||||
const productResponse = await config.fetch(getProductQuery, {
|
||||
@@ -103,22 +103,33 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({
|
||||
const addItemToWishlistResponse = await config.fetch(
|
||||
addItemToWishlistMutation,
|
||||
{
|
||||
variables: buildAddToWishlistVariables({ ...item, productResponse, wishlist }),
|
||||
variables: buildAddToWishlistVariables({
|
||||
...item,
|
||||
productResponse,
|
||||
wishlist,
|
||||
}),
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
if(addItemToWishlistResponse?.data?.createWishlistItem){
|
||||
const wishlistResponse= await commerce.getCustomerWishlist({
|
||||
if (addItemToWishlistResponse?.data?.createWishlistItem) {
|
||||
const wishlistResponse = await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
wishlist = wishlistResponse?.wishlist
|
||||
}
|
||||
|
||||
result = { data: {...wishlist, items: wishlist?.items?.map((item:any) => normalizeWishlistItem(item, config))} }
|
||||
|
||||
res.status(200).json({ data: result?.data })
|
||||
result = {
|
||||
data: {
|
||||
...wishlist,
|
||||
items: wishlist?.items?.map((item: any) =>
|
||||
normalizeWishlistItem(item, config)
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return { data: result?.data }
|
||||
}
|
||||
|
||||
export default addItem
|
||||
|
@@ -1,35 +1,45 @@
|
||||
import type { WishlistEndpoint } from '.'
|
||||
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import { normalizeWishlistItem } from '../../../lib/normalize'
|
||||
|
||||
// Return wishlist info
|
||||
const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({
|
||||
res,
|
||||
body: { customerToken, includeProducts },
|
||||
config,
|
||||
commerce,
|
||||
}) => {
|
||||
let result: { data?: any } = {}
|
||||
if (customerToken) {
|
||||
const customerId = customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName= config.defaultWishlistName
|
||||
const customerId =
|
||||
customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName = config.defaultWishlistName
|
||||
|
||||
if (!customerId) {
|
||||
// If the customerToken is invalid, then this request is too
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Wishlist not found' }],
|
||||
throw new CommerceAPIError('Wishlist not found', {
|
||||
status: 404,
|
||||
code: 'not_found',
|
||||
})
|
||||
}
|
||||
|
||||
const { wishlist } = await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
includeProducts,
|
||||
config,
|
||||
})
|
||||
|
||||
result = { data: {...wishlist, items: wishlist?.items?.map((item:any) => normalizeWishlistItem(item, config, includeProducts))} }
|
||||
result = {
|
||||
data: {
|
||||
...wishlist,
|
||||
items: wishlist?.items?.map((item: any) =>
|
||||
normalizeWishlistItem(item, config, includeProducts)
|
||||
),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
res.status(200).json({ data: result?.data ?? null })
|
||||
return { data: result?.data ?? null }
|
||||
}
|
||||
|
||||
export default getWishlist
|
||||
|
@@ -1,60 +1,69 @@
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import type { WishlistEndpoint } from '.'
|
||||
import { CommerceAPIError } from '@vercel/commerce/api/utils/errors'
|
||||
import { normalizeWishlistItem } from '../../../lib/normalize'
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import removeItemFromWishlistMutation from '../../mutations/removeItemFromWishlist-mutation'
|
||||
|
||||
// Return wishlist info
|
||||
const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({
|
||||
res,
|
||||
body: { customerToken, itemId },
|
||||
config,
|
||||
commerce,
|
||||
}) => {
|
||||
const token = customerToken ? Buffer.from(customerToken, 'base64').toString('ascii'): null;
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null;
|
||||
const token = customerToken
|
||||
? Buffer.from(customerToken, 'base64').toString('ascii')
|
||||
: null
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||
let result: { data?: any } = {}
|
||||
let wishlist: any
|
||||
|
||||
const customerId = customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName= config.defaultWishlistName
|
||||
const customerId =
|
||||
customerToken && (await getCustomerId({ customerToken, config }))
|
||||
const wishlistName = config.defaultWishlistName
|
||||
const wishlistResponse = await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
|
||||
if (!wishlist || !itemId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
wishlist = wishlistResponse?.wishlist
|
||||
|
||||
if (!wishlist) {
|
||||
throw new CommerceAPIError('Wishlist not found', { status: 404 })
|
||||
}
|
||||
const removedItem = wishlist?.items?.find(
|
||||
(item:any) => {
|
||||
return item.product.productCode === itemId;
|
||||
}
|
||||
);
|
||||
|
||||
const removedItem = wishlist?.items?.find((item: any) => {
|
||||
return item.product.productCode === itemId
|
||||
})
|
||||
|
||||
const removeItemFromWishlistResponse = await config.fetch(
|
||||
removeItemFromWishlistMutation,
|
||||
{
|
||||
variables: {
|
||||
wishlistId: wishlist?.id,
|
||||
wishlistItemId: removedItem?.id
|
||||
wishlistItemId: removedItem?.id,
|
||||
},
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
if(removeItemFromWishlistResponse?.data?.deleteWishlistItem){
|
||||
const wishlistResponse= await commerce.getCustomerWishlist({
|
||||
if (removeItemFromWishlistResponse?.data?.deleteWishlistItem) {
|
||||
const wishlistResponse = await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
wishlist = wishlistResponse?.wishlist
|
||||
}
|
||||
result = {
|
||||
data: {
|
||||
...wishlist,
|
||||
items: wishlist?.items?.map((item: any) =>
|
||||
normalizeWishlistItem(item, config)
|
||||
),
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
data: result?.data,
|
||||
}
|
||||
result = { data: {...wishlist, items: wishlist?.items?.map((item:any) => normalizeWishlistItem(item, config))} }
|
||||
res.status(200).json({ data: result?.data })
|
||||
}
|
||||
|
||||
export default removeItem
|
||||
|
@@ -9,16 +9,15 @@ import getCustomerWishlist from './operations/get-customer-wishlist'
|
||||
import getAllProductPaths from './operations/get-all-product-paths'
|
||||
import getAllProducts from './operations/get-all-products'
|
||||
import getProduct from './operations/get-product'
|
||||
import type { RequestInit } from '@vercel/fetch'
|
||||
|
||||
export interface KiboCommerceConfig extends CommerceAPIConfig {
|
||||
apiHost?: string
|
||||
clientId?: string
|
||||
sharedSecret?: string
|
||||
customerCookieMaxAgeInDays: number,
|
||||
currencyCode: string,
|
||||
documentListName: string,
|
||||
defaultWishlistName: string,
|
||||
customerCookieMaxAgeInDays: number
|
||||
currencyCode: string
|
||||
documentListName: string
|
||||
defaultWishlistName: string
|
||||
authUrl?: string
|
||||
}
|
||||
|
||||
@@ -37,7 +36,7 @@ const config: KiboCommerceConfig = {
|
||||
sharedSecret: process.env.KIBO_SHARED_SECRET || '',
|
||||
customerCookieMaxAgeInDays: 30,
|
||||
currencyCode: 'USD',
|
||||
defaultWishlistName: 'My Wishlist'
|
||||
defaultWishlistName: 'My Wishlist',
|
||||
}
|
||||
|
||||
const operations = {
|
||||
@@ -55,7 +54,7 @@ export const provider = { config, operations }
|
||||
export type KiboCommerceProvider = typeof provider
|
||||
export type KiboCommerceAPI<
|
||||
P extends KiboCommerceProvider = KiboCommerceProvider
|
||||
> = CommerceAPI<P | any>
|
||||
> = CommerceAPI<P | any>
|
||||
|
||||
export function getCommerceApi<P extends KiboCommerceProvider>(
|
||||
customProvider: P = provider as any
|
||||
|
@@ -1,6 +1,4 @@
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import type { FetchOptions } from '@vercel/fetch'
|
||||
import fetch from './fetch'
|
||||
|
||||
// This object is persisted during development
|
||||
const authCache: { kiboAuthTicket?: AppAuthTicket } = {}
|
||||
@@ -41,11 +39,11 @@ export class APIAuthenticationHelper {
|
||||
this._clientId = clientId
|
||||
this._sharedSecret = sharedSecret
|
||||
this._authUrl = authUrl
|
||||
if(!authTicketCache) {
|
||||
this._authTicketCache = new RuntimeMemCache();
|
||||
if (!authTicketCache) {
|
||||
this._authTicketCache = new RuntimeMemCache()
|
||||
}
|
||||
}
|
||||
private _buildFetchOptions(body: any = {}): FetchOptions {
|
||||
private _buildFetchOptions(body: any = {}): any {
|
||||
return {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
|
@@ -1,25 +1,25 @@
|
||||
import { KiboCommerceConfig } from './../index'
|
||||
import { getCookieExpirationDate } from '../../lib/get-cookie-expiration-date'
|
||||
import { prepareSetCookie } from '../../lib/prepare-set-cookie'
|
||||
import { setCookies } from '../../lib/set-cookie'
|
||||
import { NextApiRequest } from 'next'
|
||||
|
||||
import getAnonymousShopperToken from './get-anonymous-shopper-token'
|
||||
import type { NextRequest } from 'next/server'
|
||||
|
||||
const parseCookie = (cookieValue?: any) => {
|
||||
return cookieValue
|
||||
? JSON.parse(Buffer.from(cookieValue, 'base64').toString('ascii'))
|
||||
return cookieValue
|
||||
? JSON.parse(Buffer.from(cookieValue, 'base64').toString('ascii'))
|
||||
: null
|
||||
}
|
||||
export default class CookieHandler {
|
||||
config: KiboCommerceConfig
|
||||
request: NextApiRequest
|
||||
response: any
|
||||
request: NextRequest
|
||||
headers: HeadersInit | undefined
|
||||
accessToken: any
|
||||
constructor(config: any, req: NextApiRequest, res: any) {
|
||||
constructor(config: any, req: NextRequest) {
|
||||
this.config = config
|
||||
this.request = req
|
||||
this.response = res
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
|
||||
const encodedToken = req.cookies.get(config.customerCookie)
|
||||
const token = parseCookie(encodedToken)
|
||||
this.accessToken = token ? token.accessToken : null
|
||||
}
|
||||
@@ -36,9 +36,9 @@ export default class CookieHandler {
|
||||
}
|
||||
isShopperCookieAnonymous() {
|
||||
const customerCookieKey = this.config.customerCookie
|
||||
const shopperCookie = this.request.cookies[customerCookieKey]
|
||||
const shopperSession = parseCookie(shopperCookie);
|
||||
const isAnonymous = shopperSession?.customerAccount ? false : true
|
||||
const shopperCookie = this.request.cookies.get(customerCookieKey)
|
||||
const shopperSession = parseCookie(shopperCookie)
|
||||
const isAnonymous = shopperSession?.customerAccount ? false : true
|
||||
return isAnonymous
|
||||
}
|
||||
setAnonymousShopperCookie(anonymousShopperTokenResponse: any) {
|
||||
@@ -53,7 +53,9 @@ export default class CookieHandler {
|
||||
? { expires: cookieExpirationDate }
|
||||
: {}
|
||||
)
|
||||
setCookies(this.response, [authCookie])
|
||||
this.headers = {
|
||||
'Set-Cookie': authCookie,
|
||||
}
|
||||
}
|
||||
getAccessToken() {
|
||||
return this.accessToken
|
||||
|
@@ -1,43 +1,46 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@vercel/commerce/api'
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import fetch from './fetch'
|
||||
import { APIAuthenticationHelper } from './api-auth-helper';
|
||||
|
||||
import { APIAuthenticationHelper } from './api-auth-helper'
|
||||
|
||||
const fetchGraphqlApi: (
|
||||
getConfig: () => KiboCommerceConfig
|
||||
) => GraphQLFetcher = (getConfig) => async (
|
||||
query: string,
|
||||
{ variables, preview } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
const config = getConfig()
|
||||
const authHelper = new APIAuthenticationHelper(config);
|
||||
const apiToken = await authHelper.getAccessToken();
|
||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...fetchOptions?.headers,
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
console.warn(`Kibo API Request Correlation ID: ${res.headers.get('x-vol-correlation')}`);
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [{ message: 'Failed to fetch KiboCommerce API' }],
|
||||
status: res.status,
|
||||
) => GraphQLFetcher =
|
||||
(getConfig) =>
|
||||
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
|
||||
const config = getConfig()
|
||||
const authHelper = new APIAuthenticationHelper(config)
|
||||
const apiToken = await authHelper.getAccessToken()
|
||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
...headers,
|
||||
Authorization: `Bearer ${apiToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
console.warn(
|
||||
`Kibo API Request Correlation ID: ${res.headers.get(
|
||||
'x-vol-correlation'
|
||||
)}`
|
||||
)
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [
|
||||
{ message: 'Failed to fetch KiboCommerce API' },
|
||||
],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
||||
|
@@ -1,19 +1,19 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { GraphQLFetcher } from '@vercel/commerce/api'
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchGraphqlApi: (getConfig: () => KiboCommerceConfig) => GraphQLFetcher =
|
||||
const fetchGraphqlApi: (
|
||||
getConfig: () => KiboCommerceConfig
|
||||
) => GraphQLFetcher =
|
||||
(getConfig) =>
|
||||
async (query: string, { variables, preview } = {}, fetchOptions) => {
|
||||
async (query: string, { variables, preview } = {}, headers?: HeadersInit) => {
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl, {
|
||||
//const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
...fetchOptions,
|
||||
//const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
...fetchOptions?.headers,
|
||||
...headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
@@ -25,7 +25,9 @@ const fetchGraphqlApi: (getConfig: () => KiboCommerceConfig) => GraphQLFetcher =
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [{ message: 'Failed to fetch KiboCommerce API' }],
|
||||
errors: json.errors ?? [
|
||||
{ message: 'Failed to fetch KiboCommerce API' },
|
||||
],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
@@ -8,17 +8,13 @@ async function getCustomerId({
|
||||
customerToken: string
|
||||
config: KiboCommerceConfig
|
||||
}): Promise<string | undefined> {
|
||||
const token = customerToken ? Buffer.from(customerToken, 'base64').toString('ascii'): null;
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null;
|
||||
const { data } = await config.fetch(
|
||||
getCustomerAccountQuery,
|
||||
undefined,
|
||||
{
|
||||
headers: {
|
||||
'x-vol-user-claims': accessToken,
|
||||
},
|
||||
}
|
||||
)
|
||||
const token = customerToken
|
||||
? Buffer.from(customerToken, 'base64').toString('ascii')
|
||||
: null
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||
const { data } = await config.fetch(getCustomerAccountQuery, undefined, {
|
||||
'x-vol-user-claims': accessToken,
|
||||
})
|
||||
|
||||
return data?.customerAccount?.id
|
||||
}
|
||||
|
@@ -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:
|
||||
|
@@ -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]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@@ -8,7 +8,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 }) {
|
||||
|
@@ -1,3 +1,3 @@
|
||||
export function setCookies(res: any, cookies: string[]): void {
|
||||
res.setHeader('Set-Cookie', cookies);
|
||||
}
|
||||
res.setHeader('Set-Cookie', cookies)
|
||||
}
|
||||
|
@@ -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]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
Reference in New Issue
Block a user