SFCC provider (#727)

* new SFCC provider

* add search

* normalization + search

* categories as search results

* adress PR feedback

* Update README.md

* get all paths for SSG

* product variants and options

* Apply suggestions from code review

Co-authored-by: Luis Alvarez D. <luis@vercel.com>

* remove console log

* prettier

* clean console log

* ran prettier

* Updated readme

* remove static data and revert config changes

* set default site

Co-authored-by: Luis Alvarez D. <luis@vercel.com>
This commit is contained in:
Dom Sip
2022-04-20 18:08:26 +01:00
committed by GitHub
parent a46057c5ef
commit 66e3269b0e
61 changed files with 1516 additions and 11 deletions

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1,32 @@
import { normalizeSearchProducts } from '../../../utils/normalise-product'
import { ProductsEndpoint } from '.'
const getProducts: ProductsEndpoint['handlers']['getProducts'] = async ({
req,
res,
body: { search, categoryId, brandId, sort },
config,
}) => {
const { sdk } = config
// 'clothing' is our main category default, and a manually set category has priority
const searchTerm = categoryId ? (categoryId as string) : search || 'clothing'
const searchClient = await sdk.getSearchClient()
// use SDK search API for initial products
const searchResults = await searchClient.productSearch({
parameters: {
q: searchTerm,
limit: 20,
},
})
let products = []
let found = false
if (searchResults.total) {
found = true
products = normalizeSearchProducts(searchResults.hits) as any[]
}
res.status(200).json({ data: { products, found } })
}
export default getProducts

View File

@@ -0,0 +1,19 @@
import type { SFCCProviderAPI } from '../../..'
import { createEndpoint, GetAPISchema } from '@vercel/commerce/api'
import { ProductsSchema } from '@vercel/commerce/types/product'
import getProducts from './get-products'
import productsEndpoint from '@vercel/commerce/api/endpoints/catalog/products'
export type ProductsAPI = GetAPISchema<SFCCProviderAPI, ProductsSchema>
export type ProductsEndpoint = ProductsAPI['endpoint']
export const handlers: ProductsEndpoint['handlers'] = { getProducts }
const productsApi = createEndpoint<ProductsAPI>({
handler: productsEndpoint,
handlers,
})
export default productsApi

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1 @@
export default function noopApi(...args: any[]): void {}

View File

@@ -0,0 +1,48 @@
import type { CommerceAPI, CommerceAPIConfig } from '@vercel/commerce/api'
import { getCommerceApi as commerceApi } from '@vercel/commerce/api'
import createFetcher from './utils/fetch-local'
import sdk, { Sdk } from './utils/sfcc-sdk'
import getAllPages from './operations/get-all-pages'
import getPage from './operations/get-page'
import getSiteInfo from './operations/get-site-info'
import getCustomerWishlist from './operations/get-customer-wishlist'
import getAllProductPaths from './operations/get-all-product-paths'
import getAllProducts from './operations/get-all-products'
import getProduct from './operations/get-product'
export interface SFCCConfig extends CommerceAPIConfig {
sdk: Sdk
}
const config: SFCCConfig = {
commerceUrl: '',
apiToken: '',
cartCookie: '',
customerCookie: '',
cartCookieMaxAge: 2592000,
fetch: createFetcher(() => getCommerceApi().getConfig()),
sdk, // SalesForce Cloud Commerce API SDK
}
const operations = {
getAllPages,
getPage,
getSiteInfo,
getCustomerWishlist,
getAllProductPaths,
getAllProducts,
getProduct,
}
export const provider = { config, operations }
export type Provider = typeof provider
export type SFCCProviderAPI<P extends Provider = Provider> = CommerceAPI<
P | any
>
export function getCommerceApi<P extends Provider>(
customProvider: P = provider as any
): SFCCProviderAPI<P> {
return commerceApi(customProvider as any)
}

View File

@@ -0,0 +1,19 @@
export type Page = { url: string }
export type GetAllPagesResult = { pages: Page[] }
import type { SFCCConfig } from '../index'
export default function getAllPagesOperation() {
function getAllPages({
config,
preview,
}: {
url?: string
config?: Partial<SFCCConfig>
preview?: boolean
}): Promise<GetAllPagesResult> {
return Promise.resolve({
pages: [],
})
}
return getAllPages
}

View File

@@ -0,0 +1,45 @@
import { Product } from '@vercel/commerce/types/product'
import { OperationContext } from '@vercel/commerce/api/operations'
import { normalizeSearchProducts } from '../utils/normalise-product'
import { SFCCConfig } from '..'
export type GetAllProductPathsResult = {
products: Array<{ path: string }>
}
export default function getAllProductPathsOperation({
commerce,
}: OperationContext<any>) {
async function getAllProductPaths({
query,
config,
variables,
}: {
query?: string
config?: SFCCConfig
variables?: any
} = {}): Promise<GetAllProductPathsResult> {
// TODO: support locale
const { sdk, locale } = commerce.getConfig(config) as SFCCConfig
const searchClient = await sdk.getSearchClient()
// use SDK search API for initial products same as getAllProductsOperation
const searchResults = await searchClient.productSearch({
parameters: { q: 'dress', limit: variables?.first },
})
let products = [] as Product[]
if (searchResults.total) {
products = normalizeSearchProducts(searchResults.hits)
}
return {
products: products?.map(({ slug }: Product) => ({
path: `/${slug}`,
})),
}
}
return getAllProductPaths
}

View File

@@ -0,0 +1,40 @@
import { Product } from '@vercel/commerce/types/product'
import { GetAllProductsOperation } from '@vercel/commerce/types/product'
import type { OperationContext } from '@vercel/commerce/api/operations'
import type { SFCCConfig } from '../index'
import { normalizeSearchProducts } from '../utils/normalise-product'
export default function getAllProductsOperation({
commerce,
}: OperationContext<any>) {
async function getAllProducts<T extends GetAllProductsOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<SFCCConfig>
preview?: boolean
} = {}): Promise<{ products: Product[] | any[] }> {
// TODO: support locale
const { sdk, locale } = commerce.getConfig(config) as SFCCConfig
const searchClient = await sdk.getSearchClient()
// use SDK search API for initial products
const searchResults = await searchClient.productSearch({
parameters: { q: 'dress', limit: variables?.first },
})
let products = [] as Product[]
if (searchResults.total) {
products = normalizeSearchProducts(searchResults.hits)
}
return {
products: products,
}
}
return getAllProducts
}

