mirror of
https://github.com/vercel/commerce.git
synced 2025-07-04 12:11:22 +00:00
Feature/icky 194 (#5)
* folder and env setup * codegen.json headers removed * use-cart code flow updated * use-cart code flow updated * Implemented get-cart functionality * removed unused file * getAnonymousShopperToken function added * normalization mapping updated * PR points resolved * Anonymous shopper token query added * getAnonymousShopperToken function added * Anonymous shopper token query added Co-authored-by: Chandradeepta <43542673+Chandradeepta@users.noreply.github.com>
This commit is contained in:
parent
9e92abdda0
commit
0e5c68ef58
@ -28,3 +28,4 @@ KIBO_API_TOKEN=
|
||||
KIBO_API_URL=
|
||||
KIBO_CART_COOKIE=
|
||||
KIBO_CUSTOMER_COOKIE=
|
||||
KIBO_API_HOST=
|
||||
|
15
.vscode/launch.json
vendored
Normal file
15
.vscode/launch.json
vendored
Normal file
@ -0,0 +1,15 @@
|
||||
{
|
||||
// Use IntelliSense to learn about possible attributes.
|
||||
// Hover to view descriptions of existing attributes.
|
||||
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
|
||||
"version": "0.2.0",
|
||||
"configurations": [
|
||||
{
|
||||
"type": "node",
|
||||
"request": "attach",
|
||||
"name": "Attach to Next JS App",
|
||||
"skipFiles": ["<node_internals>/**"],
|
||||
"port": 9229
|
||||
}
|
||||
]
|
||||
}
|
@ -1 +1 @@
|
||||
# Next.js Local Provider
|
||||
# Next.js Kibo Provider
|
||||
|
23
framework/kibocommerce/api/endpoints/cart/get-cart.ts
Normal file
23
framework/kibocommerce/api/endpoints/cart/get-cart.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import { normalizeCart } from '@framework/lib/normalize'
|
||||
import { Cart } from '@framework/schema'
|
||||
import type { CartEndpoint } from '.'
|
||||
import { getCartQuery } from '../../queries/getCartQuery'
|
||||
|
||||
const getCart: CartEndpoint['handlers']['getCart'] = async ({
|
||||
res,
|
||||
body: { cartId },
|
||||
config,
|
||||
}) => {
|
||||
let currentCart: Cart = {}
|
||||
try {
|
||||
let result = await config.fetch(getCartQuery)
|
||||
currentCart = result?.data?.currentCart
|
||||
} catch (error) {
|
||||
throw error
|
||||
}
|
||||
res.status(200).json({
|
||||
data: currentCart ? normalizeCart(currentCart) : null,
|
||||
})
|
||||
}
|
||||
|
||||
export default getCart
|
@ -1 +1,26 @@
|
||||
export default function noopApi(...args: any[]): void {}
|
||||
import { GetAPISchema, createEndpoint } from '@commerce/api'
|
||||
import cartEndpoint from '@commerce/api/endpoints/cart'
|
||||
// import type { CartSchema } from '../../../types/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
|
||||
|
@ -1,6 +1,6 @@
|
||||
import type { CommerceAPI, CommerceAPIConfig } from '@commerce/api'
|
||||
import { getCommerceApi as commerceApi } from '@commerce/api'
|
||||
import createFetcher from './utils/fetch-local'
|
||||
import createFetchGraphqlApi from './utils/fetch-graphql-api'
|
||||
|
||||
import getAllPages from './operations/get-all-pages'
|
||||
import getPage from './operations/get-page'
|
||||
@ -9,15 +9,28 @@ 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 createFetchStoreApi from './utils/fetch-store-api'
|
||||
import type { RequestInit } from '@vercel/fetch'
|
||||
|
||||
export interface KiboCommerceConfig extends CommerceAPIConfig {
|
||||
apiHost?: string
|
||||
clientId?: string
|
||||
sharedSecret?: string
|
||||
storeApiFetch<T>(endpoint: string, options?: RequestInit): Promise<T>
|
||||
}
|
||||
|
||||
export interface KiboCommerceConfig extends CommerceAPIConfig {}
|
||||
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,
|
||||
fetch: createFetcher(() => getCommerceApi().getConfig()),
|
||||
fetch: createFetchGraphqlApi(() => getCommerceApi().getConfig()),
|
||||
// REST API
|
||||
apiHost: process.env.KIBO_API_HOST || '',
|
||||
clientId: process.env.KIBO_CLIENT_ID || '',
|
||||
sharedSecret: process.env.KIBO_SHARED_SECRET || '',
|
||||
storeApiFetch: createFetchStoreApi(() => getCommerceApi().getConfig()),
|
||||
}
|
||||
|
||||
const operations = {
|
||||
|
@ -0,0 +1,7 @@
|
||||
export const getAnonymousShopperTokenQuery = /* GraphQL */ `
|
||||
query {
|
||||
getAnonymousShopperToken {
|
||||
accessToken
|
||||
}
|
||||
}
|
||||
`
|
54
framework/kibocommerce/api/queries/getCartQuery.ts
Normal file
54
framework/kibocommerce/api/queries/getCartQuery.ts
Normal file
@ -0,0 +1,54 @@
|
||||
export const getCartQuery = /* GraphQL */`
|
||||
query cart {
|
||||
currentCart {
|
||||
id
|
||||
userId
|
||||
orderDiscounts {
|
||||
impact
|
||||
discount {
|
||||
id
|
||||
name
|
||||
}
|
||||
couponCode
|
||||
}
|
||||
subtotal
|
||||
shippingTotal
|
||||
total
|
||||
items {
|
||||
id
|
||||
subtotal
|
||||
unitPrice{
|
||||
extendedAmount
|
||||
}
|
||||
product {
|
||||
productCode
|
||||
variationProductCode
|
||||
name
|
||||
description
|
||||
imageUrl
|
||||
options {
|
||||
attributeFQN
|
||||
name
|
||||
value
|
||||
}
|
||||
properties {
|
||||
attributeFQN
|
||||
name
|
||||
values {
|
||||
value
|
||||
}
|
||||
}
|
||||
sku
|
||||
price {
|
||||
price
|
||||
salePrice
|
||||
}
|
||||
categories {
|
||||
id
|
||||
}
|
||||
}
|
||||
quantity
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
43
framework/kibocommerce/api/utils/fetch-graphql-api.ts
Normal file
43
framework/kibocommerce/api/utils/fetch-graphql-api.ts
Normal file
@ -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 getAnonymousShopperToken from './get-anonymous-shopper-token'
|
||||
|
||||
const fetchGraphqlApi: (
|
||||
getConfig: () => KiboCommerceConfig
|
||||
) => GraphQLFetcher = (getConfig) => async (
|
||||
query: string,
|
||||
{ variables, preview } = {},
|
||||
fetchOptions
|
||||
) => {
|
||||
const config = getConfig()
|
||||
const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), {
|
||||
...fetchOptions,
|
||||
method: 'POST',
|
||||
headers: {
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
...fetchOptions?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
// Need to fetch access token from cookie
|
||||
'x-vol-user-claims':
|
||||
'z40ROeWoZYd65SxBHSqnq/j/SRP0tIBHAf/3Sxw2MJLS8lmj1sF9Y+8eWaTObnCbAtFkiNx/BPfojtUFYQj2P9aVPgHsR+IaTpeAdfG1AM0fMLFvIrDbHK6E/BKhupU5NJQAFwYsoImRzIh8jOpXrigBWH9OW/dBjOtuAJaDaDRHdZ3xyDKZQnFa24IZN6b/UZYHf4r6arUU3MjPoVibQdtBObtJPYwe3XtOI/xaInqpehTJPq9nTZlTWR8Tv59UelC4bVWIuGtSAdawmuSS7H8pb5PemmB9MwMeLkGaWZsaRdxMfdOJE8REGqOYr3j89iEj/0a6G1zraVbLzGXyW0hVkz6InxARzA4p96n2n+ZCwWI/olcQKTxJCLsoZ3dVVkWretgUJFMxzAbzDEDtUIda+VuhzhhmlY4SFgOjxtSIudlyAcYs4xwksjDhBtt8RrTyobCUUau1sfht9Zf1pw==',
|
||||
},
|
||||
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
|
68
framework/kibocommerce/api/utils/fetch-store-api.ts
Normal file
68
framework/kibocommerce/api/utils/fetch-store-api.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import type { RequestInit, Response } from '@vercel/fetch'
|
||||
import type { KiboCommerceConfig } from '../index'
|
||||
import fetch from './fetch'
|
||||
|
||||
const fetchStoreApi = <T>(getConfig: () => KiboCommerceConfig) => async (
|
||||
endpoint: string,
|
||||
options?: RequestInit
|
||||
): Promise<T> => {
|
||||
const config = getConfig()
|
||||
let res: Response
|
||||
try {
|
||||
res = await fetch(config.apiHost + endpoint, {
|
||||
...options,
|
||||
headers: {
|
||||
...options?.headers,
|
||||
'Content-Type': 'application/json',
|
||||
Authorization: `Bearer ${config.apiToken}`,
|
||||
},
|
||||
})
|
||||
} catch (error) {
|
||||
throw new Error(`Fetch to Kibocommerce failed: ${error.message}`)
|
||||
}
|
||||
|
||||
const contentType = res.headers.get('Content-Type')
|
||||
const isJSON = contentType?.includes('application/json')
|
||||
|
||||
if (!res.ok) {
|
||||
const data = isJSON ? await res.json() : await getTextOrNull(res)
|
||||
console.log('-----------anon-----------', data)
|
||||
const headers = getRawHeaders(res)
|
||||
const msg = `Kibo Commerce API error (${
|
||||
res.status
|
||||
}) \nHeaders: ${JSON.stringify(headers, null, 2)}\n${
|
||||
typeof data === 'string' ? data : JSON.stringify(data, null, 2)
|
||||
}`
|
||||
|
||||
// throw new BigcommerceApiError(msg, res, data)
|
||||
}
|
||||
|
||||
// if (res.status !== 204 && !isJSON) {
|
||||
// throw new BigcommerceApiError(
|
||||
// `Fetch to Bigcommerce API failed, expected JSON content but found: ${contentType}`,
|
||||
// res
|
||||
// )
|
||||
// }
|
||||
|
||||
// If something was removed, the response will be empty
|
||||
return res.status === 200 && (await res.json())
|
||||
}
|
||||
export default fetchStoreApi
|
||||
|
||||
function getRawHeaders(res: Response) {
|
||||
const headers: { [key: string]: string } = {}
|
||||
|
||||
res.headers.forEach((value, key) => {
|
||||
headers[key] = value
|
||||
})
|
||||
|
||||
return headers
|
||||
}
|
||||
|
||||
function getTextOrNull(res: Response) {
|
||||
try {
|
||||
return res.text()
|
||||
} catch (err) {
|
||||
return null
|
||||
}
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
import type { KiboCommerceConfig } from '../'
|
||||
import { getAnonymousShopperTokenQuery } from '../queries/getAnonymousShopperTokenQuery'
|
||||
|
||||
async function getAnonymousShopperToken({
|
||||
config,
|
||||
}: {
|
||||
config: KiboCommerceConfig
|
||||
}): Promise<string | undefined> {
|
||||
const { data } = await config.fetch(getAnonymousShopperTokenQuery)
|
||||
return String(data?.getAnonymousShopperToken?.accessToken)
|
||||
}
|
||||
|
||||
export default getAnonymousShopperToken
|
@ -6,37 +6,28 @@ export default useCart as UseCart<typeof handler>
|
||||
|
||||
export const handler: SWRHook<any> = {
|
||||
fetchOptions: {
|
||||
query: '',
|
||||
method: 'GET',
|
||||
url: '/api/cart',
|
||||
},
|
||||
async fetcher() {
|
||||
return {
|
||||
id: '',
|
||||
createdAt: '',
|
||||
currency: { code: '' },
|
||||
taxesIncluded: '',
|
||||
lineItems: [],
|
||||
lineItemsSubtotalPrice: '',
|
||||
subtotalPrice: 0,
|
||||
totalPrice: 0,
|
||||
}
|
||||
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]
|
||||
)
|
||||
},
|
||||
useHook:
|
||||
({ useData }) =>
|
||||
(input) => {
|
||||
return useMemo(
|
||||
() =>
|
||||
Object.create(
|
||||
{},
|
||||
{
|
||||
isEmpty: {
|
||||
get() {
|
||||
return true
|
||||
},
|
||||
enumerable: true,
|
||||
},
|
||||
}
|
||||
),
|
||||
[]
|
||||
)
|
||||
},
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
{
|
||||
"provider": "kibocommerce",
|
||||
"features": {
|
||||
"wishlist": false,
|
||||
"cart": false,
|
||||
"search": false,
|
||||
"customerAuth": false
|
||||
"wishlist": true,
|
||||
"cart": true,
|
||||
"search": true,
|
||||
"customerAuth": true
|
||||
}
|
||||
}
|
||||
|
5
framework/kibocommerce/lib/get-slug.ts
Normal file
5
framework/kibocommerce/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
framework/kibocommerce/lib/immutability.ts
Normal file
13
framework/kibocommerce/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
|
137
framework/kibocommerce/lib/normalize.ts
Normal file
137
framework/kibocommerce/lib/normalize.ts
Normal file
@ -0,0 +1,137 @@
|
||||
// import type { Product } from '../types/product'
|
||||
// import type { Cart, BigcommerceCart, LineItem } from '../types/cart'
|
||||
// import type { Page } from '../types/page'
|
||||
// import type { BCCategory, Category } from '../types/site'
|
||||
// import { definitions } from '../api/definitions/store-content'
|
||||
import update from './immutability'
|
||||
import getSlug from './get-slug'
|
||||
|
||||
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): any {
|
||||
const {
|
||||
entityId: id,
|
||||
productOptions,
|
||||
prices,
|
||||
path,
|
||||
id: _,
|
||||
options: _0,
|
||||
} = productNode
|
||||
|
||||
return update(productNode, {
|
||||
id: { $set: String(id) },
|
||||
images: {
|
||||
$apply: ({ edges }: any) =>
|
||||
edges?.map(({ node: { urlOriginal, altText, ...rest } }: any) => ({
|
||||
url: urlOriginal,
|
||||
alt: altText,
|
||||
...rest,
|
||||
})),
|
||||
},
|
||||
variants: {
|
||||
$apply: ({ edges }: any) =>
|
||||
edges?.map(({ node: { entityId, productOptions, ...rest } }: any) => ({
|
||||
id: entityId,
|
||||
options: productOptions?.edges
|
||||
? productOptions.edges.map(normalizeProductOption)
|
||||
: [],
|
||||
...rest,
|
||||
})),
|
||||
},
|
||||
options: {
|
||||
$set: productOptions.edges
|
||||
? productOptions?.edges.map(normalizeProductOption)
|
||||
: [],
|
||||
},
|
||||
brand: {
|
||||
$apply: (brand: any) => (brand?.entityId ? brand?.entityId : null),
|
||||
},
|
||||
slug: {
|
||||
$set: path?.replace(/^\/+|\/+$/g, ''),
|
||||
},
|
||||
price: {
|
||||
$set: {
|
||||
value: prices?.price.value,
|
||||
currencyCode: prices?.price.currencyCode,
|
||||
},
|
||||
},
|
||||
$unset: ['entityId'],
|
||||
})
|
||||
}
|
||||
|
||||
export function normalizePage(page: any): any {
|
||||
return {
|
||||
id: String(page.id),
|
||||
name: page.name,
|
||||
is_visible: page.is_visible,
|
||||
sort_order: page.sort_order,
|
||||
body: page.body,
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
},
|
||||
path: `${item.product.productCode}/na`,
|
||||
discounts: item?.discounts?.map((discount: any) => ({
|
||||
value: discount.discounted_amount,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
export function normalizeCategory(category: any): any {
|
||||
return {
|
||||
id: `${category.entityId}`,
|
||||
name: category.name,
|
||||
slug: getSlug(category.path),
|
||||
path: category.path,
|
||||
}
|
||||
}
|
@ -11,7 +11,7 @@ import { handler as useSignup } from './auth/use-signup'
|
||||
|
||||
export const kiboCommerceProvider = {
|
||||
locale: 'en-us',
|
||||
cartCookie: 'bc_cartId',
|
||||
cartCookie: 'kibo_cart',
|
||||
fetcher,
|
||||
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
|
||||
customer: { useCustomer },
|
||||
|
@ -30,13 +30,13 @@ export default function Cart() {
|
||||
const { price: subTotal } = usePrice(
|
||||
data && {
|
||||
amount: Number(data.subtotalPrice),
|
||||
currencyCode: data.currency.code,
|
||||
currencyCode: data?.currency?.code,
|
||||
}
|
||||
)
|
||||
const { price: total } = usePrice(
|
||||
data && {
|
||||
amount: Number(data.totalPrice),
|
||||
currencyCode: data.currency.code,
|
||||
currencyCode: data?.currency?.code,
|
||||
}
|
||||
)
|
||||
|
||||
@ -83,7 +83,7 @@ export default function Cart() {
|
||||
<CartItem
|
||||
key={item.id}
|
||||
item={item}
|
||||
currencyCode={data?.currency.code!}
|
||||
currencyCode={data?.currency?.code!}
|
||||
/>
|
||||
))}
|
||||
</ul>
|
||||
|
Loading…
x
Reference in New Issue
Block a user