4
0
forked from crowetic/commerce

Merge branch 'agnostic' of github.com:vercel/commerce into agnostic

This commit is contained in:
okbel 2021-02-11 16:59:26 -03:00
commit 85f429c115
17 changed files with 591 additions and 264 deletions

View File

@ -1,6 +1,7 @@
import { FC } from 'react'
import Link from 'next/link'
import cn from 'classnames'
import type { LineItem } from '@framework/types'
import useCart from '@framework/cart/use-cart'
import useCustomer from '@framework/customer/use-customer'
import { Avatar } from '@components/common'
@ -15,7 +16,7 @@ interface Props {
const countItem = (count: number, item: LineItem) => count + item.quantity
const UserNav: FC<Props> = ({ className, children }) => {
const UserNav: FC<Props> = ({ className }) => {
const { data } = useCart()
const { data: customer } = useCustomer()
const { toggleSidebar, closeSidebarIfPresent, openModal } = useUI()

View File

@ -42,8 +42,8 @@ const WishlistCard: FC<Props> = ({ product }) => {
setLoading(true)
try {
await addItem({
productId: Number(product.id),
variantId: Number(product.variants[0].id),
productId: product.id,
variantId: product.variants[0].id,
})
openSidebar()
setLoading(false)

View File

@ -1,52 +1,4 @@
import type { HookFetcher } from '@commerce/utils/types'
import type { SwrOptions } from '@commerce/utils/use-data'
import useResponse from '@commerce/utils/use-response'
import useCommerceCart, { CartInput } from '@commerce/cart/use-cart'
import { normalizeCart } from '../lib/normalize'
import type { Cart, BigcommerceCart } from '../types'
import useCart, { UseCart } from '@commerce/cart/use-cart'
import type { BigcommerceProvider } from '..'
const defaultOpts = {
url: '/api/bigcommerce/cart',
method: 'GET',
}
export const fetcher: HookFetcher<Cart | null, CartInput> = async (
options,
{ cartId },
fetch
) => {
const data = cartId
? await fetch<BigcommerceCart>({ ...defaultOpts, ...options })
: null
return data && normalizeCart(data)
}
export function extendHook(
customFetcher: typeof fetcher,
swrOptions?: SwrOptions<Cart | null, CartInput>
) {
const useCart = () => {
const response = useCommerceCart(defaultOpts, [], customFetcher, {
revalidateOnFocus: false,
...swrOptions,
})
const res = useResponse(response, {
descriptors: {
isEmpty: {
get() {
return (response.data?.lineItems.length ?? 0) <= 0
},
enumerable: true,
},
},
})
return res
}
useCart.extend = extendHook
return useCart
}
export default extendHook(fetcher)
export default useCart as UseCart<BigcommerceProvider>

View File

@ -1,38 +1,4 @@
import type { HookFetcher } from '@commerce/utils/types'
import type { SwrOptions } from '@commerce/utils/use-data'
import useCommerceCustomer from '@commerce/use-customer'
import type { Customer, CustomerData } from '../api/customers'
import useCustomer, { UseCustomer } from '@commerce/customer/use-customer'
import type { BigcommerceProvider } from '..'
const defaultOpts = {
url: '/api/bigcommerce/customers',
method: 'GET',
}
export type { Customer }
export const fetcher: HookFetcher<Customer | null> = async (
options,
_,
fetch
) => {
const data = await fetch<CustomerData | null>({ ...defaultOpts, ...options })
return data?.customer ?? null
}
export function extendHook(
customFetcher: typeof fetcher,
swrOptions?: SwrOptions<Customer | null>
) {
const useCustomer = () => {
return useCommerceCustomer(defaultOpts, [], customFetcher, {
revalidateOnFocus: false,
...swrOptions,
})
}
useCustomer.extend = extendHook
return useCustomer
}
export default extendHook(fetcher)
export default useCustomer as UseCustomer<BigcommerceProvider>

View File

@ -1,46 +1,17 @@
import { ReactNode } from 'react'
import * as React from 'react'
import type { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { FetcherError } from '@commerce/utils/errors'
import { bigcommerceProvider, BigcommerceProvider } from './provider'
async function getText(res: Response) {
try {
return (await res.text()) || res.statusText
} catch (error) {
return res.statusText
}
}
async function getError(res: Response) {
if (res.headers.get('Content-Type')?.includes('application/json')) {
const data = await res.json()
return new FetcherError({ errors: data.errors, status: res.status })
}
return new FetcherError({ message: await getText(res), status: res.status })
}
export { bigcommerceProvider }
export type { BigcommerceProvider }
export const bigcommerceConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'bc_cartId',
async fetcher({ url, method = 'GET', variables, body: bodyObj }) {
const hasBody = Boolean(variables || bodyObj)
const body = hasBody
? JSON.stringify(variables ? { variables } : bodyObj)
: undefined
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
const res = await fetch(url!, { method, body, headers })
if (res.ok) {
const { data } = await res.json()
return data
}
throw await getError(res)
},
}
export type BigcommerceConfig = Partial<CommerceConfig>
@ -52,10 +23,13 @@ export type BigcommerceProps = {
export function CommerceProvider({ children, ...config }: BigcommerceProps) {
return (
<CoreCommerceProvider config={{ ...bigcommerceConfig, ...config }}>
<CoreCommerceProvider
provider={bigcommerceProvider}
config={{ ...bigcommerceConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce()
export const useCommerce = () => useCoreCommerce<BigcommerceProvider>()

View File

@ -0,0 +1,166 @@
import { useMemo } from 'react'
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 type { Customer, CustomerData } from './api/customers'
import useCustomer from './customer/use-customer'
import type { Cart } from './types'
async function getText(res: Response) {
try {
return (await res.text()) || res.statusText
} catch (error) {
return res.statusText
}
}
async function getError(res: Response) {
if (res.headers.get('Content-Type')?.includes('application/json')) {
const data = await res.json()
return new FetcherError({ errors: data.errors, status: res.status })
}
return new FetcherError({ message: await getText(res), status: res.status })
}
const fetcher: Fetcher = async ({
url,
method = 'GET',
variables,
body: bodyObj,
}) => {
const hasBody = Boolean(variables || bodyObj)
const body = hasBody
? JSON.stringify(variables ? { variables } : bodyObj)
: undefined
const headers = hasBody ? { 'Content-Type': 'application/json' } : undefined
const res = await fetch(url!, { method, body, headers })
if (res.ok) {
const { data } = await res.json()
return data
}
throw await getError(res)
}
const useCart: HookHandler<
Cart | null,
{},
FetchCartInput,
any,
any,
{ isEmpty?: boolean }
> = {
fetchOptions: {
url: '/api/bigcommerce/cart',
method: 'GET',
},
normalizer: normalizeCart,
useHook({ input, useData }) {
const response = useData({
swrOptions: { revalidateOnFocus: false, ...input.swrOptions },
})
return useMemo(
() =>
Object.create(response, {
isEmpty: {
get() {
return (response.data?.lineItems.length ?? 0) <= 0
},
enumerable: true,
},
}),
[response]
)
},
}
const useWishlist: HookHandler<
Wishlist | null,
{ includeProducts?: boolean },
{ customerId?: number; includeProducts: boolean },
any,
any,
{ isEmpty?: boolean }
> = {
fetchOptions: {
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]
)
},
}
const useCustomerHandler: HookHandler<
Customer | null,
{},
{},
CustomerData | null,
any
> = {
fetchOptions: {
url: '/api/bigcommerce/customers',
method: 'GET',
},
normalizer: (data) => data.customer,
useHook({ input, useData }) {
return useData({
swrOptions: {
revalidateOnFocus: false,
...input.swrOptions,
},
})
},
}
export const bigcommerceProvider = {
locale: 'en-us',
cartCookie: 'bc_cartId',
fetcher,
cartNormalizer: normalizeCart,
cart: { useCart },
wishlist: { useWishlist },
customer: { useCustomer: useCustomerHandler },
}
export type BigcommerceProvider = typeof bigcommerceProvider

View File

@ -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<Wishlist | null, UseWishlistInput> = (
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<Wishlist | null, UseWishlistInput>
) {
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<BigcommerceProvider>

View File

@ -1,28 +1,71 @@
import Cookies from 'js-cookie'
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
import type { Cart } from '../types'
import { useCommerce } from '..'
import type {
Prop,
HookFetcherFn,
UseHookInput,
UseHookResponse,
} from '../utils/types'
import useData from '../utils/use-data-2'
import { Provider, useCommerce } from '..'
export type CartResponse<Data> = ResponseState<Data> & { isEmpty?: boolean }
// Input expected by the `useCart` hook
export type CartInput = {
export type FetchCartInput = {
cartId?: Cart['id']
}
export default function useCart<Data extends Cart | null>(
options: HookFetcherOptions,
input: HookInput,
fetcherFn: HookFetcher<Data, CartInput>,
swrOptions?: SwrOptions<Data, CartInput>
): CartResponse<Data> {
const { cartCookie } = useCommerce()
const fetcher: typeof fetcherFn = (options, input, fetch) => {
input.cartId = Cookies.get(cartCookie)
return fetcherFn(options, input, fetch)
}
const response = useData(options, input, fetcher, swrOptions)
export type UseCartHandler<P extends Provider> = Prop<
Prop<P, 'cart'>,
'useCart'
>
return response
export type UseCartInput<P extends Provider> = UseHookInput<UseCartHandler<P>>
export type CartResponse<P extends Provider> = UseHookResponse<
UseCartHandler<P>
>
export type UseCart<P extends Provider> = Partial<
UseCartInput<P>
> extends UseCartInput<P>
? (input?: UseCartInput<P>) => CartResponse<P>
: (input: UseCartInput<P>) => CartResponse<P>
export const fetcher: HookFetcherFn<Cart | null, FetchCartInput> = async ({
options,
input: { cartId },
fetch,
normalize,
}) => {
const data = cartId ? await fetch({ ...options }) : null
return data && normalize ? normalize(data) : data
}
export default function useCart<P extends Provider>(
input: UseCartInput<P> = {}
) {
const { providerRef, fetcherRef, cartCookie } = useCommerce<P>()
const provider = providerRef.current
const opts = provider.cart?.useCart
const fetcherFn = opts?.fetcher ?? fetcher
const useHook = opts?.useHook ?? ((ctx) => ctx.useData())
const wrapper: typeof fetcher = (context) => {
context.input.cartId = Cookies.get(cartCookie)
return fetcherFn(context)
}
return useHook({
input,
useData(ctx) {
const response = useData(
{ ...opts!, fetcher: wrapper },
ctx?.input ?? [],
provider.fetcher ?? fetcherRef.current,
ctx?.swrOptions ?? input.swrOptions
)
return response
},
})
}

View File

@ -0,0 +1,56 @@
import type { Customer } 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 '..'
export type UseCustomerHandler<P extends Provider> = Prop<
Prop<P, 'customer'>,
'useCustomer'
>
export type UseCustomerInput<P extends Provider> = UseHookInput<
UseCustomerHandler<P>
>
export type CustomerResponse<P extends Provider> = UseHookResponse<
UseCustomerHandler<P>
>
export type UseCustomer<P extends Provider> = Partial<
UseCustomerInput<P>
> extends UseCustomerInput<P>
? (input?: UseCustomerInput<P>) => CustomerResponse<P>
: (input: UseCustomerInput<P>) => CustomerResponse<P>
export const fetcher = defaultFetcher as HookFetcherFn<Customer | null>
export default function useCustomer<P extends Provider>(
input: UseCustomerInput<P> = {}
) {
const { providerRef, fetcherRef } = useCommerce<P>()
const provider = providerRef.current
const opts = provider.customer?.useCustomer
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
},
})
}

View File

@ -6,37 +6,60 @@ import {
useMemo,
useRef,
} from 'react'
import * as React from 'react'
import { Fetcher } from './utils/types'
import { Fetcher, HookHandler } from './utils/types'
import type { FetchCartInput } from './cart/use-cart'
import type { Cart, Wishlist, Customer } from './types'
const Commerce = createContext<CommerceContextValue | {}>({})
const Commerce = createContext<CommerceContextValue<any> | {}>({})
export type CommerceProps = {
export type Provider = CommerceConfig & {
fetcher: Fetcher
cart?: {
useCart?: HookHandler<Cart | null, any, FetchCartInput>
}
wishlist?: {
useWishlist?: HookHandler<Wishlist | null, any, any>
}
customer: {
useCustomer?: HookHandler<Customer | null, any, any>
}
}
export type CommerceProps<P extends Provider> = {
children?: ReactNode
provider: P
config: CommerceConfig
}
export type CommerceConfig = { fetcher: Fetcher<any> } & Omit<
CommerceContextValue,
'fetcherRef'
export type CommerceConfig = Omit<
CommerceContextValue<any>,
'providerRef' | 'fetcherRef'
>
export type CommerceContextValue = {
fetcherRef: MutableRefObject<Fetcher<any>>
export type CommerceContextValue<P extends Provider> = {
providerRef: MutableRefObject<P>
fetcherRef: MutableRefObject<Fetcher>
locale: string
cartCookie: string
}
export function CommerceProvider({ children, config }: CommerceProps) {
export function CommerceProvider<P extends Provider>({
provider,
children,
config,
}: CommerceProps<P>) {
if (!config) {
throw new Error('CommerceProvider requires a valid config object')
}
const fetcherRef = useRef(config.fetcher)
const providerRef = useRef(provider)
// TODO: Remove the fetcherRef
const fetcherRef = useRef(provider.fetcher)
// Because the config is an object, if the parent re-renders this provider
// will re-render every consumer unless we memoize the config
const cfg = useMemo(
() => ({
providerRef,
fetcherRef,
locale: config.locale,
cartCookie: config.cartCookie,
@ -47,6 +70,6 @@ export function CommerceProvider({ children, config }: CommerceProps) {
return <Commerce.Provider value={cfg}>{children}</Commerce.Provider>
}
export function useCommerce<T extends CommerceContextValue>() {
return useContext(Commerce) as T
export function useCommerce<P extends Provider>() {
return useContext(Commerce) as CommerceContextValue<P>
}

View File

@ -1,3 +1,6 @@
import type { Wishlist as BCWishlist } from '@framework/api/wishlist'
import type { Customer as BCCustomer } from '@framework/api/customers'
export interface Discount {
// The value of the discount, can be an amount or percentage
value: number
@ -87,6 +90,12 @@ export interface Cart {
discounts?: Discount[]
}
// TODO: Properly define this type
export interface Wishlist extends BCWishlist {}
// TODO: Properly define this type
export interface Customer extends BCCustomer {}
/**
* Cart mutations
*/

View File

@ -1,5 +0,0 @@
import useData from './utils/use-data'
const useCustomer = useData
export default useCustomer

View File

@ -0,0 +1,12 @@
import { HookFetcherFn } from './types'
const defaultFetcher: HookFetcherFn<any> = async ({
options,
fetch,
normalize,
}) => {
const data = await fetch({ ...options })
return data && normalize ? normalize(data) : data
}
export default defaultFetcher

View File

@ -1,5 +1,21 @@
// Core fetcher added by CommerceProvider
export type Fetcher<T> = (options: FetcherOptions) => T | Promise<T>
import type { ConfigInterface } from 'swr'
import type { CommerceError } from './errors'
import type { ResponseState } from './use-data'
export type Override<T, K> = Omit<T, keyof K> & K
/**
* Returns the properties in T with the properties in type K changed from optional to required
*/
export type PickRequired<T, K extends keyof T> = Omit<T, K> &
Required<Pick<T, K>>
/**
* Core fetcher added by CommerceProvider
*/
export type Fetcher<T = any, B = any> = (
options: FetcherOptions<B>
) => T | Promise<T>
export type FetcherOptions<Body = any> = {
url?: string
@ -15,12 +31,76 @@ export type HookFetcher<Data, Input = null, Result = any> = (
fetch: <T = Result, Body = any>(options: FetcherOptions<Body>) => Promise<T>
) => Data | Promise<Data>
export type HookFetcherOptions = {
query?: string
url?: string
method?: string
export type HookFetcherFn<
Data,
Input = never,
Result = any,
Body = any
> = (context: {
options: HookFetcherOptions
input: Input
fetch: <T = Result, B = Body>(options: FetcherOptions<B>) => Promise<T>
normalize?(data: Result): Data
}) => Data | Promise<Data>
export type HookFetcherOptions = { method?: string } & (
| { query: string; url?: string }
| { query?: string; url: string }
)
export type HookInputValue = string | number | boolean | undefined
export type HookSwrInput = [string, HookInputValue][]
export type HookFetchInput = { [k: string]: HookInputValue }
export type HookInput = {}
export type HookHandler<
// Data obj returned by the hook and fetch operation
Data,
// Input expected by the hook
Input extends { [k: string]: unknown } = {},
// Input expected before doing a fetch operation
FetchInput extends HookFetchInput = {},
// Data returned by the API after a fetch operation
Result = any,
// Body expected by the API endpoint
Body = any,
// Custom state added to the response object of SWR
State = {}
> = {
useHook?(context: {
input: Input & { swrOptions?: SwrOptions<Data, FetchInput, Result> }
useData(context?: {
input?: HookFetchInput | HookSwrInput
swrOptions?: SwrOptions<Data, FetchInput, Result>
}): ResponseState<Data>
}): ResponseState<Data> & State
fetchOptions: HookFetcherOptions
fetcher?: HookFetcherFn<Data, FetchInput, Result, Body>
normalizer?(data: NonNullable<Result>): Data
}
export type HookInput = [string, string | number | boolean | undefined][]
export type SwrOptions<Data, Input = null, Result = any> = ConfigInterface<
Data,
CommerceError,
HookFetcher<Data, Input, Result>
>
export type Override<T, K> = Omit<T, keyof K> & K
/**
* Returns the property K from type T excluding nullables
*/
export type Prop<T, K extends keyof T> = NonNullable<T[K]>
export type UseHookParameters<H extends HookHandler<any>> = Parameters<
Prop<H, 'useHook'>
>
export type UseHookResponse<H extends HookHandler<any>> = ReturnType<
Prop<H, 'useHook'>
>
export type UseHookInput<
H extends HookHandler<any>
> = UseHookParameters<H>[0]['input']

View File

@ -0,0 +1,84 @@
import useSWR, { responseInterface } from 'swr'
import type {
HookHandler,
HookSwrInput,
HookFetchInput,
PickRequired,
Fetcher,
SwrOptions,
} from './types'
import defineProperty from './define-property'
import { CommerceError } from './errors'
export type ResponseState<Result> = responseInterface<Result, CommerceError> & {
isLoading: boolean
}
export type UseData = <
Data = any,
Input extends { [k: string]: unknown } = {},
FetchInput extends HookFetchInput = {},
Result = any,
Body = any
>(
options: PickRequired<
HookHandler<Data, Input, FetchInput, Result, Body>,
'fetcher'
>,
input: HookFetchInput | HookSwrInput,
fetcherFn: Fetcher,
swrOptions?: SwrOptions<Data, FetchInput, Result>
) => ResponseState<Data>
const useData: UseData = (options, input, fetcherFn, swrOptions) => {
const hookInput = Array.isArray(input) ? input : Object.entries(input)
const fetcher = async (
url: string,
query?: string,
method?: string,
...args: any[]
) => {
try {
return await options.fetcher({
options: { url, query, method },
// Transform the input array into an object
input: args.reduce((obj, val, i) => {
obj[hookInput[i][0]!] = val
return obj
}, {}),
fetch: fetcherFn,
normalize: options.normalizer,
})
} catch (error) {
// SWR will not log errors, but any error that's not an instance
// of CommerceError is not welcomed by this hook
if (!(error instanceof CommerceError)) {
console.error(error)
}
throw error
}
}
const response = useSWR(
() => {
const opts = options.fetchOptions
return opts
? [opts.url, opts.query, opts.method, ...hookInput.map((e) => e[1])]
: null
},
fetcher,
swrOptions
)
if (!('isLoading' in response)) {
defineProperty(response, 'isLoading', {
get() {
return response.data === undefined
},
enumerable: true,
})
}
return response
}
export default useData

View File

@ -1,5 +1,5 @@
import useSWR, { ConfigInterface, responseInterface } from 'swr'
import type { HookInput, HookFetcher, HookFetcherOptions } from './types'
import type { HookSwrInput, HookFetcher, HookFetcherOptions } from './types'
import defineProperty from './define-property'
import { CommerceError } from './errors'
import { useCommerce } from '..'
@ -16,7 +16,7 @@ export type ResponseState<Result> = responseInterface<Result, CommerceError> & {
export type UseData = <Data = any, Input = null, Result = any>(
options: HookFetcherOptions | (() => HookFetcherOptions | null),
input: HookInput,
input: HookSwrInput,
fetcherFn: HookFetcher<Data, Input, Result>,
swrOptions?: SwrOptions<Data, Input, Result>
) => ResponseState<Data>

View File

@ -1,16 +1,56 @@
import type { HookInput, HookFetcher, HookFetcherOptions } from '../utils/types'
import useData, { ResponseState, SwrOptions } from '../utils/use-data'
import type { Wishlist } 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 '..'
export type WishlistResponse<Result> = ResponseState<Result> & {
isEmpty?: boolean
}
export type UseWishlistHandler<P extends Provider> = Prop<
Prop<P, 'wishlist'>,
'useWishlist'
>
export default function useWishlist<Result, Input = null>(
options: HookFetcherOptions,
input: HookInput,
fetcherFn: HookFetcher<Result, Input>,
swrOptions?: SwrOptions<Result, Input>
): WishlistResponse<Result> {
const response = useData(options, input, fetcherFn, swrOptions)
return response
export type UseWishlistInput<P extends Provider> = UseHookInput<
UseWishlistHandler<P>
>
export type WishlistResponse<P extends Provider> = UseHookResponse<
UseWishlistHandler<P>
>
export type UseWishlist<P extends Provider> = Partial<
UseWishlistInput<P>
> extends UseWishlistInput<P>
? (input?: UseWishlistInput<P>) => WishlistResponse<P>
: (input: UseWishlistInput<P>) => WishlistResponse<P>
export const fetcher = defaultFetcher as HookFetcherFn<Wishlist | null>
export default function useWishlist<P extends Provider>(
input: UseWishlistInput<P> = {}
) {
const { providerRef, fetcherRef } = useCommerce<P>()
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
},
})
}