View File

@@ -0,0 +1,6 @@
export default function getCustomerWishlistOperation() {
function getCustomerWishlist(): any {
return { wishlist: {} }
}
return getCustomerWishlist
}

View File

@@ -0,0 +1,13 @@
export type Page = any
export type GetPageResult = { page?: Page }
export type PageVariables = {
id: number
}
export default function getPageOperation() {
function getPage(): Promise<GetPageResult> {
return Promise.resolve({})
}
return getPage
}

View File

@@ -0,0 +1,33 @@
import { GetProductOperation, Product } from '@vercel/commerce/types/product'
import type { SFCCConfig } from '../index'
import type { OperationContext } from '@vercel/commerce/api/operations'
import { normalizeProduct } from '../utils/normalise-product'
export default function getProductOperation({
commerce,
}: OperationContext<any>) {
async function getProduct<T extends GetProductOperation>({
query = '',
variables,
config,
}: {
query?: string
variables?: T['variables']
config?: Partial<SFCCConfig>
preview?: boolean
} = {}): Promise<Product | {} | any> {
// TODO: support locale
const { sdk, locale } = commerce.getConfig(config) as SFCCConfig
const shopperProductsClient = await sdk.getshopperProductsClient()
const product = await shopperProductsClient.getProduct({
parameters: { id: variables?.slug as string },
})
const normalizedProduct = normalizeProduct(product)
return {
product: normalizedProduct,
}
}
return getProduct
}

View File

@@ -0,0 +1,43 @@
import { OperationContext } from '@vercel/commerce/api/operations'
import { Category } from '@vercel/commerce/types/site'
import { SFCCConfig } from '../index'
export type GetSiteInfoResult<
T extends { categories: any[]; brands: any[] } = {
categories: Category[]
brands: any[]
}
> = T
export default function getSiteInfoOperation({}: OperationContext<any>) {
function getSiteInfo({
query,
variables,
config: cfg,
}: {
query?: string
variables?: any
config?: Partial<SFCCConfig>
preview?: boolean
} = {}): Promise<GetSiteInfoResult> {
return Promise.resolve({
categories: [
{
id: 'new-arrivals',
name: 'New Arrivals',
slug: 'new-arrivals',
path: '/new-arrivals',
},
{
id: 'womens-clothing-dresses',
name: 'Womens Clothing Dresses',
slug: 'womens-clothing-dresses',
path: '/womens-clothing-dresses',
},
],
brands: [],
})
}
return getSiteInfo
}

View File

@@ -0,0 +1,6 @@
export { default as getPage } from './get-page'
export { default as getSiteInfo } from './get-site-info'
export { default as getAllPages } from './get-all-pages'
export { default as getProduct } from './get-product'
export { default as getAllProducts } from './get-all-products'
export { default as getAllProductPaths } from './get-all-product-paths'

