Agnostic UI (#199)

* changes

* Progress

* Normalized Products output

* Progress

* Restored Index Agnostic

* Progress

* Reordering

* Moved normalizer to BC function

* Removed Futures

* More Types

* More Types

* More Types

* Fix useCallback

* Progress, Changes types, readme and restoring functionality

* Changes

* TS Issues

* Changes

* Normalizer

* Normalizing more operations

* Normalizing more operations

* changes

* Merge Issues

* Cleanup

* change

* changes

* index.ts broke my tree shaking

* slug

* Normalized Options and Swatches

* Restored Add to cart

* Correct Variant Added to Cart

* Normalizing Cart Responses

* Changes

* changes breaking

* Adding immutable normalizer for Product

* Cart Normalized

* changes

* Progress

* More updates

* Removed some comments

* Add loading state for data hooks

* Bug fix

* Changed the way isEmpty works

* Improve navbar performance

* Added useResponse hook

* added useResponse to useWhishlist

* Added husky and lint-staged

* Ran prettier fix

* Added more cart types

* Moved types.d.ts to the commerce folder

* Minor changes

* Moved normalizer to happen after fetch

* updated useCart types

* Updated normalizer for useData

* Added new normalizer for the cart to the UI

* More corrections for useCart

* Updated cart update hooks

* Removed import

* Progress

* Switch away from global types

* Making multiple changes

* Improved types for operations

* Moved and updated cart types

* Updated the useAddItem and useRemoveItem hooks

* Minor life improvement

* Minor change

* Implement Shopify Provider

* Update README.md

* Update README.md

* normalizations & missing files

* Update index.ts

* fixes

* Update normalize.ts

* fix: cart error on first load

* shopify checkout redirect & api handler

* Update get-checkout-id.ts

* userAvatar

* Fix: color option

* Update normalize.ts

* changes

* Update next.config.js

* start customer auth & signup

* Update config.ts

* Login, Sign Up, Log Out, and checkout & customer association

* Automatic login after sign-up

* Update handle-login.ts

* MOving stuff around and adding temporal new files

* changes

* Replace use-cart with the new hook

* Removed old hook

* Improved HookHandler type

* Moved types

* Simplified useData types

* Updated Fetcher type

* Moved SwrOptions type

* Removed duplicated fetcher

* Moved provider to its own file

* Added proper type for fetch input

* Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic"

This reverts commit 23c8ed7c2d, reversing
changes made to bf50965a39.

* change readme

* Revert "Merge branch 'master' of https://github.com/vercel/commerce into agnostic"

This reverts commit bf50965a39, reversing
changes made to 0dad4ddedb.

* Revert "Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic""

This reverts commit c9a43f1bce.

* align with upstream changes

* Updated how the hook input is handled

* Add more options to the hook handler

* Final touches to the hook handler type

* Moved useWishlist to use new handler

* Move useCustomer to the new hook

* Added a default fetcher

* query all products for vendors & paths, improve search

* Update use-search.tsx

* fix cart after upstream changes

* Shopify Provider (#186)

* Start of Shopify provider

* add missing comment to documentation

* add missing env vars to documentation

* update reference to types file

* Moved useSearch to the new hook

* Removed old use-data lib

* Removed generics for result and body

* Removed normalizr

* Wishlist

* New changes and initial Features API

* Fixed some types

* Fixed more types

* fixes after upstream changes

* Fixed product types

* Fixed another product type

* Updated type

* Fixed remaining issues with types

* Added a MutationHandler

* Moved the handlers to each hook

* Moved the fetcher to its own file

* Moved handler to each hook

* Added initial version of useAddItem

* Added better mutation types, and moved some hooks

* Removed use-cart-actions

* Added initial version of useAddItem

* Updated types

* Update use-add-item.tsx

* changes

* Changes

* Reordering and changes

* Adding Features APO

* Adding wishlist api

* Implementing FeaturesAPI with Wishlist

* Removing bug with touchstart

* Adding tyni typing

* moved use-remove-item

* Removed MutationHandler type

* Moved more hooks and updated types to make them smaller

* Moved data hooks to new format

* Removed no longer required types

* Removed useResponse helper

* Updated useData type

* Moved wishlist use-add-item

* Moved wishlist use-remove-item to provider

* Moved use-login and use-logout

* Update use-signup

* Removed use-action helper

* Moved auth & cart hooks + several fixes

* Updated cart item, fixed deprecations

* Update next.config.js

* Updates to wishlist feature

* Moved the features to be environment variable only

* More changes for wishlist config

* Disable wishlist

* Removed useWishlistActions

* Updated readme

* updates

* typos

* Updated the way the provider config is set

* Removed features.ts

* Removed bootstrap.js

* Aligned with upstream changes

* Updates

* shopify: changes

* shopify: changes

* Update next.config.js

* Shopify Provider Updates (#209)

* Implement Shopify Provider

* Update README.md

* Update README.md

* normalizations & missing files

* Update index.ts

* fixes

* Update normalize.ts

* fix: cart error on first load

* shopify checkout redirect & api handler

* Update get-checkout-id.ts

* Fix: color option

* Update normalize.ts

* changes

* Update next.config.js

* start customer auth & signup

* Update config.ts

* Login, Sign Up, Log Out, and checkout & customer association

* Automatic login after sign-up

* Update handle-login.ts

* changes

* Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic"

This reverts commit 23c8ed7c2d, reversing
changes made to bf50965a39.

* change readme

* Revert "Merge branch 'master' of https://github.com/vercel/commerce into agnostic"

This reverts commit bf50965a39, reversing
changes made to 0dad4ddedb.

* Revert "Revert "Merge branch 'agnostic' of https://github.com/vercel/commerce into agnostic""

This reverts commit c9a43f1bce.

* align with upstream changes

* query all products for vendors & paths, improve search

* Update use-search.tsx

* fix cart after upstream changes

* fixes after upstream changes

* Moved handler to each hook

* Added initial version of useAddItem

* Updated types

* Update use-add-item.tsx

* Moved auth & cart hooks + several fixes

* Updated cart item, fixed deprecations

* Update next.config.js

* Aligned with upstream changes

* Updates

* Update next.config.js

* Updated the commerce config structure

* Removed @framework imports within framework providers

* Fixed types

* changes

* Adding extra config

* Adding shopify commit

* Adding env templates to the providers

* Ignore some types

* Adding link for Cart

* Adding customCheckout

* multiple changes to fix the wishlist

* Shopify Provier Updates (#212)

* changes

* Adding shopify commit

* Changed to query page by id

* Fixed page query, Changed use-search GraphQl query

* Update use-search.tsx

* remove unused util

* Changed cookie expiration

* Update tsconfig.json

Co-authored-by: okbel <curciobel@gmail.com>

* Bump and adding dependency

* Adding color checks

* Now colors work with lighter colors

* Stable commerce.config.json

* Updated main readme

* Readme changes

* Default to bigcommerce

Co-authored-by: bc <bc@bcs-MacBook-Pro.fibertel.com.ar>
Co-authored-by: Luis Alvarez <luis@vercel.com>
Co-authored-by: cond0r <pinte_catalin@yahoo.com>
Co-authored-by: Peter Mekhaeil <4616064+petermekhaeil@users.noreply.github.com>
This commit is contained in:
B
2021-03-04 07:57:25 -03:00
committed by GitHub
parent b121078151
commit 9b71bd77fc
232 changed files with 20545 additions and 1895 deletions

View File

@@ -2,7 +2,6 @@ import { parseCartItem } from '../../utils/parse-item'
import getCartCookie from '../../utils/get-cart-cookie'
import type { CartHandlers } from '..'
// Return current cart info
const addItem: CartHandlers['addItem'] = async ({
res,
body: { cartId, item },
@@ -26,8 +25,14 @@ const addItem: CartHandlers['addItem'] = async ({
}),
}
const { data } = cartId
? await config.storeApiFetch(`/v3/carts/${cartId}/items?include=line_items.physical_items.options`, options)
: await config.storeApiFetch('/v3/carts?include=line_items.physical_items.options', options)
? await config.storeApiFetch(
`/v3/carts/${cartId}/items?include=line_items.physical_items.options`,
options
)
: await config.storeApiFetch(
'/v3/carts?include=line_items.physical_items.options',
options
)
// Create or update the cart cookie
res.setHeader(

View File

@@ -1,6 +1,7 @@
import type { BigcommerceCart } from '../../../types'
import { BigcommerceApiError } from '../../utils/errors'
import getCartCookie from '../../utils/get-cart-cookie'
import type { Cart, CartHandlers } from '..'
import type { CartHandlers } from '../'
// Return current cart info
const getCart: CartHandlers['getCart'] = async ({
@@ -8,11 +9,13 @@ const getCart: CartHandlers['getCart'] = async ({
body: { cartId },
config,
}) => {
let result: { data?: Cart } = {}
let result: { data?: BigcommerceCart } = {}
if (cartId) {
try {
result = await config.storeApiFetch(`/v3/carts/${cartId}?include=line_items.physical_items.options`)
result = await config.storeApiFetch(
`/v3/carts/${cartId}?include=line_items.physical_items.options`
)
} catch (error) {
if (error instanceof BigcommerceApiError && error.status === 404) {
// Remove the cookie if it exists but the cart wasn't found

View File

@@ -1,7 +1,6 @@
import getCartCookie from '../../utils/get-cart-cookie'
import type { CartHandlers } from '..'
// Return current cart info
const removeItem: CartHandlers['removeItem'] = async ({
res,
body: { cartId, itemId },

View File

@@ -2,7 +2,6 @@ import { parseCartItem } from '../../utils/parse-item'
import getCartCookie from '../../utils/get-cart-cookie'
import type { CartHandlers } from '..'
// Return current cart info
const updateItem: CartHandlers['updateItem'] = async ({
res,
body: { cartId, itemId, item },

View File

@@ -8,63 +8,25 @@ import getCart from './handlers/get-cart'
import addItem from './handlers/add-item'
import updateItem from './handlers/update-item'
import removeItem from './handlers/remove-item'
type OptionSelections = {
option_id: Number
option_value: Number|String
}
export type ItemBody = {
productId: number
variantId: number
quantity?: number
optionSelections?: OptionSelections
}
export type AddItemBody = { item: ItemBody }
export type UpdateItemBody = { itemId: string; item: ItemBody }
export type RemoveItemBody = { itemId: string }
// TODO: this type should match:
// https://developer.bigcommerce.com/api-reference/cart-checkout/server-server-cart-api/cart/getacart#responses
export type Cart = {
id: string
parent_id?: string
customer_id: number
email: string
currency: { code: string }
tax_included: boolean
base_amount: number
discount_amount: number
cart_amount: number
line_items: {
custom_items: any[]
digital_items: any[]
gift_certificates: any[]
physical_items: any[]
}
// TODO: add missing fields
}
import type {
BigcommerceCart,
GetCartHandlerBody,
AddCartItemHandlerBody,
UpdateCartItemHandlerBody,
RemoveCartItemHandlerBody,
} from '../../types'
export type CartHandlers = {
getCart: BigcommerceHandler<Cart, { cartId?: string }>
addItem: BigcommerceHandler<Cart, { cartId?: string } & Partial<AddItemBody>>
updateItem: BigcommerceHandler<
Cart,
{ cartId?: string } & Partial<UpdateItemBody>
>
removeItem: BigcommerceHandler<
Cart,
{ cartId?: string } & Partial<RemoveItemBody>
>
getCart: BigcommerceHandler<BigcommerceCart, GetCartHandlerBody>
addItem: BigcommerceHandler<BigcommerceCart, AddCartItemHandlerBody>
updateItem: BigcommerceHandler<BigcommerceCart, UpdateCartItemHandlerBody>
removeItem: BigcommerceHandler<BigcommerceCart, RemoveCartItemHandlerBody>
}
const METHODS = ['GET', 'POST', 'PUT', 'DELETE']
// TODO: a complete implementation should have schema validation for `req.body`
const cartApi: BigcommerceApiHandler<Cart, CartHandlers> = async (
const cartApi: BigcommerceApiHandler<BigcommerceCart, CartHandlers> = async (
req,
res,
config,

View File

@@ -1,4 +1,5 @@
import getAllProducts, { ProductEdge } from '../../operations/get-all-products'
import { Product } from '@commerce/types'
import getAllProducts, { ProductEdge } from '../../../product/get-all-products'
import type { ProductsHandlers } from '../products'
const SORT: { [key: string]: string | undefined } = {
@@ -6,6 +7,7 @@ const SORT: { [key: string]: string | undefined } = {
trending: 'total_sold',
price: 'price',
}
const LIMIT = 12
// Return current cart info
@@ -44,21 +46,25 @@ const getProducts: ProductsHandlers['getProducts'] = async ({
const { data } = await config.storeApiFetch<{ data: { id: number }[] }>(
url.pathname + url.search
)
const entityIds = data.map((p) => p.id)
const found = entityIds.length > 0
// We want the GraphQL version of each product
const graphqlData = await getAllProducts({
variables: { first: LIMIT, 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
[k: number]: Product
}>((prods, p) => {
prods[p.node.entityId] = p
prods[Number(p.id)] = p
return prods
}, {})
const products: ProductEdge[] = found ? [] : graphqlData.products
const products: Product[] = found ? [] : graphqlData.products
// Populate the products array with the graphql products, in the order
// assigned by the list of entity ids

View File

@@ -1,27 +1,27 @@
import type { Product } from '@commerce/types'
import isAllowedMethod from '../utils/is-allowed-method'
import createApiHandler, {
BigcommerceApiHandler,
BigcommerceHandler,
} from '../utils/create-api-handler'
import { BigcommerceApiError } from '../utils/errors'
import type { ProductEdge } from '../operations/get-all-products'
import getProducts from './handlers/get-products'
export type SearchProductsData = {
products: ProductEdge[]
products: Product[]
found: boolean
}
export type ProductsHandlers = {
getProducts: BigcommerceHandler<
SearchProductsData,
{ search?: 'string'; category?: string; brand?: string; sort?: string }
{ search?: string; category?: string; brand?: string; sort?: string }
>
}
const METHODS = ['GET']
// TODO: a complete implementation should have schema validation for `req.body`
// TODO(lf): a complete implementation should have schema validation for `req.body`
const productsApi: BigcommerceApiHandler<
SearchProductsData,
ProductsHandlers

View File

@@ -1,5 +1,5 @@
import { FetcherError } from '@commerce/utils/errors'
import login from '../../operations/login'
import login from '../../../auth/login'
import type { LoginHandlers } from '../login'
const invalidCredentials = /invalid credentials/i

View File

@@ -1,5 +1,5 @@
import { BigcommerceApiError } from '../../utils/errors'
import login from '../../operations/login'
import login from '../../../auth/login'
import { SignupHandlers } from '../signup'
const signup: SignupHandlers['signup'] = async ({

View File

@@ -1,43 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, getConfig } from '..'
import { definitions } from '../definitions/store-content'
export type Page = definitions['page_Full']
export type GetAllPagesResult<
T extends { pages: any[] } = { pages: Page[] }
> = T
async function getAllPages(opts?: {
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllPagesResult>
async function getAllPages<T extends { pages: any[] }>(opts: {
url: string
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllPagesResult<T>>
async function getAllPages({
config,
preview,
}: {
url?: string
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetAllPagesResult> {
config = 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<
RecursivePartial<{ data: Page[] }>
>('/v3/content/pages')
const pages = (data as RecursiveRequired<typeof data>) ?? []
return {
pages: preview ? pages : pages.filter((p) => p.is_visible),
}
}
export default getAllPages

View File

@@ -1,71 +0,0 @@
import type {
GetAllProductPathsQuery,
GetAllProductPathsQueryVariables,
} from '../../schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, getConfig } from '..'
export const getAllProductPathsQuery = /* GraphQL */ `
query getAllProductPaths($first: Int = 100) {
site {
products(first: $first) {
edges {
node {
path
}
}
}
}
}
`
export type ProductPath = NonNullable<
NonNullable<GetAllProductPathsQuery['site']['products']['edges']>[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<GetAllProductPathsResult>
async function getAllProductPaths<
T extends { products: any[] },
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
}): Promise<GetAllProductPathsResult<T>>
async function getAllProductPaths({
query = getAllProductPathsQuery,
variables,
config,
}: {
query?: string
variables?: GetAllProductPathsQueryVariables
config?: BigcommerceConfig
} = {}): Promise<GetAllProductPathsResult> {
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<GetAllProductPathsQuery>
>(query, { variables })
const products = data.site?.products?.edges
return {
products: filterEdges(products as RecursiveRequired<typeof products>),
}
}
export default getAllProductPaths

View File

@@ -1,132 +0,0 @@
import type {
GetAllProductsQuery,
GetAllProductsQueryVariables,
} from '../../schema'
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, getConfig } from '..'
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<GetAllProductsQuery['site']['products']['edges']>[0]
>
export type ProductNode = ProductEdge['node']
export type GetAllProductsResult<
T extends Record<keyof GetAllProductsResult, any[]> = {
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<GetAllProductsResult>
async function getAllProducts<
T extends Record<keyof GetAllProductsResult, any[]>,
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetAllProductsResult<T>>
async function getAllProducts({
query = getAllProductsQuery,
variables: { field = 'products', ...vars } = {},
config,
}: {
query?: string
variables?: ProductVariables
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetAllProductsResult> {
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<RecursivePartial<GetAllProductsQuery>>(
query,
{ variables }
)
const edges = data.site?.[field]?.edges
const products = filterEdges(edges as RecursiveRequired<typeof edges>)
if (locale && config.applyLocale) {
products.forEach((product: RecursivePartial<ProductEdge>) => {
if (product.node) setProductLocaleMeta(product.node)
})
}
return { products }
}
export default getAllProducts

View File

@@ -1,34 +0,0 @@
import { GetCustomerIdQuery } from '../../schema'
import { BigcommerceConfig, getConfig } from '..'
export const getCustomerIdQuery = /* GraphQL */ `
query getCustomerId {
customer {
entityId
}
}
`
async function getCustomerId({
customerToken,
config,
}: {
customerToken: string
config?: BigcommerceConfig
}): Promise<number | undefined> {
config = getConfig(config)
const { data } = await config.fetch<GetCustomerIdQuery>(
getCustomerIdQuery,
undefined,
{
headers: {
cookie: `${config.customerCookie}=${customerToken}`,
},
}
)
return data?.customer?.entityId
}
export default getCustomerId

View File

@@ -1,87 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { definitions } from '../definitions/wishlist'
import { BigcommerceConfig, getConfig } from '..'
import getAllProducts, { ProductEdge } from './get-all-products'
export type Wishlist = Omit<definitions['wishlist_Full'], 'items'> & {
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<GetCustomerWishlistResult>
async function getCustomerWishlist<
T extends { wishlist?: any },
V = any
>(opts: {
url: string
variables: V
config?: BigcommerceConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult<T>>
async function getCustomerWishlist({
config,
variables,
includeProducts,
}: {
url?: string
variables: GetCustomerWishlistVariables
config?: BigcommerceConfig
includeProducts?: boolean
}): Promise<GetCustomerWishlistResult> {
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[p.node.entityId] = p
return prods
}, {})
// Populate the wishlist items with the graphql products
wishlist.items.forEach((item) => {
const product = item && productsById[item.product_id!]
if (item && product) {
item.product = product.node
}
})
}
}
return { wishlist: wishlist as RecursiveRequired<typeof wishlist> }
}
export default getCustomerWishlist

View File

@@ -1,53 +0,0 @@
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import { BigcommerceConfig, getConfig } from '..'
import { definitions } from '../definitions/store-content'
export type Page = definitions['page_Full']
export type GetPageResult<T extends { page?: any } = { page?: Page }> = T
export type PageVariables = {
id: number
}
async function getPage(opts: {
url?: string
variables: PageVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult>
async function getPage<T extends { page?: any }, V = any>(opts: {
url: string
variables: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult<T>>
async function getPage({
url,
variables,
config,
preview,
}: {
url?: string
variables: PageVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetPageResult> {
config = 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<RecursivePartial<{ data: Page[] }>>(
url || `/v3/content/pages?id=${variables.id}&include=body`
)
const firstPage = data?.[0]
const page = firstPage as RecursiveRequired<typeof firstPage>
if (preview || page?.is_visible) {
return { page }
}
return {}
}
export default getPage

View File

@@ -1,118 +0,0 @@
import type { GetProductQuery, GetProductQueryVariables } from '../../schema'
import setProductLocaleMeta from '../utils/set-product-locale-meta'
import { productInfoFragment } from '../fragments/product'
import { BigcommerceConfig, getConfig } from '..'
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<GetProductResult>
async function getProduct<T extends { product?: any }, V = any>(opts: {
query: string
variables: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetProductResult<T>>
async function getProduct({
query = getProductQuery,
variables: { slug, ...vars },
config,
}: {
query?: string
variables: ProductVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetProductResult> {
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<GetProductQuery>(query, { variables })
const product = data.site?.route?.node
if (product?.__typename === 'Product') {
if (locale && config.applyLocale) {
setProductLocaleMeta(product)
}
return { product }
}
return {}
}
export default getProduct

View File

@@ -1,106 +0,0 @@
import type { GetSiteInfoQuery, GetSiteInfoQueryVariables } from '../../schema'
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
import filterEdges from '../utils/filter-edges'
import { BigcommerceConfig, getConfig } from '..'
import { categoryTreeItemFragment } from '../fragments/category-tree'
// Get 3 levels of categories
export const getSiteInfoQuery = /* GraphQL */ `
query getSiteInfo {
site {
categoryTree {
...categoryTreeItem
children {
...categoryTreeItem
children {
...categoryTreeItem
}
}
}
brands {
pageInfo {
startCursor
endCursor
}
edges {
cursor
node {
entityId
name
defaultImage {
urlOriginal
altText
}
pageTitle
metaDesc
metaKeywords
searchKeywords
path
}
}
}
}
}
${categoryTreeItemFragment}
`
export type CategoriesTree = NonNullable<
GetSiteInfoQuery['site']['categoryTree']
>
export type BrandEdge = NonNullable<
NonNullable<GetSiteInfoQuery['site']['brands']['edges']>[0]
>
export type Brands = BrandEdge[]
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: CategoriesTree
brands: Brands
}
> = T
async function getSiteInfo(opts?: {
variables?: GetSiteInfoQueryVariables
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetSiteInfoResult>
async function getSiteInfo<
T extends { categories: any[]; brands: any[] },
V = any
>(opts: {
query: string
variables?: V
config?: BigcommerceConfig
preview?: boolean
}): Promise<GetSiteInfoResult<T>>
async function getSiteInfo({
query = getSiteInfoQuery,
variables,
config,
}: {
query?: string
variables?: GetSiteInfoQueryVariables
config?: BigcommerceConfig
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
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<GetSiteInfoQuery>>(
query,
{ variables }
)
const categories = data.site?.categoryTree
const brands = data.site?.brands?.edges
return {
categories: (categories as RecursiveRequired<typeof categories>) ?? [],
brands: filterEdges(brands as RecursiveRequired<typeof brands>),
}
}
export default getSiteInfo

View File

@@ -1,73 +0,0 @@
import type { ServerResponse } from 'http'
import type { LoginMutation, LoginMutationVariables } from '../../schema'
import type { RecursivePartial } from '../utils/types'
import concatHeader from '../utils/concat-cookie'
import { BigcommerceConfig, getConfig } from '..'
export const loginMutation = /* GraphQL */ `
mutation login($email: String!, $password: String!) {
login(email: $email, password: $password) {
result
}
}
`
export type LoginResult<T extends { result?: any } = { result?: string }> = T
export type LoginVariables = LoginMutationVariables
async function login(opts: {
variables: LoginVariables
config?: BigcommerceConfig
res: ServerResponse
}): Promise<LoginResult>
async function login<T extends { result?: any }, V = any>(opts: {
query: string
variables: V
res: ServerResponse
config?: BigcommerceConfig
}): Promise<LoginResult<T>>
async function login({
query = loginMutation,
variables,
res: response,
config,
}: {
query?: string
variables: LoginVariables
res: ServerResponse
config?: BigcommerceConfig
}): Promise<LoginResult> {
config = getConfig(config)
const { data, res } = await config.fetch<RecursivePartial<LoginMutation>>(
query,
{ variables }
)
// Bigcommerce returns a Set-Cookie header with the auth cookie
let cookie = res.headers.get('Set-Cookie')
if (cookie && typeof cookie === 'string') {
// In development, don't set a secure cookie or the browser will ignore it
if (process.env.NODE_ENV !== 'production') {
cookie = cookie.replace('; Secure', '')
// SameSite=none can't be set unless the cookie is Secure
// bc seems to sometimes send back SameSite=None rather than none so make
// this case insensitive
cookie = cookie.replace(/; SameSite=none/gi, '; SameSite=lax')
}
response.setHeader(
'Set-Cookie',
concatHeader(response.getHeader('Set-Cookie'), cookie)!
)
}
return {
result: data.login?.result,
}
}
export default login

View File

@@ -1,14 +1,28 @@
import type { ItemBody as WishlistItemBody } from '../wishlist'
import type { ItemBody } from '../cart'
import type { CartItemBody, OptionSelections } from '../../types'
export const parseWishlistItem = (item: WishlistItemBody) => ({
product_id: item.productId,
variant_id: item.variantId,
type BCWishlistItemBody = {
product_id: number
variant_id: number
}
type BCCartItemBody = {
product_id: number
variant_id: number
quantity?: number
option_selections?: OptionSelections
}
export const parseWishlistItem = (
item: WishlistItemBody
): BCWishlistItemBody => ({
product_id: Number(item.productId),
variant_id: Number(item.variantId),
})
export const parseCartItem = (item: ItemBody) => ({
export const parseCartItem = (item: CartItemBody): BCCartItemBody => ({
quantity: item.quantity,
product_id: item.productId,
variant_id: item.variantId,
option_selections: item.optionSelections
product_id: Number(item.productId),
variant_id: Number(item.variantId),
option_selections: item.optionSelections,
})

View File

@@ -1,4 +1,4 @@
import type { ProductNode } from '../operations/get-all-products'
import type { ProductNode } from '../../product/get-all-products'
import type { RecursivePartial } from './types'
export default function setProductLocaleMeta(

View File

@@ -1,6 +1,6 @@
import type { WishlistHandlers } from '..'
import getCustomerId from '../../operations/get-customer-id'
import getCustomerWishlist from '../../operations/get-customer-wishlist'
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
import { parseWishlistItem } from '../../utils/parse-item'
// Returns the wishlist of the signed customer

View File

@@ -1,5 +1,5 @@
import getCustomerId from '../../operations/get-customer-id'
import getCustomerWishlist from '../../operations/get-customer-wishlist'
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist from '../../../customer/get-customer-wishlist'
import type { Wishlist, WishlistHandlers } from '..'
// Return wishlist info

View File

@@ -1,7 +1,7 @@
import getCustomerId from '../../operations/get-customer-id'
import getCustomerId from '../../../customer/get-customer-id'
import getCustomerWishlist, {
Wishlist,
} from '../../operations/get-customer-wishlist'
} from '../../../customer/get-customer-wishlist'
import type { WishlistHandlers } from '..'
// Return current wishlist info

View File

@@ -7,24 +7,25 @@ import { BigcommerceApiError } from '../utils/errors'
import type {
Wishlist,
WishlistItem,
} from '../operations/get-customer-wishlist'
} from '../../customer/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: number
variantId: number
productId: Product['id']
variantId: ProductVariant['id']
}
export type AddItemBody = { item: ItemBody }
export type RemoveItemBody = { itemId: string }
export type RemoveItemBody = { itemId: Product['id'] }
export type WishlistBody = {
customer_id: number
customer_id: Customer['entityId']
is_public: number
name: string
items: any[]