From 11200b3bb1853fc48f17da323f0120ed0dc13098 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 09:48:59 -0500 Subject: [PATCH 01/11] Updated catalog products --- .../api/endpoints/catalog/products/index.ts | 12 ++++++++++-- pages/api/catalog/products.ts | 11 ++--------- 2 files changed, 12 insertions(+), 11 deletions(-) diff --git a/framework/bigcommerce/api/endpoints/catalog/products/index.ts b/framework/bigcommerce/api/endpoints/catalog/products/index.ts index 3e13e68b8..555740f60 100644 --- a/framework/bigcommerce/api/endpoints/catalog/products/index.ts +++ b/framework/bigcommerce/api/endpoints/catalog/products/index.ts @@ -1,4 +1,5 @@ -import type { GetAPISchema } from '@commerce/api' +import { GetAPISchema, createEndpoint } from '@commerce/api' +import productsEndpoint from '@commerce/api/endpoints/catalog/products' import type { ProductsSchema } from '../../../../types/product' import type { BigcommerceAPI } from '../../..' import getProducts from './get-products' @@ -7,4 +8,11 @@ export type ProductsAPI = GetAPISchema export type ProductsEndpoint = ProductsAPI['endpoint'] -export const handlers = { getProducts } +export const handlers: ProductsEndpoint['handlers'] = { getProducts } + +const productsApi = createEndpoint({ + handler: productsEndpoint, + handlers, +}) + +export default productsApi diff --git a/pages/api/catalog/products.ts b/pages/api/catalog/products.ts index 5e5a4707d..631bfd516 100644 --- a/pages/api/catalog/products.ts +++ b/pages/api/catalog/products.ts @@ -1,11 +1,4 @@ -import products from '@commerce/api/endpoints/catalog/products' -import { - ProductsAPI, - handlers, -} from '@framework/api/endpoints/catalog/products' +import productsApi from '@framework/api/endpoints/catalog/products' import commerce from '@lib/api/commerce' -export default commerce.endpoint({ - handler: products as ProductsAPI['endpoint']['handler'], - handlers, -}) +export default productsApi(commerce) From 800ba45faeebe86cbcd3d75026c7b1b1aaee468b Mon Sep 17 00:00:00 2001 From: cond0r Date: Mon, 24 May 2021 19:44:38 +0300 Subject: [PATCH 02/11] Fetch only first 100 best selling products (#310) * Fetch only first 250 best selling products * Update get-all-product-paths.ts * Update use-customer.tsx --- framework/shopify/customer/use-customer.tsx | 14 ++++++++----- .../shopify/product/get-all-product-paths.ts | 21 +++++++++---------- framework/shopify/product/get-all-products.ts | 7 +++---- 3 files changed, 22 insertions(+), 20 deletions(-) diff --git a/framework/shopify/customer/use-customer.tsx b/framework/shopify/customer/use-customer.tsx index 137f0da74..7b600838e 100644 --- a/framework/shopify/customer/use-customer.tsx +++ b/framework/shopify/customer/use-customer.tsx @@ -10,11 +10,15 @@ export const handler: SWRHook = { query: getCustomerQuery, }, async fetcher({ options, fetch }) { - const data = await fetch({ - ...options, - variables: { customerAccessToken: getCustomerToken() }, - }) - return data.customer ?? null + const customerAccessToken = getCustomerToken() + if (customerAccessToken) { + const data = await fetch({ + ...options, + variables: { customerAccessToken: getCustomerToken() }, + }) + return data.customer + } + return null }, useHook: ({ useData }) => (input) => { return useData({ diff --git a/framework/shopify/product/get-all-product-paths.ts b/framework/shopify/product/get-all-product-paths.ts index 4431d1e53..e8ee04065 100644 --- a/framework/shopify/product/get-all-product-paths.ts +++ b/framework/shopify/product/get-all-product-paths.ts @@ -1,6 +1,4 @@ -import { Product } from '@commerce/types' import { getConfig, ShopifyConfig } from '../api' -import fetchAllProducts from '../api/utils/fetch-all-products' import { ProductEdge } from '../schema' import getAllProductsPathsQuery from '../utils/queries/get-all-products-paths-query' @@ -21,21 +19,22 @@ const getAllProductPaths = async (options?: { config?: ShopifyConfig preview?: boolean }): Promise => { - let { config, variables = { first: 250 } } = options ?? {} + let { config, variables = { first: 100, sortKey: 'BEST_SELLING' } } = + options ?? {} config = getConfig(config) - const products = await fetchAllProducts({ - config, - query: getAllProductsPathsQuery, + const { data } = await config.fetch(getAllProductsPathsQuery, { variables, }) return { - products: products?.map(({ node: { handle } }: ProductEdge) => ({ - node: { - path: `/${handle}`, - }, - })), + products: data.products?.edges?.map( + ({ node: { handle } }: ProductEdge) => ({ + node: { + path: `/${handle}`, + }, + }) + ), } } diff --git a/framework/shopify/product/get-all-products.ts b/framework/shopify/product/get-all-products.ts index 14e486563..3915abebf 100644 --- a/framework/shopify/product/get-all-products.ts +++ b/framework/shopify/product/get-all-products.ts @@ -27,10 +27,9 @@ const getAllProducts = async (options: { { variables } ) - const products = - data.products?.edges?.map(({ node: p }: ProductEdge) => - normalizeProduct(p) - ) ?? [] + const products = data.products?.edges?.map(({ node: p }: ProductEdge) => + normalizeProduct(p) + ) return { products, From 676b614bf65b4828931c17dcb8cb1d012f58a244 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 11:56:23 -0500 Subject: [PATCH 03/11] Moved checkout api --- .../api/endpoints/checkout/checkout.ts | 62 +++++++++++++++++++ .../api/endpoints/checkout/index.ts | 18 ++++++ framework/bigcommerce/types/checkout.ts | 1 + framework/commerce/api/endpoints/checkout.ts | 35 +++++++++++ framework/commerce/api/index.ts | 2 + framework/commerce/types/checkout.ts | 10 +++ pages/api/bigcommerce/checkout.ts | 3 - pages/api/checkout.ts | 4 ++ 8 files changed, 132 insertions(+), 3 deletions(-) create mode 100644 framework/bigcommerce/api/endpoints/checkout/checkout.ts create mode 100644 framework/bigcommerce/api/endpoints/checkout/index.ts create mode 100644 framework/bigcommerce/types/checkout.ts create mode 100644 framework/commerce/api/endpoints/checkout.ts create mode 100644 framework/commerce/types/checkout.ts delete mode 100644 pages/api/bigcommerce/checkout.ts create mode 100644 pages/api/checkout.ts diff --git a/framework/bigcommerce/api/endpoints/checkout/checkout.ts b/framework/bigcommerce/api/endpoints/checkout/checkout.ts new file mode 100644 index 000000000..517a57950 --- /dev/null +++ b/framework/bigcommerce/api/endpoints/checkout/checkout.ts @@ -0,0 +1,62 @@ +import type { CheckoutEndpoint } from '.' + +const fullCheckout = true + +const checkout: CheckoutEndpoint['handlers']['checkout'] = async ({ + req, + res, + config, +}) => { + const { cookies } = req + const cartId = cookies[config.cartCookie] + + if (!cartId) { + res.redirect('/cart') + return + } + + const { data } = await config.storeApiFetch( + `/v3/carts/${cartId}/redirect_urls`, + { + method: 'POST', + } + ) + + if (fullCheckout) { + res.redirect(data.checkout_url) + return + } + + // TODO: make the embedded checkout work too! + const html = ` + + + + + + Checkout + + + + +
+ + + ` + + res.status(200) + res.setHeader('Content-Type', 'text/html') + res.write(html) + res.end() +} + +export default checkout diff --git a/framework/bigcommerce/api/endpoints/checkout/index.ts b/framework/bigcommerce/api/endpoints/checkout/index.ts new file mode 100644 index 000000000..eaba32e47 --- /dev/null +++ b/framework/bigcommerce/api/endpoints/checkout/index.ts @@ -0,0 +1,18 @@ +import { GetAPISchema, createEndpoint } from '@commerce/api' +import checkoutEndpoint from '@commerce/api/endpoints/checkout' +import type { CheckoutSchema } from '../../../types/checkout' +import type { BigcommerceAPI } from '../..' +import checkout from './checkout' + +export type CheckoutAPI = GetAPISchema + +export type CheckoutEndpoint = CheckoutAPI['endpoint'] + +export const handlers: CheckoutEndpoint['handlers'] = { checkout } + +const checkoutApi = createEndpoint({ + handler: checkoutEndpoint, + handlers, +}) + +export default checkoutApi diff --git a/framework/bigcommerce/types/checkout.ts b/framework/bigcommerce/types/checkout.ts new file mode 100644 index 000000000..4e2412ef6 --- /dev/null +++ b/framework/bigcommerce/types/checkout.ts @@ -0,0 +1 @@ +export * from '@commerce/types/checkout' diff --git a/framework/commerce/api/endpoints/checkout.ts b/framework/commerce/api/endpoints/checkout.ts new file mode 100644 index 000000000..b39239a6a --- /dev/null +++ b/framework/commerce/api/endpoints/checkout.ts @@ -0,0 +1,35 @@ +import type { CheckoutSchema } from '../../types/checkout' +import { CommerceAPIError } from '../utils/errors' +import isAllowedOperation from '../utils/is-allowed-operation' +import type { GetAPISchema } from '..' + +const checkoutEndpoint: GetAPISchema< + any, + CheckoutSchema +>['endpoint']['handler'] = async (ctx) => { + const { req, res, handlers } = ctx + + if ( + !isAllowedOperation(req, res, { + GET: handlers['checkout'], + }) + ) { + return + } + + try { + const body = null + return await handlers['checkout']({ ...ctx, body }) + } catch (error) { + console.error(error) + + const message = + error instanceof CommerceAPIError + ? 'An unexpected error ocurred with the Commerce API' + : 'An unexpected error ocurred' + + res.status(500).json({ data: null, errors: [{ message }] }) + } +} + +export default checkoutEndpoint diff --git a/framework/commerce/api/index.ts b/framework/commerce/api/index.ts index 6a28a9597..8a08b92ee 100644 --- a/framework/commerce/api/index.ts +++ b/framework/commerce/api/index.ts @@ -8,6 +8,7 @@ import type { LogoutSchema } from '../types/logout' import type { SignupSchema } from '../types/signup' import type { ProductsSchema } from '../types/product' import type { WishlistSchema } from '../types/wishlist' +import type { CheckoutSchema } from '../types/checkout' import { defaultOperations, OPERATIONS, @@ -23,6 +24,7 @@ export type APISchemas = | SignupSchema | ProductsSchema | WishlistSchema + | CheckoutSchema export type GetAPISchema< C extends CommerceAPI, diff --git a/framework/commerce/types/checkout.ts b/framework/commerce/types/checkout.ts new file mode 100644 index 000000000..9e3c7ecfa --- /dev/null +++ b/framework/commerce/types/checkout.ts @@ -0,0 +1,10 @@ +export type CheckoutSchema = { + endpoint: { + options: {} + handlers: { + checkout: { + data: null + } + } + } +} diff --git a/pages/api/bigcommerce/checkout.ts b/pages/api/bigcommerce/checkout.ts deleted file mode 100644 index bd754deab..000000000 --- a/pages/api/bigcommerce/checkout.ts +++ /dev/null @@ -1,3 +0,0 @@ -import checkoutApi from '@framework/api/checkout' - -export default checkoutApi() diff --git a/pages/api/checkout.ts b/pages/api/checkout.ts new file mode 100644 index 000000000..7bf0fd9aa --- /dev/null +++ b/pages/api/checkout.ts @@ -0,0 +1,4 @@ +import checkoutApi from '@framework/api/endpoints/checkout' +import commerce from '@lib/api/commerce' + +export default checkoutApi(commerce) From a1167e46f7f2f509f9e1c08e7b635dde38c300a4 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 12:16:11 -0500 Subject: [PATCH 04/11] Added the get customer wishlist operation --- .../api/endpoints/wishlist/add-item.ts | 5 +- .../api/endpoints/wishlist/get-wishlist.ts | 5 +- .../api/endpoints/wishlist/remove-item.ts | 5 +- .../wishlist/utils/get-customer-id.ts | 4 +- framework/bigcommerce/api/index.ts | 3 +- .../api/operations/get-customer-wishlist.ts | 81 +++++++++++++++++ .../api/wishlist/handlers/add-item.ts | 2 +- .../api/wishlist/handlers/get-wishlist.ts | 2 +- .../api/wishlist/handlers/remove-item.ts | 2 +- framework/bigcommerce/api/wishlist/index.ts | 2 +- .../customer/get-customer-wishlist.ts | 88 ------------------- framework/bigcommerce/types/wishlist.ts | 1 + framework/commerce/api/operations.ts | 17 ++++ framework/commerce/types/wishlist.ts | 7 ++ 14 files changed, 123 insertions(+), 101 deletions(-) create mode 100644 framework/bigcommerce/api/operations/get-customer-wishlist.ts delete mode 100644 framework/bigcommerce/customer/get-customer-wishlist.ts diff --git a/framework/bigcommerce/api/endpoints/wishlist/add-item.ts b/framework/bigcommerce/api/endpoints/wishlist/add-item.ts index c2fdd9eff..4c5970a5d 100644 --- a/framework/bigcommerce/api/endpoints/wishlist/add-item.ts +++ b/framework/bigcommerce/api/endpoints/wishlist/add-item.ts @@ -1,4 +1,4 @@ -import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import getCustomerWishlist from '../../operations/get-customer-wishlist' import { parseWishlistItem } from '../../utils/parse-item' import getCustomerId from './utils/get-customer-id' import type { WishlistEndpoint } from '.' @@ -8,6 +8,7 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({ res, body: { customerToken, item }, config, + commerce, }) => { if (!item) { return res.status(400).json({ @@ -26,7 +27,7 @@ const addItem: WishlistEndpoint['handlers']['addItem'] = async ({ }) } - const { wishlist } = await getCustomerWishlist({ + const { wishlist } = await commerce.getCustomerWishlist({ variables: { customerId }, config, }) diff --git a/framework/bigcommerce/api/endpoints/wishlist/get-wishlist.ts b/framework/bigcommerce/api/endpoints/wishlist/get-wishlist.ts index 43dcc0bfb..21443119c 100644 --- a/framework/bigcommerce/api/endpoints/wishlist/get-wishlist.ts +++ b/framework/bigcommerce/api/endpoints/wishlist/get-wishlist.ts @@ -1,13 +1,14 @@ import type { Wishlist } from '../../../types/wishlist' import type { WishlistEndpoint } from '.' import getCustomerId from './utils/get-customer-id' -import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import getCustomerWishlist from '../../operations/get-customer-wishlist' // Return wishlist info const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({ res, body: { customerToken, includeProducts }, config, + commerce, }) => { let result: { data?: Wishlist } = {} @@ -23,7 +24,7 @@ const getWishlist: WishlistEndpoint['handlers']['getWishlist'] = async ({ }) } - const { wishlist } = await getCustomerWishlist({ + const { wishlist } = await commerce.getCustomerWishlist({ variables: { customerId }, includeProducts, config, diff --git a/framework/bigcommerce/api/endpoints/wishlist/remove-item.ts b/framework/bigcommerce/api/endpoints/wishlist/remove-item.ts index 7a243b322..22ac31cf9 100644 --- a/framework/bigcommerce/api/endpoints/wishlist/remove-item.ts +++ b/framework/bigcommerce/api/endpoints/wishlist/remove-item.ts @@ -1,5 +1,5 @@ import type { Wishlist } from '../../../types/wishlist' -import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import getCustomerWishlist from '../../operations/get-customer-wishlist' import getCustomerId from './utils/get-customer-id' import type { WishlistEndpoint } from '.' @@ -8,12 +8,13 @@ const removeItem: WishlistEndpoint['handlers']['removeItem'] = async ({ res, body: { customerToken, itemId }, config, + commerce, }) => { const customerId = customerToken && (await getCustomerId({ customerToken, config })) const { wishlist } = (customerId && - (await getCustomerWishlist({ + (await commerce.getCustomerWishlist({ variables: { customerId }, config, }))) || diff --git a/framework/bigcommerce/api/endpoints/wishlist/utils/get-customer-id.ts b/framework/bigcommerce/api/endpoints/wishlist/utils/get-customer-id.ts index 5f9fc49df..603f8be2d 100644 --- a/framework/bigcommerce/api/endpoints/wishlist/utils/get-customer-id.ts +++ b/framework/bigcommerce/api/endpoints/wishlist/utils/get-customer-id.ts @@ -15,7 +15,7 @@ async function getCustomerId({ }: { customerToken: string config: BigcommerceConfig -}): Promise { +}): Promise { const { data } = await config.fetch( getCustomerIdQuery, undefined, @@ -26,7 +26,7 @@ async function getCustomerId({ } ) - return data?.customer?.entityId + return String(data?.customer?.entityId) } export default getCustomerId diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index 9eeea5b49..d33c5620b 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -20,6 +20,7 @@ import login from './operations/login' 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' export interface BigcommerceConfig extends CommerceAPIConfig { // Indicates if the returned metadata with translations should be applied to the @@ -115,7 +116,7 @@ const config2: BigcommerceConfig = { export const provider = { config: config2, - operations: { login, getAllPages, getPage, getSiteInfo }, + operations: { login, getAllPages, getPage, getSiteInfo, getCustomerWishlist }, } export type Provider = typeof provider diff --git a/framework/bigcommerce/api/operations/get-customer-wishlist.ts b/framework/bigcommerce/api/operations/get-customer-wishlist.ts new file mode 100644 index 000000000..f4036ee51 --- /dev/null +++ b/framework/bigcommerce/api/operations/get-customer-wishlist.ts @@ -0,0 +1,81 @@ +import type { + OperationContext, + OperationOptions, +} from '@commerce/api/operations' +import type { + GetCustomerWishlistOperation, + Wishlist, +} from '../../types/wishlist' +import type { RecursivePartial, RecursiveRequired } from '../utils/types' +import { BigcommerceConfig, Provider } from '..' +import getAllProducts, { ProductEdge } from '../../product/get-all-products' + +export default function getCustomerWishlistOperation({ + commerce, +}: OperationContext) { + async function getCustomerWishlist< + T extends GetCustomerWishlistOperation + >(opts: { + variables: T['variables'] + config?: BigcommerceConfig + includeProducts?: boolean + }): Promise + + async function getCustomerWishlist( + opts: { + variables: T['variables'] + config?: BigcommerceConfig + includeProducts?: boolean + } & OperationOptions + ): Promise + + async function getCustomerWishlist({ + config, + variables, + includeProducts, + }: { + url?: string + variables: T['variables'] + config?: BigcommerceConfig + includeProducts?: boolean + }): Promise { + config = commerce.getConfig(config) + + const { data = [] } = await config.storeApiFetch< + RecursivePartial<{ data: Wishlist[] }> + >(`/v3/wishlists?customer_id=${variables.customerId}`) + const wishlist = data[0] + + if (includeProducts && wishlist?.items?.length) { + const entityIds = wishlist.items + ?.map((item) => item?.product_id) + .filter((id): id is number => !!id) + + if (entityIds?.length) { + const graphqlData = await getAllProducts({ + variables: { first: 100, entityIds }, + config, + }) + // Put the products in an object that we can use to get them by id + const productsById = graphqlData.products.reduce<{ + [k: number]: ProductEdge + }>((prods, p) => { + prods[Number(p.id)] = p as any + return prods + }, {}) + // Populate the wishlist items with the graphql products + wishlist.items.forEach((item) => { + const product = item && productsById[item.product_id!] + if (item && product) { + // @ts-ignore Fix this type when the wishlist type is properly defined + item.product = product + } + }) + } + } + + return { wishlist: wishlist as RecursiveRequired } + } + + return getCustomerWishlist +} diff --git a/framework/bigcommerce/api/wishlist/handlers/add-item.ts b/framework/bigcommerce/api/wishlist/handlers/add-item.ts index cbf0ec9d6..aeaac2545 100644 --- a/framework/bigcommerce/api/wishlist/handlers/add-item.ts +++ b/framework/bigcommerce/api/wishlist/handlers/add-item.ts @@ -1,6 +1,6 @@ import type { WishlistHandlers } from '..' import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' -import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import getCustomerWishlist from '../../operations/get-customer-wishlist' import { parseWishlistItem } from '../../utils/parse-item' // Returns the wishlist of the signed customer diff --git a/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts b/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts index a1c74fb29..c5db21243 100644 --- a/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts +++ b/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts @@ -1,5 +1,5 @@ import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' -import getCustomerWishlist from '../../../customer/get-customer-wishlist' +import getCustomerWishlist from '../../operations/get-customer-wishlist' import type { Wishlist, WishlistHandlers } from '..' // Return wishlist info diff --git a/framework/bigcommerce/api/wishlist/handlers/remove-item.ts b/framework/bigcommerce/api/wishlist/handlers/remove-item.ts index ea5764a28..32e612119 100644 --- a/framework/bigcommerce/api/wishlist/handlers/remove-item.ts +++ b/framework/bigcommerce/api/wishlist/handlers/remove-item.ts @@ -1,7 +1,7 @@ import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' import getCustomerWishlist, { Wishlist, -} from '../../../customer/get-customer-wishlist' +} from '../../operations/get-customer-wishlist' import type { WishlistHandlers } from '..' // Return current wishlist info diff --git a/framework/bigcommerce/api/wishlist/index.ts b/framework/bigcommerce/api/wishlist/index.ts index 7c700689c..c7bef234f 100644 --- a/framework/bigcommerce/api/wishlist/index.ts +++ b/framework/bigcommerce/api/wishlist/index.ts @@ -7,7 +7,7 @@ import { BigcommerceApiError } from '../utils/errors' import type { Wishlist, WishlistItem, -} from '../../customer/get-customer-wishlist' +} from '../operations/get-customer-wishlist' import getWishlist from './handlers/get-wishlist' import addItem from './handlers/add-item' import removeItem from './handlers/remove-item' diff --git a/framework/bigcommerce/customer/get-customer-wishlist.ts b/framework/bigcommerce/customer/get-customer-wishlist.ts deleted file mode 100644 index 97e5654a9..000000000 --- a/framework/bigcommerce/customer/get-customer-wishlist.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' -import { definitions } from '../api/definitions/wishlist' -import { BigcommerceConfig, getConfig } from '../api' -import getAllProducts, { ProductEdge } from '../product/get-all-products' - -export type Wishlist = Omit & { - items?: WishlistItem[] -} - -export type WishlistItem = NonNullable< - definitions['wishlist_Full']['items'] ->[0] & { - product?: ProductEdge['node'] -} - -export type GetCustomerWishlistResult< - T extends { wishlist?: any } = { wishlist?: Wishlist } -> = T - -export type GetCustomerWishlistVariables = { - customerId: number -} - -async function getCustomerWishlist(opts: { - variables: GetCustomerWishlistVariables - config?: BigcommerceConfig - includeProducts?: boolean -}): Promise - -async function getCustomerWishlist< - T extends { wishlist?: any }, - V = any ->(opts: { - url: string - variables: V - config?: BigcommerceConfig - includeProducts?: boolean -}): Promise> - -async function getCustomerWishlist({ - config, - variables, - includeProducts, -}: { - url?: string - variables: GetCustomerWishlistVariables - config?: BigcommerceConfig - includeProducts?: boolean -}): Promise { - config = getConfig(config) - - const { data = [] } = await config.storeApiFetch< - RecursivePartial<{ data: Wishlist[] }> - >(`/v3/wishlists?customer_id=${variables.customerId}`) - const wishlist = data[0] - - if (includeProducts && wishlist?.items?.length) { - const entityIds = wishlist.items - ?.map((item) => item?.product_id) - .filter((id): id is number => !!id) - - if (entityIds?.length) { - const graphqlData = await getAllProducts({ - variables: { first: 100, entityIds }, - config, - }) - // Put the products in an object that we can use to get them by id - const productsById = graphqlData.products.reduce<{ - [k: number]: ProductEdge - }>((prods, p) => { - prods[Number(p.id)] = p as any - return prods - }, {}) - // Populate the wishlist items with the graphql products - wishlist.items.forEach((item) => { - const product = item && productsById[item.product_id!] - if (item && product) { - // @ts-ignore Fix this type when the wishlist type is properly defined - item.product = product - } - }) - } - } - - return { wishlist: wishlist as RecursiveRequired } -} - -export default getCustomerWishlist diff --git a/framework/bigcommerce/types/wishlist.ts b/framework/bigcommerce/types/wishlist.ts index 9c98a8df4..510299eac 100644 --- a/framework/bigcommerce/types/wishlist.ts +++ b/framework/bigcommerce/types/wishlist.ts @@ -20,3 +20,4 @@ export type WishlistTypes = { } export type WishlistSchema = Core.WishlistSchema +export type GetCustomerWishlistOperation = Core.GetCustomerWishlistOperation diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index b6529f059..2b8c9b77a 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -2,6 +2,7 @@ import type { ServerResponse } from 'http' import type { LoginOperation } from '../types/login' import type { GetAllPagesOperation, GetPageOperation } from '../types/page' import type { GetSiteInfoOperation } from '../types/site' +import type { GetCustomerWishlistOperation } from '../types/wishlist' import type { APIProvider, CommerceAPI } from '.' const noop = () => { @@ -77,6 +78,22 @@ export type Operations

= { } & OperationOptions ): Promise } + + getCustomerWishlist: { + (opts: { + variables: T['variables'] + config?: P['config'] + includeProducts?: boolean + }): Promise + + ( + opts: { + variables: T['variables'] + config?: P['config'] + includeProducts?: boolean + } & OperationOptions + ): Promise + } } export type APIOperations

= { diff --git a/framework/commerce/types/wishlist.ts b/framework/commerce/types/wishlist.ts index 70f900362..24c0a7c28 100644 --- a/framework/commerce/types/wishlist.ts +++ b/framework/commerce/types/wishlist.ts @@ -30,3 +30,10 @@ export type WishlistSchema = { } } } + +export type GetCustomerWishlistOperation< + T extends WishlistTypes = WishlistTypes +> = { + data: { wishlist?: T['wishlist'] } + variables: { customerId: string } +} From 29a720e75ec2de9f901b63a892ed9f99a4abe132 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 12:16:39 -0500 Subject: [PATCH 05/11] Removed old wishlist stuff --- .../api/wishlist/handlers/add-item.ts | 56 ---------- .../api/wishlist/handlers/get-wishlist.ts | 37 ------- .../api/wishlist/handlers/remove-item.ts | 39 ------- framework/bigcommerce/api/wishlist/index.ts | 104 ------------------ 4 files changed, 236 deletions(-) delete mode 100644 framework/bigcommerce/api/wishlist/handlers/add-item.ts delete mode 100644 framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts delete mode 100644 framework/bigcommerce/api/wishlist/handlers/remove-item.ts delete mode 100644 framework/bigcommerce/api/wishlist/index.ts diff --git a/framework/bigcommerce/api/wishlist/handlers/add-item.ts b/framework/bigcommerce/api/wishlist/handlers/add-item.ts deleted file mode 100644 index aeaac2545..000000000 --- a/framework/bigcommerce/api/wishlist/handlers/add-item.ts +++ /dev/null @@ -1,56 +0,0 @@ -import type { WishlistHandlers } from '..' -import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' -import getCustomerWishlist from '../../operations/get-customer-wishlist' -import { parseWishlistItem } from '../../utils/parse-item' - -// Returns the wishlist of the signed customer -const addItem: WishlistHandlers['addItem'] = async ({ - res, - body: { customerToken, item }, - config, -}) => { - if (!item) { - return res.status(400).json({ - data: null, - errors: [{ message: 'Missing item' }], - }) - } - - const customerId = - customerToken && (await getCustomerId({ customerToken, config })) - - if (!customerId) { - return res.status(400).json({ - data: null, - errors: [{ message: 'Invalid request' }], - }) - } - - const { wishlist } = await getCustomerWishlist({ - variables: { customerId }, - config, - }) - const options = { - method: 'POST', - body: JSON.stringify( - wishlist - ? { - items: [parseWishlistItem(item)], - } - : { - name: 'Wishlist', - customer_id: customerId, - items: [parseWishlistItem(item)], - is_public: false, - } - ), - } - - const { data } = wishlist - ? await config.storeApiFetch(`/v3/wishlists/${wishlist.id}/items`, options) - : await config.storeApiFetch('/v3/wishlists', options) - - res.status(200).json({ data }) -} - -export default addItem diff --git a/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts b/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts deleted file mode 100644 index c5db21243..000000000 --- a/framework/bigcommerce/api/wishlist/handlers/get-wishlist.ts +++ /dev/null @@ -1,37 +0,0 @@ -import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' -import getCustomerWishlist from '../../operations/get-customer-wishlist' -import type { Wishlist, WishlistHandlers } from '..' - -// Return wishlist info -const getWishlist: WishlistHandlers['getWishlist'] = async ({ - res, - body: { customerToken, includeProducts }, - config, -}) => { - let result: { data?: Wishlist } = {} - - if (customerToken) { - const customerId = - customerToken && (await getCustomerId({ customerToken, config })) - - if (!customerId) { - // If the customerToken is invalid, then this request is too - return res.status(404).json({ - data: null, - errors: [{ message: 'Wishlist not found' }], - }) - } - - const { wishlist } = await getCustomerWishlist({ - variables: { customerId }, - includeProducts, - config, - }) - - result = { data: wishlist } - } - - res.status(200).json({ data: result.data ?? null }) -} - -export default getWishlist diff --git a/framework/bigcommerce/api/wishlist/handlers/remove-item.ts b/framework/bigcommerce/api/wishlist/handlers/remove-item.ts deleted file mode 100644 index 32e612119..000000000 --- a/framework/bigcommerce/api/wishlist/handlers/remove-item.ts +++ /dev/null @@ -1,39 +0,0 @@ -import getCustomerId from '../../endpoints/wishlist/utils/get-customer-id' -import getCustomerWishlist, { - Wishlist, -} from '../../operations/get-customer-wishlist' -import type { WishlistHandlers } from '..' - -// Return current wishlist info -const removeItem: WishlistHandlers['removeItem'] = async ({ - res, - body: { customerToken, itemId }, - config, -}) => { - const customerId = - customerToken && (await getCustomerId({ customerToken, config })) - const { wishlist } = - (customerId && - (await getCustomerWishlist({ - variables: { customerId }, - config, - }))) || - {} - - if (!wishlist || !itemId) { - return res.status(400).json({ - data: null, - errors: [{ message: 'Invalid request' }], - }) - } - - const result = await config.storeApiFetch<{ data: Wishlist } | null>( - `/v3/wishlists/${wishlist.id}/items/${itemId}`, - { method: 'DELETE' } - ) - const data = result?.data ?? null - - res.status(200).json({ data }) -} - -export default removeItem diff --git a/framework/bigcommerce/api/wishlist/index.ts b/framework/bigcommerce/api/wishlist/index.ts deleted file mode 100644 index c7bef234f..000000000 --- a/framework/bigcommerce/api/wishlist/index.ts +++ /dev/null @@ -1,104 +0,0 @@ -import isAllowedMethod from '../utils/is-allowed-method' -import createApiHandler, { - BigcommerceApiHandler, - BigcommerceHandler, -} from '../utils/create-api-handler' -import { BigcommerceApiError } from '../utils/errors' -import type { - Wishlist, - WishlistItem, -} from '../operations/get-customer-wishlist' -import getWishlist from './handlers/get-wishlist' -import addItem from './handlers/add-item' -import removeItem from './handlers/remove-item' -import type { Product, ProductVariant, Customer } from '@commerce/types' - -export type { Wishlist, WishlistItem } - -export type ItemBody = { - productId: Product['id'] - variantId: ProductVariant['id'] -} - -export type AddItemBody = { item: ItemBody } - -export type RemoveItemBody = { itemId: Product['id'] } - -export type WishlistBody = { - customer_id: Customer['entityId'] - is_public: number - name: string - items: any[] -} - -export type AddWishlistBody = { wishlist: WishlistBody } - -export type WishlistHandlers = { - getWishlist: BigcommerceHandler< - Wishlist, - { customerToken?: string; includeProducts?: boolean } - > - addItem: BigcommerceHandler< - Wishlist, - { customerToken?: string } & Partial - > - removeItem: BigcommerceHandler< - Wishlist, - { customerToken?: string } & Partial - > -} - -const METHODS = ['GET', 'POST', 'DELETE'] - -// TODO: a complete implementation should have schema validation for `req.body` -const wishlistApi: BigcommerceApiHandler = async ( - req, - res, - config, - handlers -) => { - if (!isAllowedMethod(req, res, METHODS)) return - - const { cookies } = req - const customerToken = cookies[config.customerCookie] - - try { - // Return current wishlist info - if (req.method === 'GET') { - const body = { - customerToken, - includeProducts: req.query.products === '1', - } - return await handlers['getWishlist']({ req, res, config, body }) - } - - // Add an item to the wishlist - if (req.method === 'POST') { - const body = { ...req.body, customerToken } - return await handlers['addItem']({ req, res, config, body }) - } - - // Remove an item from the wishlist - if (req.method === 'DELETE') { - const body = { ...req.body, customerToken } - return await handlers['removeItem']({ req, res, config, body }) - } - } catch (error) { - console.error(error) - - const message = - error instanceof BigcommerceApiError - ? 'An unexpected error ocurred with the Bigcommerce API' - : 'An unexpected error ocurred' - - res.status(500).json({ data: null, errors: [{ message }] }) - } -} - -export const handlers = { - getWishlist, - addItem, - removeItem, -} - -export default createApiHandler(wishlistApi, handlers, {}) From 3fe75a2d6732c9aeaeea01db4fd1ada0b8598b20 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 16:32:45 -0500 Subject: [PATCH 06/11] Added getAllProductPaths operation --- framework/bigcommerce/api/index.ts | 10 ++- .../api/operations/get-all-product-paths.ts | 66 +++++++++++++++++ .../product/get-all-product-paths.ts | 71 ------------------- framework/commerce/api/operations.ts | 15 ++++ framework/commerce/types/product.ts | 7 ++ 5 files changed, 97 insertions(+), 72 deletions(-) create mode 100644 framework/bigcommerce/api/operations/get-all-product-paths.ts delete mode 100644 framework/bigcommerce/product/get-all-product-paths.ts diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index d33c5620b..542439cbb 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -21,6 +21,7 @@ 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' export interface BigcommerceConfig extends CommerceAPIConfig { // Indicates if the returned metadata with translations should be applied to the @@ -116,7 +117,14 @@ const config2: BigcommerceConfig = { export const provider = { config: config2, - operations: { login, getAllPages, getPage, getSiteInfo, getCustomerWishlist }, + operations: { + login, + getAllPages, + getPage, + getSiteInfo, + getCustomerWishlist, + getAllProductPaths, + }, } export type Provider = typeof provider diff --git a/framework/bigcommerce/api/operations/get-all-product-paths.ts b/framework/bigcommerce/api/operations/get-all-product-paths.ts new file mode 100644 index 000000000..302a4017b --- /dev/null +++ b/framework/bigcommerce/api/operations/get-all-product-paths.ts @@ -0,0 +1,66 @@ +import type { + OperationContext, + OperationOptions, +} from '@commerce/api/operations' +import type { GetAllProductPathsQuery } from '../../schema' +import type { GetAllProductPathsOperation } from '../../types/product' +import type { RecursivePartial, RecursiveRequired } from '../utils/types' +import filterEdges from '../utils/filter-edges' +import { BigcommerceConfig, Provider } from '..' + +export const getAllProductPathsQuery = /* GraphQL */ ` + query getAllProductPaths($first: Int = 100) { + site { + products(first: $first) { + edges { + node { + path + } + } + } + } + } +` + +export default function getAllProductPathsOperation({ + commerce, +}: OperationContext) { + async function getAllProductPaths< + T extends GetAllProductPathsOperation + >(opts?: { + variables?: T['variables'] + config?: BigcommerceConfig + }): Promise + + async function getAllProductPaths< + T extends GetAllProductPathsOperation + >(opts: { + variables?: T['variables'] + config?: BigcommerceConfig + } & OperationOptions): Promise + + async function getAllProductPaths({ + query = getAllProductPathsQuery, + variables, + config, + }: { + query?: string + variables?: T['variables'] + config?: BigcommerceConfig + } = {}): Promise { + config = commerce.getConfig(config) + // RecursivePartial forces the method to check for every prop in the data, which is + // required in case there's a custom `query` + const { data } = await config.fetch< + RecursivePartial + >(query, { variables }) + const products = data.site?.products?.edges + + return { + products: filterEdges(products as RecursiveRequired).map( + ({ node }) => node + ), + } + } + return getAllProductPaths +} diff --git a/framework/bigcommerce/product/get-all-product-paths.ts b/framework/bigcommerce/product/get-all-product-paths.ts deleted file mode 100644 index c1b23b38d..000000000 --- a/framework/bigcommerce/product/get-all-product-paths.ts +++ /dev/null @@ -1,71 +0,0 @@ -import type { - GetAllProductPathsQuery, - GetAllProductPathsQueryVariables, -} from '../schema' -import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' -import filterEdges from '../api/utils/filter-edges' -import { BigcommerceConfig, getConfig } from '../api' - -export const getAllProductPathsQuery = /* GraphQL */ ` - query getAllProductPaths($first: Int = 100) { - site { - products(first: $first) { - edges { - node { - path - } - } - } - } - } -` - -export type ProductPath = NonNullable< - NonNullable[0] -> - -export type ProductPaths = ProductPath[] - -export type { GetAllProductPathsQueryVariables } - -export type GetAllProductPathsResult< - T extends { products: any[] } = { products: ProductPaths } -> = T - -async function getAllProductPaths(opts?: { - variables?: GetAllProductPathsQueryVariables - config?: BigcommerceConfig -}): Promise - -async function getAllProductPaths< - T extends { products: any[] }, - V = any ->(opts: { - query: string - variables?: V - config?: BigcommerceConfig -}): Promise> - -async function getAllProductPaths({ - query = getAllProductPathsQuery, - variables, - config, -}: { - query?: string - variables?: GetAllProductPathsQueryVariables - config?: BigcommerceConfig -} = {}): Promise { - config = getConfig(config) - // RecursivePartial forces the method to check for every prop in the data, which is - // required in case there's a custom `query` - const { data } = await config.fetch< - RecursivePartial - >(query, { variables }) - const products = data.site?.products?.edges - - return { - products: filterEdges(products as RecursiveRequired), - } -} - -export default getAllProductPaths diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index 2b8c9b77a..eebff74aa 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -3,6 +3,7 @@ import type { LoginOperation } from '../types/login' import type { GetAllPagesOperation, GetPageOperation } from '../types/page' import type { GetSiteInfoOperation } from '../types/site' import type { GetCustomerWishlistOperation } from '../types/wishlist' +import type { GetAllProductPathsOperation } from '../types/product' import type { APIProvider, CommerceAPI } from '.' const noop = () => { @@ -94,6 +95,20 @@ export type Operations

= { } & OperationOptions ): Promise } + + getAllProductPaths: { + (opts: { + variables?: T['variables'] + config?: P['config'] + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + } & OperationOptions + ): Promise + } } export type APIOperations

= { diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index d33cf3743..22a045f48 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -65,3 +65,10 @@ export type ProductsSchema = { } } } + +export type GetAllProductPathsOperation< + T extends ProductTypes = ProductTypes +> = { + data: { products: Pick[] } + variables: { first?: number } +} From c2a5904e01bf6dd05b6ceb5121ab6cb7eb874070 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 16:34:06 -0500 Subject: [PATCH 07/11] updated reference to operation --- pages/product/[slug].tsx | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index f1a94032a..282df7247 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -10,7 +10,6 @@ import { ProductView } from '@components/product' import { getConfig } from '@framework/api' import getProduct from '@framework/product/get-product' -import getAllProductPaths from '@framework/product/get-all-product-paths' export async function getStaticProps({ params, @@ -39,18 +38,18 @@ export async function getStaticProps({ } export async function getStaticPaths({ locales }: GetStaticPathsContext) { - const { products } = await getAllProductPaths() + const { products } = await commerce.getAllProductPaths() return { paths: locales ? locales.reduce((arr, locale) => { // Add a product path for every locale products.forEach((product) => { - arr.push(`/${locale}/product${product.node.path}`) + arr.push(`/${locale}/product${product.path}`) }) return arr }, []) - : products.map((product) => `/product${product.node.path}`), + : products.map((product) => `/product${product.path}`), fallback: 'blocking', } } From be5d47bac32c1ffdfccc7482a028cde5098ff132 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 17:22:46 -0500 Subject: [PATCH 08/11] Moved getAllProducts --- .../catalog/products/get-products.ts | 14 +- framework/bigcommerce/api/index.ts | 2 + .../api/operations/get-all-products.ts | 160 ++++++++++++++++++ .../api/operations/get-customer-wishlist.ts | 14 +- .../api/utils/set-product-locale-meta.ts | 2 +- .../bigcommerce/product/get-all-products.ts | 135 --------------- framework/bigcommerce/product/index.ts | 2 +- framework/bigcommerce/types/wishlist.ts | 2 +- framework/commerce/api/operations.ts | 21 ++- framework/commerce/types/product.ts | 9 + pages/index.tsx | 3 +- 11 files changed, 209 insertions(+), 155 deletions(-) create mode 100644 framework/bigcommerce/api/operations/get-all-products.ts delete mode 100644 framework/bigcommerce/product/get-all-products.ts diff --git a/framework/bigcommerce/api/endpoints/catalog/products/get-products.ts b/framework/bigcommerce/api/endpoints/catalog/products/get-products.ts index e0cc27912..8471767aa 100644 --- a/framework/bigcommerce/api/endpoints/catalog/products/get-products.ts +++ b/framework/bigcommerce/api/endpoints/catalog/products/get-products.ts @@ -1,6 +1,5 @@ import { Product } from '@commerce/types/product' import { ProductsEndpoint } from '.' -import getAllProducts from '../../../../product/get-all-products' const SORT: { [key: string]: string | undefined } = { latest: 'id', @@ -15,6 +14,7 @@ const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({ res, body: { search, category, brand, sort }, config, + commerce, }) => { // Use a dummy base as we only care about the relative path const url = new URL('/v3/catalog/products', 'http://a') @@ -47,18 +47,18 @@ const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({ url.pathname + url.search ) - const entityIds = data.map((p) => p.id) - const found = entityIds.length > 0 + const ids = data.map((p) => String(p.id)) + const found = ids.length > 0 // We want the GraphQL version of each product - const graphqlData = await getAllProducts({ - variables: { first: LIMIT, entityIds }, + const graphqlData = await commerce.getAllProducts({ + variables: { first: LIMIT, ids }, config, }) // Put the products in an object that we can use to get them by id const productsById = graphqlData.products.reduce<{ - [k: number]: Product + [k: string]: Product }>((prods, p) => { prods[Number(p.id)] = p return prods @@ -68,7 +68,7 @@ const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({ // Populate the products array with the graphql products, in the order // assigned by the list of entity ids - entityIds.forEach((id) => { + ids.forEach((id) => { const product = productsById[id] if (product) products.push(product) }) diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index 542439cbb..c81107f5e 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -22,6 +22,7 @@ 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' export interface BigcommerceConfig extends CommerceAPIConfig { // Indicates if the returned metadata with translations should be applied to the @@ -124,6 +125,7 @@ export const provider = { getSiteInfo, getCustomerWishlist, getAllProductPaths, + getAllProducts, }, } diff --git a/framework/bigcommerce/api/operations/get-all-products.ts b/framework/bigcommerce/api/operations/get-all-products.ts new file mode 100644 index 000000000..ef9696c73 --- /dev/null +++ b/framework/bigcommerce/api/operations/get-all-products.ts @@ -0,0 +1,160 @@ +import type { + OperationContext, + OperationOptions, +} from '@commerce/api/operations' +import type { + GetAllProductsQuery, + GetAllProductsQueryVariables, +} from '../../schema' +import type { GetAllProductsOperation } from '../../types/product' +import type { RecursivePartial, RecursiveRequired } from '../utils/types' +import filterEdges from '../utils/filter-edges' +import setProductLocaleMeta from '../utils/set-product-locale-meta' +import { productConnectionFragment } from '../fragments/product' +import { BigcommerceConfig, Provider } from '..' +import { normalizeProduct } from '../../lib/normalize' + +export const getAllProductsQuery = /* GraphQL */ ` + query getAllProducts( + $hasLocale: Boolean = false + $locale: String = "null" + $entityIds: [Int!] + $first: Int = 10 + $products: Boolean = false + $featuredProducts: Boolean = false + $bestSellingProducts: Boolean = false + $newestProducts: Boolean = false + ) { + site { + products(first: $first, entityIds: $entityIds) @include(if: $products) { + ...productConnnection + } + featuredProducts(first: $first) @include(if: $featuredProducts) { + ...productConnnection + } + bestSellingProducts(first: $first) @include(if: $bestSellingProducts) { + ...productConnnection + } + newestProducts(first: $first) @include(if: $newestProducts) { + ...productConnnection + } + } + } + + ${productConnectionFragment} +` + +export type ProductEdge = NonNullable< + NonNullable[0] +> + +export type ProductNode = ProductEdge['node'] + +export type GetAllProductsResult< + T extends Record = { + products: ProductEdge[] + } +> = T + +const FIELDS = [ + 'products', + 'featuredProducts', + 'bestSellingProducts', + 'newestProducts', +] + +export type ProductTypes = + | 'products' + | 'featuredProducts' + | 'bestSellingProducts' + | 'newestProducts' + +export type ProductVariables = { field?: ProductTypes } & Omit< + GetAllProductsQueryVariables, + ProductTypes | 'hasLocale' +> + +function getProductsType( + relevance?: GetAllProductsOperation['variables']['relevance'] +): ProductTypes { + switch (relevance) { + case 'featured': + return 'featuredProducts' + case 'best_selling': + return 'bestSellingProducts' + case 'newest': + return 'newestProducts' + default: + return 'products' + } +} + +export default function getAllProductsOperation({ + commerce, +}: OperationContext) { + async function getAllProducts(opts?: { + variables?: T['variables'] + config?: BigcommerceConfig + preview?: boolean + }): Promise + + async function getAllProducts( + opts: { + variables?: T['variables'] + config?: BigcommerceConfig + preview?: boolean + } & OperationOptions + ): Promise + + async function getAllProducts({ + query = getAllProductsQuery, + variables: vars = {}, + config, + }: { + query?: string + variables?: T['variables'] + config?: BigcommerceConfig + preview?: boolean + } = {}): Promise { + config = commerce.getConfig(config) + + const locale = config.locale + const field = getProductsType(vars.relevance) + const variables: GetAllProductsQueryVariables = { + locale, + hasLocale: !!locale, + } + + if (!FIELDS.includes(field)) { + throw new Error( + `The field variable has to match one of ${FIELDS.join(', ')}` + ) + } + + variables[field] = true + + if (vars.first) variables.first = vars.first + if (vars.ids) variables.entityIds = vars.ids.map((id) => Number(id)) + + // RecursivePartial forces the method to check for every prop in the data, which is + // required in case there's a custom `query` + const { data } = await config.fetch>( + query, + { variables } + ) + const edges = data.site?.[field]?.edges + const products = filterEdges(edges as RecursiveRequired) + + if (locale && config.applyLocale) { + products.forEach((product: RecursivePartial) => { + if (product.node) setProductLocaleMeta(product.node) + }) + } + + return { + products: products.map(({ node }) => normalizeProduct(node as any)), + } + } + + return getAllProducts +} diff --git a/framework/bigcommerce/api/operations/get-customer-wishlist.ts b/framework/bigcommerce/api/operations/get-customer-wishlist.ts index f4036ee51..fc9487ffe 100644 --- a/framework/bigcommerce/api/operations/get-customer-wishlist.ts +++ b/framework/bigcommerce/api/operations/get-customer-wishlist.ts @@ -8,7 +8,7 @@ import type { } from '../../types/wishlist' import type { RecursivePartial, RecursiveRequired } from '../utils/types' import { BigcommerceConfig, Provider } from '..' -import getAllProducts, { ProductEdge } from '../../product/get-all-products' +import getAllProducts, { ProductEdge } from './get-all-products' export default function getCustomerWishlistOperation({ commerce, @@ -47,13 +47,13 @@ export default function getCustomerWishlistOperation({ const wishlist = data[0] if (includeProducts && wishlist?.items?.length) { - const entityIds = wishlist.items - ?.map((item) => item?.product_id) - .filter((id): id is number => !!id) + const ids = wishlist.items + ?.map((item) => (item?.product_id ? String(item?.product_id) : null)) + .filter((id): id is string => !!id) - if (entityIds?.length) { - const graphqlData = await getAllProducts({ - variables: { first: 100, entityIds }, + if (ids?.length) { + const graphqlData = await commerce.getAllProducts({ + variables: { first: 100, ids }, config, }) // Put the products in an object that we can use to get them by id diff --git a/framework/bigcommerce/api/utils/set-product-locale-meta.ts b/framework/bigcommerce/api/utils/set-product-locale-meta.ts index 974a197bd..767286477 100644 --- a/framework/bigcommerce/api/utils/set-product-locale-meta.ts +++ b/framework/bigcommerce/api/utils/set-product-locale-meta.ts @@ -1,4 +1,4 @@ -import type { ProductNode } from '../../product/get-all-products' +import type { ProductNode } from '../operations/get-all-products' import type { RecursivePartial } from './types' export default function setProductLocaleMeta( diff --git a/framework/bigcommerce/product/get-all-products.ts b/framework/bigcommerce/product/get-all-products.ts deleted file mode 100644 index 4c563bc62..000000000 --- a/framework/bigcommerce/product/get-all-products.ts +++ /dev/null @@ -1,135 +0,0 @@ -import type { - GetAllProductsQuery, - GetAllProductsQueryVariables, -} from '../schema' -import type { Product } from '@commerce/types' -import type { RecursivePartial, RecursiveRequired } from '../api/utils/types' -import filterEdges from '../api/utils/filter-edges' -import setProductLocaleMeta from '../api/utils/set-product-locale-meta' -import { productConnectionFragment } from '../api/fragments/product' -import { BigcommerceConfig, getConfig } from '../api' -import { normalizeProduct } from '../lib/normalize' - -export const getAllProductsQuery = /* GraphQL */ ` - query getAllProducts( - $hasLocale: Boolean = false - $locale: String = "null" - $entityIds: [Int!] - $first: Int = 10 - $products: Boolean = false - $featuredProducts: Boolean = false - $bestSellingProducts: Boolean = false - $newestProducts: Boolean = false - ) { - site { - products(first: $first, entityIds: $entityIds) @include(if: $products) { - ...productConnnection - } - featuredProducts(first: $first) @include(if: $featuredProducts) { - ...productConnnection - } - bestSellingProducts(first: $first) @include(if: $bestSellingProducts) { - ...productConnnection - } - newestProducts(first: $first) @include(if: $newestProducts) { - ...productConnnection - } - } - } - - ${productConnectionFragment} -` - -export type ProductEdge = NonNullable< - NonNullable[0] -> - -export type ProductNode = ProductEdge['node'] - -export type GetAllProductsResult< - T extends Record = { - products: ProductEdge[] - } -> = T - -const FIELDS = [ - 'products', - 'featuredProducts', - 'bestSellingProducts', - 'newestProducts', -] - -export type ProductTypes = - | 'products' - | 'featuredProducts' - | 'bestSellingProducts' - | 'newestProducts' - -export type ProductVariables = { field?: ProductTypes } & Omit< - GetAllProductsQueryVariables, - ProductTypes | 'hasLocale' -> - -async function getAllProducts(opts?: { - variables?: ProductVariables - config?: BigcommerceConfig - preview?: boolean -}): Promise<{ products: Product[] }> - -async function getAllProducts< - T extends Record, - V = any ->(opts: { - query: string - variables?: V - config?: BigcommerceConfig - preview?: boolean -}): Promise> - -async function getAllProducts({ - query = getAllProductsQuery, - variables: { field = 'products', ...vars } = {}, - config, -}: { - query?: string - variables?: ProductVariables - config?: BigcommerceConfig - preview?: boolean - // TODO: fix the product type here -} = {}): Promise<{ products: Product[] | any[] }> { - config = getConfig(config) - - const locale = vars.locale || config.locale - const variables: GetAllProductsQueryVariables = { - ...vars, - locale, - hasLocale: !!locale, - } - - if (!FIELDS.includes(field)) { - throw new Error( - `The field variable has to match one of ${FIELDS.join(', ')}` - ) - } - - variables[field] = true - - // RecursivePartial forces the method to check for every prop in the data, which is - // required in case there's a custom `query` - const { data } = await config.fetch>( - query, - { variables } - ) - const edges = data.site?.[field]?.edges - const products = filterEdges(edges as RecursiveRequired) - - if (locale && config.applyLocale) { - products.forEach((product: RecursivePartial) => { - if (product.node) setProductLocaleMeta(product.node) - }) - } - - return { products: products.map(({ node }) => normalizeProduct(node as any)) } -} - -export default getAllProducts diff --git a/framework/bigcommerce/product/index.ts b/framework/bigcommerce/product/index.ts index b290c189f..d5b1cefac 100644 --- a/framework/bigcommerce/product/index.ts +++ b/framework/bigcommerce/product/index.ts @@ -1,4 +1,4 @@ export { default as usePrice } from './use-price' export { default as useSearch } from './use-search' export { default as getProduct } from './get-product' -export { default as getAllProducts } from './get-all-products' +export { default as getAllProducts } from '../api/operations/get-all-products' diff --git a/framework/bigcommerce/types/wishlist.ts b/framework/bigcommerce/types/wishlist.ts index 510299eac..1e148b88c 100644 --- a/framework/bigcommerce/types/wishlist.ts +++ b/framework/bigcommerce/types/wishlist.ts @@ -1,6 +1,6 @@ import * as Core from '@commerce/types/wishlist' import { definitions } from '../api/definitions/wishlist' -import type { ProductEdge } from '../product/get-all-products' +import type { ProductEdge } from '../api/operations/get-all-products' export * from '@commerce/types/wishlist' diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index eebff74aa..c11ebf965 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -3,7 +3,10 @@ import type { LoginOperation } from '../types/login' import type { GetAllPagesOperation, GetPageOperation } from '../types/page' import type { GetSiteInfoOperation } from '../types/site' import type { GetCustomerWishlistOperation } from '../types/wishlist' -import type { GetAllProductPathsOperation } from '../types/product' +import type { + GetAllProductPathsOperation, + GetAllProductsOperation, +} from '../types/product' import type { APIProvider, CommerceAPI } from '.' const noop = () => { @@ -109,6 +112,22 @@ export type Operations

= { } & OperationOptions ): Promise } + + getAllProducts: { + (opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables?: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } } export type APIOperations

= { diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index 22a045f48..ed7e0e85b 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -72,3 +72,12 @@ export type GetAllProductPathsOperation< data: { products: Pick[] } variables: { first?: number } } + +export type GetAllProductsOperation = { + data: { products: T['product'][] } + variables: { + relevance?: 'featured' | 'best_selling' | 'newest' + ids?: string[] + first?: number + } +} diff --git a/pages/index.tsx b/pages/index.tsx index d74a649fb..b33ed585b 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -6,7 +6,6 @@ import { ProductCard } from '@components/product' import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next' import { getConfig } from '@framework/api' -import getAllProducts from '@framework/product/get-all-products' export async function getStaticProps({ preview, @@ -14,7 +13,7 @@ export async function getStaticProps({ }: GetStaticPropsContext) { const config = getConfig({ locale }) - const { products } = await getAllProducts({ + const { products } = await commerce.getAllProducts({ variables: { first: 12 }, config, preview, From a1066299643f16ce4a78bbc4c50b0f958ac3381f Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 18:39:07 -0500 Subject: [PATCH 09/11] Updated getProduct operation --- framework/bigcommerce/api/index.ts | 2 + .../api/operations/get-all-products.ts | 28 +--- .../bigcommerce/api/operations/get-product.ts | 120 +++++++++++++++++ framework/bigcommerce/product/get-product.ts | 121 ------------------ framework/bigcommerce/product/index.ts | 2 - framework/commerce/api/operations.ts | 17 +++ framework/commerce/types/product.ts | 5 + pages/product/[slug].tsx | 4 +- 8 files changed, 147 insertions(+), 152 deletions(-) create mode 100644 framework/bigcommerce/api/operations/get-product.ts delete mode 100644 framework/bigcommerce/product/get-product.ts diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index c81107f5e..c9567dc9a 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -23,6 +23,7 @@ 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' export interface BigcommerceConfig extends CommerceAPIConfig { // Indicates if the returned metadata with translations should be applied to the @@ -126,6 +127,7 @@ export const provider = { getCustomerWishlist, getAllProductPaths, getAllProducts, + getProduct, }, } diff --git a/framework/bigcommerce/api/operations/get-all-products.ts b/framework/bigcommerce/api/operations/get-all-products.ts index ef9696c73..c5e16a276 100644 --- a/framework/bigcommerce/api/operations/get-all-products.ts +++ b/framework/bigcommerce/api/operations/get-all-products.ts @@ -56,27 +56,9 @@ export type GetAllProductsResult< } > = T -const FIELDS = [ - 'products', - 'featuredProducts', - 'bestSellingProducts', - 'newestProducts', -] - -export type ProductTypes = - | 'products' - | 'featuredProducts' - | 'bestSellingProducts' - | 'newestProducts' - -export type ProductVariables = { field?: ProductTypes } & Omit< - GetAllProductsQueryVariables, - ProductTypes | 'hasLocale' -> - function getProductsType( relevance?: GetAllProductsOperation['variables']['relevance'] -): ProductTypes { +) { switch (relevance) { case 'featured': return 'featuredProducts' @@ -118,19 +100,13 @@ export default function getAllProductsOperation({ } = {}): Promise { config = commerce.getConfig(config) - const locale = config.locale + const { locale } = config const field = getProductsType(vars.relevance) const variables: GetAllProductsQueryVariables = { locale, hasLocale: !!locale, } - if (!FIELDS.includes(field)) { - throw new Error( - `The field variable has to match one of ${FIELDS.join(', ')}` - ) - } - variables[field] = true if (vars.first) variables.first = vars.first diff --git a/framework/bigcommerce/api/operations/get-product.ts b/framework/bigcommerce/api/operations/get-product.ts new file mode 100644 index 000000000..31de42ef6 --- /dev/null +++ b/framework/bigcommerce/api/operations/get-product.ts @@ -0,0 +1,120 @@ +import type { + OperationContext, + OperationOptions, +} from '@commerce/api/operations' +import type { GetProductOperation } from '../../types/product' +import type { GetProductQuery, GetProductQueryVariables } from '../../schema' +import setProductLocaleMeta from '../utils/set-product-locale-meta' +import { productInfoFragment } from '../fragments/product' +import { BigcommerceConfig, Provider } from '..' +import { normalizeProduct } from '../../lib/normalize' + +export const getProductQuery = /* GraphQL */ ` + query getProduct( + $hasLocale: Boolean = false + $locale: String = "null" + $path: String! + ) { + site { + route(path: $path) { + node { + __typename + ... on Product { + ...productInfo + variants { + edges { + node { + entityId + defaultImage { + urlOriginal + altText + isDefault + } + prices { + ...productPrices + } + inventory { + aggregated { + availableToSell + warningLevel + } + isInStock + } + productOptions { + edges { + node { + __typename + entityId + displayName + ...multipleChoiceOption + } + } + } + } + } + } + } + } + } + } + } + + ${productInfoFragment} +` + +// TODO: See if this type is useful for defining the Product type +// export type ProductNode = Extract< +// GetProductQuery['site']['route']['node'], +// { __typename: 'Product' } +// > + +export default function getAllProductPathsOperation({ + commerce, +}: OperationContext) { + async function getProduct(opts: { + variables: T['variables'] + config?: BigcommerceConfig + preview?: boolean + }): Promise + + async function getProduct( + opts: { + variables: T['variables'] + config?: BigcommerceConfig + preview?: boolean + } & OperationOptions + ): Promise + + async function getProduct({ + query = getProductQuery, + variables: { slug, ...vars }, + config, + }: { + query?: string + variables: T['variables'] + config?: BigcommerceConfig + preview?: boolean + }): Promise { + config = commerce.getConfig(config) + + const { locale } = config + const variables: GetProductQueryVariables = { + locale, + hasLocale: !!locale, + path: slug ? `/${slug}/` : vars.path!, + } + const { data } = await config.fetch(query, { variables }) + const product = data.site?.route?.node + + if (product?.__typename === 'Product') { + if (locale && config.applyLocale) { + setProductLocaleMeta(product) + } + + return { product: normalizeProduct(product as any) } + } + + return {} + } + return getProduct +} diff --git a/framework/bigcommerce/product/get-product.ts b/framework/bigcommerce/product/get-product.ts deleted file mode 100644 index b52568b62..000000000 --- a/framework/bigcommerce/product/get-product.ts +++ /dev/null @@ -1,121 +0,0 @@ -import type { GetProductQuery, GetProductQueryVariables } from '../schema' -import setProductLocaleMeta from '../api/utils/set-product-locale-meta' -import { productInfoFragment } from '../api/fragments/product' -import { BigcommerceConfig, getConfig } from '../api' -import { normalizeProduct } from '../lib/normalize' -import type { Product } from '@commerce/types' - -export const getProductQuery = /* GraphQL */ ` - query getProduct( - $hasLocale: Boolean = false - $locale: String = "null" - $path: String! - ) { - site { - route(path: $path) { - node { - __typename - ... on Product { - ...productInfo - variants { - edges { - node { - entityId - defaultImage { - urlOriginal - altText - isDefault - } - prices { - ...productPrices - } - inventory { - aggregated { - availableToSell - warningLevel - } - isInStock - } - productOptions { - edges { - node { - __typename - entityId - displayName - ...multipleChoiceOption - } - } - } - } - } - } - } - } - } - } - } - - ${productInfoFragment} -` - -export type ProductNode = Extract< - GetProductQuery['site']['route']['node'], - { __typename: 'Product' } -> - -export type GetProductResult< - T extends { product?: any } = { product?: ProductNode } -> = T - -export type ProductVariables = { locale?: string } & ( - | { path: string; slug?: never } - | { path?: never; slug: string } -) - -async function getProduct(opts: { - variables: ProductVariables - config?: BigcommerceConfig - preview?: boolean -}): Promise - -async function getProduct(opts: { - query: string - variables: V - config?: BigcommerceConfig - preview?: boolean -}): Promise> - -async function getProduct({ - query = getProductQuery, - variables: { slug, ...vars }, - config, -}: { - query?: string - variables: ProductVariables - config?: BigcommerceConfig - preview?: boolean -}): Promise { - config = getConfig(config) - - const locale = vars.locale || config.locale - const variables: GetProductQueryVariables = { - ...vars, - locale, - hasLocale: !!locale, - path: slug ? `/${slug}/` : vars.path!, - } - const { data } = await config.fetch(query, { variables }) - const product = data.site?.route?.node - - if (product?.__typename === 'Product') { - if (locale && config.applyLocale) { - setProductLocaleMeta(product) - } - - return { product: normalizeProduct(product as any) } - } - - return {} -} - -export default getProduct diff --git a/framework/bigcommerce/product/index.ts b/framework/bigcommerce/product/index.ts index d5b1cefac..426a3edcd 100644 --- a/framework/bigcommerce/product/index.ts +++ b/framework/bigcommerce/product/index.ts @@ -1,4 +1,2 @@ export { default as usePrice } from './use-price' export { default as useSearch } from './use-search' -export { default as getProduct } from './get-product' -export { default as getAllProducts } from '../api/operations/get-all-products' diff --git a/framework/commerce/api/operations.ts b/framework/commerce/api/operations.ts index c11ebf965..aa30f426e 100644 --- a/framework/commerce/api/operations.ts +++ b/framework/commerce/api/operations.ts @@ -6,6 +6,7 @@ import type { GetCustomerWishlistOperation } from '../types/wishlist' import type { GetAllProductPathsOperation, GetAllProductsOperation, + GetProductOperation, } from '../types/product' import type { APIProvider, CommerceAPI } from '.' @@ -128,6 +129,22 @@ export type Operations

= { } & OperationOptions ): Promise } + + getProduct: { + (opts: { + variables: T['variables'] + config?: P['config'] + preview?: boolean + }): Promise + + ( + opts: { + variables: T['variables'] + config?: P['config'] + preview?: boolean + } & OperationOptions + ): Promise + } } export type APIOperations

= { diff --git a/framework/commerce/types/product.ts b/framework/commerce/types/product.ts index ed7e0e85b..f69827f89 100644 --- a/framework/commerce/types/product.ts +++ b/framework/commerce/types/product.ts @@ -81,3 +81,8 @@ export type GetAllProductsOperation = { first?: number } } + +export type GetProductOperation = { + data: { product?: T['product'] } + variables: { path: string; slug?: never } | { path?: never; slug: string } +} diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index 282df7247..6f47e5dfa 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -7,9 +7,7 @@ import { useRouter } from 'next/router' import commerce from '@lib/api/commerce' import { Layout } from '@components/common' import { ProductView } from '@components/product' - import { getConfig } from '@framework/api' -import getProduct from '@framework/product/get-product' export async function getStaticProps({ params, @@ -18,7 +16,7 @@ export async function getStaticProps({ }: GetStaticPropsContext<{ slug: string }>) { const config = getConfig({ locale }) const { pages } = await commerce.getAllPages({ config, preview }) - const { product } = await getProduct({ + const { product } = await commerce.getProduct({ variables: { slug: params!.slug }, config, preview, From ee3e4143b9b9f17550ffa2b58e0bc50c208cea51 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 19:01:27 -0500 Subject: [PATCH 10/11] Removed old getConfig and references --- framework/bigcommerce/api/checkout.ts | 77 ------------------- framework/bigcommerce/api/index.ts | 71 +++-------------- .../api/operations/get-all-pages.ts | 10 +-- .../api/operations/get-all-products.ts | 11 ++- .../bigcommerce/api/operations/get-page.ts | 10 +-- .../bigcommerce/api/operations/get-product.ts | 11 ++- .../api/operations/get-site-info.ts | 12 ++- .../api/utils/create-api-handler.ts | 58 -------------- .../api/utils/fetch-graphql-api.ts | 4 +- .../bigcommerce/api/utils/fetch-store-api.ts | 5 +- pages/[...pages].tsx | 3 +- pages/blog.tsx | 3 +- pages/cart.tsx | 3 +- pages/index.tsx | 5 +- pages/orders.tsx | 3 +- pages/product/[slug].tsx | 3 +- pages/profile.tsx | 3 +- pages/search.tsx | 3 +- pages/wishlist.tsx | 3 +- 19 files changed, 51 insertions(+), 247 deletions(-) delete mode 100644 framework/bigcommerce/api/checkout.ts delete mode 100644 framework/bigcommerce/api/utils/create-api-handler.ts diff --git a/framework/bigcommerce/api/checkout.ts b/framework/bigcommerce/api/checkout.ts deleted file mode 100644 index 530f4c40a..000000000 --- a/framework/bigcommerce/api/checkout.ts +++ /dev/null @@ -1,77 +0,0 @@ -import isAllowedMethod from './utils/is-allowed-method' -import createApiHandler, { - BigcommerceApiHandler, -} from './utils/create-api-handler' -import { BigcommerceApiError } from './utils/errors' - -const METHODS = ['GET'] -const fullCheckout = true - -// TODO: a complete implementation should have schema validation for `req.body` -const checkoutApi: BigcommerceApiHandler = async (req, res, config) => { - if (!isAllowedMethod(req, res, METHODS)) return - - const { cookies } = req - const cartId = cookies[config.cartCookie] - - try { - if (!cartId) { - res.redirect('/cart') - return - } - - const { data } = await config.storeApiFetch( - `/v3/carts/${cartId}/redirect_urls`, - { - method: 'POST', - } - ) - - if (fullCheckout) { - res.redirect(data.checkout_url) - return - } - - // TODO: make the embedded checkout work too! - const html = ` - - - - - - Checkout - - - - -

- - - ` - - res.status(200) - res.setHeader('Content-Type', 'text/html') - res.write(html) - res.end() - } catch (error) { - console.error(error) - - const message = - error instanceof BigcommerceApiError - ? 'An unexpected error ocurred with the Bigcommerce API' - : 'An unexpected error ocurred' - - res.status(500).json({ data: null, errors: [{ message }] }) - } -} - -export default createApiHandler(checkoutApi, {}, {}) diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index c9567dc9a..a6ee3f508 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -61,47 +61,9 @@ if (!(STORE_API_URL && STORE_API_TOKEN && STORE_API_CLIENT_ID)) { ) } -export class Config { - private config: BigcommerceConfig - - constructor(config: Omit) { - this.config = { - ...config, - // The customerCookie is not customizable for now, BC sets the cookie and it's - // not important to rename it - customerCookie: 'SHOP_TOKEN', - } - } - - getConfig(userConfig: Partial = {}) { - return Object.entries(userConfig).reduce( - (cfg, [key, value]) => Object.assign(cfg, { [key]: value }), - { ...this.config } - ) - } - - setConfig(newConfig: Partial) { - Object.assign(this.config, newConfig) - } -} - const ONE_DAY = 60 * 60 * 24 -const config = new Config({ - commerceUrl: API_URL, - apiToken: API_TOKEN, - cartCookie: process.env.BIGCOMMERCE_CART_COOKIE ?? 'bc_cartId', - cartCookieMaxAge: ONE_DAY * 30, - fetch: fetchGraphqlApi, - applyLocale: true, - // REST API only - storeApiUrl: STORE_API_URL, - storeApiToken: STORE_API_TOKEN, - storeApiClientId: STORE_API_CLIENT_ID, - storeChannelId: STORE_CHANNEL_ID, - storeApiFetch: fetchStoreApi, -}) -const config2: BigcommerceConfig = { +const config: BigcommerceConfig = { commerceUrl: API_URL, apiToken: API_TOKEN, customerCookie: 'SHOP_TOKEN', @@ -117,20 +79,19 @@ const config2: BigcommerceConfig = { storeApiFetch: fetchStoreApi, } -export const provider = { - config: config2, - operations: { - login, - getAllPages, - getPage, - getSiteInfo, - getCustomerWishlist, - getAllProductPaths, - getAllProducts, - getProduct, - }, +const operations = { + login, + getAllPages, + getPage, + getSiteInfo, + getCustomerWishlist, + getAllProductPaths, + getAllProducts, + getProduct, } +export const provider = { config, operations } + export type Provider = typeof provider export type APIs = @@ -149,11 +110,3 @@ export function getCommerceApi

( ): BigcommerceAPI

{ return commerceApi(customProvider) } - -export function getConfig(userConfig?: Partial) { - return config.getConfig(userConfig) -} - -export function setConfig(newConfig: Partial) { - return config.setConfig(newConfig) -} diff --git a/framework/bigcommerce/api/operations/get-all-pages.ts b/framework/bigcommerce/api/operations/get-all-pages.ts index 2f07670ff..3a9b64b1f 100644 --- a/framework/bigcommerce/api/operations/get-all-pages.ts +++ b/framework/bigcommerce/api/operations/get-all-pages.ts @@ -10,13 +10,13 @@ export default function getAllPagesOperation({ commerce, }: OperationContext) { async function getAllPages(opts?: { - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise async function getAllPages( opts: { - config?: BigcommerceConfig + config?: Partial preview?: boolean } & OperationOptions ): Promise @@ -26,13 +26,13 @@ export default function getAllPagesOperation({ preview, }: { url?: string - config?: BigcommerceConfig + config?: Partial preview?: boolean } = {}): Promise { - config = commerce.getConfig(config) + const cfg = commerce.getConfig(config) // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `url` - const { data } = await config.storeApiFetch< + const { data } = await cfg.storeApiFetch< RecursivePartial<{ data: Page[] }> >('/v3/content/pages') const pages = (data as RecursiveRequired) ?? [] diff --git a/framework/bigcommerce/api/operations/get-all-products.ts b/framework/bigcommerce/api/operations/get-all-products.ts index c5e16a276..c2652f5bf 100644 --- a/framework/bigcommerce/api/operations/get-all-products.ts +++ b/framework/bigcommerce/api/operations/get-all-products.ts @@ -76,14 +76,14 @@ export default function getAllProductsOperation({ }: OperationContext) { async function getAllProducts(opts?: { variables?: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise async function getAllProducts( opts: { variables?: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean } & OperationOptions ): Promise @@ -91,15 +91,14 @@ export default function getAllProductsOperation({ async function getAllProducts({ query = getAllProductsQuery, variables: vars = {}, - config, + config: cfg, }: { query?: string variables?: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean } = {}): Promise { - config = commerce.getConfig(config) - + const config = commerce.getConfig(cfg) const { locale } = config const field = getProductsType(vars.relevance) const variables: GetAllProductsQueryVariables = { diff --git a/framework/bigcommerce/api/operations/get-page.ts b/framework/bigcommerce/api/operations/get-page.ts index a5bca327c..e8f852e92 100644 --- a/framework/bigcommerce/api/operations/get-page.ts +++ b/framework/bigcommerce/api/operations/get-page.ts @@ -11,14 +11,14 @@ export default function getPageOperation({ }: OperationContext) { async function getPage(opts: { variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise async function getPage( opts: { variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean } & OperationOptions ): Promise @@ -31,13 +31,13 @@ export default function getPageOperation({ }: { url?: string variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise { - config = commerce.getConfig(config) + const cfg = commerce.getConfig(config) // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `url` - const { data } = await config.storeApiFetch< + const { data } = await cfg.storeApiFetch< RecursivePartial<{ data: Page[] }> >(url || `/v3/content/pages?id=${variables.id}&include=body`) const firstPage = data?.[0] diff --git a/framework/bigcommerce/api/operations/get-product.ts b/framework/bigcommerce/api/operations/get-product.ts index 31de42ef6..fb356e952 100644 --- a/framework/bigcommerce/api/operations/get-product.ts +++ b/framework/bigcommerce/api/operations/get-product.ts @@ -73,14 +73,14 @@ export default function getAllProductPathsOperation({ }: OperationContext) { async function getProduct(opts: { variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise async function getProduct( opts: { variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean } & OperationOptions ): Promise @@ -88,15 +88,14 @@ export default function getAllProductPathsOperation({ async function getProduct({ query = getProductQuery, variables: { slug, ...vars }, - config, + config: cfg, }: { query?: string variables: T['variables'] - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise { - config = commerce.getConfig(config) - + const config = commerce.getConfig(cfg) const { locale } = config const variables: GetProductQueryVariables = { locale, diff --git a/framework/bigcommerce/api/operations/get-site-info.ts b/framework/bigcommerce/api/operations/get-site-info.ts index cca11ed74..afe5f3626 100644 --- a/framework/bigcommerce/api/operations/get-site-info.ts +++ b/framework/bigcommerce/api/operations/get-site-info.ts @@ -53,13 +53,13 @@ export default function getSiteInfoOperation({ commerce, }: OperationContext) { async function getSiteInfo(opts?: { - config?: BigcommerceConfig + config?: Partial preview?: boolean }): Promise async function getSiteInfo( opts: { - config?: BigcommerceConfig + config?: Partial preview?: boolean } & OperationOptions ): Promise @@ -69,15 +69,13 @@ export default function getSiteInfoOperation({ config, }: { query?: string - config?: BigcommerceConfig + config?: Partial preview?: boolean } = {}): Promise { - config = commerce.getConfig(config) + const cfg = commerce.getConfig(config) // RecursivePartial forces the method to check for every prop in the data, which is // required in case there's a custom `query` - const { data } = await config.fetch>( - query - ) + const { data } = await cfg.fetch>(query) const categories = data.site?.categoryTree const brands = data.site?.brands?.edges diff --git a/framework/bigcommerce/api/utils/create-api-handler.ts b/framework/bigcommerce/api/utils/create-api-handler.ts deleted file mode 100644 index c1d651d9b..000000000 --- a/framework/bigcommerce/api/utils/create-api-handler.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { NextApiHandler, NextApiRequest, NextApiResponse } from 'next' -import { BigcommerceConfig, getConfig } from '..' - -export type BigcommerceApiHandler< - T = any, - H extends BigcommerceHandlers = {}, - Options extends {} = {} -> = ( - req: NextApiRequest, - res: NextApiResponse>, - config: BigcommerceConfig, - handlers: H, - // Custom configs that may be used by a particular handler - options: Options -) => void | Promise - -export type BigcommerceHandler = (options: { - req: NextApiRequest - res: NextApiResponse> - config: BigcommerceConfig - body: Body -}) => void | Promise - -export type BigcommerceHandlers = { - [k: string]: BigcommerceHandler -} - -export type BigcommerceApiResponse = { - data: T | null - errors?: { message: string; code?: string }[] -} - -export default function createApiHandler< - T = any, - H extends BigcommerceHandlers = {}, - Options extends {} = {} ->( - handler: BigcommerceApiHandler, - handlers: H, - defaultOptions: Options -) { - return function getApiHandler({ - config, - operations, - options, - }: { - config?: BigcommerceConfig - operations?: Partial - options?: Options extends {} ? Partial : never - } = {}): NextApiHandler { - const ops = { ...handlers, ...operations } - const opts = { ...defaultOptions, ...options } - - return function apiHandler(req, res) { - return handler(req, res, getConfig(config), ops, opts) - } - } -} diff --git a/framework/bigcommerce/api/utils/fetch-graphql-api.ts b/framework/bigcommerce/api/utils/fetch-graphql-api.ts index a449b81e0..9c2eaa2ac 100644 --- a/framework/bigcommerce/api/utils/fetch-graphql-api.ts +++ b/framework/bigcommerce/api/utils/fetch-graphql-api.ts @@ -1,15 +1,15 @@ import { FetcherError } from '@commerce/utils/errors' import type { GraphQLFetcher } from '@commerce/api' -import { getConfig } from '..' +import { provider } from '..' import fetch from './fetch' +const { config } = provider const fetchGraphqlApi: GraphQLFetcher = async ( query: string, { variables, preview } = {}, fetchOptions ) => { // log.warn(query) - const config = getConfig() const res = await fetch(config.commerceUrl + (preview ? '/preview' : ''), { ...fetchOptions, method: 'POST', diff --git a/framework/bigcommerce/api/utils/fetch-store-api.ts b/framework/bigcommerce/api/utils/fetch-store-api.ts index 7e59b9f06..68817417e 100644 --- a/framework/bigcommerce/api/utils/fetch-store-api.ts +++ b/framework/bigcommerce/api/utils/fetch-store-api.ts @@ -1,13 +1,14 @@ import type { RequestInit, Response } from '@vercel/fetch' -import { getConfig } from '..' +import { provider } from '..' import { BigcommerceApiError, BigcommerceNetworkError } from './errors' import fetch from './fetch' +const { config } = provider + export default async function fetchStoreApi( endpoint: string, options?: RequestInit ): Promise { - const config = getConfig() let res: Response try { diff --git a/pages/[...pages].tsx b/pages/[...pages].tsx index 2fd13d674..3e6ef65c9 100644 --- a/pages/[...pages].tsx +++ b/pages/[...pages].tsx @@ -8,7 +8,6 @@ import { Text } from '@components/ui' import { Layout } from '@components/common' import getSlug from '@lib/get-slug' import { missingLocaleInPages } from '@lib/usage-warns' -import { getConfig } from '@framework/api' import { defaultPageProps } from '@lib/defaults' export async function getStaticProps({ @@ -16,7 +15,7 @@ export async function getStaticProps({ params, locale, }: GetStaticPropsContext<{ pages: string[] }>) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ preview, config }) const path = params?.pages.join('/') const slug = locale ? `${locale}/${path}` : path diff --git a/pages/blog.tsx b/pages/blog.tsx index 10dba8c56..c1bdc4624 100644 --- a/pages/blog.tsx +++ b/pages/blog.tsx @@ -1,5 +1,4 @@ import type { GetStaticPropsContext } from 'next' -import { getConfig } from '@framework/api' import commerce from '@lib/api/commerce' import { Layout } from '@components/common' import { Container } from '@components/ui' @@ -8,7 +7,7 @@ export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) return { props: { pages }, diff --git a/pages/cart.tsx b/pages/cart.tsx index e318ee6e9..d14a7bfd2 100644 --- a/pages/cart.tsx +++ b/pages/cart.tsx @@ -1,5 +1,4 @@ import type { GetStaticPropsContext } from 'next' -import { getConfig } from '@framework/api' import useCart from '@framework/cart/use-cart' import usePrice from '@framework/product/use-price' import commerce from '@lib/api/commerce' @@ -12,7 +11,7 @@ export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) return { props: { pages }, diff --git a/pages/index.tsx b/pages/index.tsx index b33ed585b..87881d2b9 100644 --- a/pages/index.tsx +++ b/pages/index.tsx @@ -5,14 +5,11 @@ import { ProductCard } from '@components/product' // import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid' import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next' -import { getConfig } from '@framework/api' - export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) - + const config = { locale } const { products } = await commerce.getAllProducts({ variables: { first: 12 }, config, diff --git a/pages/orders.tsx b/pages/orders.tsx index f1dd630ac..c43ff9e5a 100644 --- a/pages/orders.tsx +++ b/pages/orders.tsx @@ -3,13 +3,12 @@ import commerce from '@lib/api/commerce' import { Bag } from '@components/icons' import { Layout } from '@components/common' import { Container, Text } from '@components/ui' -import { getConfig } from '@framework/api' export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) return { props: { pages }, diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx index 6f47e5dfa..bf71ac21e 100644 --- a/pages/product/[slug].tsx +++ b/pages/product/[slug].tsx @@ -7,14 +7,13 @@ import { useRouter } from 'next/router' import commerce from '@lib/api/commerce' import { Layout } from '@components/common' import { ProductView } from '@components/product' -import { getConfig } from '@framework/api' export async function getStaticProps({ params, locale, preview, }: GetStaticPropsContext<{ slug: string }>) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) const { product } = await commerce.getProduct({ variables: { slug: params!.slug }, diff --git a/pages/profile.tsx b/pages/profile.tsx index 0c9d7b26d..b73469fa5 100644 --- a/pages/profile.tsx +++ b/pages/profile.tsx @@ -1,5 +1,4 @@ import type { GetStaticPropsContext } from 'next' -import { getConfig } from '@framework/api' import useCustomer from '@framework/customer/use-customer' import commerce from '@lib/api/commerce' import { Layout } from '@components/common' @@ -9,7 +8,7 @@ export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) return { props: { pages }, diff --git a/pages/search.tsx b/pages/search.tsx index 46311a5dc..53452b74a 100644 --- a/pages/search.tsx +++ b/pages/search.tsx @@ -8,7 +8,6 @@ import { Layout } from '@components/common' import { ProductCard } from '@components/product' import { Container, Grid, Skeleton } from '@components/ui' -import { getConfig } from '@framework/api' import useSearch from '@framework/product/use-search' import commerce from '@lib/api/commerce' import rangeMap from '@lib/range-map' @@ -36,7 +35,7 @@ export async function getStaticProps({ preview, locale, }: GetStaticPropsContext) { - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) const { categories, brands } = await commerce.getSiteInfo({ config, preview }) return { diff --git a/pages/wishlist.tsx b/pages/wishlist.tsx index ef49dfff3..9927c536a 100644 --- a/pages/wishlist.tsx +++ b/pages/wishlist.tsx @@ -4,7 +4,6 @@ import { Heart } from '@components/icons' import { Layout } from '@components/common' import { Text, Container } from '@components/ui' import { defaultPageProps } from '@lib/defaults' -import { getConfig } from '@framework/api' import { useCustomer } from '@framework/customer' import { WishlistCard } from '@components/wishlist' import useWishlist from '@framework/wishlist/use-wishlist' @@ -20,7 +19,7 @@ export async function getStaticProps({ } } - const config = getConfig({ locale }) + const config = { locale } const { pages } = await commerce.getAllPages({ config, preview }) return { props: { From 4cc3613f648abf81f1bbb89c0978eaba67f1dcc2 Mon Sep 17 00:00:00 2001 From: Luis Alvarez Date: Mon, 24 May 2021 19:06:07 -0500 Subject: [PATCH 11/11] Removed is-allowed-method from BC --- framework/bigcommerce/api/index.ts | 1 - .../api/utils/is-allowed-method.ts | 28 ------------------- 2 files changed, 29 deletions(-) delete mode 100644 framework/bigcommerce/api/utils/is-allowed-method.ts diff --git a/framework/bigcommerce/api/index.ts b/framework/bigcommerce/api/index.ts index a6ee3f508..300f1087b 100644 --- a/framework/bigcommerce/api/index.ts +++ b/framework/bigcommerce/api/index.ts @@ -1,4 +1,3 @@ -import type { NextApiHandler } from 'next' import type { RequestInit } from '@vercel/fetch' import { CommerceAPI, diff --git a/framework/bigcommerce/api/utils/is-allowed-method.ts b/framework/bigcommerce/api/utils/is-allowed-method.ts deleted file mode 100644 index 78bbba568..000000000 --- a/framework/bigcommerce/api/utils/is-allowed-method.ts +++ /dev/null @@ -1,28 +0,0 @@ -import type { NextApiRequest, NextApiResponse } from 'next' - -export default function isAllowedMethod( - req: NextApiRequest, - res: NextApiResponse, - allowedMethods: string[] -) { - const methods = allowedMethods.includes('OPTIONS') - ? allowedMethods - : [...allowedMethods, 'OPTIONS'] - - if (!req.method || !methods.includes(req.method)) { - res.status(405) - res.setHeader('Allow', methods.join(', ')) - res.end() - return false - } - - if (req.method === 'OPTIONS') { - res.status(200) - res.setHeader('Allow', methods.join(', ')) - res.setHeader('Content-Length', '0') - res.end() - return false - } - - return true -}