View File

@@ -0,0 +1,34 @@
import { FetcherError } from '@vercel/commerce/utils/errors'
import type { GraphQLFetcher } from '@vercel/commerce/api'
import type { SFCCConfig } from '../index'
import fetch from './fetch'
const fetchGraphqlApi: (getConfig: () => SFCCConfig) => GraphQLFetcher =
(getConfig) =>
async (query: string, { variables, preview } = {}, fetchOptions) => {
const config = getConfig()
const res = await fetch(config.commerceUrl, {
...fetchOptions,
method: 'POST',
headers: {
...fetchOptions?.headers,
'Content-Type': 'application/json',
},
body: JSON.stringify({
query,
variables,
}),
})
const json = await res.json()
if (json.errors) {
throw new FetcherError({
errors: json.errors ?? [{ message: 'Failed to fetch for API' }],
status: res.status,
})
}
return { data: json.data, res }
}
export default fetchGraphqlApi

View File

@@ -0,0 +1,3 @@
import zeitFetch from '@vercel/fetch'
export default zeitFetch()

View File

@@ -0,0 +1,42 @@
import { ClientConfig, Customer } from 'commerce-sdk'
// client configuration parameters
export const clientConfig: ClientConfig = {
headers: {
authorization: ``,
},
parameters: {
clientId: process.env.SFCC_CLIENT_ID || '',
organizationId: process.env.SFCC_ORG_ID || '',
shortCode: process.env.SFCC_SHORT_CODE || '',
siteId: process.env.SFCC_SITE_ID || '',
},
}
/**
* Get the shopper or guest JWT/access token, along with a refresh token, using client credentials
*
* @returns guest user authorization token
*/
export async function getGuestUserAuthToken(): Promise<Customer.ShopperLogin.TokenResponse> {
const credentials = `${process.env.SFCC_CLIENT_ID}:${process.env.SFCC_CLIENT_SECRET}`
const base64data = Buffer.from(credentials).toString('base64')
const headers = { Authorization: `Basic ${base64data}` }
const client = new Customer.ShopperLogin(clientConfig)
return await client.getAccessToken({
headers,
body: {
grant_type: 'client_credentials',
},
})
}
export const getConfigAuth = async () => {
const shopperToken = await getGuestUserAuthToken()
const configAuth = {
...clientConfig,
headers: { authorization: `Bearer ${shopperToken.access_token}` },
}
return configAuth
}

View File

@@ -0,0 +1,96 @@
import { Product as SFCCProduct, Search } from 'commerce-sdk'
import type {
Product,
ProductImage,
ProductOption,
ProductVariant,
} from '@vercel/commerce/types/product'
const normaliseOptions = (
options: SFCCProduct.ShopperProducts.Product['variationAttributes']
): Product['options'] => {
if (!Array.isArray(options)) return []
return options.map((option) => {
return {
id: option.id,
displayName: option.name as string,
values: option.values!.map((value) => ({ label: value.name })),
} as ProductOption
})
}
const normaliseVariants = (
variants: SFCCProduct.ShopperProducts.Product['variants']
): Product['variants'] => {
if (!Array.isArray(variants)) return []
return variants.map((variant) => {
const options = [] as ProductOption[]
if (variant.variationValues) {
for (const [key, value] of Object.entries(variant.variationValues)) {
const variantOptionObject = {
id: `${variant.productId}-${key}`,
displayName: key,
values: [
{
label: value,
},
],
}
options.push(variantOptionObject)
}
}
return {
id: variant.productId,
options,
} as ProductVariant
})
}
export function normalizeProduct(
product: SFCCProduct.ShopperProducts.Product
): Product {
return {
id: product.id,
// TODO: use `name-ID` as a virtual slug (for search 1:1)
slug: product.id, // use product ID as a slug
name: product.name!,
description: product.longDescription!,
price: {
value: product.price!,
currencyCode: product.currency,
},
images: product.imageGroups![0].images.map((image) => ({
url: image.disBaseLink,
altText: image.title,
})) as ProductImage[],
variants: normaliseVariants(product.variants),
options: normaliseOptions(product.variationAttributes),
}
}
export function normalizeSearchProducts(
products: Search.ShopperSearch.ProductSearchHit[]
): Product[] {
return products.map((product) => ({
id: product.productId,
slug: product.productId, // use product ID as a slug
name: product.productName!,
description: '',
price: {
value: product.price!,
currencyCode: product.currency,
},
images: [
{
url: product.image!.link,
altText: product.productName,
} as ProductImage,
],
variants: normaliseVariants(product.variants),
options: normaliseOptions(product.variationAttributes),
}))
}

