mirror of
https://github.com/vercel/commerce.git
synced 2025-07-23 04:36:49 +00:00
[WIP] Node.js provider for the API (#252)
* Adding multiple initial files * Updated the default cart endpoint * Fixes * Updated CommerceAPI class for better usage * Adding more migration changes * Taking multiple steps into better API types * Adding more experimental types * Removed many testing types * Adding types, fixes and other updates * Updated commerce types * Updated types for hooks now using the API * Updated mutation types * Simplified cart types for the provider * Updated cart hooks * Remove normalizers from the hooks * Updated cart endpoint * Removed cart handlers * bug fixes * Improve quantity input behavior in cart item * Removed endpoints folder * Making progress on api operations * Moved method * Moved types * Changed the way ops are created * Added customer endpoint * Login endpoint * Added logout endpoint * Add missing logout files * Added signup endpoint * Removed customers old endpoints * Moved endpoints to nested folder * Removed old customer endpoint builders * Updated login operation * Updated login operation * Added getAllPages operation * Renamed endpoint operations to handlers * Changed import * Renamed operations to handlers in usage * Moved getAllPages everywhere * Moved getPage * Updated getPage usage * Moved getSiteInfo * Added def types for product * Updated type * moved products catalog endpoint * removed old catalog endpoint * Moved wishlist * Removed commerce.endpoint * Replaced references to commerce.endpoint * Updated catalog products * Moved checkout api * Added the get customer wishlist operation * Removed old wishlist stuff * Added getAllProductPaths operation * updated reference to operation * Moved getAllProducts * Updated getProduct operation * Removed old getConfig and references * Removed is-allowed-method from BC * Updated types for auth hooks * Updated useCustomer and core types * Updated useData and util hooks * Updated useSearch hook * Updated types for useWishlist * Added index for types * Fixes * Updated urls to the API * Renamed fetchInput to fetcherInput * Updated fetch type * Fixes in search hook * Updated Shopify Provider Structure (#340) * Add codegen, update fragments & schemas * Update checkout-create.ts * Update checkout-create.ts * Update README.md * Update product mutations & queries * Uptate customer fetch types * Update schemas * Start updates * Moved Page, AllPages & Site Info * Moved product, all products (paths) * Add translations, update operations & fixes * Update api endpoints, types & fixes * Add api checkout endpoint * Updates * Fixes * Update commerce.config.json Co-authored-by: B <curciobelen@gmail.com> * Added category type and normalizer * updated init script to exclude other providers * Excluded swell and venture temporarily * Fix category & color normalization * Fixed category normalizer in shopify * Don't use getSlug for category on /search * Update colors.ts Co-authored-by: cond0r <pinte_catalin@yahoo.com> Co-authored-by: B <curciobelen@gmail.com>
This commit is contained in:
46
framework/bigcommerce/api/operations/get-all-pages.ts
Normal file
46
framework/bigcommerce/api/operations/get-all-pages.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { Page, GetAllPagesOperation } from '../../types/page'
|
||||
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||
import { BigcommerceConfig, Provider } from '..'
|
||||
|
||||
export default function getAllPagesOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllPages<T extends GetAllPagesOperation>(opts?: {
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllPages<T extends GetAllPagesOperation>(
|
||||
opts: {
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllPages<T extends GetAllPagesOperation>({
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<T['data']> {
|
||||
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 cfg.storeApiFetch<
|
||||
RecursivePartial<{ data: Page[] }>
|
||||
>('/v3/content/pages')
|
||||
const pages = (data as RecursiveRequired<typeof data>) ?? []
|
||||
|
||||
return {
|
||||
pages: preview ? pages : pages.filter((p) => p.is_visible),
|
||||
}
|
||||
}
|
||||
|
||||
return getAllPages
|
||||
}
|
@@ -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<Provider>) {
|
||||
async function getAllProductPaths<
|
||||
T extends GetAllProductPathsOperation
|
||||
>(opts?: {
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllProductPaths<T extends GetAllProductPathsOperation>({
|
||||
query = getAllProductPathsQuery,
|
||||
variables,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
} = {}): Promise<T['data']> {
|
||||
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<GetAllProductPathsQuery>
|
||||
>(query, { variables })
|
||||
const products = data.site?.products?.edges
|
||||
|
||||
return {
|
||||
products: filterEdges(products as RecursiveRequired<typeof products>).map(
|
||||
({ node }) => node
|
||||
),
|
||||
}
|
||||
}
|
||||
return getAllProductPaths
|
||||
}
|
135
framework/bigcommerce/api/operations/get-all-products.ts
Normal file
135
framework/bigcommerce/api/operations/get-all-products.ts
Normal file
@@ -0,0 +1,135 @@
|
||||
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<GetAllProductsQuery['site']['products']['edges']>[0]
|
||||
>
|
||||
|
||||
export type ProductNode = ProductEdge['node']
|
||||
|
||||
export type GetAllProductsResult<
|
||||
T extends Record<keyof GetAllProductsResult, any[]> = {
|
||||
products: ProductEdge[]
|
||||
}
|
||||
> = T
|
||||
|
||||
function getProductsType(
|
||||
relevance?: GetAllProductsOperation['variables']['relevance']
|
||||
) {
|
||||
switch (relevance) {
|
||||
case 'featured':
|
||||
return 'featuredProducts'
|
||||
case 'best_selling':
|
||||
return 'bestSellingProducts'
|
||||
case 'newest':
|
||||
return 'newestProducts'
|
||||
default:
|
||||
return 'products'
|
||||
}
|
||||
}
|
||||
|
||||
export default function getAllProductsOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getAllProducts<T extends GetAllProductsOperation>(opts?: {
|
||||
variables?: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getAllProducts<T extends GetAllProductsOperation>(
|
||||
opts: {
|
||||
variables?: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getAllProducts<T extends GetAllProductsOperation>({
|
||||
query = getAllProductsQuery,
|
||||
variables: vars = {},
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables?: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<T['data']> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
const { locale } = config
|
||||
const field = getProductsType(vars.relevance)
|
||||
const variables: GetAllProductsQueryVariables = {
|
||||
locale,
|
||||
hasLocale: !!locale,
|
||||
}
|
||||
|
||||
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<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: products.map(({ node }) => normalizeProduct(node as any)),
|
||||
}
|
||||
}
|
||||
|
||||
return getAllProducts
|
||||
}
|
@@ -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 './get-all-products'
|
||||
|
||||
export default function getCustomerWishlistOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getCustomerWishlist<
|
||||
T extends GetCustomerWishlistOperation
|
||||
>(opts: {
|
||||
variables: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
includeProducts?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
includeProducts?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getCustomerWishlist<T extends GetCustomerWishlistOperation>({
|
||||
config,
|
||||
variables,
|
||||
includeProducts,
|
||||
}: {
|
||||
url?: string
|
||||
variables: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
includeProducts?: boolean
|
||||
}): Promise<T['data']> {
|
||||
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 ids = wishlist.items
|
||||
?.map((item) => (item?.product_id ? String(item?.product_id) : null))
|
||||
.filter((id): id is string => !!id)
|
||||
|
||||
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
|
||||
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<typeof wishlist> }
|
||||
}
|
||||
|
||||
return getCustomerWishlist
|
||||
}
|
54
framework/bigcommerce/api/operations/get-page.ts
Normal file
54
framework/bigcommerce/api/operations/get-page.ts
Normal file
@@ -0,0 +1,54 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { GetPageOperation, Page } from '../../types/page'
|
||||
import type { RecursivePartial, RecursiveRequired } from '../utils/types'
|
||||
import type { BigcommerceConfig, Provider } from '..'
|
||||
import { normalizePage } from '../../lib/normalize'
|
||||
|
||||
export default function getPageOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getPage<T extends GetPageOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getPage<T extends GetPageOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getPage<T extends GetPageOperation>({
|
||||
url,
|
||||
variables,
|
||||
config,
|
||||
preview,
|
||||
}: {
|
||||
url?: string
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']> {
|
||||
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 cfg.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: normalizePage(page as any) }
|
||||
}
|
||||
return {}
|
||||
}
|
||||
|
||||
return getPage
|
||||
}
|
119
framework/bigcommerce/api/operations/get-product.ts
Normal file
119
framework/bigcommerce/api/operations/get-product.ts
Normal file
@@ -0,0 +1,119 @@
|
||||
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<Provider>) {
|
||||
async function getProduct<T extends GetProductOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getProduct<T extends GetProductOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getProduct<T extends GetProductOperation>({
|
||||
query = getProductQuery,
|
||||
variables: { slug, ...vars },
|
||||
config: cfg,
|
||||
}: {
|
||||
query?: string
|
||||
variables: T['variables']
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']> {
|
||||
const config = commerce.getConfig(cfg)
|
||||
const { locale } = config
|
||||
const variables: GetProductQueryVariables = {
|
||||
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: normalizeProduct(product as any) }
|
||||
}
|
||||
|
||||
return {}
|
||||
}
|
||||
return getProduct
|
||||
}
|
87
framework/bigcommerce/api/operations/get-site-info.ts
Normal file
87
framework/bigcommerce/api/operations/get-site-info.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { GetSiteInfoOperation } from '../../types/site'
|
||||
import type { GetSiteInfoQuery } from '../../schema'
|
||||
import filterEdges from '../utils/filter-edges'
|
||||
import type { BigcommerceConfig, Provider } from '..'
|
||||
import { categoryTreeItemFragment } from '../fragments/category-tree'
|
||||
import { normalizeCategory } from '../../lib/normalize'
|
||||
|
||||
// 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 default function getSiteInfoOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>(opts?: {
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>(
|
||||
opts: {
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function getSiteInfo<T extends GetSiteInfoOperation>({
|
||||
query = getSiteInfoQuery,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
config?: Partial<BigcommerceConfig>
|
||||
preview?: boolean
|
||||
} = {}): Promise<T['data']> {
|
||||
const cfg = commerce.getConfig(config)
|
||||
const { data } = await cfg.fetch<GetSiteInfoQuery>(query)
|
||||
const categories = data.site.categoryTree.map(normalizeCategory)
|
||||
const brands = data.site?.brands?.edges
|
||||
|
||||
return {
|
||||
categories: categories ?? [],
|
||||
brands: filterEdges(brands),
|
||||
}
|
||||
}
|
||||
|
||||
return getSiteInfo
|
||||
}
|
79
framework/bigcommerce/api/operations/login.ts
Normal file
79
framework/bigcommerce/api/operations/login.ts
Normal file
@@ -0,0 +1,79 @@
|
||||
import type { ServerResponse } from 'http'
|
||||
import type {
|
||||
OperationContext,
|
||||
OperationOptions,
|
||||
} from '@commerce/api/operations'
|
||||
import type { LoginOperation } from '../../types/login'
|
||||
import type { LoginMutation } from '../../schema'
|
||||
import type { RecursivePartial } from '../utils/types'
|
||||
import concatHeader from '../utils/concat-cookie'
|
||||
import type { BigcommerceConfig, Provider } from '..'
|
||||
|
||||
export const loginMutation = /* GraphQL */ `
|
||||
mutation login($email: String!, $password: String!) {
|
||||
login(email: $email, password: $password) {
|
||||
result
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export default function loginOperation({
|
||||
commerce,
|
||||
}: OperationContext<Provider>) {
|
||||
async function login<T extends LoginOperation>(opts: {
|
||||
variables: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
res: ServerResponse
|
||||
}): Promise<T['data']>
|
||||
|
||||
async function login<T extends LoginOperation>(
|
||||
opts: {
|
||||
variables: T['variables']
|
||||
config?: BigcommerceConfig
|
||||
res: ServerResponse
|
||||
} & OperationOptions
|
||||
): Promise<T['data']>
|
||||
|
||||
async function login<T extends LoginOperation>({
|
||||
query = loginMutation,
|
||||
variables,
|
||||
res: response,
|
||||
config,
|
||||
}: {
|
||||
query?: string
|
||||
variables: T['variables']
|
||||
res: ServerResponse
|
||||
config?: BigcommerceConfig
|
||||
}): Promise<T['data']> {
|
||||
config = commerce.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,
|
||||
}
|
||||
}
|
||||
|
||||
return login
|
||||
}
|
Reference in New Issue
Block a user