({
+ handler: wishlistEndpoint,
+ handlers,
+})
+
+export default wishlistApi
diff --git a/framework/kibocommerce/api/endpoints/wishlist/remove-item.ts b/framework/kibocommerce/api/endpoints/wishlist/remove-item.ts
new file mode 100644
index 000000000..ae6a8d81c
--- /dev/null
+++ b/framework/kibocommerce/api/endpoints/wishlist/remove-item.ts
@@ -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
diff --git a/framework/kibocommerce/api/fragments/cartItemDetails.ts b/framework/kibocommerce/api/fragments/cartItemDetails.ts
new file mode 100644
index 000000000..951813073
--- /dev/null
+++ b/framework/kibocommerce/api/fragments/cartItemDetails.ts
@@ -0,0 +1,11 @@
+import { productDetails } from '../fragments/productDetails'
+export const cartItemDetails = /*GraphQL*/`
+fragment cartItemDetails on CartItem {
+ id
+ product {
+ ...productDetails
+ }
+ quantity
+}
+${productDetails}
+`;
diff --git a/framework/kibocommerce/api/fragments/category.ts b/framework/kibocommerce/api/fragments/category.ts
new file mode 100644
index 000000000..e6be159b2
--- /dev/null
+++ b/framework/kibocommerce/api/fragments/category.ts
@@ -0,0 +1,11 @@
+export const CategoryInfo = /* GraphQL */`
+fragment categoryInfo on PrCategory {
+ categoryId
+ categoryCode
+ isDisplayed
+ content {
+ name
+ slug
+ description
+ }
+}`;
\ No newline at end of file
diff --git a/framework/kibocommerce/api/fragments/product.ts b/framework/kibocommerce/api/fragments/product.ts
new file mode 100644
index 000000000..b69d00827
--- /dev/null
+++ b/framework/kibocommerce/api/fragments/product.ts
@@ -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}
+`;
diff --git a/framework/kibocommerce/api/fragments/productDetails.ts b/framework/kibocommerce/api/fragments/productDetails.ts
new file mode 100644
index 000000000..e29ffa7b7
--- /dev/null
+++ b/framework/kibocommerce/api/fragments/productDetails.ts
@@ -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
+ }
+}
+`
diff --git a/framework/kibocommerce/api/fragments/search.ts b/framework/kibocommerce/api/fragments/search.ts
new file mode 100644
index 000000000..dee242a07
--- /dev/null
+++ b/framework/kibocommerce/api/fragments/search.ts
@@ -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}
+`;
diff --git a/framework/kibocommerce/api/index.ts b/framework/kibocommerce/api/index.ts
new file mode 100644
index 000000000..a79745c57
--- /dev/null
+++ b/framework/kibocommerce/api/index.ts
@@ -0,0 +1,64 @@
+import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
+import { getCommerceApi as commerceApi } from '@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
+
+export function getCommerceApi
(
+ customProvider: P = provider as any
+): KiboCommerceAPI
{
+ return commerceApi(customProvider as any)
+}
diff --git a/framework/kibocommerce/api/mutations/addItemToWishlist-mutation.ts b/framework/kibocommerce/api/mutations/addItemToWishlist-mutation.ts
new file mode 100644
index 000000000..f9088b0bb
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/addItemToWishlist-mutation.ts
@@ -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;
diff --git a/framework/kibocommerce/api/mutations/addToCart-mutation.ts b/framework/kibocommerce/api/mutations/addToCart-mutation.ts
new file mode 100644
index 000000000..7cbf68801
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/addToCart-mutation.ts
@@ -0,0 +1,12 @@
+import { cartItemDetails } from './../fragments/cartItemDetails'
+
+const addToCurrentCartMutation = /*GraphQL*/ `
+${cartItemDetails}
+
+mutation addToCart($productToAdd:CartItemInput!){
+ addItemToCurrentCart(cartItemInput: $productToAdd) {
+ ...cartItemDetails
+ }
+}`
+
+export default addToCurrentCartMutation
diff --git a/framework/kibocommerce/api/mutations/create-wishlist-mutation.ts b/framework/kibocommerce/api/mutations/create-wishlist-mutation.ts
new file mode 100644
index 000000000..66ad88309
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/create-wishlist-mutation.ts
@@ -0,0 +1,11 @@
+const createWishlist = /*GraphQL*/`
+mutation createWishlist($wishlistInput:WishlistInput!) {
+ createWishlist(wishlistInput:$wishlistInput){
+ id
+ name
+ customerAccountId
+ }
+ }
+`;
+
+export default createWishlist;
\ No newline at end of file
diff --git a/framework/kibocommerce/api/mutations/login-mutation.ts b/framework/kibocommerce/api/mutations/login-mutation.ts
new file mode 100644
index 000000000..730adeda1
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/login-mutation.ts
@@ -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
+ }
+ }
+ }
+`
+
diff --git a/framework/kibocommerce/api/mutations/removeItemFromCart-mutation.ts b/framework/kibocommerce/api/mutations/removeItemFromCart-mutation.ts
new file mode 100644
index 000000000..3cf5c5af5
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/removeItemFromCart-mutation.ts
@@ -0,0 +1,9 @@
+/*
+* Delete cart based on current user session
+*/
+const removeItemFromCartMutation = /*GraphQL*/`
+mutation deleteCartItem($id: String!) {
+ deleteCurrentCartItem(cartItemId:$id)
+}`;
+
+export default removeItemFromCartMutation;
diff --git a/framework/kibocommerce/api/mutations/removeItemFromWishlist-mutation.ts b/framework/kibocommerce/api/mutations/removeItemFromWishlist-mutation.ts
new file mode 100644
index 000000000..ce3d994a5
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/removeItemFromWishlist-mutation.ts
@@ -0,0 +1,8 @@
+const removeItemFromWishlistMutation = /* GraphQL */`
+mutation deletewishlistitem($wishlistId: String!, $wishlistItemId: String!) {
+ deleteWishlistItem(wishlistId: $wishlistId, wishlistItemId:$wishlistItemId)
+ }
+`;
+
+export default removeItemFromWishlistMutation;
+
diff --git a/framework/kibocommerce/api/mutations/signup-mutation.ts b/framework/kibocommerce/api/mutations/signup-mutation.ts
new file mode 100644
index 000000000..bb25534ab
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/signup-mutation.ts
@@ -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
+};
+
diff --git a/framework/kibocommerce/api/mutations/updateCartItemQuantity-mutation.ts b/framework/kibocommerce/api/mutations/updateCartItemQuantity-mutation.ts
new file mode 100644
index 000000000..7b2cd5c82
--- /dev/null
+++ b/framework/kibocommerce/api/mutations/updateCartItemQuantity-mutation.ts
@@ -0,0 +1,9 @@
+const updateCartItemQuantityMutation = /*GraphQL*/`
+mutation updateCartItemQuantity($itemId:String!, $quantity: Int!){
+ updateCurrentCartItemQuantity(cartItemId:$itemId, quantity:$quantity){
+ id
+ quantity
+ }
+}`;
+
+export default updateCartItemQuantityMutation;
diff --git a/framework/kibocommerce/api/operations/get-all-pages.ts b/framework/kibocommerce/api/operations/get-all-pages.ts
new file mode 100644
index 000000000..6bc0e4a8c
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-all-pages.ts
@@ -0,0 +1,38 @@
+import type { OperationContext } from '@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) {
+
+ async function getAllPages({
+ query = getAllPagesQuery,
+ config,
+ variables,
+ }: {
+ url?: string
+ config?: Partial
+ variables?: GetPagesQueryParams
+ preview?: boolean
+ query?: string
+ } = {}): Promise {
+ 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
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/api/operations/get-all-product-paths.ts b/framework/kibocommerce/api/operations/get-all-product-paths.ts
new file mode 100644
index 000000000..3067b67fc
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-all-product-paths.ts
@@ -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 {
+
+ 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
+}
diff --git a/framework/kibocommerce/api/operations/get-all-products.ts b/framework/kibocommerce/api/operations/get-all-products.ts
new file mode 100644
index 000000000..c60b88f4e
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-all-products.ts
@@ -0,0 +1,32 @@
+import { Product } from '@commerce/types/product'
+import { GetAllProductsOperation } from '@commerce/types/product'
+import type { OperationContext } from '@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) {
+ async function getAllProducts({
+ query = getAllProductsQuery,
+ variables,
+ config,
+ }: {
+ query?: string
+ variables?: T['variables']
+ config?: Partial
+ 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
+}
diff --git a/framework/kibocommerce/api/operations/get-customer-wishlist.ts b/framework/kibocommerce/api/operations/get-customer-wishlist.ts
new file mode 100644
index 000000000..a2a60e9ae
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-customer-wishlist.ts
@@ -0,0 +1,57 @@
+import type {
+ OperationContext,
+ OperationOptions,
+} from '@commerce/api/operations'
+import type {
+ GetCustomerWishlistOperation,
+ Wishlist,
+} from '@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) {
+ async function getCustomerWishlist<
+ T extends GetCustomerWishlistOperation
+ >(opts: {
+ variables: T['variables']
+ config?: KiboCommerceConfig
+ includeProducts?: boolean
+ }): Promise
+
+ async function getCustomerWishlist(
+ opts: {
+ variables: T['variables']
+ config?: KiboCommerceConfig
+ includeProducts?: boolean
+ } & OperationOptions
+ ): Promise
+
+ async function getCustomerWishlist({
+ config,
+ variables,
+ includeProducts,
+ }: {
+ url?: string
+ variables: T['variables']
+ config?: KiboCommerceConfig
+ includeProducts?: boolean
+ }): Promise {
+ 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
+}
diff --git a/framework/kibocommerce/api/operations/get-page.ts b/framework/kibocommerce/api/operations/get-page.ts
new file mode 100644
index 000000000..8cfccb7db
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-page.ts
@@ -0,0 +1,40 @@
+import type {
+ OperationContext,
+} from '@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) {
+ async function getPage({
+ url,
+ variables,
+ config,
+ preview,
+ }: {
+ url?: string
+ variables: GetPageQueryParams
+ config?: Partial
+ preview?: boolean
+ }): Promise {
+ // 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
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/api/operations/get-product.ts b/framework/kibocommerce/api/operations/get-product.ts
new file mode 100644
index 000000000..a3acf44d9
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-product.ts
@@ -0,0 +1,35 @@
+import type { KiboCommerceConfig } from '../index'
+import { Product } from '@commerce/types/product'
+import { GetProductOperation } from '@commerce/types/product'
+import type { OperationContext } from '@commerce/api/operations'
+import { getProductQuery } from '../queries/get-product-query'
+import { normalizeProduct } from '../../lib/normalize'
+
+export default function getProductOperation({
+ commerce,
+}: OperationContext) {
+
+ async function getProduct({
+ query = getProductQuery,
+ variables,
+ config,
+ }: {
+ query?: string
+ variables?: T['variables']
+ config?: Partial
+ preview?: boolean
+ } = {}): Promise {
+ 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
+}
diff --git a/framework/kibocommerce/api/operations/get-site-info.ts b/framework/kibocommerce/api/operations/get-site-info.ts
new file mode 100644
index 000000000..1bd0ddf63
--- /dev/null
+++ b/framework/kibocommerce/api/operations/get-site-info.ts
@@ -0,0 +1,35 @@
+import { OperationContext } from '@commerce/api/operations'
+import { Category } from '@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) {
+ async function getSiteInfo({
+ query= categoryTreeQuery,
+ variables,
+ config,
+ }: {
+ query?: string
+ variables?: any
+ config?: Partial
+ preview?: boolean
+ } = {}): Promise {
+ 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
+}
diff --git a/framework/kibocommerce/api/operations/index.ts b/framework/kibocommerce/api/operations/index.ts
new file mode 100644
index 000000000..086fdf83a
--- /dev/null
+++ b/framework/kibocommerce/api/operations/index.ts
@@ -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'
diff --git a/framework/kibocommerce/api/queries/get-all-pages-query.ts b/framework/kibocommerce/api/queries/get-all-pages-query.ts
new file mode 100644
index 000000000..6926914f5
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-all-pages-query.ts
@@ -0,0 +1,11 @@
+export const getAllPagesQuery = /* GraphQL */`
+query($documentListName: String!) {
+ documentListDocuments(documentListName:$documentListName){
+ items {
+ id
+ name
+ listFQN
+ properties
+ }
+ }
+ }`;
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-all-products-query.ts b/framework/kibocommerce/api/queries/get-all-products-query.ts
new file mode 100644
index 000000000..3c6599e34
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-all-products-query.ts
@@ -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
+ }
+ }
+}
+`
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-anonymous-shopper-token-query.ts b/framework/kibocommerce/api/queries/get-anonymous-shopper-token-query.ts
new file mode 100644
index 000000000..031ffc0ee
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-anonymous-shopper-token-query.ts
@@ -0,0 +1,11 @@
+export const getAnonymousShopperTokenQuery = /* GraphQL */ `
+ query {
+ getAnonymousShopperToken {
+ accessToken
+ accessTokenExpiration
+ refreshToken
+ refreshTokenExpiration
+ jwtAccessToken
+ }
+ }
+`
diff --git a/framework/kibocommerce/api/queries/get-cart-query.ts b/framework/kibocommerce/api/queries/get-cart-query.ts
new file mode 100644
index 000000000..5bbf5bbfa
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-cart-query.ts
@@ -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}
+`
diff --git a/framework/kibocommerce/api/queries/get-categories-tree-query.ts b/framework/kibocommerce/api/queries/get-categories-tree-query.ts
new file mode 100644
index 000000000..984833630
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-categories-tree-query.ts
@@ -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}`;
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-customer-account-query.ts b/framework/kibocommerce/api/queries/get-customer-account-query.ts
new file mode 100644
index 000000000..9528b8467
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-customer-account-query.ts
@@ -0,0 +1,12 @@
+export const getCustomerAccountQuery = /* GraphQL */`
+query getUser {
+ customerAccount:getCurrentAccount {
+ id
+ firstName
+ lastName
+ emailAddress
+ userName
+ isAnonymous
+ }
+}
+`
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-customer-wishlist-query.ts b/framework/kibocommerce/api/queries/get-customer-wishlist-query.ts
new file mode 100644
index 000000000..d2ae3edec
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-customer-wishlist-query.ts
@@ -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}
+`
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-page-query.ts b/framework/kibocommerce/api/queries/get-page-query.ts
new file mode 100644
index 000000000..69371d003
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-page-query.ts
@@ -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
+ }
+ }
+ }
+`;
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/get-product-query.ts b/framework/kibocommerce/api/queries/get-product-query.ts
new file mode 100644
index 000000000..47db311e4
--- /dev/null
+++ b/framework/kibocommerce/api/queries/get-product-query.ts
@@ -0,0 +1,15 @@
+import { productInfo } from '../fragments/product';
+
+export const getProductQuery = /* GraphQL */`
+${productInfo}
+
+ query product(
+ $productCode: String!
+ ) {
+ product(
+ productCode: $productCode
+ ) {
+ ...productInfo
+ }
+ }
+`
\ No newline at end of file
diff --git a/framework/kibocommerce/api/queries/product-search-query.ts b/framework/kibocommerce/api/queries/product-search-query.ts
new file mode 100644
index 000000000..d22f0ef12
--- /dev/null
+++ b/framework/kibocommerce/api/queries/product-search-query.ts
@@ -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;
diff --git a/framework/kibocommerce/api/utils/api-auth-helper.ts b/framework/kibocommerce/api/utils/api-auth-helper.ts
new file mode 100644
index 000000000..cc4c0acf0
--- /dev/null
+++ b/framework/kibocommerce/api/utils/api-auth-helper.ts
@@ -0,0 +1,110 @@
+import getNextConfig from 'next/config'
+import type { KiboCommerceConfig } from '../index'
+import type { FetchOptions } from '@vercel/fetch'
+import fetch from './fetch'
+
+interface AppAuthTicket {
+ access_token: string
+ token_type: string
+ expires_in: number
+ expires_at: number
+ refresh_token: string | null
+}
+
+interface AuthTicketCache {
+ getAuthTicket: () => Promise
+ setAuthTicket: (kiboAuthTicket: AppAuthTicket) => void
+}
+
+class RuntimeMemCache implements AuthTicketCache {
+ constructor() {}
+ async getAuthTicket() {
+ const { serverRuntimeConfig } = getNextConfig()
+ return serverRuntimeConfig.kiboAuthTicket
+ }
+ setAuthTicket(kiboAuthTicket: AppAuthTicket) {
+ const { serverRuntimeConfig } = getNextConfig()
+ serverRuntimeConfig.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 {
+ // 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 {
+ // 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
+ }
+}
diff --git a/framework/kibocommerce/api/utils/cookie-handler.ts b/framework/kibocommerce/api/utils/cookie-handler.ts
new file mode 100644
index 000000000..ee7b5a1e3
--- /dev/null
+++ b/framework/kibocommerce/api/utils/cookie-handler.ts
@@ -0,0 +1,52 @@
+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'
+
+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 = encodedToken
+ ? JSON.parse(Buffer.from(encodedToken, 'base64').toString('ascii'))
+ : null
+ this.accessToken = token ? token.accessToken : null
+ }
+
+ async getAnonymousToken() {
+ const response: any = await getAnonymousShopperToken({
+ config: this.config,
+ })
+ let anonymousAccessToken = response?.accessToken
+ return {
+ response,
+ accessToken: anonymousAccessToken,
+ }
+ }
+
+ 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
+ }
+}
diff --git a/framework/kibocommerce/api/utils/fetch-graphql-api.ts b/framework/kibocommerce/api/utils/fetch-graphql-api.ts
new file mode 100644
index 000000000..8638b35b7
--- /dev/null
+++ b/framework/kibocommerce/api/utils/fetch-graphql-api.ts
@@ -0,0 +1,43 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { GraphQLFetcher } from '@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
diff --git a/framework/kibocommerce/api/utils/fetch-local.ts b/framework/kibocommerce/api/utils/fetch-local.ts
new file mode 100644
index 000000000..2612188a9
--- /dev/null
+++ b/framework/kibocommerce/api/utils/fetch-local.ts
@@ -0,0 +1,36 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { GraphQLFetcher } from '@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
diff --git a/framework/kibocommerce/api/utils/fetch.ts b/framework/kibocommerce/api/utils/fetch.ts
new file mode 100644
index 000000000..9d9fff3ed
--- /dev/null
+++ b/framework/kibocommerce/api/utils/fetch.ts
@@ -0,0 +1,3 @@
+import zeitFetch from '@vercel/fetch'
+
+export default zeitFetch()
diff --git a/framework/kibocommerce/api/utils/get-anonymous-shopper-token.ts b/framework/kibocommerce/api/utils/get-anonymous-shopper-token.ts
new file mode 100644
index 000000000..9325a4ecd
--- /dev/null
+++ b/framework/kibocommerce/api/utils/get-anonymous-shopper-token.ts
@@ -0,0 +1,13 @@
+import type { KiboCommerceConfig } from '../'
+import { getAnonymousShopperTokenQuery } from '../queries/get-anonymous-shopper-token-query'
+
+async function getAnonymousShopperToken({
+ config,
+}: {
+ config: KiboCommerceConfig
+}): Promise {
+ const { data } = await config.fetch(getAnonymousShopperTokenQuery)
+ return data?.getAnonymousShopperToken
+}
+
+export default getAnonymousShopperToken
diff --git a/framework/kibocommerce/api/utils/get-customer-id.ts b/framework/kibocommerce/api/utils/get-customer-id.ts
new file mode 100644
index 000000000..5ba3d7787
--- /dev/null
+++ b/framework/kibocommerce/api/utils/get-customer-id.ts
@@ -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 {
+ 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
diff --git a/framework/kibocommerce/auth/index.ts b/framework/kibocommerce/auth/index.ts
new file mode 100644
index 000000000..36e757a89
--- /dev/null
+++ b/framework/kibocommerce/auth/index.ts
@@ -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'
diff --git a/framework/kibocommerce/auth/use-login.tsx b/framework/kibocommerce/auth/use-login.tsx
new file mode 100644
index 000000000..672263f7c
--- /dev/null
+++ b/framework/kibocommerce/auth/use-login.tsx
@@ -0,0 +1,42 @@
+import { MutationHook } from '@commerce/utils/types'
+import useLogin, { UseLogin } from '@commerce/auth/use-login'
+
+import { useCallback } from 'react'
+import { CommerceError } from '@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
+
+export const handler: MutationHook = {
+ 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 { revalidate } = useCustomer()
+ const {revalidate: revalidateCart} = useCart()
+ return useCallback(
+ async function login(input) {
+ const data = await fetch({ input })
+ await revalidate()
+ await revalidateCart()
+ return data
+ },
+ [fetch, revalidate, revalidateCart]
+ )
+ },
+}
diff --git a/framework/kibocommerce/auth/use-logout.tsx b/framework/kibocommerce/auth/use-logout.tsx
new file mode 100644
index 000000000..3e344fb09
--- /dev/null
+++ b/framework/kibocommerce/auth/use-logout.tsx
@@ -0,0 +1,29 @@
+import { useCallback } from 'react'
+import type { MutationHook } from '@commerce/utils/types'
+import useLogout, { UseLogout } from '@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
+
+export const handler: MutationHook = {
+ 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]
+ )
+ },
+}
diff --git a/framework/kibocommerce/auth/use-signup.tsx b/framework/kibocommerce/auth/use-signup.tsx
new file mode 100644
index 000000000..da06fd3eb
--- /dev/null
+++ b/framework/kibocommerce/auth/use-signup.tsx
@@ -0,0 +1,44 @@
+import { useCallback } from 'react'
+import type { MutationHook } from '@commerce/utils/types'
+import { CommerceError } from '@commerce/utils/errors'
+import useSignup, { UseSignup } from '@commerce/auth/use-signup'
+import type { SignupHook } from '../types/signup'
+import useCustomer from '../customer/use-customer'
+
+export default useSignup as UseSignup
+
+export const handler: MutationHook = {
+ 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 { revalidate } = useCustomer()
+
+ return useCallback(
+ async function signup(input) {
+ const data = await fetch({ input })
+ await revalidate()
+ return data
+ },
+ [fetch, revalidate]
+ )
+ },
+}
diff --git a/framework/kibocommerce/cart/index.ts b/framework/kibocommerce/cart/index.ts
new file mode 100644
index 000000000..3b8ba990e
--- /dev/null
+++ b/framework/kibocommerce/cart/index.ts
@@ -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'
diff --git a/framework/kibocommerce/cart/use-add-item.tsx b/framework/kibocommerce/cart/use-add-item.tsx
new file mode 100644
index 000000000..1ac6ac6f8
--- /dev/null
+++ b/framework/kibocommerce/cart/use-add-item.tsx
@@ -0,0 +1,44 @@
+import { useCallback } from 'react'
+import type { MutationHook } from '@commerce/utils/types'
+import { CommerceError } from '@commerce/utils/errors'
+import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
+import type { AddItemHook } from '@commerce/types/cart'
+import useCart from './use-cart'
+
+export default useAddItem as UseAddItem
+
+export const handler: MutationHook = {
+ 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]
+ )
+ },
+}
diff --git a/framework/kibocommerce/cart/use-cart.tsx b/framework/kibocommerce/cart/use-cart.tsx
new file mode 100644
index 000000000..0c565e094
--- /dev/null
+++ b/framework/kibocommerce/cart/use-cart.tsx
@@ -0,0 +1,33 @@
+import { useMemo } from 'react'
+import { SWRHook } from '@commerce/utils/types'
+import useCart, { UseCart } from '@commerce/cart/use-cart'
+
+export default useCart as UseCart
+
+export const handler: SWRHook = {
+ 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]
+ )
+ },
+}
diff --git a/framework/kibocommerce/cart/use-remove-item.tsx b/framework/kibocommerce/cart/use-remove-item.tsx
new file mode 100644
index 000000000..1376f29ce
--- /dev/null
+++ b/framework/kibocommerce/cart/use-remove-item.tsx
@@ -0,0 +1,56 @@
+import { useCallback } from 'react'
+import type {
+ MutationHookContext,
+ HookFetcherContext,
+} from '@commerce/utils/types'
+import { ValidationError } from '@commerce/utils/errors'
+import useRemoveItem, { UseRemoveItem } from '@commerce/cart/use-remove-item'
+import type { Cart, LineItem, RemoveItemHook } from '@commerce/types/cart'
+import useCart from './use-cart'
+
+export type RemoveItemFn = T extends LineItem
+ ? (input?: RemoveItemActionInput) => Promise
+ : (input: RemoveItemActionInput) => Promise
+
+export type RemoveItemActionInput = T extends LineItem
+ ? Partial
+ : RemoveItemHook['actionInput']
+
+export default useRemoveItem as UseRemoveItem
+
+export const handler = {
+ fetchOptions: {
+ url: '/api/cart',
+ method: 'DELETE',
+ },
+ async fetcher({
+ input: { itemId },
+ options,
+ fetch,
+ }: HookFetcherContext) {
+ return await fetch({ ...options, body: { itemId } })
+ },
+ useHook: ({ fetch }: MutationHookContext) => <
+ T extends LineItem | undefined = undefined
+ >(
+ ctx: { item?: T } = {}
+ ) => {
+ const { item } = ctx
+ const { mutate } = useCart()
+ const removeItem: RemoveItemFn = 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, [fetch, mutate])
+ },
+}
diff --git a/framework/kibocommerce/cart/use-update-item.tsx b/framework/kibocommerce/cart/use-update-item.tsx
new file mode 100644
index 000000000..0f9f5754d
--- /dev/null
+++ b/framework/kibocommerce/cart/use-update-item.tsx
@@ -0,0 +1,84 @@
+import { useCallback } from 'react'
+import debounce from 'lodash.debounce'
+import type {
+ MutationHookContext,
+ HookFetcherContext,
+} from '@commerce/utils/types'
+import { ValidationError } from '@commerce/utils/errors'
+import useUpdateItem, { UseUpdateItem } from '@commerce/cart/use-update-item'
+import type { LineItem, UpdateItemHook } from '@commerce/types/cart'
+import { handler as removeItemHandler } from './use-remove-item'
+import useCart from './use-cart'
+
+export type UpdateItemActionInput = T extends LineItem
+ ? Partial
+ : UpdateItemHook['actionInput']
+
+export default useUpdateItem as UseUpdateItem
+
+export const handler = {
+ fetchOptions: {
+ url: '/api/cart',
+ method: 'PUT',
+ },
+ async fetcher({
+ input: { itemId, item },
+ options,
+ fetch,
+ }: HookFetcherContext) {
+ 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) => <
+ T extends LineItem | undefined = undefined
+ >(
+ ctx: {
+ item?: T
+ wait?: number
+ } = {}
+ ) => {
+ const { item } = ctx
+ const { mutate } = useCart() as any
+
+ return useCallback(
+ debounce(async (input: UpdateItemActionInput) => {
+ 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]
+ )
+ },
+}
diff --git a/framework/kibocommerce/checkout/use-checkout.tsx b/framework/kibocommerce/checkout/use-checkout.tsx
new file mode 100644
index 000000000..8ba12c14a
--- /dev/null
+++ b/framework/kibocommerce/checkout/use-checkout.tsx
@@ -0,0 +1,14 @@
+import { SWRHook } from '@commerce/utils/types'
+import useCheckout, { UseCheckout } from '@commerce/checkout/use-checkout'
+
+export default useCheckout as UseCheckout
+
+export const handler: SWRHook = {
+ fetchOptions: {
+ query: '',
+ },
+ async fetcher({ input, options, fetch }) {},
+ useHook:
+ ({ useData }) =>
+ async (input) => ({}),
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/codegen.json b/framework/kibocommerce/codegen.json
new file mode 100644
index 000000000..cf25363ed
--- /dev/null
+++ b/framework/kibocommerce/codegen.json
@@ -0,0 +1,23 @@
+{
+ "schema": {
+ "https://t17194-s21127.dev10.kubedev.kibo-dev.com/graphql": {}
+ },
+
+ "generates": {
+ "./framework/kibocommerce/schema.d.ts": {
+ "plugins": ["typescript", "typescript-operations"],
+ "config": {
+ "scalars": {
+ "ID": "string"
+ }
+ }
+ },
+ "./framework/kibocommerce/schema.graphql": {
+ "plugins": ["schema-ast"]
+ }
+ },
+ "hooks": {
+ "afterAllFileWrite": ["prettier --write"]
+ }
+ }
+
\ No newline at end of file
diff --git a/framework/kibocommerce/commerce.config.json b/framework/kibocommerce/commerce.config.json
new file mode 100644
index 000000000..cd58f1e29
--- /dev/null
+++ b/framework/kibocommerce/commerce.config.json
@@ -0,0 +1,9 @@
+{
+ "provider": "kibocommerce",
+ "features": {
+ "wishlist": true,
+ "cart": true,
+ "search": true,
+ "customerAuth": true
+ }
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/customer/address/use-add-item.tsx b/framework/kibocommerce/customer/address/use-add-item.tsx
new file mode 100644
index 000000000..70bd044b2
--- /dev/null
+++ b/framework/kibocommerce/customer/address/use-add-item.tsx
@@ -0,0 +1,15 @@
+import useAddItem, { UseAddItem } from '@commerce/customer/address/use-add-item'
+import { MutationHook } from '@commerce/utils/types'
+
+export default useAddItem as UseAddItem
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: '',
+ },
+ async fetcher({ input, options, fetch }) {},
+ useHook:
+ ({ fetch }) =>
+ () =>
+ async () => ({}),
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/customer/card/use-add-item.tsx b/framework/kibocommerce/customer/card/use-add-item.tsx
new file mode 100644
index 000000000..d6bd0d77f
--- /dev/null
+++ b/framework/kibocommerce/customer/card/use-add-item.tsx
@@ -0,0 +1,15 @@
+import useAddItem, { UseAddItem } from '@commerce/customer/card/use-add-item'
+import { MutationHook } from '@commerce/utils/types'
+
+export default useAddItem as UseAddItem
+
+export const handler: MutationHook = {
+ fetchOptions: {
+ query: '',
+ },
+ async fetcher({ input, options, fetch }) {},
+ useHook:
+ ({ fetch }) =>
+ () =>
+ async () => ({}),
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/customer/index.ts b/framework/kibocommerce/customer/index.ts
new file mode 100644
index 000000000..6c903ecc5
--- /dev/null
+++ b/framework/kibocommerce/customer/index.ts
@@ -0,0 +1 @@
+export { default as useCustomer } from './use-customer'
diff --git a/framework/kibocommerce/customer/use-customer.tsx b/framework/kibocommerce/customer/use-customer.tsx
new file mode 100644
index 000000000..238b1229b
--- /dev/null
+++ b/framework/kibocommerce/customer/use-customer.tsx
@@ -0,0 +1,24 @@
+import { SWRHook } from '@commerce/utils/types'
+import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
+import type { CustomerHook } from '../types/customer'
+
+export default useCustomer as UseCustomer
+
+export const handler: SWRHook = {
+ 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,
+ },
+ })
+ },
+}
diff --git a/framework/kibocommerce/fetcher.ts b/framework/kibocommerce/fetcher.ts
new file mode 100644
index 000000000..f8ca0c578
--- /dev/null
+++ b/framework/kibocommerce/fetcher.ts
@@ -0,0 +1,41 @@
+import { FetcherError } from '@commerce/utils/errors'
+import type { Fetcher } from '@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
diff --git a/framework/kibocommerce/index.tsx b/framework/kibocommerce/index.tsx
new file mode 100644
index 000000000..af8b66ee6
--- /dev/null
+++ b/framework/kibocommerce/index.tsx
@@ -0,0 +1,9 @@
+import { getCommerceProvider, useCommerce as useCoreCommerce } from '@commerce'
+import { kiboCommerceProvider, KibocommerceProvider } from './provider'
+
+export { kiboCommerceProvider }
+export type { KibocommerceProvider }
+
+export const CommerceProvider = getCommerceProvider(kiboCommerceProvider)
+
+export const useCommerce = () => useCoreCommerce()
diff --git a/framework/kibocommerce/lib/get-cookie-expiration-date.ts b/framework/kibocommerce/lib/get-cookie-expiration-date.ts
new file mode 100644
index 000000000..89fd24504
--- /dev/null
+++ b/framework/kibocommerce/lib/get-cookie-expiration-date.ts
@@ -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;
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/lib/get-slug.ts b/framework/kibocommerce/lib/get-slug.ts
new file mode 100644
index 000000000..329c5a27e
--- /dev/null
+++ b/framework/kibocommerce/lib/get-slug.ts
@@ -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
diff --git a/framework/kibocommerce/lib/immutability.ts b/framework/kibocommerce/lib/immutability.ts
new file mode 100644
index 000000000..488d3570f
--- /dev/null
+++ b/framework/kibocommerce/lib/immutability.ts
@@ -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
diff --git a/framework/kibocommerce/lib/normalize.ts b/framework/kibocommerce/lib/normalize.ts
new file mode 100644
index 000000000..5fd03b855
--- /dev/null
+++ b/framework/kibocommerce/lib/normalize.ts
@@ -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,
+ }
+}
diff --git a/framework/kibocommerce/lib/prepare-set-cookie.ts b/framework/kibocommerce/lib/prepare-set-cookie.ts
new file mode 100644
index 000000000..c1aeb1c83
--- /dev/null
+++ b/framework/kibocommerce/lib/prepare-set-cookie.ts
@@ -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
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/lib/product-search-vars.ts b/framework/kibocommerce/lib/product-search-vars.ts
new file mode 100644
index 000000000..37c4d81eb
--- /dev/null
+++ b/framework/kibocommerce/lib/product-search-vars.ts
@@ -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
+ }
+}
diff --git a/framework/kibocommerce/lib/set-cookie.ts b/framework/kibocommerce/lib/set-cookie.ts
new file mode 100644
index 000000000..2c194c921
--- /dev/null
+++ b/framework/kibocommerce/lib/set-cookie.ts
@@ -0,0 +1,3 @@
+export function setCookies(res: any, cookies: string[]): void {
+ res.setHeader('Set-Cookie', cookies);
+}
\ No newline at end of file
diff --git a/framework/kibocommerce/next.config.js b/framework/kibocommerce/next.config.js
new file mode 100644
index 000000000..79a348c88
--- /dev/null
+++ b/framework/kibocommerce/next.config.js
@@ -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'],
+ },
+}
diff --git a/framework/kibocommerce/product/index.ts b/framework/kibocommerce/product/index.ts
new file mode 100644
index 000000000..426a3edcd
--- /dev/null
+++ b/framework/kibocommerce/product/index.ts
@@ -0,0 +1,2 @@
+export { default as usePrice } from './use-price'
+export { default as useSearch } from './use-search'
diff --git a/framework/kibocommerce/product/use-price.tsx b/framework/kibocommerce/product/use-price.tsx
new file mode 100644
index 000000000..0174faf5e
--- /dev/null
+++ b/framework/kibocommerce/product/use-price.tsx
@@ -0,0 +1,2 @@
+export * from '@commerce/product/use-price'
+export { default } from '@commerce/product/use-price'
diff --git a/framework/kibocommerce/product/use-search.tsx b/framework/kibocommerce/product/use-search.tsx
new file mode 100644
index 000000000..204ca3181
--- /dev/null
+++ b/framework/kibocommerce/product/use-search.tsx
@@ -0,0 +1,37 @@
+import { SWRHook } from '@commerce/utils/types'
+import useSearch, { UseSearch } from '@commerce/product/use-search'
+export default useSearch as UseSearch
+
+export const handler: SWRHook = {
+ 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 },
+ })
+ },
+}
diff --git a/framework/kibocommerce/provider.ts b/framework/kibocommerce/provider.ts
new file mode 100644
index 000000000..8ca1ddfde
--- /dev/null
+++ b/framework/kibocommerce/provider.ts
@@ -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
diff --git a/framework/kibocommerce/schema.d.ts b/framework/kibocommerce/schema.d.ts
new file mode 100644
index 000000000..cf52ddec9
--- /dev/null
+++ b/framework/kibocommerce/schema.d.ts
@@ -0,0 +1,11399 @@
+export type Maybe = T | null
+export type Exact = {
+ [K in keyof T]: T[K]
+}
+export type MakeOptional = Omit &
+ { [SubKey in K]?: Maybe }
+export type MakeMaybe = Omit &
+ { [SubKey in K]: Maybe }
+/** All built-in and custom scalars, mapped to their actual values */
+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 AccountPasswordInfoCollectionInput = {
+ totalCount: Scalars['Int']
+ items?: Maybe>>
+}
+
+export type AccountPasswordInfoInput = {
+ accountId: Scalars['Int']
+ userId?: Maybe
+ unlockAccount?: Maybe
+ passwordInfo?: Maybe
+}
+
+export type AccountSalesRep = {
+ __typename?: 'AccountSalesRep'
+ _get?: Maybe
+ _root?: Maybe
+ accountId: Scalars['Int']
+ adminUserId?: Maybe
+}
+
+export type AccountSalesRep_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AccountSalesRepInput = {
+ accountId: Scalars['Int']
+ adminUserId?: Maybe
+}
+
+export type ActiveDateRange = {
+ __typename?: 'ActiveDateRange'
+ _get?: Maybe
+ _root?: Maybe
+ startDate?: Maybe
+ endDate?: Maybe
+}
+
+export type ActiveDateRange_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type ActiveDateRangeInput = {
+ startDate?: Maybe
+ endDate?: Maybe
+}
+
+export type AddressValidationRequestInput = {
+ address?: Maybe
+}
+
+export type AddressValidationResponse = {
+ __typename?: 'AddressValidationResponse'
+ _get?: Maybe
+ _root?: Maybe
+ addressCandidates?: Maybe>>
+}
+
+export type AddressValidationResponse_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type Adjustment = {
+ __typename?: 'Adjustment'
+ _get?: Maybe
+ _root?: Maybe
+ amount?: Maybe
+ description?: Maybe
+ internalComment?: Maybe
+}
+
+export type Adjustment_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AdjustmentInput = {
+ amount?: Maybe
+ description?: Maybe
+ internalComment?: Maybe
+}
+
+export type AppliedLineItemProductDiscount = {
+ __typename?: 'AppliedLineItemProductDiscount'
+ _get?: Maybe
+ _root?: Maybe
+ appliesToSalePrice?: Maybe
+ discountQuantity: Scalars['Int']
+ productQuantity?: Maybe
+ impactPerUnit?: Maybe
+}
+
+export type AppliedLineItemProductDiscount_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AppliedLineItemProductDiscountInput = {
+ appliesToSalePrice?: Maybe
+ discountQuantity: Scalars['Int']
+ productQuantity?: Maybe
+ impactPerUnit?: Maybe
+}
+
+export type AppliedLineItemShippingDiscount = {
+ __typename?: 'AppliedLineItemShippingDiscount'
+ _get?: Maybe
+ _root?: Maybe
+ methodCode?: Maybe
+ discount?: Maybe
+ discountQuantity: Scalars['Int']
+ impactPerUnit: Scalars['Float']
+}
+
+export type AppliedLineItemShippingDiscount_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AppliedLineItemShippingDiscountInput = {
+ methodCode?: Maybe
+ discount?: Maybe
+ discountQuantity: Scalars['Int']
+ impactPerUnit: Scalars['Float']
+}
+
+export type AttributeDetail = {
+ __typename?: 'AttributeDetail'
+ _get?: Maybe
+ _root?: Maybe
+ valueType?: Maybe
+ inputType?: Maybe
+ dataType?: Maybe
+ usageType?: Maybe
+ dataTypeSequence: Scalars['Int']
+ name?: Maybe
+ description?: Maybe
+ validation?: Maybe
+ searchableInStorefront?: Maybe
+ searchDisplayValue?: Maybe
+ allowFilteringAndSortingInStorefront?: Maybe
+ indexValueWithCase?: Maybe
+ customWeightInStorefrontSearch?: Maybe
+ displayIntention?: Maybe
+}
+
+export type AttributeDetail_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AttributeVocabularyValueDisplayInfo = {
+ __typename?: 'AttributeVocabularyValueDisplayInfo'
+ _get?: Maybe
+ _root?: Maybe
+ cmsId?: Maybe
+ imageUrl?: Maybe
+ colorValue?: Maybe
+}
+
+export type AttributeVocabularyValueDisplayInfo_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AuditRecord = {
+ __typename?: 'AuditRecord'
+ _get?: Maybe
+ _root?: Maybe
+ id?: Maybe
+ changes?: Maybe>>
+ auditInfo?: Maybe
+}
+
+export type AuditRecord_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AuditRecordChange = {
+ __typename?: 'AuditRecordChange'
+ _get?: Maybe
+ _root?: Maybe
+ type?: Maybe
+ path?: Maybe
+ fields?: Maybe>>
+}
+
+export type AuditRecordChange_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AuditRecordChangeField = {
+ __typename?: 'AuditRecordChangeField'
+ _get?: Maybe
+ _root?: Maybe
+ name?: Maybe
+ oldValue?: Maybe
+ newValue?: Maybe
+}
+
+export type AuditRecordChangeField_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type AuditRecordChangeFieldInput = {
+ name?: Maybe
+ oldValue?: Maybe
+ newValue?: Maybe
+}
+
+export type AuditRecordChangeInput = {
+ type?: Maybe
+ path?: Maybe
+ fields?: Maybe>>
+}
+
+export type AuditRecordInput = {
+ id?: Maybe
+ changes?: Maybe>>
+ auditInfo?: Maybe
+}
+
+export type B2BAccount = {
+ __typename?: 'B2BAccount'
+ _get?: Maybe
+ _root?: Maybe
+ users?: Maybe>>
+ isActive?: Maybe
+ priceList?: Maybe
+ salesReps?: Maybe>>
+ rootAccountId?: Maybe
+ parentAccountId?: Maybe
+ approvalStatus?: Maybe
+ id: Scalars['Int']
+ customerSet?: Maybe
+ commerceSummary?: Maybe
+ contacts?: Maybe>>
+ companyOrOrganization?: Maybe
+ notes?: Maybe>>
+ attributes?: Maybe>>
+ segments?: Maybe>>
+ taxId?: Maybe
+ externalId?: Maybe
+ auditInfo?: Maybe
+ customerSinceDate?: Maybe
+ accountType?: Maybe
+}
+
+export type B2BAccount_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type B2BAccountCollection = {
+ __typename?: 'B2BAccountCollection'
+ _get?: Maybe
+ _root?: Maybe
+ startIndex: Scalars['Int']
+ pageSize: Scalars['Int']
+ pageCount: Scalars['Int']
+ totalCount: Scalars['Int']
+ items?: Maybe>>
+}
+
+export type B2BAccountCollection_GetArgs = {
+ path: Scalars['String']
+ defaultValue?: Maybe
+ allowUndefined?: Maybe
+}
+
+export type B2BAccountInput = {
+ users?: Maybe>>
+ isActive?: Maybe
+ priceList?: Maybe
+ salesReps?: Maybe>>
+ rootAccountId?: Maybe
+ parentAccountId?: Maybe
+ approvalStatus?: Maybe
+ id: Scalars['Int']
+ customerSet?: Maybe
+ commerceSummary?: Maybe
+ contacts?: Maybe>>
+ companyOrOrganization?: Maybe
+ notes?: Maybe>>
+ attributes?: Maybe>>
+ segments?: Maybe>>
+ taxId?: Maybe
+ externalId?: Maybe
+ auditInfo?: Maybe
+ customerSinceDate?: Maybe
+ accountType?: Maybe
+}
+
+export type B2BUser = {
+ __typename?: 'B2BUser'
+ _get?: Maybe
+ _root?: Maybe
+ emailAddress?: Maybe
+ userName?: Maybe
+ firstName?: Maybe
+ lastName?: Maybe
+ localeCode?: Maybe
+ userId?: Maybe
+ roles?: Maybe>>
+ isLocked?: Maybe
+ isActive?: Maybe
+ isRemoved?: Maybe