mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
Monorepo with Turborepo (#651)
* Moved everything * Figuring out how to make imports work * Updated exports * Added missing exports * Added @vercel/commerce-local to `site` * Updated commerce config * Updated exports and commerce config * Updated commerce hoc * Fixed exports in local * Added publish config * Updated imports in site * It's actually working * Don't use debugger in dev for better speeds * Improved DX when editing packages * Set up eslint with husky * Updated prettier config * Added prettier setup to every package * Moved bigcommerce * Moved Bigcommerce to src and package updates * Updated setup of bigcommerce * Moved definitions script * Moved commercejs * Move to src * Fixed types in commercejs * Moved kibocommerce * Moved kibocommerce to src * Added package/tsconfig to kibocommerce * Fixed imports and other things * Moved ordercloud * Moved ordercloud to src * Fixed imports * Added missing prettier files * Moved Saleor * Moved Saleor to src * Fixed imports * Replaced all imports to @commerce * Added prettierignore/rc to all providers * Moved shopify to src * Build shopify in packages * Moved Spree * Moved spree to src * Updated spree * Moved swell * Moved swell to src * Fixed type imports in swell * Moved Vendure to packages * Moved vendure to src * Fixed imports in vendure * Added codegen to saleor * Updated codegen setup for shopify * Added codegen to vendure * Added codegen to kibocommerce * Added all packages to site's deps * Updated codegen setup in bigcommerce * Minor fixes * Updated providers' names in site * Updated packages based on Bel's changes * Updated turbo to latest * Fixed ts complains * Set npm engine in root * New lockfile install * remove engines * Regen lockfile * Switched from npm to yarn * Updated typesVersions in all packages * Moved dep * Updated SWR to the just released 1.2.0 * Removed "isolatedModules" from packages * Updated list of providers and default * Updated swell declaration * Removed next import from kibocommerce * Added COMMERCE_PROVIDER log * Added another log * Updated turbo config * Updated docs * Removed test logs Co-authored-by: Jared Palmer <jared@jaredpalmer.com>
This commit is contained in:
7
packages/kibocommerce/.env.template
Normal file
7
packages/kibocommerce/.env.template
Normal file
@@ -0,0 +1,7 @@
|
||||
COMMERCE_PROVIDER=kibocommerce
|
||||
KIBO_API_URL=
|
||||
KIBO_CART_COOKIE=
|
||||
KIBO_CUSTOMER_COOKIE=
|
||||
KIBO_CLIENT_ID=
|
||||
KIBO_SHARED_SECRET=
|
||||
KIBO_AUTH_URL=
|
2
packages/kibocommerce/.prettierignore
Normal file
2
packages/kibocommerce/.prettierignore
Normal file
@@ -0,0 +1,2 @@
|
||||
node_modules
|
||||
dist
|
6
packages/kibocommerce/.prettierrc
Normal file
6
packages/kibocommerce/.prettierrc
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"semi": false,
|
||||
"singleQuote": true,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false
|
||||
}
|
37
packages/kibocommerce/README.md
Normal file
37
packages/kibocommerce/README.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# Kibo Commerce Provider
|
||||
|
||||
If you already have a Kibo Commerce account and want to use your current store, then copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git):
|
||||
|
||||
```bash
|
||||
cp packages/kibocommerce/.env.template .env.local
|
||||
```
|
||||
|
||||
Then, set the environment variables in `.env.local` to match the ones from your store.
|
||||
|
||||
```
|
||||
COMMERCE_PROVIDER='kibocommerce'
|
||||
KIBO_API_URL= 'https://t1234-s1234.sandbox.mozu.com/graphql'
|
||||
KIBO_CART_COOKIE='kibo_cart'
|
||||
KIBO_CUSTOMER_COOKIE='kibo_customer'
|
||||
KIBO_CLIENT_ID='KIBO.APP.1.0.0.Release'
|
||||
KIBO_SHARED_SECRET='12345secret'
|
||||
KIBO_AUTH_URL='https://home.mozu.com'
|
||||
```
|
||||
|
||||
- `KIBO_API_URL` - link to your Kibo Commerce GraphQL API instance.
|
||||
- `KIBO_CART_COOKIE` - configurable cookie name for cart.
|
||||
- `KIBO_CUSTOMER_COOKIE` - configurable cookie name for shopper identifier/authentication cookie
|
||||
- `KIBO_CLIENT_ID` - Unique Application (Client) ID of your Application
|
||||
- `KIBO_SHARED_SECRET` - Secret API key used to authenticate application/client id.
|
||||
|
||||
|
||||
Your Kibo Client ID and Shared Secret can be found from your [Kibo eCommerce Dev Center](https://mozu.com/login)
|
||||
|
||||
Visit [Kibo documentation](https://apidocs.kibong-perf.com/?spec=graphql#auth) for more details on API authentication
|
||||
|
||||
Based on the config, this integration will handle Authenticating your application against the Kibo API using the Kibo Client ID and Kibo Shared Secret.
|
||||
## Contribute
|
||||
|
||||
Our commitment to Open Source can be found [here](https://vercel.com/oss).
|
||||
|
||||
If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues).
|
21
packages/kibocommerce/codegen.json
Normal file
21
packages/kibocommerce/codegen.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"schema": {
|
||||
"https://t17194-s21127.dev10.kubedev.kibo-dev.com/graphql": {}
|
||||
},
|
||||
"generates": {
|
||||
"./schema.d.ts": {
|
||||
"plugins": ["typescript", "typescript-operations"],
|
||||
"config": {
|
||||
"scalars": {
|
||||
"ID": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"./schema.graphql": {
|
||||
"plugins": ["schema-ast"]
|
||||
}
|
||||
},
|
||||
"hooks": {
|
||||
"afterAllFileWrite": ["prettier --write"]
|
||||
}
|
||||
}
|
78
packages/kibocommerce/package.json
Normal file
78
packages/kibocommerce/package.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"name": "@vercel/commerce-kibocommerce",
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"scripts": {
|
||||
"build": "rm -fr dist/* && tsc",
|
||||
"dev": "npm run build -- --watch",
|
||||
"prettier-fix": "prettier --write .",
|
||||
"generate": "graphql-codegen"
|
||||
},
|
||||
"sideEffects": false,
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": "./dist/index.js",
|
||||
"./*": [
|
||||
"./dist/*.js",
|
||||
"./dist/*/index.js"
|
||||
],
|
||||
"./next.config": "./dist/next.config.cjs"
|
||||
},
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"src/*",
|
||||
"src/*/index"
|
||||
],
|
||||
"next.config": [
|
||||
"dist/next.config.d.cts"
|
||||
]
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist",
|
||||
"schema.d.ts"
|
||||
],
|
||||
"publishConfig": {
|
||||
"typesVersions": {
|
||||
"*": {
|
||||
"*": [
|
||||
"dist/*.d.ts",
|
||||
"dist/*/index.d.ts"
|
||||
],
|
||||
"next.config": [
|
||||
"dist/next.config.d.cts"
|
||||
]
|
||||
}
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@vercel/commerce": "^0.0.1",
|
||||
"@vercel/fetch": "^6.1.1"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"next": "^12",
|
||||
"react": "^17",
|
||||
"react-dom": "^17"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@graphql-codegen/cli": "^2.3.1",
|
||||
"@graphql-codegen/schema-ast": "^2.4.1",
|
||||
"@graphql-codegen/typescript": "^2.4.2",
|
||||
"@graphql-codegen/typescript-operations": "^2.2.2",
|
||||
"@types/node": "^17.0.8",
|
||||
"@types/react": "^17.0.38",
|
||||
"lint-staged": "^12.1.7",
|
||||
"next": "^12.0.8",
|
||||
"prettier": "^2.5.1",
|
||||
"react": "^17.0.2",
|
||||
"react-dom": "^17.0.2",
|
||||
"typescript": "^4.5.4"
|
||||
},
|
||||
"lint-staged": {
|
||||
"**/*.{js,jsx,ts,tsx,json}": [
|
||||
"prettier --write",
|
||||
"git add"
|
||||
]
|
||||
}
|
||||
}
|
11399
packages/kibocommerce/schema.d.ts
vendored
Normal file
11399
packages/kibocommerce/schema.d.ts
vendored
Normal file
File diff suppressed because it is too large
Load Diff
9129
packages/kibocommerce/schema.graphql
Normal file
9129
packages/kibocommerce/schema.graphql
Normal file
File diff suppressed because it is too large
Load Diff
101
packages/kibocommerce/src/api/endpoints/cart/add-item.ts
Normal file
101
packages/kibocommerce/src/api/endpoints/cart/add-item.ts
Normal file
@@ -0,0 +1,101 @@
|
||||
import { normalizeCart } from '../../../lib/normalize'
|
||||
import type { CartEndpoint } from '.'
|
||||
import addToCurrentCartMutation from '../../../api/mutations/addToCart-mutation'
|
||||
|
||||
import { getProductQuery } from '../../../api/queries/get-product-query'
|
||||
import { getCartQuery } from '../../../api/queries/get-cart-query'
|
||||
import CookieHandler from '../../../api/utils/cookie-handler'
|
||||
|
||||
const buildAddToCartVariables = ({
|
||||
productId,
|
||||
variantId,
|
||||
quantity = 1,
|
||||
productResponse,
|
||||
}: {
|
||||
productId: string
|
||||
variantId: string
|
||||
quantity: number
|
||||
productResponse: any
|
||||
}) => {
|
||||
const { product } = productResponse.data
|
||||
|
||||
const selectedOptions = product.variations?.find(
|
||||
(v: any) => v.productCode === variantId
|
||||
).options
|
||||
|
||||
let options: any[] = []
|
||||
selectedOptions?.forEach((each: any) => {
|
||||
product?.options
|
||||
.filter((option: any) => {
|
||||
return option.attributeFQN == each.attributeFQN
|
||||
})
|
||||
.forEach((po: any) => {
|
||||
options.push({
|
||||
attributeFQN: po.attributeFQN,
|
||||
name: po.attributeDetail.name,
|
||||
value: po.values?.find((v: any) => v.value == each.value).value,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
productToAdd: {
|
||||
product: {
|
||||
productCode: productId,
|
||||
variationProductCode: variantId ? variantId : null,
|
||||
options,
|
||||
},
|
||||
quantity,
|
||||
fulfillmentMethod: 'Ship',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
const addItem: CartEndpoint['handlers']['addItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, 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)
|
||||
let accessToken = null
|
||||
|
||||
if (!cookieHandler.getAccessToken()) {
|
||||
let anonymousShopperTokenResponse = await cookieHandler.getAnonymousToken()
|
||||
accessToken = anonymousShopperTokenResponse.accessToken;
|
||||
} else {
|
||||
accessToken = cookieHandler.getAccessToken()
|
||||
}
|
||||
|
||||
const addToCartResponse = await config.fetch(
|
||||
addToCurrentCartMutation,
|
||||
{
|
||||
variables: buildAddToCartVariables({ ...item, productResponse }),
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
let currentCart = null
|
||||
if (addToCartResponse.data.addItemToCurrentCart) {
|
||||
let result = await config.fetch(
|
||||
getCartQuery,
|
||||
{},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
}
|
||||
|
||||
export default addItem
|
41
packages/kibocommerce/src/api/endpoints/cart/get-cart.ts
Normal file
41
packages/kibocommerce/src/api/endpoints/cart/get-cart.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import CookieHandler from '../../../api/utils/cookie-handler'
|
||||
import { normalizeCart } from '../../../lib/normalize'
|
||||
import { Cart } from '../../../../schema'
|
||||
import type { CartEndpoint } from '.'
|
||||
import { getCartQuery } from '../../queries/get-cart-query'
|
||||
|
||||
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId },
|
||||
config,
|
||||
}) => {
|
||||
let currentCart: Cart = {}
|
||||
try {
|
||||
const cookieHandler = new CookieHandler(config, req, res)
|
||||
let accessToken = null
|
||||
|
||||
if (!cookieHandler.getAccessToken()) {
|
||||
let anonymousShopperTokenResponse = await cookieHandler.getAnonymousToken()
|
||||
const response = anonymousShopperTokenResponse.response
|
||||
accessToken = anonymousShopperTokenResponse.accessToken
|
||||
cookieHandler.setAnonymousShopperCookie(response)
|
||||
} else {
|
||||
accessToken = cookieHandler.getAccessToken()
|
||||
}
|
||||
|
||||
let result = await config.fetch(
|
||||
getCartQuery,
|
||||
{},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
res.status(200).json({
|
||||
data: currentCart ? normalizeCart(currentCart) : null,
|
||||
})
|
||||
}
|
||||
|
||||
export default getCart
|
25
packages/kibocommerce/src/api/endpoints/cart/index.ts
Normal file
25
packages/kibocommerce/src/api/endpoints/cart/index.ts
Normal file
@@ -0,0 +1,25 @@
|
||||
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 updateItem from './update-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
export type CartAPI = GetAPISchema<KiboCommerceAPI, any>
|
||||
|
||||
export type CartEndpoint = CartAPI['endpoint']
|
||||
|
||||
export const handlers: CartEndpoint['handlers'] = {
|
||||
getCart,
|
||||
addItem,
|
||||
updateItem,
|
||||
removeItem,
|
||||
}
|
||||
|
||||
const cartApi = createEndpoint<CartAPI>({
|
||||
handler: cartEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default cartApi
|
45
packages/kibocommerce/src/api/endpoints/cart/remove-item.ts
Normal file
45
packages/kibocommerce/src/api/endpoints/cart/remove-item.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { normalizeCart } from '../../../lib/normalize'
|
||||
import type { CartEndpoint } from '.'
|
||||
import removeItemFromCartMutation from '../../../api/mutations/removeItemFromCart-mutation'
|
||||
import { getCartQuery } from '../../../api/queries/get-cart-query'
|
||||
|
||||
const removeItem: CartEndpoint['handlers']['removeItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, itemId },
|
||||
config,
|
||||
}) => {
|
||||
if (!itemId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
const token = encodedToken
|
||||
? Buffer.from(encodedToken, 'base64').toString('ascii')
|
||||
: null
|
||||
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||
|
||||
const removeItemResponse = await config.fetch(
|
||||
removeItemFromCartMutation,
|
||||
{
|
||||
variables: { id: itemId },
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
let currentCart = null
|
||||
if (removeItemResponse.data.deleteCurrentCartItem) {
|
||||
let result = await config.fetch(
|
||||
getCartQuery,
|
||||
{},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
}
|
||||
|
||||
export default removeItem
|
45
packages/kibocommerce/src/api/endpoints/cart/update-item.ts
Normal file
45
packages/kibocommerce/src/api/endpoints/cart/update-item.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { normalizeCart } from '../../../lib/normalize'
|
||||
import type { CartEndpoint } from '.'
|
||||
import { getCartQuery } from '../../../api/queries/get-cart-query'
|
||||
import updateCartItemQuantityMutation from '../../../api/mutations/updateCartItemQuantity-mutation'
|
||||
|
||||
const updateItem: CartEndpoint['handlers']['updateItem'] = async ({
|
||||
req,
|
||||
res,
|
||||
body: { cartId, itemId, item },
|
||||
config,
|
||||
}) => {
|
||||
if (!itemId || !item) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
const token = encodedToken
|
||||
? Buffer.from(encodedToken, 'base64').toString('ascii')
|
||||
: null
|
||||
|
||||
const accessToken = token ? JSON.parse(token).accessToken : null
|
||||
|
||||
const updateItemResponse = await config.fetch(
|
||||
updateCartItemQuantityMutation,
|
||||
{
|
||||
variables: { itemId: itemId, quantity: item.quantity },
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
let currentCart = null
|
||||
if (updateItemResponse.data) {
|
||||
let result = await config.fetch(
|
||||
getCartQuery,
|
||||
{},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
currentCart = result?.data?.currentCart
|
||||
}
|
||||
res.status(200).json({ data: normalizeCart(currentCart) })
|
||||
}
|
||||
|
||||
export default updateItem
|
@@ -0,0 +1,17 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import productsEndpoint from '@vercel/commerce/api/endpoints/catalog/products'
|
||||
import type { KiboCommerceAPI } from '../../..'
|
||||
import getProducts from '../products/products'
|
||||
|
||||
export type ProductsAPI = GetAPISchema<KiboCommerceAPI, any>
|
||||
|
||||
export type ProductsEndpoint = ProductsAPI['endpoint']
|
||||
|
||||
export const handlers: ProductsEndpoint['handlers'] = { getProducts }
|
||||
|
||||
const productsApi = createEndpoint<ProductsAPI>({
|
||||
handler: productsEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default productsApi
|
@@ -0,0 +1,31 @@
|
||||
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'
|
||||
|
||||
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
|
||||
res,
|
||||
body: { search, categoryId, brandId, sort },
|
||||
config,
|
||||
}) => {
|
||||
const pageSize = 100;
|
||||
const filters = {};
|
||||
const startIndex = 0;
|
||||
const variables = buildProductSearchVars({
|
||||
categoryCode: categoryId,
|
||||
pageSize,
|
||||
search,
|
||||
sort,
|
||||
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 : [];
|
||||
|
||||
res.status(200).json({ data: { products, found } });
|
||||
}
|
||||
|
||||
export default getProducts
|
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
1
packages/kibocommerce/src/api/endpoints/customer/card.ts
Normal file
1
packages/kibocommerce/src/api/endpoints/customer/card.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
36
packages/kibocommerce/src/api/endpoints/customer/customer.ts
Normal file
36
packages/kibocommerce/src/api/endpoints/customer/customer.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import CookieHandler from '../../../api/utils/cookie-handler'
|
||||
import type { CustomerEndpoint } from '.'
|
||||
import { getCustomerAccountQuery } from '../../queries/get-customer-account-query'
|
||||
import { normalizeCustomer } from '../../../lib/normalize'
|
||||
|
||||
const getLoggedInCustomer: CustomerEndpoint['handlers']['getLoggedInCustomer'] = async ({
|
||||
req,
|
||||
res,
|
||||
config,
|
||||
}) => {
|
||||
const cookieHandler = new CookieHandler(config, req, res)
|
||||
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' }],
|
||||
})
|
||||
}
|
||||
|
||||
return res.status(200).json({ data: { customer } })
|
||||
}
|
||||
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
|
||||
export default getLoggedInCustomer
|
18
packages/kibocommerce/src/api/endpoints/customer/index.ts
Normal file
18
packages/kibocommerce/src/api/endpoints/customer/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import customerEndpoint from '@vercel/commerce/api/endpoints/customer'
|
||||
import type { CustomerSchema } from '../../../types/customer'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import getLoggedInCustomer from './customer'
|
||||
|
||||
export type CustomerAPI = GetAPISchema<KiboCommerceAPI, CustomerSchema>
|
||||
|
||||
export type CustomerEndpoint = CustomerAPI['endpoint']
|
||||
|
||||
export const handlers: CustomerEndpoint['handlers'] = { getLoggedInCustomer }
|
||||
|
||||
const customerApi = createEndpoint<CustomerAPI>({
|
||||
handler: customerEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default customerApi
|
20
packages/kibocommerce/src/api/endpoints/login/index.ts
Normal file
20
packages/kibocommerce/src/api/endpoints/login/index.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import loginEndpoint from '@vercel/commerce/api/endpoints/login'
|
||||
import type { LoginSchema } from '../../../types/login'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import login from './login'
|
||||
|
||||
export type LoginAPI = GetAPISchema<KiboCommerceAPI, LoginSchema>
|
||||
|
||||
export type LoginEndpoint = LoginAPI['endpoint']
|
||||
|
||||
export const handlers: LoginEndpoint['handlers'] = { login }
|
||||
|
||||
const loginApi = createEndpoint<LoginAPI>({
|
||||
handler: loginEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default loginApi;
|
||||
|
||||
|
66
packages/kibocommerce/src/api/endpoints/login/login.ts
Normal file
66
packages/kibocommerce/src/api/endpoints/login/login.ts
Normal file
@@ -0,0 +1,66 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { LoginEndpoint } from '.'
|
||||
import { loginMutation } from '../../mutations/login-mutation'
|
||||
import { prepareSetCookie } from '../../../lib/prepare-set-cookie';
|
||||
import { setCookies } from '../../../lib/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;
|
||||
try {
|
||||
|
||||
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 authCookie = prepareSetCookie(
|
||||
config.customerCookie,
|
||||
JSON.stringify(token),
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate }: {},
|
||||
)
|
||||
setCookies(res, [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 error
|
||||
}
|
||||
|
||||
res.status(200).json({ data: response })
|
||||
}
|
||||
|
||||
export default login
|
18
packages/kibocommerce/src/api/endpoints/logout/index.ts
Normal file
18
packages/kibocommerce/src/api/endpoints/logout/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import logoutEndpoint from '@vercel/commerce/api/endpoints/logout'
|
||||
import type { LogoutSchema } from '../../../types/logout'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import logout from './logout'
|
||||
|
||||
export type LogoutAPI = GetAPISchema<KiboCommerceAPI, LogoutSchema>
|
||||
|
||||
export type LogoutEndpoint = LogoutAPI['endpoint']
|
||||
|
||||
export const handlers: LogoutEndpoint['handlers'] = { logout }
|
||||
|
||||
const logoutApi = createEndpoint<LogoutAPI>({
|
||||
handler: logoutEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default logoutApi
|
22
packages/kibocommerce/src/api/endpoints/logout/logout.ts
Normal file
22
packages/kibocommerce/src/api/endpoints/logout/logout.ts
Normal file
@@ -0,0 +1,22 @@
|
||||
import type { LogoutEndpoint } from '.'
|
||||
import {prepareSetCookie} from '../../../lib/prepare-set-cookie';
|
||||
import {setCookies} from '../../../lib/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])
|
||||
|
||||
// Only allow redirects to a relative URL
|
||||
if (redirectTo?.startsWith('/')) {
|
||||
res.redirect(redirectTo)
|
||||
} else {
|
||||
res.status(200).json({ data: null })
|
||||
}
|
||||
}
|
||||
|
||||
export default logout
|
18
packages/kibocommerce/src/api/endpoints/signup/index.ts
Normal file
18
packages/kibocommerce/src/api/endpoints/signup/index.ts
Normal file
@@ -0,0 +1,18 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import signupEndpoint from '@vercel/commerce/api/endpoints/signup'
|
||||
import type { SignupSchema } from '../../../types/signup'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import signup from './signup'
|
||||
|
||||
export type SignupAPI = GetAPISchema<KiboCommerceAPI, SignupSchema>
|
||||
|
||||
export type SignupEndpoint = SignupAPI['endpoint']
|
||||
|
||||
export const handlers: SignupEndpoint['handlers'] = { signup }
|
||||
|
||||
const singupApi = createEndpoint<SignupAPI>({
|
||||
handler: signupEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default singupApi
|
91
packages/kibocommerce/src/api/endpoints/signup/signup.ts
Normal file
91
packages/kibocommerce/src/api/endpoints/signup/signup.ts
Normal file
@@ -0,0 +1,91 @@
|
||||
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 { 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;
|
||||
try {
|
||||
|
||||
// Register user
|
||||
const registerUserVariables = {
|
||||
customerAccountInput: {
|
||||
emailAddress: email,
|
||||
firstName: firstName,
|
||||
lastName: lastName,
|
||||
acceptsMarketing: true,
|
||||
id: 0
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
response = await config.fetch(registerUserLoginMutation, { variables: registerUserLoginVairables})
|
||||
const { account: token } = response.data;
|
||||
|
||||
// Set Cookie
|
||||
const cookieExpirationDate = getCookieExpirationDate(config.customerCookieMaxAgeInDays)
|
||||
|
||||
const authCookie = prepareSetCookie(
|
||||
config.customerCookie,
|
||||
JSON.stringify(token),
|
||||
token.accessTokenExpiration ? { expires: cookieExpirationDate }: {},
|
||||
)
|
||||
|
||||
setCookies(res, [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 error
|
||||
}
|
||||
|
||||
res.status(200).json({ data: response })
|
||||
}
|
||||
|
||||
export default signup
|
124
packages/kibocommerce/src/api/endpoints/wishlist/add-item.ts
Normal file
124
packages/kibocommerce/src/api/endpoints/wishlist/add-item.ts
Normal file
@@ -0,0 +1,124 @@
|
||||
import getCustomerWishlist from '../../operations/get-customer-wishlist'
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import type { WishlistEndpoint } from '.'
|
||||
import { normalizeWishlistItem } from '../../../lib/normalize'
|
||||
import { getProductQuery } from '../../../api/queries/get-product-query'
|
||||
import addItemToWishlistMutation from '../../mutations/addItemToWishlist-mutation'
|
||||
import createWishlist from '../../mutations/create-wishlist-mutation'
|
||||
|
||||
// Return wishlist info
|
||||
const buildAddToWishlistVariables = ({
|
||||
productId,
|
||||
variantId,
|
||||
productResponse,
|
||||
wishlist
|
||||
}: {
|
||||
productId: string
|
||||
variantId: string
|
||||
productResponse: any
|
||||
wishlist: any
|
||||
}) => {
|
||||
const { product } = productResponse.data
|
||||
|
||||
const selectedOptions = product.variations?.find(
|
||||
(v: any) => v.productCode === variantId
|
||||
).options
|
||||
const quantity=1
|
||||
let options: any[] = []
|
||||
selectedOptions?.forEach((each: any) => {
|
||||
product?.options
|
||||
.filter((option: any) => {
|
||||
return option.attributeFQN == each.attributeFQN
|
||||
})
|
||||
.forEach((po: any) => {
|
||||
options.push({
|
||||
attributeFQN: po.attributeFQN,
|
||||
name: po.attributeDetail.name,
|
||||
value: po.values?.find((v: any) => v.value == each.value).value,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
return {
|
||||
wishlistId: wishlist?.id,
|
||||
wishlistItemInput: {
|
||||
quantity,
|
||||
product: {
|
||||
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;
|
||||
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
|
||||
|
||||
if (!customerId) {
|
||||
return res.status(400).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Invalid request' }],
|
||||
})
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
const productResponse = await config.fetch(getProductQuery, {
|
||||
variables: { productCode: item?.productId },
|
||||
})
|
||||
|
||||
const addItemToWishlistResponse = await config.fetch(
|
||||
addItemToWishlistMutation,
|
||||
{
|
||||
variables: buildAddToWishlistVariables({ ...item, productResponse, wishlist }),
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
if(addItemToWishlistResponse?.data?.createWishlistItem){
|
||||
const wishlistResponse= await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
}
|
||||
|
||||
result = { data: {...wishlist, items: wishlist?.items?.map((item:any) => normalizeWishlistItem(item, config))} }
|
||||
|
||||
res.status(200).json({ data: result?.data })
|
||||
}
|
||||
|
||||
export default addItem
|
@@ -0,0 +1,35 @@
|
||||
import type { WishlistEndpoint } from '.'
|
||||
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
|
||||
if (!customerId) {
|
||||
// If the customerToken is invalid, then this request is too
|
||||
return res.status(404).json({
|
||||
data: null,
|
||||
errors: [{ message: 'Wishlist 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))} }
|
||||
}
|
||||
|
||||
res.status(200).json({ data: result?.data ?? null })
|
||||
}
|
||||
|
||||
export default getWishlist
|
23
packages/kibocommerce/src/api/endpoints/wishlist/index.ts
Normal file
23
packages/kibocommerce/src/api/endpoints/wishlist/index.ts
Normal file
@@ -0,0 +1,23 @@
|
||||
import { GetAPISchema, createEndpoint } from '@vercel/commerce/api'
|
||||
import wishlistEndpoint from '@vercel/commerce/api/endpoints/wishlist'
|
||||
import type { KiboCommerceAPI } from '../..'
|
||||
import getWishlist from './get-wishlist'
|
||||
import addItem from './add-item'
|
||||
import removeItem from './remove-item'
|
||||
|
||||
export type WishlistAPI = GetAPISchema<KiboCommerceAPI, any>
|
||||
|
||||
export type WishlistEndpoint = WishlistAPI['endpoint']
|
||||
|
||||
export const handlers: WishlistEndpoint['handlers'] = {
|
||||
getWishlist,
|
||||
addItem,
|
||||
removeItem,
|
||||
}
|
||||
|
||||
const wishlistApi = createEndpoint<WishlistAPI>({
|
||||
handler: wishlistEndpoint,
|
||||
handlers,
|
||||
})
|
||||
|
||||
export default wishlistApi
|
@@ -0,0 +1,60 @@
|
||||
import getCustomerId from '../../utils/get-customer-id'
|
||||
import type { WishlistEndpoint } from '.'
|
||||
import { normalizeWishlistItem } from '../../../lib/normalize'
|
||||
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;
|
||||
let result: { data?: any } = {}
|
||||
let wishlist: any
|
||||
|
||||
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' }],
|
||||
})
|
||||
}
|
||||
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
|
||||
},
|
||||
},
|
||||
{ headers: { 'x-vol-user-claims': accessToken } }
|
||||
)
|
||||
|
||||
if(removeItemFromWishlistResponse?.data?.deleteWishlistItem){
|
||||
const wishlistResponse= await commerce.getCustomerWishlist({
|
||||
variables: { customerId, wishlistName },
|
||||
config,
|
||||
})
|
||||
wishlist= wishlistResponse?.wishlist
|
||||
}
|
||||
result = { data: {...wishlist, items: wishlist?.items?.map((item:any) => normalizeWishlistItem(item, config))} }
|
||||
res.status(200).json({ data: result?.data })
|
||||
}
|
||||
|
||||
export default removeItem
|
11
packages/kibocommerce/src/api/fragments/cartItemDetails.ts
Normal file
11
packages/kibocommerce/src/api/fragments/cartItemDetails.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
import { productDetails } from '../fragments/productDetails'
|
||||
export const cartItemDetails = /*GraphQL*/`
|
||||
fragment cartItemDetails on CartItem {
|
||||
id
|
||||
product {
|
||||
...productDetails
|
||||
}
|
||||
quantity
|
||||
}
|
||||
${productDetails}
|
||||
`;
|
11
packages/kibocommerce/src/api/fragments/category.ts
Normal file
11
packages/kibocommerce/src/api/fragments/category.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const CategoryInfo = /* GraphQL */`
|
||||
fragment categoryInfo on PrCategory {
|
||||
categoryId
|
||||
categoryCode
|
||||
isDisplayed
|
||||
content {
|
||||
name
|
||||
slug
|
||||
description
|
||||
}
|
||||
}`;
|
98
packages/kibocommerce/src/api/fragments/product.ts
Normal file
98
packages/kibocommerce/src/api/fragments/product.ts
Normal file
@@ -0,0 +1,98 @@
|
||||
export const productPrices = /* GraphQL */`
|
||||
fragment productPrices on Product {
|
||||
price {
|
||||
price
|
||||
salePrice
|
||||
}
|
||||
priceRange {
|
||||
lower { price, salePrice}
|
||||
upper { price, salePrice }
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const productAttributes = /* GraphQL */`
|
||||
fragment productAttributes on Product {
|
||||
properties {
|
||||
attributeFQN
|
||||
attributeDetail {
|
||||
name
|
||||
}
|
||||
isHidden
|
||||
values {
|
||||
value
|
||||
stringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const productContent = /* GraphQL */`
|
||||
fragment productContent on Product {
|
||||
content {
|
||||
productFullDescription
|
||||
productShortDescription
|
||||
seoFriendlyUrl
|
||||
productName
|
||||
productImages {
|
||||
imageUrl
|
||||
imageLabel
|
||||
mediaType
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const productOptions = /* GraphQL */`
|
||||
fragment productOptions on Product {
|
||||
options {
|
||||
attributeFQN
|
||||
attributeDetail {
|
||||
name
|
||||
}
|
||||
isProductImageGroupSelector
|
||||
isRequired
|
||||
isMultiValue
|
||||
values {
|
||||
value
|
||||
isSelected
|
||||
deltaPrice
|
||||
stringValue
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
export const productInfo = /* GraphQL */`
|
||||
fragment productInfo on Product {
|
||||
productCode
|
||||
productUsage
|
||||
|
||||
purchasableState {
|
||||
isPurchasable
|
||||
}
|
||||
|
||||
variations {
|
||||
productCode,
|
||||
options {
|
||||
__typename
|
||||
attributeFQN
|
||||
value
|
||||
}
|
||||
}
|
||||
|
||||
categories {
|
||||
categoryCode
|
||||
categoryId
|
||||
content {
|
||||
name
|
||||
slug
|
||||
}
|
||||
}
|
||||
|
||||
...productPrices
|
||||
...productAttributes
|
||||
...productContent
|
||||
...productOptions
|
||||
}
|
||||
${productPrices}
|
||||
${productAttributes}
|
||||
${productContent}
|
||||
${productOptions}
|
||||
`;
|
30
packages/kibocommerce/src/api/fragments/productDetails.ts
Normal file
30
packages/kibocommerce/src/api/fragments/productDetails.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
export const productDetails = /* GraphQL */ `
|
||||
fragment productDetails on CrProduct {
|
||||
productCode
|
||||
name
|
||||
description
|
||||
imageUrl
|
||||
imageAlternateText
|
||||
sku
|
||||
variationProductCode
|
||||
price {
|
||||
price
|
||||
salePrice
|
||||
}
|
||||
options {
|
||||
attributeFQN
|
||||
name
|
||||
value
|
||||
}
|
||||
properties {
|
||||
attributeFQN
|
||||
name
|
||||
values {
|
||||
value
|
||||
}
|
||||
}
|
||||
categories {
|
||||
id
|
||||
}
|
||||
}
|
||||
`
|
32
packages/kibocommerce/src/api/fragments/search.ts
Normal file
32
packages/kibocommerce/src/api/fragments/search.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { productInfo } from './product';
|
||||
|
||||
export const searchFacets = /* GraphQL */`
|
||||
fragment searchFacets on Facet {
|
||||
label
|
||||
field
|
||||
values {
|
||||
label
|
||||
value
|
||||
isApplied
|
||||
filterValue
|
||||
isDisplayed
|
||||
count
|
||||
}
|
||||
}`;
|
||||
|
||||
export const searchResults = /* GraphQL */`
|
||||
fragment searchResults on ProductSearchResult {
|
||||
totalCount
|
||||
pageSize
|
||||
pageCount
|
||||
startIndex
|
||||
items {
|
||||
...productInfo
|
||||
}
|
||||
facets {
|
||||
...searchFacets
|
||||
}
|
||||
}
|
||||
${searchFacets}
|
||||
${productInfo}
|
||||
`;
|
64
packages/kibocommerce/src/api/index.ts
Normal file
64
packages/kibocommerce/src/api/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { CommerceAPI, CommerceAPIConfig } from '@vercel/commerce/api'
|
||||
import { getCommerceApi as commerceApi } from '@vercel/commerce/api'
|
||||
import createFetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
|
||||
import getAllPages from './operations/get-all-pages'
|
||||
import getPage from './operations/get-page'
|
||||
import getSiteInfo from './operations/get-site-info'
|
||||
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,
|
||||
authUrl?: string
|
||||
}
|
||||
|
||||
const config: KiboCommerceConfig = {
|
||||
commerceUrl: process.env.KIBO_API_URL || '',
|
||||
apiToken: process.env.KIBO_API_TOKEN || '',
|
||||
cartCookie: process.env.KIBO_CART_COOKIE || '',
|
||||
customerCookie: process.env.KIBO_CUSTOMER_COOKIE || '',
|
||||
cartCookieMaxAge: 2592000,
|
||||
documentListName: 'siteSnippets@mozu',
|
||||
fetch: createFetchGraphqlApi(() => getCommerceApi().getConfig()),
|
||||
authUrl: process.env.KIBO_AUTH_URL || '',
|
||||
// REST API
|
||||
apiHost: process.env.KIBO_API_HOST || '',
|
||||
clientId: process.env.KIBO_CLIENT_ID || '',
|
||||
sharedSecret: process.env.KIBO_SHARED_SECRET || '',
|
||||
customerCookieMaxAgeInDays: 30,
|
||||
currencyCode: 'USD',
|
||||
defaultWishlistName: 'My Wishlist'
|
||||
}
|
||||
|
||||
const operations = {
|
||||
getAllPages,
|
||||
getPage,
|
||||
getSiteInfo,
|
||||
getCustomerWishlist,
|
||||
getAllProductPaths,
|
||||
getAllProducts,
|
||||
getProduct,
|
||||
}
|
||||
|
||||
export const provider = { config, operations }
|
||||
|
||||
export type KiboCommerceProvider = typeof provider
|
||||
export type KiboCommerceAPI<
|
||||
P extends KiboCommerceProvider = KiboCommerceProvider
|
||||
> = CommerceAPI<P | any>
|
||||
|
||||
export function getCommerceApi<P extends KiboCommerceProvider>(
|
||||
customProvider: P = provider as any
|
||||
): KiboCommerceAPI<P> {
|
||||
return commerceApi(customProvider as any)
|
||||
}
|
@@ -0,0 +1,21 @@
|
||||
import {productDetails} from '../fragments/productDetails'
|
||||
const addItemToWishlistMutation = /* GraphQL */`
|
||||
mutation createWishlistItem(
|
||||
$wishlistId: String!
|
||||
$wishlistItemInput: WishlistItemInput
|
||||
) {
|
||||
createWishlistItem(
|
||||
wishlistId: $wishlistId
|
||||
wishlistItemInput: $wishlistItemInput
|
||||
) {
|
||||
id
|
||||
quantity
|
||||
product {
|
||||
...productDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
${productDetails}
|
||||
`;
|
||||
|
||||
export default addItemToWishlistMutation;
|
@@ -0,0 +1,12 @@
|
||||
import { cartItemDetails } from './../fragments/cartItemDetails'
|
||||
|
||||
const addToCurrentCartMutation = /*GraphQL*/ `
|
||||
${cartItemDetails}
|
||||
|
||||
mutation addToCart($productToAdd:CartItemInput!){
|
||||
addItemToCurrentCart(cartItemInput: $productToAdd) {
|
||||
...cartItemDetails
|
||||
}
|
||||
}`
|
||||
|
||||
export default addToCurrentCartMutation
|
@@ -0,0 +1,11 @@
|
||||
const createWishlist = /*GraphQL*/`
|
||||
mutation createWishlist($wishlistInput:WishlistInput!) {
|
||||
createWishlist(wishlistInput:$wishlistInput){
|
||||
id
|
||||
name
|
||||
customerAccountId
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export default createWishlist;
|
20
packages/kibocommerce/src/api/mutations/login-mutation.ts
Normal file
20
packages/kibocommerce/src/api/mutations/login-mutation.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
|
||||
export const loginMutation = /* GraphQL */`
|
||||
mutation login($loginInput:CustomerUserAuthInfoInput!) {
|
||||
account:createCustomerAuthTicket(customerUserAuthInfoInput:$loginInput) {
|
||||
accessToken
|
||||
userId
|
||||
refreshToken
|
||||
refreshTokenExpiration
|
||||
accessTokenExpiration
|
||||
customerAccount {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
emailAddress
|
||||
userName
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
@@ -0,0 +1,9 @@
|
||||
/*
|
||||
* Delete cart based on current user session
|
||||
*/
|
||||
const removeItemFromCartMutation = /*GraphQL*/`
|
||||
mutation deleteCartItem($id: String!) {
|
||||
deleteCurrentCartItem(cartItemId:$id)
|
||||
}`;
|
||||
|
||||
export default removeItemFromCartMutation;
|
@@ -0,0 +1,8 @@
|
||||
const removeItemFromWishlistMutation = /* GraphQL */`
|
||||
mutation deletewishlistitem($wishlistId: String!, $wishlistItemId: String!) {
|
||||
deleteWishlistItem(wishlistId: $wishlistId, wishlistItemId:$wishlistItemId)
|
||||
}
|
||||
`;
|
||||
|
||||
export default removeItemFromWishlistMutation;
|
||||
|
41
packages/kibocommerce/src/api/mutations/signup-mutation.ts
Normal file
41
packages/kibocommerce/src/api/mutations/signup-mutation.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
|
||||
const registerUserMutation = /* GraphQL */`
|
||||
mutation registerUser($customerAccountInput: CustomerAccountInput!) {
|
||||
account:createCustomerAccount(customerAccountInput:$customerAccountInput) {
|
||||
emailAddress
|
||||
userName
|
||||
firstName
|
||||
lastName
|
||||
localeCode
|
||||
userId
|
||||
id
|
||||
isAnonymous
|
||||
attributes {
|
||||
values
|
||||
fullyQualifiedName
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
const registerUserLoginMutation = /* GraphQL */`
|
||||
mutation registerUserLogin($accountId: Int!, $customerLoginInfoInput: CustomerLoginInfoInput!) {
|
||||
account:createCustomerAccountLogin(accountId:$accountId, customerLoginInfoInput:$customerLoginInfoInput) {
|
||||
accessToken
|
||||
accessTokenExpiration
|
||||
refreshToken
|
||||
refreshTokenExpiration
|
||||
userId
|
||||
customerAccount {
|
||||
id
|
||||
emailAddress
|
||||
firstName
|
||||
userName
|
||||
}
|
||||
}
|
||||
}`;
|
||||
|
||||
export {
|
||||
registerUserMutation,
|
||||
registerUserLoginMutation
|
||||
};
|
||||
|
@@ -0,0 +1,9 @@
|
||||
const updateCartItemQuantityMutation = /*GraphQL*/`
|
||||
mutation updateCartItemQuantity($itemId:String!, $quantity: Int!){
|
||||
updateCurrentCartItemQuantity(cartItemId:$itemId, quantity:$quantity){
|
||||
id
|
||||
quantity
|
||||
}
|
||||
}`;
|
||||
|
||||
export default updateCartItemQuantityMutation;
|
38
packages/kibocommerce/src/api/operations/get-all-pages.ts
Normal file
38
packages/kibocommerce/src/api/operations/get-all-pages.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import type { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import { getAllPagesQuery } from '../queries/get-all-pages-query'
|
||||
import { GetPagesQueryParams } from "../../types/page";
|
||||
import { normalizePage } from '../../lib/normalize'
|
||||
|
||||
export type GetAllPagesResult<
|
||||
T extends { pages: any[] } = { pages: any[] }
|
||||
> = T
|
||||
|
||||
export default function getAllPagesOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
|
||||
async function getAllPages({
|
||||
query = getAllPagesQuery,
|
||||
config,
|
||||
variables,
|
||||
}: {
|
||||
url?: string
|
||||
config?: Partial<KiboCommerceConfig>
|
||||
variables?: GetPagesQueryParams
|
||||
preview?: boolean
|
||||
query?: string
|
||||
} = {}): Promise<GetAllPagesResult> {
|
||||
const cfg = commerce.getConfig(config)
|
||||
variables = {
|
||||
documentListName: cfg.documentListName
|
||||
}
|
||||
const { data } = await cfg.fetch(query, { variables });
|
||||
|
||||
const pages = data.documentListDocuments.items.map(normalizePage);
|
||||
|
||||
return { pages }
|
||||
}
|
||||
|
||||
return getAllPages
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
import { KiboCommerceConfig } from '../index'
|
||||
import { getAllProductsQuery } from '../queries/get-all-products-query';
|
||||
import { normalizeProduct } from '../../lib/normalize'
|
||||
|
||||
export type GetAllProductPathsResult = {
|
||||
products: Array<{ path: string }>
|
||||
}
|
||||
|
||||
export default function getAllProductPathsOperation({commerce,}: any) {
|
||||
async function getAllProductPaths({ config }: {config?: KiboCommerceConfig } = {}): Promise<GetAllProductPathsResult> {
|
||||
|
||||
const cfg = commerce.getConfig(config)
|
||||
|
||||
const productVariables = {startIndex: 0, pageSize: 100};
|
||||
const { data } = await cfg.fetch(getAllProductsQuery, { variables: productVariables });
|
||||
|
||||
const normalizedProducts = data.products.items ? data.products.items.map( (item:any) => normalizeProduct(item, cfg)) : [];
|
||||
const products = normalizedProducts.map((product: any) => ({ path: product.path }))
|
||||
|
||||
return Promise.resolve({
|
||||
products: products
|
||||
})
|
||||
}
|
||||
|
||||
return getAllProductPaths
|
||||
}
|
32
packages/kibocommerce/src/api/operations/get-all-products.ts
Normal file
32
packages/kibocommerce/src/api/operations/get-all-products.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { GetAllProductsOperation } from '@vercel/commerce/types/product'
|
||||
import type { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import { getAllProductsQuery } from '../queries/get-all-products-query';
|
||||
import { normalizeProduct } from '../../lib/normalize'
|
||||
|
||||
export default function getAllProductsOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
async function getAllProducts<T extends GetAllProductsOperation>({
|
||||
query = getAllProductsQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: Partial<KiboCommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<{ products: Product[] | any[] }> {
|
||||
|
||||
const cfg = commerce.getConfig(config)
|
||||
const { data } = await cfg.fetch(query);
|
||||
|
||||
let normalizedProducts = data.products.items ? data.products.items.map( (item:any) => normalizeProduct(item, cfg)) : [];
|
||||
|
||||
return {
|
||||
products: normalizedProducts,
|
||||
}
|
||||
}
|
||||
return getAllProducts
|
||||
}
|
@@ -0,0 +1,57 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@vercel/commerce/api/operations'
|
||||
import type {
|
||||
GetCustomerWishlistOperation,
|
||||
Wishlist,
|
||||
} from '@vercel/commerce/types/wishlist'
|
||||
// import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||
import { KiboCommerceConfig } from '..'
|
||||
// import getAllProducts, { ProductEdge } from './get-all-products'
|
||||
import {getCustomerWishlistQuery} from '../queries/get-customer-wishlist-query'
|
||||
|
||||
export default function getCustomerWishlistOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
async function getCustomerWishlist<
|
||||
T extends GetCustomerWishlistOperation
|
||||
>(opts: {
|
||||
variables: T['variables']
|
||||
config?: KiboCommerceConfig
|
||||
includeProducts?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: KiboCommerceConfig
|
||||
includeProducts?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>({
|
||||
config,
|
||||
variables,
|
||||
includeProducts,
|
||||
}: {
|
||||
url?: string
|
||||
variables: T['variables']
|
||||
config?: KiboCommerceConfig
|
||||
includeProducts?: boolean
|
||||
}): Promise<T['data']> {
|
||||
let customerWishlist ={}
|
||||
try {
|
||||
|
||||
config = commerce.getConfig(config)
|
||||
const result= await config?.fetch(getCustomerWishlistQuery,{variables})
|
||||
customerWishlist= result?.data?.customerWishlist;
|
||||
} catch(e) {
|
||||
customerWishlist= {}
|
||||
}
|
||||
|
||||
return { wishlist: customerWishlist as any }
|
||||
}
|
||||
|
||||
return getCustomerWishlist
|
||||
}
|
40
packages/kibocommerce/src/api/operations/get-page.ts
Normal file
40
packages/kibocommerce/src/api/operations/get-page.ts
Normal file
@@ -0,0 +1,40 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
} from '@vercel/commerce/api/operations'
|
||||
import type { KiboCommerceConfig, KiboCommerceProvider } from '..'
|
||||
import { normalizePage } from '../../lib/normalize'
|
||||
import { getPageQuery } from '../queries/get-page-query'
|
||||
import type { Page, GetPageQueryParams } from "../../types/page";
|
||||
import type { Document } from '../../../schema'
|
||||
|
||||
export default function getPageOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
async function getPage<T extends Page>({
|
||||
url,
|
||||
variables,
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: GetPageQueryParams
|
||||
config?: Partial<KiboCommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<any> {
|
||||
// RecursivePartial forces the method to check for every prop in the data, which is
|
||||
// required in case there's a custom `url`
|
||||
const cfg = commerce.getConfig(config)
|
||||
const pageVariables = { documentListName: cfg.documentListName, filter: `id eq ${variables.id}` }
|
||||
|
||||
const { data } = await cfg.fetch(getPageQuery, { variables: pageVariables })
|
||||
|
||||
const firstPage = data.documentListDocuments.items?.[0];
|
||||
const page = firstPage as Document
|
||||
if (preview || page?.properties?.is_visible) {
|
||||
return { page: normalizePage(page as any) }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
return getPage
|
||||
}
|
35
packages/kibocommerce/src/api/operations/get-product.ts
Normal file
35
packages/kibocommerce/src/api/operations/get-product.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import { Product } from '@vercel/commerce/types/product'
|
||||
import { GetProductOperation } from '@vercel/commerce/types/product'
|
||||
import type { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import { getProductQuery } from '../queries/get-product-query'
|
||||
import { normalizeProduct } from '../../lib/normalize'
|
||||
|
||||
export default function getProductOperation({
|
||||
commerce,
|
||||
}: OperationContext<any>) {
|
||||
|
||||
async function getProduct<T extends GetProductOperation>({
|
||||
query = getProductQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: Partial<KiboCommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<Product | {} | any> {
|
||||
const productVariables = { productCode: variables?.slug}
|
||||
|
||||
const cfg = commerce.getConfig(config)
|
||||
const { data } = await cfg.fetch(query, { variables: productVariables });
|
||||
|
||||
const normalizedProduct = normalizeProduct(data.product, cfg)
|
||||
|
||||
return {
|
||||
product: normalizedProduct
|
||||
}
|
||||
}
|
||||
|
||||
return getProduct
|
||||
}
|
35
packages/kibocommerce/src/api/operations/get-site-info.ts
Normal file
35
packages/kibocommerce/src/api/operations/get-site-info.ts
Normal file
@@ -0,0 +1,35 @@
|
||||
import { OperationContext } from '@vercel/commerce/api/operations'
|
||||
import { Category } from '@vercel/commerce/types/site'
|
||||
import { KiboCommerceConfig } from '../index'
|
||||
import {categoryTreeQuery} from '../queries/get-categories-tree-query'
|
||||
import { normalizeCategory } from '../../lib/normalize'
|
||||
|
||||
export type GetSiteInfoResult<
|
||||
T extends { categories: any[]; brands: any[] } = {
|
||||
categories: Category[]
|
||||
brands: any[]
|
||||
}
|
||||
> = T
|
||||
|
||||
export default function getSiteInfoOperation({commerce}: OperationContext<any>) {
|
||||
async function getSiteInfo({
|
||||
query= categoryTreeQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: any
|
||||
config?: Partial<KiboCommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<GetSiteInfoResult> {
|
||||
const cfg = commerce.getConfig(config)
|
||||
const { data } = await cfg.fetch(query);
|
||||
const categories= data.categories.items.map(normalizeCategory);
|
||||
return Promise.resolve({
|
||||
categories: categories ?? [],
|
||||
brands: [],
|
||||
})
|
||||
}
|
||||
|
||||
return getSiteInfo
|
||||
}
|
6
packages/kibocommerce/src/api/operations/index.ts
Normal file
6
packages/kibocommerce/src/api/operations/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export { default as getPage } from './get-page'
|
||||
export { default as getSiteInfo } from './get-site-info'
|
||||
export { default as getAllPages } from './get-all-pages'
|
||||
export { default as getProduct } from './get-product'
|
||||
export { default as getAllProducts } from './get-all-products'
|
||||
export { default as getAllProductPaths } from './get-all-product-paths'
|
11
packages/kibocommerce/src/api/queries/get-all-pages-query.ts
Normal file
11
packages/kibocommerce/src/api/queries/get-all-pages-query.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export const getAllPagesQuery = /* GraphQL */`
|
||||
query($documentListName: String!) {
|
||||
documentListDocuments(documentListName:$documentListName){
|
||||
items {
|
||||
id
|
||||
name
|
||||
listFQN
|
||||
properties
|
||||
}
|
||||
}
|
||||
}`;
|
@@ -0,0 +1,21 @@
|
||||
import { productInfo } from '../fragments/product';
|
||||
|
||||
export const getAllProductsQuery = /* GraphQL */`
|
||||
${productInfo}
|
||||
|
||||
query products(
|
||||
$filter: String
|
||||
$startIndex: Int
|
||||
$pageSize: Int
|
||||
) {
|
||||
products(
|
||||
filter: $filter
|
||||
startIndex: $startIndex
|
||||
pageSize: $pageSize
|
||||
) {
|
||||
items {
|
||||
...productInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,11 @@
|
||||
export const getAnonymousShopperTokenQuery = /* GraphQL */ `
|
||||
query {
|
||||
getAnonymousShopperToken {
|
||||
accessToken
|
||||
accessTokenExpiration
|
||||
refreshToken
|
||||
refreshTokenExpiration
|
||||
jwtAccessToken
|
||||
}
|
||||
}
|
||||
`
|
32
packages/kibocommerce/src/api/queries/get-cart-query.ts
Normal file
32
packages/kibocommerce/src/api/queries/get-cart-query.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { productDetails } from '../fragments/productDetails'
|
||||
export const getCartQuery = /* GraphQL */`
|
||||
query cart {
|
||||
currentCart {
|
||||
id
|
||||
userId
|
||||
orderDiscounts {
|
||||
impact
|
||||
discount {
|
||||
id
|
||||
name
|
||||
}
|
||||
couponCode
|
||||
}
|
||||
subtotal
|
||||
shippingTotal
|
||||
total
|
||||
items {
|
||||
id
|
||||
subtotal
|
||||
unitPrice{
|
||||
extendedAmount
|
||||
}
|
||||
product {
|
||||
...productDetails
|
||||
}
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
${productDetails}
|
||||
`
|
@@ -0,0 +1,29 @@
|
||||
import { CategoryInfo } from '../fragments/category'
|
||||
|
||||
export const categoryTreeQuery = /* GraphQL */`
|
||||
query GetCategoryTree {
|
||||
categories: categoriesTree {
|
||||
items {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
childrenCategories {
|
||||
...categoryInfo
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${CategoryInfo}`;
|
@@ -0,0 +1,12 @@
|
||||
export const getCustomerAccountQuery = /* GraphQL */`
|
||||
query getUser {
|
||||
customerAccount:getCurrentAccount {
|
||||
id
|
||||
firstName
|
||||
lastName
|
||||
emailAddress
|
||||
userName
|
||||
isAnonymous
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,25 @@
|
||||
import {productDetails} from '../fragments/productDetails'
|
||||
export const getCustomerWishlistQuery= /* GraphQL */`
|
||||
query wishlist($customerId: Int!, $wishlistName: String!) {
|
||||
customerWishlist(customerAccountId:$customerId ,wishlistName: $wishlistName){
|
||||
customerAccountId
|
||||
name
|
||||
id
|
||||
userId
|
||||
items {
|
||||
id
|
||||
quantity
|
||||
total
|
||||
subtotal
|
||||
unitPrice{
|
||||
extendedAmount
|
||||
}
|
||||
quantity
|
||||
product {
|
||||
...productDetails
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
${productDetails}
|
||||
`
|
14
packages/kibocommerce/src/api/queries/get-page-query.ts
Normal file
14
packages/kibocommerce/src/api/queries/get-page-query.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
export const getPageQuery = /* GraphQL */`
|
||||
query($documentListName: String!, $filter: String!) {
|
||||
documentListDocuments(documentListName: $documentListName, filter: $filter){
|
||||
startIndex
|
||||
totalCount
|
||||
items {
|
||||
id
|
||||
name
|
||||
listFQN
|
||||
properties
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
15
packages/kibocommerce/src/api/queries/get-product-query.ts
Normal file
15
packages/kibocommerce/src/api/queries/get-product-query.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { productInfo } from '../fragments/product';
|
||||
|
||||
export const getProductQuery = /* GraphQL */`
|
||||
${productInfo}
|
||||
|
||||
query product(
|
||||
$productCode: String!
|
||||
) {
|
||||
product(
|
||||
productCode: $productCode
|
||||
) {
|
||||
...productInfo
|
||||
}
|
||||
}
|
||||
`
|
@@ -0,0 +1,20 @@
|
||||
import { searchResults } from '../fragments/search'
|
||||
|
||||
const query = /* GraphQL */`
|
||||
query ProductSearch($query:String, $startIndex:Int,
|
||||
$pageSize:Int, $sortBy:String, $filter:String,$facetTemplate:String,$facetValueFilter:String ) {
|
||||
products:productSearch (
|
||||
query:$query,
|
||||
startIndex: $startIndex,
|
||||
pageSize:$pageSize,
|
||||
sortBy: $sortBy,
|
||||
filter:$filter,
|
||||
facetTemplate:$facetTemplate,
|
||||
facetValueFilter:$facetValueFilter
|
||||
) {
|
||||
...searchResults
|
||||
}
|
||||
}
|
||||
${searchResults}
|
||||
`;
|
||||
export default query;
|
110
packages/kibocommerce/src/api/utils/api-auth-helper.ts
Normal file
110
packages/kibocommerce/src/api/utils/api-auth-helper.ts
Normal file
@@ -0,0 +1,110 @@
|
||||
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 } = {}
|
||||
|
||||
interface AppAuthTicket {
|
||||
access_token: string
|
||||
token_type: string
|
||||
expires_in: number
|
||||
expires_at: number
|
||||
refresh_token: string | null
|
||||
}
|
||||
|
||||
interface AuthTicketCache {
|
||||
getAuthTicket: () => Promise<AppAuthTicket | undefined>
|
||||
setAuthTicket: (kiboAuthTicket: AppAuthTicket) => void
|
||||
}
|
||||
|
||||
class RuntimeMemCache implements AuthTicketCache {
|
||||
constructor() {}
|
||||
async getAuthTicket() {
|
||||
return authCache.kiboAuthTicket
|
||||
}
|
||||
setAuthTicket(kiboAuthTicket: AppAuthTicket) {
|
||||
authCache.kiboAuthTicket = kiboAuthTicket
|
||||
}
|
||||
}
|
||||
|
||||
export class APIAuthenticationHelper {
|
||||
private _clientId: string
|
||||
private _sharedSecret: string
|
||||
private _authUrl: string
|
||||
private _authTicketCache!: AuthTicketCache
|
||||
|
||||
constructor(
|
||||
{ clientId = '', sharedSecret = '', authUrl = '' }: KiboCommerceConfig,
|
||||
authTicketCache?: AuthTicketCache
|
||||
) {
|
||||
this._clientId = clientId
|
||||
this._sharedSecret = sharedSecret
|
||||
this._authUrl = authUrl
|
||||
if(!authTicketCache) {
|
||||
this._authTicketCache = new RuntimeMemCache();
|
||||
}
|
||||
}
|
||||
private _buildFetchOptions(body: any = {}): FetchOptions {
|
||||
return {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify(body),
|
||||
}
|
||||
}
|
||||
private _calculateTicketExpiration(kiboAuthTicket: AppAuthTicket) {
|
||||
//calculate how many milliseconds until auth expires
|
||||
const millisecsUntilExpiration = kiboAuthTicket.expires_in * 1000
|
||||
kiboAuthTicket.expires_at = Date.now() + millisecsUntilExpiration
|
||||
|
||||
return kiboAuthTicket
|
||||
}
|
||||
public async authenticate(): Promise<AppAuthTicket> {
|
||||
// create oauth fetch options
|
||||
const options = this._buildFetchOptions({
|
||||
client_id: this._clientId,
|
||||
client_secret: this._sharedSecret,
|
||||
grant_type: 'client_credentials',
|
||||
})
|
||||
// perform authentication
|
||||
const authTicket = await fetch(
|
||||
`${this._authUrl}/api/platform/applications/authtickets/oauth`,
|
||||
options
|
||||
).then((response) => response.json())
|
||||
// set expiration time in ms on auth ticket
|
||||
this._calculateTicketExpiration(authTicket)
|
||||
// set authentication ticket on next server runtime object
|
||||
this._authTicketCache.setAuthTicket(authTicket)
|
||||
|
||||
return authTicket
|
||||
}
|
||||
public async refreshTicket(kiboAuthTicket: AppAuthTicket) {
|
||||
// create oauth refresh fetch options
|
||||
const options = this._buildFetchOptions({
|
||||
refreshToken: kiboAuthTicket?.refresh_token,
|
||||
})
|
||||
// perform auth ticket refresh
|
||||
const refreshedTicket = await fetch(
|
||||
`${this._authUrl}/api/platform/applications/authtickets/refresh-ticket`,
|
||||
options
|
||||
).then((response) => response.json())
|
||||
|
||||
return refreshedTicket
|
||||
}
|
||||
public async getAccessToken(): Promise<string> {
|
||||
// get current Kibo API auth ticket
|
||||
let authTicket = await this._authTicketCache.getAuthTicket()
|
||||
|
||||
// if no current ticket, perform auth
|
||||
// or if ticket expired, refresh auth
|
||||
if (!authTicket) {
|
||||
authTicket = await this.authenticate()
|
||||
} else if (authTicket.expires_at < Date.now()) {
|
||||
authTicket = await this.refreshTicket(authTicket)
|
||||
}
|
||||
|
||||
return authTicket!.access_token
|
||||
}
|
||||
}
|
61
packages/kibocommerce/src/api/utils/cookie-handler.ts
Normal file
61
packages/kibocommerce/src/api/utils/cookie-handler.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
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'
|
||||
|
||||
const parseCookie = (cookieValue?: any) => {
|
||||
return cookieValue
|
||||
? JSON.parse(Buffer.from(cookieValue, 'base64').toString('ascii'))
|
||||
: null
|
||||
}
|
||||
export default class CookieHandler {
|
||||
config: KiboCommerceConfig
|
||||
request: NextApiRequest
|
||||
response: any
|
||||
accessToken: any
|
||||
constructor(config: any, req: NextApiRequest, res: any) {
|
||||
this.config = config
|
||||
this.request = req
|
||||
this.response = res
|
||||
const encodedToken = req.cookies[config.customerCookie]
|
||||
const token = parseCookie(encodedToken)
|
||||
this.accessToken = token ? token.accessToken : null
|
||||
}
|
||||
|
||||
async getAnonymousToken() {
|
||||
const response: any = await getAnonymousShopperToken({
|
||||
config: this.config,
|
||||
})
|
||||
let anonymousAccessToken = response?.accessToken
|
||||
return {
|
||||
response,
|
||||
accessToken: anonymousAccessToken,
|
||||
}
|
||||
}
|
||||
isShopperCookieAnonymous() {
|
||||
const customerCookieKey = this.config.customerCookie
|
||||
const shopperCookie = this.request.cookies[customerCookieKey]
|
||||
const shopperSession = parseCookie(shopperCookie);
|
||||
const isAnonymous = shopperSession?.customerAccount ? false : true
|
||||
return isAnonymous
|
||||
}
|
||||
setAnonymousShopperCookie(anonymousShopperTokenResponse: any) {
|
||||
const cookieExpirationDate = getCookieExpirationDate(
|
||||
this.config.customerCookieMaxAgeInDays
|
||||
)
|
||||
|
||||
const authCookie = prepareSetCookie(
|
||||
this.config.customerCookie,
|
||||
JSON.stringify(anonymousShopperTokenResponse),
|
||||
anonymousShopperTokenResponse?.accessTokenExpiration
|
||||
? { expires: cookieExpirationDate }
|
||||
: {}
|
||||
)
|
||||
setCookies(this.response, [authCookie])
|
||||
}
|
||||
getAccessToken() {
|
||||
return this.accessToken
|
||||
}
|
||||
}
|
43
packages/kibocommerce/src/api/utils/fetch-graphql-api.ts
Normal file
43
packages/kibocommerce/src/api/utils/fetch-graphql-api.ts
Normal file
@@ -0,0 +1,43 @@
|
||||
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';
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
36
packages/kibocommerce/src/api/utils/fetch-local.ts
Normal file
36
packages/kibocommerce/src/api/utils/fetch-local.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
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 =
|
||||
(getConfig) =>
|
||||
async (query: string, { variables, preview } = {}, fetchOptions) => {
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl, {
|
||||
//const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({
|
||||
query,
|
||||
variables,
|
||||
}),
|
||||
})
|
||||
|
||||
const json = await res.json()
|
||||
if (json.errors) {
|
||||
throw new FetcherError({
|
||||
errors: json.errors ?? [{ message: 'Failed to fetch KiboCommerce API' }],
|
||||
status: res.status,
|
||||
})
|
||||
}
|
||||
|
||||
return { data: json.data, res }
|
||||
}
|
||||
|
||||
export default fetchGraphqlApi
|
3
packages/kibocommerce/src/api/utils/fetch.ts
Normal file
3
packages/kibocommerce/src/api/utils/fetch.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
import zeitFetch from '@vercel/fetch'
|
||||
|
||||
export default zeitFetch()
|
@@ -0,0 +1,13 @@
|
||||
import type { KiboCommerceConfig } from '../'
|
||||
import { getAnonymousShopperTokenQuery } from '../queries/get-anonymous-shopper-token-query'
|
||||
|
||||
async function getAnonymousShopperToken({
|
||||
config,
|
||||
}: {
|
||||
config: KiboCommerceConfig
|
||||
}): Promise<string | undefined> {
|
||||
const { data } = await config.fetch(getAnonymousShopperTokenQuery)
|
||||
return data?.getAnonymousShopperToken
|
||||
}
|
||||
|
||||
export default getAnonymousShopperToken
|
26
packages/kibocommerce/src/api/utils/get-customer-id.ts
Normal file
26
packages/kibocommerce/src/api/utils/get-customer-id.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { KiboCommerceConfig } from '..'
|
||||
import { getCustomerAccountQuery } from '../queries/get-customer-account-query'
|
||||
|
||||
async function getCustomerId({
|
||||
customerToken,
|
||||
config,
|
||||
}: {
|
||||
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,
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
return data?.customerAccount?.id
|
||||
}
|
||||
|
||||
export default getCustomerId
|
3
packages/kibocommerce/src/auth/index.ts
Normal file
3
packages/kibocommerce/src/auth/index.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export { default as useLogin } from './use-login'
|
||||
export { default as useLogout } from './use-logout'
|
||||
export { default as useSignup } from './use-signup'
|
43
packages/kibocommerce/src/auth/use-login.tsx
Normal file
43
packages/kibocommerce/src/auth/use-login.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
|
||||
|
||||
import { useCallback } from 'react'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
import type { LoginHook } from '../types/login'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import useCart from '../cart/use-cart'
|
||||
export default useLogin as UseLogin<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LoginHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/login',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: { email, password }, options, fetch }) {
|
||||
if (!(email && password)) {
|
||||
throw new CommerceError({
|
||||
message: 'An email and password are required to login',
|
||||
})
|
||||
}
|
||||
|
||||
return fetch({
|
||||
...options,
|
||||
body: { email, password },
|
||||
})
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCustomer()
|
||||
const { mutate: mutateCart } = useCart()
|
||||
return useCallback(
|
||||
async function login(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate()
|
||||
await mutateCart()
|
||||
return data
|
||||
},
|
||||
[fetch, mutate, mutateCart]
|
||||
)
|
||||
},
|
||||
}
|
29
packages/kibocommerce/src/auth/use-logout.tsx
Normal file
29
packages/kibocommerce/src/auth/use-logout.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
|
||||
import type { LogoutHook } from '../types/logout'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
import useCart from '../cart/use-cart'
|
||||
|
||||
export default useLogout as UseLogout<typeof handler>
|
||||
|
||||
export const handler: MutationHook<LogoutHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/logout',
|
||||
method: 'GET',
|
||||
},
|
||||
useHook: ({ fetch }) => () => {
|
||||
const { mutate } = useCustomer()
|
||||
const { mutate: mutateCart } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function logout() {
|
||||
const data = await fetch()
|
||||
await mutate(null, false)
|
||||
await mutateCart(null, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate, mutateCart]
|
||||
)
|
||||
},
|
||||
}
|
46
packages/kibocommerce/src/auth/use-signup.tsx
Normal file
46
packages/kibocommerce/src/auth/use-signup.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup'
|
||||
import type { SignupHook } from '../types/signup'
|
||||
import useCustomer from '../customer/use-customer'
|
||||
|
||||
export default useSignup as UseSignup<typeof handler>
|
||||
|
||||
export const handler: MutationHook<SignupHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/signup',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({
|
||||
input: { firstName, lastName, email, password },
|
||||
options,
|
||||
fetch,
|
||||
}) {
|
||||
if (!(firstName && lastName && email && password)) {
|
||||
throw new CommerceError({
|
||||
message:
|
||||
'A first name, last name, email and password are required to signup',
|
||||
})
|
||||
}
|
||||
|
||||
return fetch({
|
||||
...options,
|
||||
body: { firstName, lastName, email, password },
|
||||
})
|
||||
},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() => {
|
||||
const { mutate } = useCustomer()
|
||||
|
||||
return useCallback(
|
||||
async function signup(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate()
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
4
packages/kibocommerce/src/cart/index.ts
Normal file
4
packages/kibocommerce/src/cart/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export { default as useCart } from './use-cart'
|
||||
export { default as useAddItem } from './use-add-item'
|
||||
export { default as useRemoveItem } from './use-remove-item'
|
||||
export { default as useUpdateItem } from './use-update-item'
|
44
packages/kibocommerce/src/cart/use-add-item.tsx
Normal file
44
packages/kibocommerce/src/cart/use-add-item.tsx
Normal file
@@ -0,0 +1,44 @@
|
||||
import { useCallback } from 'react'
|
||||
import type { MutationHook } from '@vercel/commerce/utils/types'
|
||||
import { CommerceError } from '@vercel/commerce/utils/errors'
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
|
||||
import type { AddItemHook } from '@vercel/commerce/types/cart'
|
||||
import useCart from './use-cart'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<AddItemHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
method: 'POST',
|
||||
},
|
||||
async fetcher({ input: item, options, fetch }) {
|
||||
if (
|
||||
item.quantity &&
|
||||
(!Number.isInteger(item.quantity) || item.quantity! < 1)
|
||||
) {
|
||||
throw new CommerceError({
|
||||
message: 'The item quantity has to be a valid integer greater than 0',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
...options,
|
||||
body: { item },
|
||||
})
|
||||
|
||||
return data
|
||||
},
|
||||
useHook: ({ fetch }) => () => {
|
||||
const { mutate } = useCart()
|
||||
|
||||
return useCallback(
|
||||
async function addItem(input) {
|
||||
const data = await fetch({ input })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
},
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
33
packages/kibocommerce/src/cart/use-cart.tsx
Normal file
33
packages/kibocommerce/src/cart/use-cart.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { useMemo } from 'react'
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
|
||||
|
||||
export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
method: 'GET',
|
||||
url: '/api/cart',
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
return await fetch({ ...options })
|
||||
},
|
||||
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
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}),
|
||||
[response]
|
||||
)
|
||||
},
|
||||
}
|
56
packages/kibocommerce/src/cart/use-remove-item.tsx
Normal file
56
packages/kibocommerce/src/cart/use-remove-item.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback } from 'react'
|
||||
import type {
|
||||
MutationHookContext,
|
||||
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 useCart from './use-cart'
|
||||
|
||||
export type RemoveItemFn<T = any> = T extends LineItem
|
||||
? (input?: RemoveItemActionInput<T>) => Promise<Cart | null | undefined>
|
||||
: (input: RemoveItemActionInput<T>) => Promise<Cart | null>
|
||||
|
||||
export type RemoveItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<RemoveItemHook['actionInput']>
|
||||
: RemoveItemHook['actionInput']
|
||||
|
||||
export default useRemoveItem as UseRemoveItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
method: 'DELETE',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId },
|
||||
options,
|
||||
fetch,
|
||||
}: 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
|
||||
|
||||
if (!itemId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({ input: { itemId } })
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}
|
||||
|
||||
return useCallback(removeItem as RemoveItemFn<T>, [fetch, mutate])
|
||||
},
|
||||
}
|
84
packages/kibocommerce/src/cart/use-update-item.tsx
Normal file
84
packages/kibocommerce/src/cart/use-update-item.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useCallback } from 'react'
|
||||
import debounce from 'lodash.debounce'
|
||||
import type {
|
||||
MutationHookContext,
|
||||
HookFetcherContext,
|
||||
} from '@vercel/commerce/utils/types'
|
||||
import { ValidationError } from '@vercel/commerce/utils/errors'
|
||||
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'
|
||||
|
||||
export type UpdateItemActionInput<T = any> = T extends LineItem
|
||||
? Partial<UpdateItemHook['actionInput']>
|
||||
: UpdateItemHook['actionInput']
|
||||
|
||||
export default useUpdateItem as UseUpdateItem<typeof handler>
|
||||
|
||||
export const handler = {
|
||||
fetchOptions: {
|
||||
url: '/api/cart',
|
||||
method: 'PUT',
|
||||
},
|
||||
async fetcher({
|
||||
input: { itemId, item },
|
||||
options,
|
||||
fetch,
|
||||
}: HookFetcherContext<UpdateItemHook>) {
|
||||
if (Number.isInteger(item.quantity)) {
|
||||
// Also allow the update hook to remove an item if the quantity is lower than 1
|
||||
if (item.quantity! < 1) {
|
||||
return removeItemHandler.fetcher({
|
||||
options: removeItemHandler.fetchOptions,
|
||||
input: { itemId },
|
||||
fetch,
|
||||
})
|
||||
}
|
||||
} else if (item.quantity) {
|
||||
throw new ValidationError({
|
||||
message: 'The item quantity has to be a valid integer',
|
||||
})
|
||||
}
|
||||
|
||||
return await fetch({
|
||||
...options,
|
||||
body: { itemId, item },
|
||||
})
|
||||
},
|
||||
useHook: ({ fetch }: MutationHookContext<UpdateItemHook>) => <
|
||||
T extends LineItem | undefined = undefined
|
||||
>(
|
||||
ctx: {
|
||||
item?: T
|
||||
wait?: number
|
||||
} = {}
|
||||
) => {
|
||||
const { item } = ctx
|
||||
const { mutate } = useCart() as any
|
||||
|
||||
return useCallback(
|
||||
debounce(async (input: UpdateItemActionInput<T>) => {
|
||||
const itemId = input.id ?? item?.id
|
||||
const productId = input.productId ?? item?.productId
|
||||
const variantId = input.productId ?? item?.variantId
|
||||
|
||||
if (!itemId || !productId || !variantId) {
|
||||
throw new ValidationError({
|
||||
message: 'Invalid input used for this operation',
|
||||
})
|
||||
}
|
||||
|
||||
const data = await fetch({
|
||||
input: {
|
||||
itemId,
|
||||
item: { productId, variantId, quantity: input.quantity },
|
||||
},
|
||||
})
|
||||
await mutate(data, false)
|
||||
return data
|
||||
}, ctx.wait ?? 500),
|
||||
[fetch, mutate]
|
||||
)
|
||||
},
|
||||
}
|
14
packages/kibocommerce/src/checkout/use-checkout.tsx
Normal file
14
packages/kibocommerce/src/checkout/use-checkout.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCheckout, { UseCheckout } from '@vercel/commerce/checkout/use-checkout'
|
||||
|
||||
export default useCheckout as UseCheckout<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
async (input) => ({}),
|
||||
}
|
9
packages/kibocommerce/src/commerce.config.json
Normal file
9
packages/kibocommerce/src/commerce.config.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"provider": "kibocommerce",
|
||||
"features": {
|
||||
"wishlist": true,
|
||||
"cart": true,
|
||||
"search": true,
|
||||
"customerAuth": true
|
||||
}
|
||||
}
|
15
packages/kibocommerce/src/customer/address/use-add-item.tsx
Normal file
15
packages/kibocommerce/src/customer/address/use-add-item.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/address/use-add-item'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() =>
|
||||
async () => ({}),
|
||||
}
|
15
packages/kibocommerce/src/customer/card/use-add-item.tsx
Normal file
15
packages/kibocommerce/src/customer/card/use-add-item.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import useAddItem, { UseAddItem } from '@vercel/commerce/customer/card/use-add-item'
|
||||
import { MutationHook } from '@vercel/commerce/utils/types'
|
||||
|
||||
export default useAddItem as UseAddItem<typeof handler>
|
||||
|
||||
export const handler: MutationHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
},
|
||||
async fetcher({ input, options, fetch }) {},
|
||||
useHook:
|
||||
({ fetch }) =>
|
||||
() =>
|
||||
async () => ({}),
|
||||
}
|
1
packages/kibocommerce/src/customer/index.ts
Normal file
1
packages/kibocommerce/src/customer/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default as useCustomer } from './use-customer'
|
24
packages/kibocommerce/src/customer/use-customer.tsx
Normal file
24
packages/kibocommerce/src/customer/use-customer.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useCustomer, { UseCustomer } from '@vercel/commerce/customer/use-customer'
|
||||
import type { CustomerHook } from '../types/customer'
|
||||
|
||||
export default useCustomer as UseCustomer<typeof handler>
|
||||
|
||||
export const handler: SWRHook<CustomerHook> = {
|
||||
fetchOptions: {
|
||||
url: '/api/customer',
|
||||
method: 'GET',
|
||||
},
|
||||
async fetcher({ options, fetch }) {
|
||||
const data = await fetch(options)
|
||||
return data?.customer ?? null
|
||||
},
|
||||
useHook: ({ useData }) => (input) => {
|
||||
return useData({
|
||||
swrOptions: {
|
||||
revalidateOnFocus: false,
|
||||
...input?.swrOptions,
|
||||
},
|
||||
})
|
||||
},
|
||||
}
|
41
packages/kibocommerce/src/fetcher.ts
Normal file
41
packages/kibocommerce/src/fetcher.ts
Normal file
@@ -0,0 +1,41 @@
|
||||
import { FetcherError } from '@vercel/commerce/utils/errors'
|
||||
import type { Fetcher } from '@vercel/commerce/utils/types'
|
||||
|
||||
async function getText(res: Response) {
|
||||
try {
|
||||
return (await res.text()) || res.statusText
|
||||
} catch (error) {
|
||||
return res.statusText
|
||||
}
|
||||
}
|
||||
|
||||
async function getError(res: Response) {
|
||||
if (res.headers.get('Content-Type')?.includes('application/json')) {
|
||||
const data = await res.json()
|
||||
return new FetcherError({ errors: data.errors, status: res.status })
|
||||
}
|
||||
return new FetcherError({ message: await getText(res), status: res.status })
|
||||
}
|
||||
|
||||
const fetcher: Fetcher = async ({
|
||||
url,
|
||||
method = 'GET',
|
||||
variables,
|
||||
body: bodyObj,
|
||||
}) => {
|
||||
const hasBody = Boolean(variables || bodyObj)
|
||||
const body = hasBody
|
||||
? JSON.stringify(variables ? { variables } : bodyObj)
|
||||
: undefined
|
||||
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
|
||||
const res = await fetch(url!, { method, body, headers })
|
||||
|
||||
if (res.ok) {
|
||||
const { data } = await res.json()
|
||||
return data
|
||||
}
|
||||
|
||||
throw await getError(res)
|
||||
}
|
||||
|
||||
export default fetcher
|
9
packages/kibocommerce/src/index.tsx
Normal file
9
packages/kibocommerce/src/index.tsx
Normal file
@@ -0,0 +1,9 @@
|
||||
import { getCommerceProvider, useCommerce as useCoreCommerce } from '@vercel/commerce'
|
||||
import { kiboCommerceProvider, KibocommerceProvider } from './provider'
|
||||
|
||||
export { kiboCommerceProvider }
|
||||
export type { KibocommerceProvider }
|
||||
|
||||
export const CommerceProvider = getCommerceProvider(kiboCommerceProvider)
|
||||
|
||||
export const useCommerce = () => useCoreCommerce()
|
@@ -0,0 +1,8 @@
|
||||
export function getCookieExpirationDate(maxAgeInDays: number){
|
||||
const today = new Date();
|
||||
const expirationDate = new Date();
|
||||
|
||||
const cookieExpirationDate = new Date ( expirationDate.setDate(today.getDate() + maxAgeInDays) )
|
||||
|
||||
return cookieExpirationDate;
|
||||
}
|
5
packages/kibocommerce/src/lib/get-slug.ts
Normal file
5
packages/kibocommerce/src/lib/get-slug.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
// Remove trailing and leading slash, usually included in nodes
|
||||
// returned by the BigCommerce API
|
||||
const getSlug = (path: string) => path.replace(/^\/|\/$/g, '')
|
||||
|
||||
export default getSlug
|
13
packages/kibocommerce/src/lib/immutability.ts
Normal file
13
packages/kibocommerce/src/lib/immutability.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import update, { Context } from 'immutability-helper'
|
||||
|
||||
const c = new Context()
|
||||
|
||||
c.extend('$auto', function (value, object) {
|
||||
return object ? c.update(object, value) : c.update({}, value)
|
||||
})
|
||||
|
||||
c.extend('$autoArray', function (value, object) {
|
||||
return object ? c.update(object, value) : c.update([], value)
|
||||
})
|
||||
|
||||
export default c.update
|
194
packages/kibocommerce/src/lib/normalize.ts
Normal file
194
packages/kibocommerce/src/lib/normalize.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
import update from './immutability'
|
||||
import getSlug from './get-slug'
|
||||
import type { PrCategory, CustomerAccountInput, Document } from '../../schema'
|
||||
import { Page } from '../types/page';
|
||||
import { Customer } from '../types/customer'
|
||||
|
||||
function normalizeProductOption(productOption: any) {
|
||||
const {
|
||||
node: { entityId, values: { edges = [] } = {}, ...rest },
|
||||
} = productOption
|
||||
|
||||
return {
|
||||
id: entityId,
|
||||
values: edges?.map(({ node }: any) => node),
|
||||
...rest,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeProduct(productNode: any, config: any): any {
|
||||
const product = {
|
||||
id: productNode.productCode,
|
||||
name: productNode.content.productName,
|
||||
vendor: '',
|
||||
path: `/${productNode.productCode}`,
|
||||
slug: productNode.productCode,
|
||||
price: {
|
||||
value: productNode?.price?.price,
|
||||
currencyCode: config.currencyCode,
|
||||
},
|
||||
descriptionHtml: productNode.content.productShortDescription,
|
||||
|
||||
images: productNode.content.productImages.map((p: any) => ({
|
||||
url: `http:${p.imageUrl}`,
|
||||
altText: p.imageLabel,
|
||||
})),
|
||||
|
||||
variants: productNode.variations?.map((v: any) => ({
|
||||
id: v.productCode,
|
||||
options: v.options.map((o: any) => ({
|
||||
['__typename']: 'MultipleChoiceOption',
|
||||
id: o.attributeFQN,
|
||||
displayName:
|
||||
o.attributeFQN.split('~')[1][0].toUpperCase() +
|
||||
o.attributeFQN.split('~')[1].slice(1).toLowerCase(),
|
||||
values: [{ label: o.value.toString() }],
|
||||
})),
|
||||
})) || [
|
||||
{
|
||||
id: '',
|
||||
},
|
||||
],
|
||||
|
||||
options:
|
||||
productNode.options?.map((o: any) => ({
|
||||
id: o.attributeFQN,
|
||||
displayName: o.attributeDetail.name,
|
||||
values: o.values.map((v: any) => ({
|
||||
label: v.value.toString(),
|
||||
hexColors: '',
|
||||
})),
|
||||
})) || [],
|
||||
}
|
||||
|
||||
return product
|
||||
}
|
||||
|
||||
export function normalizePage(page: Document): Page {
|
||||
return {
|
||||
id: String(page.id),
|
||||
name: String(page.name),
|
||||
url: page.properties.url,
|
||||
body: page.properties.body,
|
||||
is_visible: page.properties.is_visible,
|
||||
sort_order: page.properties.sort_order
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCart(data: any): any {
|
||||
return {
|
||||
id: data.id,
|
||||
customerId: data.userId,
|
||||
email: data?.email,
|
||||
createdAt: data?.created_time,
|
||||
currency: {
|
||||
code: 'USD',
|
||||
},
|
||||
taxesIncluded: true,
|
||||
lineItems: data.items.map(normalizeLineItem),
|
||||
lineItemsSubtotalPrice: data?.items.reduce(
|
||||
(acc: number, obj: { subtotal: number }) => acc + obj.subtotal,
|
||||
0
|
||||
),
|
||||
subtotalPrice: data?.subtotal,
|
||||
totalPrice: data?.total,
|
||||
discounts: data.orderDiscounts?.map((discount: any) => ({
|
||||
value: discount.impact,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCustomer(customer: CustomerAccountInput): Customer {
|
||||
return {
|
||||
id: customer.id,
|
||||
firstName: customer.firstName,
|
||||
lastName: customer.lastName,
|
||||
email: customer.emailAddress,
|
||||
userName: customer.userName,
|
||||
isAnonymous: customer.isAnonymous
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeLineItem(item: any): any {
|
||||
return {
|
||||
id: item.id,
|
||||
variantId: item.product.variationProductCode,
|
||||
productId: String(item.product.productCode),
|
||||
name: item.product.name,
|
||||
quantity: item.quantity,
|
||||
variant: {
|
||||
id: item.product.variationProductCode,
|
||||
sku: item.product?.sku,
|
||||
name: item.product.name,
|
||||
image: {
|
||||
url: item?.product?.imageUrl,
|
||||
},
|
||||
requiresShipping: item?.is_require_shipping,
|
||||
price: item?.unitPrice.extendedAmount,
|
||||
listPrice: 0,
|
||||
},
|
||||
options: item.product.options,
|
||||
path: `${item.product.productCode}`,
|
||||
discounts: item?.discounts?.map((discount: any) => ({
|
||||
value: discount.discounted_amount,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCategory(category: PrCategory): any {
|
||||
return {
|
||||
id: category?.categoryCode,
|
||||
name: category?.content?.name,
|
||||
slug: category?.content?.slug,
|
||||
path: `/${category?.content?.slug}`,
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeWishlistItem(
|
||||
item: any,
|
||||
config: any,
|
||||
includeProducts=false
|
||||
): any {
|
||||
if (includeProducts) {
|
||||
return {
|
||||
id: item.id,
|
||||
product: getProuducts(item, config),
|
||||
}
|
||||
} else {
|
||||
return getProuducts(item, config)
|
||||
}
|
||||
}
|
||||
|
||||
function getProuducts(item: any, config: any): any {
|
||||
return {
|
||||
variant_id: item.product.variationProductCode || '',
|
||||
id: String(item.product.productCode),
|
||||
product_id: String(item.product.productCode),
|
||||
name: item.product.name,
|
||||
quantity: item.quantity,
|
||||
images: [
|
||||
{
|
||||
url: `http:${item.product.imageUrl}`,
|
||||
alt: item.product.imageAlternateText,
|
||||
},
|
||||
],
|
||||
price: {
|
||||
value: item.product.price.price,
|
||||
retailPrice: item.product.price.retailPrice || 0,
|
||||
currencyCode: config.currencyCode,
|
||||
},
|
||||
variants: [
|
||||
{
|
||||
id: item.product.variationProductCode || '',
|
||||
sku: item.product?.sku,
|
||||
name: item.product.name,
|
||||
image: {
|
||||
url: item?.product.imageUrl,
|
||||
},
|
||||
},
|
||||
],
|
||||
options: item.product.options,
|
||||
path: `/${item.product.productCode}`,
|
||||
description: item.product.description,
|
||||
}
|
||||
}
|
15
packages/kibocommerce/src/lib/prepare-set-cookie.ts
Normal file
15
packages/kibocommerce/src/lib/prepare-set-cookie.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
export function prepareSetCookie(name: string, value: string, options: any = {}): string {
|
||||
const encodedValue = Buffer.from(value).toString('base64')
|
||||
const cookieValue = [`${name}=${encodedValue}`];
|
||||
|
||||
if (options.maxAge) {
|
||||
cookieValue.push(`Max-Age=${options.maxAge}`);
|
||||
}
|
||||
|
||||
if (options.expires && !options.maxAge) {
|
||||
cookieValue.push(`Expires=${options.expires.toUTCString()}`);
|
||||
}
|
||||
|
||||
const cookie = cookieValue.join('; ')
|
||||
return cookie
|
||||
}
|
55
packages/kibocommerce/src/lib/product-search-vars.ts
Normal file
55
packages/kibocommerce/src/lib/product-search-vars.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
function getFacetValueFilter(categoryCode: string, filters = []) {
|
||||
let facetValueFilter = '';
|
||||
if (categoryCode) {
|
||||
facetValueFilter = `categoryCode:${categoryCode},`;
|
||||
}
|
||||
return facetValueFilter + filters.join(',');
|
||||
}
|
||||
|
||||
export const buildProductSearchVars = ({
|
||||
categoryCode = '',
|
||||
pageSize = 5,
|
||||
filters = {} as any,
|
||||
startIndex = 0,
|
||||
sort = '',
|
||||
search = '',
|
||||
}) => {
|
||||
let facetTemplate = '';
|
||||
let filter = '';
|
||||
let sortBy;
|
||||
if (categoryCode) {
|
||||
facetTemplate = `categoryCode:${categoryCode}`;
|
||||
filter = `categoryCode req ${categoryCode}`;
|
||||
}
|
||||
const facetFilterList = Object.keys(filters).filter(k => filters[k].length).reduce((accum, k): any => {
|
||||
return [...accum, ...filters[k].map((facetValue: any) => `Tenant~${k}:${facetValue}`)];
|
||||
}, []);
|
||||
|
||||
const facetValueFilter = getFacetValueFilter(categoryCode, facetFilterList);
|
||||
|
||||
switch(sort) {
|
||||
case 'latest-desc':
|
||||
sortBy= 'createDate desc';
|
||||
break;
|
||||
case 'price-asc':
|
||||
sortBy= 'price asc';
|
||||
break;
|
||||
case 'price-desc':
|
||||
sortBy= 'price desc';
|
||||
break;
|
||||
case 'trending-desc':
|
||||
default:
|
||||
sortBy= '';
|
||||
break;
|
||||
}
|
||||
|
||||
return {
|
||||
query: search,
|
||||
startIndex,
|
||||
pageSize,
|
||||
sortBy,
|
||||
filter: filter,
|
||||
facetTemplate,
|
||||
facetValueFilter
|
||||
}
|
||||
}
|
3
packages/kibocommerce/src/lib/set-cookie.ts
Normal file
3
packages/kibocommerce/src/lib/set-cookie.ts
Normal file
@@ -0,0 +1,3 @@
|
||||
export function setCookies(res: any, cookies: string[]): void {
|
||||
res.setHeader('Set-Cookie', cookies);
|
||||
}
|
12
packages/kibocommerce/src/next.config.cjs
Normal file
12
packages/kibocommerce/src/next.config.cjs
Normal file
@@ -0,0 +1,12 @@
|
||||
const commerce = require('./commerce.config.json')
|
||||
|
||||
module.exports = {
|
||||
commerce,
|
||||
serverRuntimeConfig: {
|
||||
// Will only be available on the server side
|
||||
kiboAuthTicket: null
|
||||
},
|
||||
images: {
|
||||
domains: ['d1slj7rdbjyb5l.cloudfront.net', 'cdn-tp1.mozu.com', 'cdn-sb.mozu.com'],
|
||||
},
|
||||
}
|
2
packages/kibocommerce/src/product/index.ts
Normal file
2
packages/kibocommerce/src/product/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { default as usePrice } from './use-price'
|
||||
export { default as useSearch } from './use-search'
|
2
packages/kibocommerce/src/product/use-price.tsx
Normal file
2
packages/kibocommerce/src/product/use-price.tsx
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from '@vercel/commerce/product/use-price'
|
||||
export { default } from '@vercel/commerce/product/use-price'
|
37
packages/kibocommerce/src/product/use-search.tsx
Normal file
37
packages/kibocommerce/src/product/use-search.tsx
Normal file
@@ -0,0 +1,37 @@
|
||||
import { SWRHook } from '@vercel/commerce/utils/types'
|
||||
import useSearch, { UseSearch } from '@vercel/commerce/product/use-search'
|
||||
export default useSearch as UseSearch<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
method: 'GET',
|
||||
url: '/api/catalog/products',
|
||||
},
|
||||
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
|
||||
// Use a dummy base as we only care about the relative path
|
||||
const url = new URL(options.url!, 'http://a')
|
||||
|
||||
if (search) url.searchParams.set('search', search)
|
||||
if (Number.isInteger(Number(categoryId)))
|
||||
url.searchParams.set('categoryId', String(categoryId))
|
||||
if (Number.isInteger(brandId))
|
||||
url.searchParams.set('brandId', String(brandId))
|
||||
if (sort) url.searchParams.set('sort', sort)
|
||||
|
||||
return fetch({
|
||||
url: url.pathname + url.search,
|
||||
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 },
|
||||
})
|
||||
},
|
||||
}
|
30
packages/kibocommerce/src/provider.ts
Normal file
30
packages/kibocommerce/src/provider.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import fetcher from './fetcher'
|
||||
import { handler as useCart } from './cart/use-cart'
|
||||
import { handler as useAddItem } from './cart/use-add-item'
|
||||
import { handler as useUpdateItem } from './cart/use-update-item'
|
||||
import { handler as useRemoveItem } from './cart/use-remove-item'
|
||||
import { handler as useCustomer } from './customer/use-customer'
|
||||
import { handler as useSearch } from './product/use-search'
|
||||
import { handler as useLogin } from './auth/use-login'
|
||||
import { handler as useLogout } from './auth/use-logout'
|
||||
import { handler as useSignup } from './auth/use-signup'
|
||||
import { handler as useWishlist } from './wishlist/use-wishlist'
|
||||
import { handler as useWishlistAddItem } from './wishlist/use-add-item'
|
||||
import { handler as useWishlistRemoveItem } from './wishlist/use-remove-item'
|
||||
|
||||
export const kiboCommerceProvider = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'kibo_cart',
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
wishlist: {
|
||||
useWishlist,
|
||||
useAddItem: useWishlistAddItem,
|
||||
useRemoveItem: useWishlistRemoveItem,
|
||||
},
|
||||
customer: { useCustomer },
|
||||
products: { useSearch },
|
||||
auth: { useLogin, useLogout, useSignup },
|
||||
}
|
||||
|
||||
export type KibocommerceProvider = typeof kiboCommerceProvider
|
27
packages/kibocommerce/src/types/customer.ts
Normal file
27
packages/kibocommerce/src/types/customer.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
import * as Core from '@vercel/commerce/types/customer'
|
||||
export type Maybe<T> = T | null
|
||||
export * from '@vercel/commerce/types/customer'
|
||||
export type Scalars = {
|
||||
ID: string
|
||||
String: string
|
||||
Boolean: boolean
|
||||
Int: number
|
||||
Float: number
|
||||
/** The `AnyScalar` type allows any scalar value by examining the input and passing the serialize, parseValue, and parseLiteral operations to their respective types. */
|
||||
AnyScalar: any
|
||||
/** DateTime custom scalar type */
|
||||
DateTime: any
|
||||
/** Object custom scalar type */
|
||||
Object: any
|
||||
}
|
||||
|
||||
export type Customer = {
|
||||
id: Scalars['Int'],
|
||||
firstName?: Maybe<Scalars['String']>,
|
||||
lastName?: Maybe<Scalars['String']>,
|
||||
email?: Maybe<Scalars['String']>,
|
||||
userName?: Maybe<Scalars['String']>,
|
||||
isAnonymous?: Maybe<Scalars['Boolean']>
|
||||
}
|
||||
|
||||
export type CustomerSchema = Core.CustomerSchema
|
8
packages/kibocommerce/src/types/login.ts
Normal file
8
packages/kibocommerce/src/types/login.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
import * as Core from '@vercel/commerce/types/login'
|
||||
import type { CustomerUserAuthInfoInput } from '../../schema'
|
||||
|
||||
export * from '@vercel/commerce/types/login'
|
||||
|
||||
export type LoginOperation = Core.LoginOperation & {
|
||||
variables: CustomerUserAuthInfoInput
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user