View File

@@ -0,0 +1,19 @@
import { Product, Search } from 'commerce-sdk'
import { getConfigAuth } from './get-auth-token'
const getSearchClient = async () => {
const configAuth = await getConfigAuth()
return new Search.ShopperSearch(configAuth)
}
const getshopperProductsClient = async () => {
const configAuth = await getConfigAuth()
return new Product.ShopperProducts(configAuth)
}
export const sdk = {
getshopperProductsClient,
getSearchClient,
}
export type Sdk = typeof sdk
export default sdk

View File

@@ -0,0 +1,3 @@
export { default as useLogin } from './use-login'
export { default as useLogout } from './use-logout'
export { default as useSignup } from './use-signup'

View File

@@ -0,0 +1,16 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useLogin, { UseLogin } from '@vercel/commerce/auth/use-login'
export default useLogin as UseLogin<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook: () => () => {
return async function () {}
},
}

View File

@@ -0,0 +1,17 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useLogout, { UseLogout } from '@vercel/commerce/auth/use-logout'
export default useLogout as UseLogout<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
async () => {},
}

View File

@@ -0,0 +1,19 @@
import { useCallback } from 'react'
import useCustomer from '../customer/use-customer'
import { MutationHook } from '@vercel/commerce/utils/types'
import useSignup, { UseSignup } from '@vercel/commerce/auth/use-signup'
export default useSignup as UseSignup<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return null
},
useHook:
({ fetch }) =>
() =>
() => {},
}

View File

@@ -0,0 +1,4 @@
export { default as useCart } from './use-cart'
export { default as useAddItem } from './use-add-item'
export { default as useRemoveItem } from './use-remove-item'
export { default as useUpdateItem } from './use-update-item'

View File

@@ -0,0 +1,17 @@
import useAddItem, { UseAddItem } from '@vercel/commerce/cart/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function addItem() {
return {}
}
},
}

View File

@@ -0,0 +1,42 @@
import { useMemo } from 'react'
import { SWRHook } from '@vercel/commerce/utils/types'
import useCart, { UseCart } from '@vercel/commerce/cart/use-cart'
export default useCart as UseCart<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher() {
return {
id: '',
createdAt: '',
currency: { code: '' },
taxesIncluded: '',
lineItems: [],
lineItemsSubtotalPrice: '',
subtotalPrice: 0,
totalPrice: 0,
}
},
useHook:
({ useData }) =>
(input) => {
return useMemo(
() =>
Object.create(
{},
{
isEmpty: {
get() {
return true
},
enumerable: true,
},
}
),
[]
)
},
}

View File

@@ -0,0 +1,20 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useRemoveItem, {
UseRemoveItem,
} from '@vercel/commerce/cart/use-remove-item'
export default useRemoveItem as UseRemoveItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function removeItem(input) {
return {}
}
},
}

View File

@@ -0,0 +1,20 @@
import { MutationHook } from '@vercel/commerce/utils/types'
import useUpdateItem, {
UseUpdateItem,
} from '@vercel/commerce/cart/use-update-item'
export default useUpdateItem as UseUpdateItem<any>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() => {
return async function addItem() {
return {}
}
},
}

View File

@@ -0,0 +1,16 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useCheckout, {
UseCheckout,
} from '@vercel/commerce/checkout/use-checkout'
export default useCheckout as UseCheckout<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ useData }) =>
async (input) => ({}),
}

View File

@@ -0,0 +1,10 @@
{
"provider": "sfcc",
"features": {
"wishlist": false,
"cart": false,
"search": true,
"customerAuth": false,
"customCheckout": false
}
}

View File

@@ -0,0 +1,17 @@
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/address/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() =>
async () => ({}),
}

View File

@@ -0,0 +1,17 @@
import useAddItem, {
UseAddItem,
} from '@vercel/commerce/customer/card/use-add-item'
import { MutationHook } from '@vercel/commerce/utils/types'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook:
({ fetch }) =>
() =>
async () => ({}),
}

View File

@@ -0,0 +1 @@
export { default as useCustomer } from './use-customer'

View File

@@ -0,0 +1,17 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useCustomer, {
UseCustomer,
} from '@vercel/commerce/customer/use-customer'
export default useCustomer as UseCustomer<typeof handler>
export const handler: SWRHook<any> = {
fetchOptions: {
query: '',
},
async fetcher({ input, options, fetch }) {},
useHook: () => () => {
return async function addItem() {
return {}
}
},
}

View File

