diff --git a/framework/bigcommerce/product/use-search.tsx b/framework/bigcommerce/product/use-search.tsx index 2b4ee7593..52db6a72d 100644 --- a/framework/bigcommerce/product/use-search.tsx +++ b/framework/bigcommerce/product/use-search.tsx @@ -1,63 +1,4 @@ -import type { HookFetcher } from '@commerce/utils/types' -import type { SwrOptions } from '@commerce/utils/use-data' -import useCommerceSearch from '@commerce/products/use-search' -import type { SearchProductsData } from '../api/catalog/products' +import useSearch, { UseSearch } from '@commerce/products/use-search' +import type { BigcommerceProvider } from '..' -const defaultOpts = { - url: '/api/bigcommerce/catalog/products', - method: 'GET', -} - -export type SearchProductsInput = { - search?: string - categoryId?: number - brandId?: number - sort?: string -} - -export const fetcher: HookFetcher = ( - options, - { search, categoryId, brandId, sort }, - fetch -) => { - // Use a dummy base as we only care about the relative path - const url = new URL(options?.url ?? defaultOpts.url, 'http://a') - - if (search) url.searchParams.set('search', search) - if (Number.isInteger(categoryId)) - url.searchParams.set('category', String(categoryId)) - if (Number.isInteger(brandId)) url.searchParams.set('brand', String(brandId)) - if (sort) url.searchParams.set('sort', sort) - - return fetch({ - url: url.pathname + url.search, - method: options?.method ?? defaultOpts.method, - }) -} - -export function extendHook( - customFetcher: typeof fetcher, - swrOptions?: SwrOptions -) { - const useSearch = (input: SearchProductsInput = {}) => { - const response = useCommerceSearch( - defaultOpts, - [ - ['search', input.search], - ['categoryId', input.categoryId], - ['brandId', input.brandId], - ['sort', input.sort], - ], - customFetcher, - { revalidateOnFocus: false, ...swrOptions } - ) - - return response - } - - useSearch.extend = extendHook - - return useSearch -} - -export default extendHook(fetcher) +export default useSearch as UseSearch diff --git a/framework/bigcommerce/provider.tsx b/framework/bigcommerce/provider.tsx index d3ae4d171..653a28a87 100644 --- a/framework/bigcommerce/provider.tsx +++ b/framework/bigcommerce/provider.tsx @@ -5,6 +5,7 @@ import type { FetchCartInput } from '@commerce/cart/use-cart' import { normalizeCart } from './lib/normalize' import type { Wishlist } from './api/wishlist' import type { Customer, CustomerData } from './api/customers' +import type { SearchProductsData } from './api/catalog/products' import useCustomer from './customer/use-customer' import type { Cart } from './types' @@ -153,6 +154,56 @@ const useCustomerHandler: HookHandler< }, } +export type SearchProductsInput = { + search?: string + categoryId?: number + brandId?: number + sort?: string +} + +const useSearch: HookHandler< + SearchProductsData, + SearchProductsInput, + SearchProductsInput, + any, + any +> = { + fetchOptions: { + url: '/api/bigcommerce/catalog/products', + method: 'GET', + }, + fetcher({ input: { search, categoryId, brandId, sort }, options, fetch }) { + // 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', search) + if (Number.isInteger(categoryId)) + url.searchParams.set('category', String(categoryId)) + if (Number.isInteger(brandId)) + url.searchParams.set('brand', String(brandId)) + if (sort) url.searchParams.set('sort', sort) + + return fetch({ + url: url.pathname + url.search, + method: options.method, + }) + }, + useHook({ input, useData }) { + return useData({ + input: [ + ['search', input.search], + ['categoryId', input.categoryId], + ['brandId', input.brandId], + ['sort', input.sort], + ], + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) + }, +} + export const bigcommerceProvider = { locale: 'en-us', cartCookie: 'bc_cartId', @@ -161,6 +212,7 @@ export const bigcommerceProvider = { cart: { useCart }, wishlist: { useWishlist }, customer: { useCustomer: useCustomerHandler }, + products: { useSearch }, } export type BigcommerceProvider = typeof bigcommerceProvider diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index ccfc07e66..d8d882f93 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -8,7 +8,7 @@ import { } from 'react' import { Fetcher, HookHandler } from './utils/types' import type { FetchCartInput } from './cart/use-cart' -import type { Cart, Wishlist, Customer } from './types' +import type { Cart, Wishlist, Customer, SearchProductsData } from './types' const Commerce = createContext | {}>({}) @@ -23,6 +23,9 @@ export type Provider = CommerceConfig & { customer: { useCustomer?: HookHandler } + products: { + useSearch?: HookHandler + } } export type CommerceProps

= { diff --git a/framework/commerce/products/use-search.tsx b/framework/commerce/products/use-search.tsx index 637c8a899..9971c309d 100644 --- a/framework/commerce/products/use-search.tsx +++ b/framework/commerce/products/use-search.tsx @@ -1,5 +1,57 @@ -import useData from '../utils/use-data' +import type { SearchProductsData } from '../types' +import type { + Prop, + HookFetcherFn, + UseHookInput, + UseHookResponse, +} from '../utils/types' +import defaultFetcher from '../utils/default-fetcher' +import useData from '../utils/use-data-2' +import { Provider, useCommerce } from '..' +import { BigcommerceProvider } from '@framework' -const useSearch = useData +export type UseSearchHandler

= Prop< + Prop, + 'useSearch' +> -export default useSearch +export type UseSeachInput

= UseHookInput< + UseSearchHandler

+> + +export type SearchResponse

= UseHookResponse< + UseSearchHandler

+> + +export type UseSearch

= Partial< + UseSeachInput

+> extends UseSeachInput

+ ? (input?: UseSeachInput

) => SearchResponse

+ : (input: UseSeachInput

) => SearchResponse

+ +export const fetcher = defaultFetcher as HookFetcherFn + +export default function useSearch

( + input: UseSeachInput

= {} +) { + const { providerRef, fetcherRef } = useCommerce

() + + const provider = providerRef.current + const opts = provider.products?.useSearch + + const fetcherFn = opts?.fetcher ?? fetcher + const useHook = opts?.useHook ?? ((ctx) => ctx.useData()) + + return useHook({ + input, + useData(ctx) { + const response = useData( + { ...opts!, fetcher: fetcherFn }, + ctx?.input ?? [], + provider.fetcher ?? fetcherRef.current, + ctx?.swrOptions ?? input.swrOptions + ) + return response + }, + }) +} diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index 31aaa6fd7..41aedb228 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -1,5 +1,6 @@ import type { Wishlist as BCWishlist } from '@framework/api/wishlist' import type { Customer as BCCustomer } from '@framework/api/customers' +import type { SearchProductsData as BCSearchProductsData } from '@framework/api/catalog/products' export interface Discount { // The value of the discount, can be an amount or percentage @@ -96,6 +97,9 @@ export interface Wishlist extends BCWishlist {} // TODO: Properly define this type export interface Customer extends BCCustomer {} +// TODO: Properly define this type +export interface SearchProductsData extends BCSearchProductsData {} + /** * Cart mutations */