diff --git a/framework/bigcommerce/cart/use-cart.tsx b/framework/bigcommerce/cart/use-cart.tsx index ba005ec59..4f8a5cbcd 100644 --- a/framework/bigcommerce/cart/use-cart.tsx +++ b/framework/bigcommerce/cart/use-cart.tsx @@ -1,4 +1,4 @@ -import useCommerceCart, { UseCart } from '@commerce/cart/use-cart' +import useCart, { UseCart } from '@commerce/cart/use-cart' import type { BigcommerceProvider } from '..' -export default useCommerceCart as UseCart +export default useCart as UseCart diff --git a/framework/bigcommerce/provider.tsx b/framework/bigcommerce/provider.tsx index 364e87539..60106f7f8 100644 --- a/framework/bigcommerce/provider.tsx +++ b/framework/bigcommerce/provider.tsx @@ -3,6 +3,8 @@ import { FetcherError } from '@commerce/utils/errors' import type { Fetcher, HookHandler } from '@commerce/utils/types' import type { FetchCartInput } from '@commerce/cart/use-cart' import { normalizeCart } from './lib/normalize' +import type { Wishlist } from './api/wishlist' +import useCustomer from './customer/use-customer' import type { Cart } from './types' async function getText(res: Response) { @@ -76,9 +78,9 @@ const useCart: HookHandler< } const useWishlist: HookHandler< - Cart | null, - {}, - FetchCartInput, + Wishlist | null, + { includeProducts?: boolean }, + { customerId?: number; includeProducts: boolean }, any, any, { isEmpty?: boolean } @@ -87,6 +89,45 @@ const useWishlist: HookHandler< url: '/api/bigcommerce/wishlist', method: 'GET', }, + fetcher({ input: { customerId, includeProducts }, options, fetch }) { + if (!customerId) return null + + // Use a dummy base as we only care about the relative path + const url = new URL(options.url!, 'http://a') + + if (includeProducts) url.searchParams.set('products', '1') + + return fetch({ + url: url.pathname + url.search, + method: options.method, + }) + }, + useHook({ input, useData }) { + const { data: customer } = useCustomer() + const response = useData({ + input: [ + ['customerId', customer?.id], + ['includeProducts', input.includeProducts], + ], + swrOptions: { + revalidateOnFocus: false, + ...input.swrOptions, + }, + }) + + return useMemo( + () => + Object.create(response, { + isEmpty: { + get() { + return (response.data?.items?.length || 0) <= 0 + }, + enumerable: true, + }, + }), + [response] + ) + }, } export const bigcommerceProvider = { diff --git a/framework/bigcommerce/wishlist/use-wishlist.tsx b/framework/bigcommerce/wishlist/use-wishlist.tsx index 730bf19dc..dfa3d9dbc 100644 --- a/framework/bigcommerce/wishlist/use-wishlist.tsx +++ b/framework/bigcommerce/wishlist/use-wishlist.tsx @@ -1,78 +1,4 @@ -import { HookFetcher } from '@commerce/utils/types' -import { SwrOptions } from '@commerce/utils/use-data' -import useResponse from '@commerce/utils/use-response' -import useCommerceWishlist from '@commerce/wishlist/use-wishlist' -import type { Wishlist } from '../api/wishlist' -import useCustomer from '../customer/use-customer' +import useWishlist, { UseWishlist } from '@commerce/wishlist/use-wishlist' +import type { BigcommerceProvider } from '..' -const defaultOpts = { - url: '/api/bigcommerce/wishlist', - method: 'GET', -} - -export type { Wishlist } - -export interface UseWishlistOptions { - includeProducts?: boolean -} - -export interface UseWishlistInput extends UseWishlistOptions { - customerId?: number -} - -export const fetcher: HookFetcher = ( - options, - { customerId, includeProducts }, - fetch -) => { - if (!customerId) return null - - // Use a dummy base as we only care about the relative path - const url = new URL(options?.url ?? defaultOpts.url, 'http://a') - - if (includeProducts) url.searchParams.set('products', '1') - - return fetch({ - url: url.pathname + url.search, - method: options?.method ?? defaultOpts.method, - }) -} - -export function extendHook( - customFetcher: typeof fetcher, - swrOptions?: SwrOptions -) { - const useWishlist = ({ includeProducts }: UseWishlistOptions = {}) => { - const { data: customer } = useCustomer() - const response = useCommerceWishlist( - defaultOpts, - [ - ['customerId', customer?.id], - ['includeProducts', includeProducts], - ], - customFetcher, - { - revalidateOnFocus: false, - ...swrOptions, - } - ) - const res = useResponse(response, { - descriptors: { - isEmpty: { - get() { - return (response.data?.items?.length || 0) <= 0 - }, - set: (x) => x, - }, - }, - }) - - return res - } - - useWishlist.extend = extendHook - - return useWishlist -} - -export default extendHook(fetcher) +export default useWishlist as UseWishlist diff --git a/framework/commerce/cart/use-cart.tsx b/framework/commerce/cart/use-cart.tsx index 9915c7478..6b1a3c789 100644 --- a/framework/commerce/cart/use-cart.tsx +++ b/framework/commerce/cart/use-cart.tsx @@ -60,7 +60,7 @@ export default function useCart

( input, useData(ctx) { const response = useData( - { ...opts, fetcher: wrapper }, + { ...opts!, fetcher: wrapper }, ctx?.input ?? [], provider.fetcher ?? fetcherRef.current, ctx?.swrOptions ?? input.swrOptions diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx index fa7e3e7e8..cb4136e3b 100644 --- a/framework/commerce/index.tsx +++ b/framework/commerce/index.tsx @@ -8,18 +8,18 @@ import { } from 'react' import * as React from 'react' import { Fetcher, HookHandler } from './utils/types' -import { Cart } from './types' import type { FetchCartInput } from './cart/use-cart' +import type { Cart, Wishlist } from './types' const Commerce = createContext | {}>({}) export type Provider = CommerceConfig & { fetcher: Fetcher cart?: { - useCart?: HookHandler + useCart?: HookHandler } wishlist?: { - useWishlist?: HookHandler + useWishlist?: HookHandler } } diff --git a/framework/commerce/types.ts b/framework/commerce/types.ts index cc04f52e1..743a93e4e 100644 --- a/framework/commerce/types.ts +++ b/framework/commerce/types.ts @@ -1,3 +1,5 @@ +import type { Wishlist as BCWishlist } from '@framework/api/wishlist' + export interface Discount { // The value of the discount, can be an amount or percentage value: number @@ -87,6 +89,9 @@ export interface Cart { discounts?: Discount[] } +// TODO: Properly define this type +export interface Wishlist extends BCWishlist {} + /** * Cart mutations */ diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts index 7a4b73f93..dbde3e7ec 100644 --- a/framework/commerce/utils/types.ts +++ b/framework/commerce/utils/types.ts @@ -33,21 +33,20 @@ export type HookFetcher = ( export type HookFetcherFn< Data, - Input = unknown, + Input = never, Result = any, Body = any > = (context: { - options: HookFetcherOptions | null + options: HookFetcherOptions input: Input fetch: (options: FetcherOptions) => Promise normalize?(data: Result): Data }) => Data | Promise -export type HookFetcherOptions = { - query?: string - url?: string - method?: string -} +export type HookFetcherOptions = { method?: string } & ( + | { query: string; url?: string } + | { query?: string; url: string } +) export type HookInputValue = string | number | boolean | undefined @@ -63,7 +62,7 @@ export type HookHandler< // Input expected by the hook Input extends { [k: string]: unknown } = {}, // Input expected before doing a fetch operation - FetchInput extends HookFetchInput = never, + FetchInput extends HookFetchInput = {}, // Data returned by the API after a fetch operation Result = any, // Body expected by the API endpoint @@ -78,7 +77,7 @@ export type HookHandler< swrOptions?: SwrOptions }): ResponseState }): ResponseState & State - fetchOptions?: HookFetcherOptions + fetchOptions: HookFetcherOptions fetcher?: HookFetcherFn normalizer?(data: Result): Data } diff --git a/framework/commerce/utils/use-data-2.ts b/framework/commerce/utils/use-data-2.ts index e79daf632..cc4d2cc5b 100644 --- a/framework/commerce/utils/use-data-2.ts +++ b/framework/commerce/utils/use-data-2.ts @@ -17,7 +17,7 @@ export type ResponseState = responseInterface & { export type UseData = < Data = any, Input extends { [k: string]: unknown } = {}, - FetchInput extends HookFetchInput = never, + FetchInput extends HookFetchInput = {}, Result = any, Body = any >( @@ -33,7 +33,7 @@ export type UseData = < const useData: UseData = (options, input, fetcherFn, swrOptions) => { const hookInput = Array.isArray(input) ? input : Object.entries(input) const fetcher = async ( - url?: string, + url: string, query?: string, method?: string, ...args: any[] diff --git a/framework/commerce/wishlist/use-wishlist.tsx b/framework/commerce/wishlist/use-wishlist.tsx index 5272766c6..c2e0d2dc1 100644 --- a/framework/commerce/wishlist/use-wishlist.tsx +++ b/framework/commerce/wishlist/use-wishlist.tsx @@ -1,20 +1,62 @@ +import type { Wishlist } from '../types' import type { - HookSwrInput, - HookFetcher, - HookFetcherOptions, + Prop, + HookFetcherFn, + UseHookInput, + UseHookResponse, } from '../utils/types' -import useData, { ResponseState, SwrOptions } from '../utils/use-data' +import useData from '../utils/use-data-2' +import { Provider, useCommerce } from '..' -export type WishlistResponse = ResponseState & { - isEmpty?: boolean +export type UseWishlistHandler

= Prop< + Prop, + 'useWishlist' +> + +export type UseWishlistInput

= UseHookInput< + UseWishlistHandler

+> + +export type WishlistResponse

= UseHookResponse< + UseWishlistHandler

+> + +export type UseWishlist

= Partial< + WishlistResponse

+> extends WishlistResponse

+ ? (input?: WishlistResponse

) => WishlistResponse

+ : (input: WishlistResponse

) => WishlistResponse

+ +export const fetcher: HookFetcherFn = async ({ + options, + fetch, + normalize, +}) => { + const data = await fetch({ ...options }) + return data && normalize ? normalize(data) : data } -export default function useWishlist( - options: HookFetcherOptions, - input: HookSwrInput, - fetcherFn: HookFetcher, - swrOptions?: SwrOptions -): WishlistResponse { - const response = useData(options, input, fetcherFn, swrOptions) - return response +export default function useWishlist

( + input: UseWishlistInput

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

() + + const provider = providerRef.current + const opts = provider.wishlist?.useWishlist + + 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 + }, + }) }