@@ -0,0 +1,17 @@
import { Fetcher } from '@vercel/commerce/utils/types'
const clientFetcher: Fetcher = async ({ method, url, body }) => {
const response = await fetch(url!, {
method,
body: body ? JSON.stringify(body) : undefined,
headers: {
'Content-Type': 'application/json',
},
})
.then((response) => response.json())
.then((response) => response.data)
return response
}
export default clientFetcher

View File

@@ -0,0 +1,12 @@
import {
getCommerceProvider,
useCommerce as useCoreCommerce,
} from '@vercel/commerce'
import { sfccProvider, SfccProvider } from './provider'
export { sfccProvider }
export type { SfccProvider }
export const CommerceProvider = getCommerceProvider(sfccProvider)
export const useCommerce = () => useCoreCommerce<SfccProvider>()

View File

@@ -0,0 +1,12 @@
const commerce = require('./commerce.config.json')
module.exports = {
commerce,
images: {
domains: [
'localhost',
'edge.disstg.commercecloud.salesforce.com',
'zzte-053.sandbox.us02.dx.commercecloud.salesforce.com',
],
},
}

View File

@@ -0,0 +1,2 @@
export { default as usePrice } from './use-price'
export { default as useSearch } from './use-search'

View File

@@ -0,0 +1,2 @@
export * from '@vercel/commerce/product/use-price'
export { default } from '@vercel/commerce/product/use-price'

View File

@@ -0,0 +1,42 @@
import { SWRHook } from '@vercel/commerce/utils/types'
import useSearch, { UseSearch } from '@vercel/commerce/product/use-search'
import { SearchProductsHook } from '@vercel/commerce/types/product'
export default useSearch as UseSearch<typeof handler>
export const handler: SWRHook<SearchProductsHook> = {
fetchOptions: {
url: '/api/catalog/products',
method: 'GET',
},
fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) {
console.log('search', search, categoryId, options)
// Use a dummy base as we only care about the relative path
const url = new URL(options.url!, 'http://a')
if (search) url.searchParams.set('search', String(search))
if (categoryId) url.searchParams.set('categoryId', String(categoryId))
if (brandId) url.searchParams.set('brandId', String(brandId))
if (sort) url.searchParams.set('sort', String(sort))
return fetch({
url: url.pathname + url.search,
method: options.method,
})
},
useHook:
({ useData }) =>
(input = {}) => {
return useData({
input: [
['search', input.search],
['categoryId', input.categoryId],
['brandId', input.brandId],
['sort', input.sort],
],
swrOptions: {
revalidateOnFocus: false,
...input.swrOptions,
},
})
},
}

View File

@@ -0,0 +1,22 @@
import fetcher from './fetcher'
import { handler as useCart } from './cart/use-cart'
import { handler as useAddItem } from './cart/use-add-item'
import { handler as useUpdateItem } from './cart/use-update-item'
import { handler as useRemoveItem } from './cart/use-remove-item'
import { handler as useCustomer } from './customer/use-customer'
import { handler as useSearch } from './product/use-search'
import { handler as useLogin } from './auth/use-login'
import { handler as useLogout } from './auth/use-logout'
import { handler as useSignup } from './auth/use-signup'
export const sfccProvider = {
locale: 'en-us',
cartCookie: 'session',
fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
customer: { useCustomer },
products: { useSearch },
auth: { useLogin, useLogout, useSignup },
}
export type SfccProvider = typeof sfccProvider

View File

@@ -0,0 +1,13 @@
import { useCallback } from 'react'
export function emptyHook() {
const useEmptyHook = async (options = {}) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@@ -0,0 +1,17 @@
import { useCallback } from 'react'
type Options = {
includeProducts?: boolean
}
export function emptyHook(options?: Options) {
const useEmptyHook = async ({ id }: { id: string | number }) => {
return useCallback(async function () {
return Promise.resolve()
}, [])
}
return useEmptyHook
}
export default emptyHook

View File

@@ -0,0 +1,43 @@
import { HookFetcher } from '@vercel/commerce/utils/types'
import type { Product } from '@vercel/commerce/types/product'
const defaultOpts = {}
export type Wishlist = {
items: [
{
product_id: number
variant_id: number
id: number
product: Product
}
]
}
export interface UseWishlistOptions {
includeProducts?: boolean
}
export interface UseWishlistInput extends UseWishlistOptions {
customerId?: number
}
export const fetcher: HookFetcher<Wishlist | null, UseWishlistInput> = () => {
return null
}
export function extendHook(
customFetcher: typeof fetcher,
// swrOptions?: SwrOptions<Wishlist | null, UseWishlistInput>
swrOptions?: any
) {
const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => {
return { data: null }
}
useWishlist.extend = extendHook
return useWishlist
}
export default extendHook(fetcher)