>
->
-
-export type UseAddItem = Partial<
- UseAddItemInput
-> extends UseAddItemInput
- ? (input?: UseAddItemInput
) => (input: Input) => UseAddItemResult
- : (input: UseAddItemInput
) => (input: Input) => UseAddItemResult
+export type UseAddItem<
+ H extends MutationHook = MutationHook
+> = ReturnType
export const fetcher: HookFetcherFn<
Cart,
AddCartItemBody
-> = async ({ options, input, fetch }) => {
- return fetch({ ...options, body: input })
+> = mutationFetcher
+
+const fn = (provider: Provider) => provider.cart?.useAddItem!
+
+const useAddItem: UseAddItem = (...args) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(...args)
}
-type X = UseAddItemResult
-
-export default function useAddItem(
- input: UseAddItemInput
-) {
- const { providerRef, fetcherRef } = useCommerce
()
-
- const provider = providerRef.current
- const opts = provider.cart?.useAddItem
-
- const fetcherFn = opts?.fetcher ?? fetcher
- const useHook = opts?.useHook ?? (() => () => {})
- const fetchFn = provider.fetcher ?? fetcherRef.current
- const action = useHook({ input })
-
- return useCallback(
- function addItem(input: Input) {
- return action({
- input,
- fetch({ input }) {
- return fetcherFn({
- input,
- options: opts!.fetchOptions,
- fetch: fetchFn,
- })
- },
- })
- },
- [input, fetchFn, opts?.fetchOptions]
- )
-}
+export default useAddItem
diff --git a/framework/commerce/cart/use-cart-actions.tsx b/framework/commerce/cart/use-cart-actions.tsx
deleted file mode 100644
index 3ba4b2e1a..000000000
--- a/framework/commerce/cart/use-cart-actions.tsx
+++ /dev/null
@@ -1,17 +0,0 @@
-import type { HookFetcher, HookFetcherOptions } from '../utils/types'
-import useAddItem from './use-add-item'
-import useRemoveItem from './use-remove-item'
-import useUpdateItem from './use-update-item'
-
-// This hook is probably not going to be used, but it's here
-// to show how a commerce should be structuring it
-export default function useCartActions(
- options: HookFetcherOptions,
- fetcher: HookFetcher
-) {
- const addItem = useAddItem(options, fetcher)
- const updateItem = useUpdateItem(options, fetcher)
- const removeItem = useRemoveItem(options, fetcher)
-
- return { addItem, updateItem, removeItem }
-}
diff --git a/framework/commerce/cart/use-cart.tsx b/framework/commerce/cart/use-cart.tsx
index f7b384047..fbed715c8 100644
--- a/framework/commerce/cart/use-cart.tsx
+++ b/framework/commerce/cart/use-cart.tsx
@@ -1,34 +1,21 @@
import Cookies from 'js-cookie'
+import { useHook, useSWRHook } from '../utils/use-hook'
+import type { HookFetcherFn, SWRHook } from '../utils/types'
import type { Cart } from '../types'
-import type {
- Prop,
- HookFetcherFn,
- UseHookInput,
- UseHookResponse,
-} from '../utils/types'
-import useData from '../utils/use-data'
import { Provider, useCommerce } from '..'
export type FetchCartInput = {
cartId?: Cart['id']
}
-export type UseCartHandler = Prop<
- Prop
,
- 'useCart'
->
-
-export type UseCartInput
= UseHookInput>
-
-export type CartResponse = UseHookResponse<
- UseCartHandler
->
-
-export type UseCart
= Partial<
- UseCartInput
-> extends UseCartInput
- ? (input?: UseCartInput
) => CartResponse
- : (input: UseCartInput
) => CartResponse
+export type UseCart<
+ H extends SWRHook = SWRHook<
+ Cart | null,
+ {},
+ FetchCartInput,
+ { isEmpty?: boolean }
+ >
+> = ReturnType
export const fetcher: HookFetcherFn = async ({
options,
@@ -38,32 +25,17 @@ export const fetcher: HookFetcherFn = async ({
return cartId ? await fetch({ ...options }) : null
}
-export default function useCart(
- input: UseCartInput
= {}
-) {
- const { providerRef, fetcherRef, cartCookie } = useCommerce
()
-
- const provider = providerRef.current
- const opts = provider.cart?.useCart
-
- const fetcherFn = opts?.fetcher ?? fetcher
- const useHook = opts?.useHook ?? ((ctx) => ctx.useData())
+const fn = (provider: Provider) => provider.cart?.useCart!
+const useCart: UseCart = (input) => {
+ const hook = useHook(fn)
+ const { cartCookie } = useCommerce()
+ const fetcherFn = hook.fetcher ?? fetcher
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
- },
- })
+ return useSWRHook({ ...hook, fetcher: wrapper })(input)
}
+
+export default useCart
diff --git a/framework/commerce/cart/use-remove-item.tsx b/framework/commerce/cart/use-remove-item.tsx
index 8a63b1b73..a9d1b37d2 100644
--- a/framework/commerce/cart/use-remove-item.tsx
+++ b/framework/commerce/cart/use-remove-item.tsx
@@ -1,10 +1,35 @@
-import useAction from '../utils/use-action'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from '../utils/types'
+import type { Cart, LineItem, RemoveCartItemBody } from '../types'
+import type { Provider } from '..'
-// Input expected by the action returned by the `useRemoveItem` hook
-export interface RemoveItemInput {
+/**
+ * Input expected by the action returned by the `useRemoveItem` hook
+ */
+export type RemoveItemInput = {
id: string
}
-const useRemoveItem = useAction
+export type UseRemoveItem<
+ H extends MutationHook = MutationHook<
+ Cart | null,
+ { item?: LineItem },
+ RemoveItemInput,
+ RemoveCartItemBody
+ >
+> = ReturnType
+
+export const fetcher: HookFetcherFn<
+ Cart | null,
+ RemoveCartItemBody
+> = mutationFetcher
+
+const fn = (provider: Provider) => provider.cart?.useRemoveItem!
+
+const useRemoveItem: UseRemoveItem = (input) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(input)
+}
export default useRemoveItem
diff --git a/framework/commerce/cart/use-update-item.tsx b/framework/commerce/cart/use-update-item.tsx
index e1adcb5fb..f8d0f1a40 100644
--- a/framework/commerce/cart/use-update-item.tsx
+++ b/framework/commerce/cart/use-update-item.tsx
@@ -1,11 +1,38 @@
-import useAction from '../utils/use-action'
-import type { CartItemBody } from '../types'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from '../utils/types'
+import type { Cart, CartItemBody, LineItem, UpdateCartItemBody } from '../types'
+import type { Provider } from '..'
-// Input expected by the action returned by the `useUpdateItem` hook
+/**
+ * Input expected by the action returned by the `useUpdateItem` hook
+ */
export type UpdateItemInput = T & {
id: string
}
-const useUpdateItem = useAction
+export type UseUpdateItem<
+ H extends MutationHook = MutationHook<
+ Cart | null,
+ {
+ item?: LineItem
+ wait?: number
+ },
+ UpdateItemInput,
+ UpdateCartItemBody
+ >
+> = ReturnType
+
+export const fetcher: HookFetcherFn<
+ Cart | null,
+ UpdateCartItemBody
+> = mutationFetcher
+
+const fn = (provider: Provider) => provider.cart?.useUpdateItem!
+
+const useUpdateItem: UseUpdateItem = (input) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(input)
+}
export default useUpdateItem
diff --git a/framework/commerce/config.json b/framework/commerce/config.json
new file mode 100644
index 000000000..a0e7afc5d
--- /dev/null
+++ b/framework/commerce/config.json
@@ -0,0 +1,5 @@
+{
+ "features": {
+ "wishlist": true
+ }
+}
diff --git a/framework/commerce/customer/use-customer.tsx b/framework/commerce/customer/use-customer.tsx
index 25112128e..5d6416a4b 100644
--- a/framework/commerce/customer/use-customer.tsx
+++ b/framework/commerce/customer/use-customer.tsx
@@ -1,56 +1,20 @@
+import { useHook, useSWRHook } from '../utils/use-hook'
+import { SWRFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, SWRHook } from '../utils/types'
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'
-import { Provider, useCommerce } from '..'
+import { Provider } from '..'
-export type UseCustomerHandler = Prop<
- Prop
,
- 'useCustomer'
->
+export type UseCustomer<
+ H extends SWRHook = SWRHook
+> = ReturnType
-export type UseCustomerInput = UseHookInput<
- UseCustomerHandler
->
+export const fetcher: HookFetcherFn = SWRFetcher
-export type CustomerResponse = UseHookResponse<
- UseCustomerHandler
->
+const fn = (provider: Provider) => provider.customer?.useCustomer!
-export type UseCustomer
= Partial<
- UseCustomerInput
-> extends UseCustomerInput
- ? (input?: UseCustomerInput
) => CustomerResponse
- : (input: UseCustomerInput
) => CustomerResponse
-
-export const fetcher = defaultFetcher as HookFetcherFn
-
-export default function useCustomer(
- input: UseCustomerInput
= {}
-) {
- const { providerRef, fetcherRef } = useCommerce
()
-
- 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
- },
- })
+const useCustomer: UseCustomer = (input) => {
+ const hook = useHook(fn)
+ return useSWRHook({ fetcher, ...hook })(input)
}
+
+export default useCustomer
diff --git a/framework/commerce/index.tsx b/framework/commerce/index.tsx
index 243fba2db..07bf74a22 100644
--- a/framework/commerce/index.tsx
+++ b/framework/commerce/index.tsx
@@ -6,7 +6,7 @@ import {
useMemo,
useRef,
} from 'react'
-import { Fetcher, HookHandler, MutationHandler } from './utils/types'
+import { Fetcher, SWRHook, MutationHook } from './utils/types'
import type { FetchCartInput } from './cart/use-cart'
import type { Cart, Wishlist, Customer, SearchProductsData } from './types'
@@ -15,17 +15,26 @@ const Commerce = createContext | {}>({})
export type Provider = CommerceConfig & {
fetcher: Fetcher
cart?: {
- useCart?: HookHandler
- useAddItem?: MutationHandler
+ useCart?: SWRHook
+ useAddItem?: MutationHook
+ useUpdateItem?: MutationHook
+ useRemoveItem?: MutationHook
}
wishlist?: {
- useWishlist?: HookHandler
+ useWishlist?: SWRHook
+ useAddItem?: MutationHook
+ useRemoveItem?: MutationHook
}
- customer: {
- useCustomer?: HookHandler
+ customer?: {
+ useCustomer?: SWRHook
}
- products: {
- useSearch?: HookHandler
+ products?: {
+ useSearch?: SWRHook
+ }
+ auth?: {
+ useSignup?: MutationHook
+ useLogin?: MutationHook
+ useLogout?: MutationHook
}
}
diff --git a/framework/commerce/product/use-search.tsx b/framework/commerce/product/use-search.tsx
new file mode 100644
index 000000000..d2b782045
--- /dev/null
+++ b/framework/commerce/product/use-search.tsx
@@ -0,0 +1,20 @@
+import { useHook, useSWRHook } from '../utils/use-hook'
+import { SWRFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, SWRHook } from '../utils/types'
+import type { SearchProductsData } from '../types'
+import { Provider } from '..'
+
+export type UseSearch<
+ H extends SWRHook = SWRHook
+> = ReturnType
+
+export const fetcher: HookFetcherFn = SWRFetcher
+
+const fn = (provider: Provider) => provider.products?.useSearch!
+
+const useSearch: UseSearch = (input) => {
+ const hook = useHook(fn)
+ return useSWRHook({ fetcher, ...hook })(input)
+}
+
+export default useSearch
diff --git a/framework/commerce/products/use-search.tsx b/framework/commerce/products/use-search.tsx
deleted file mode 100644
index 1f887f5fe..000000000
--- a/framework/commerce/products/use-search.tsx
+++ /dev/null
@@ -1,57 +0,0 @@
-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'
-import { Provider, useCommerce } from '..'
-import { BigcommerceProvider } from '@framework'
-
-export type UseSearchHandler = Prop<
- Prop
,
- '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 bf635c9dc..0ae766095 100644
--- a/framework/commerce/types.ts
+++ b/framework/commerce/types.ts
@@ -2,6 +2,10 @@ 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 type CommerceProviderConfig = {
+ features: Record
+}
+
export type Discount = {
// The value of the discount, can be an amount or percentage
value: number
diff --git a/framework/commerce/use-login.tsx b/framework/commerce/use-login.tsx
index 2a251fea3..755e10fd9 100644
--- a/framework/commerce/use-login.tsx
+++ b/framework/commerce/use-login.tsx
@@ -1,5 +1,19 @@
-import useAction from './utils/use-action'
+import { useHook, useMutationHook } from './utils/use-hook'
+import { mutationFetcher } from './utils/default-fetcher'
+import type { MutationHook, HookFetcherFn } from './utils/types'
+import type { Provider } from '.'
-const useLogin = useAction
+export type UseLogin<
+ H extends MutationHook = MutationHook
+> = ReturnType
+
+export const fetcher: HookFetcherFn = mutationFetcher
+
+const fn = (provider: Provider) => provider.auth?.useLogin!
+
+const useLogin: UseLogin = (...args) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(...args)
+}
export default useLogin
diff --git a/framework/commerce/use-logout.tsx b/framework/commerce/use-logout.tsx
index ef3fc4135..0a80c318b 100644
--- a/framework/commerce/use-logout.tsx
+++ b/framework/commerce/use-logout.tsx
@@ -1,5 +1,19 @@
-import useAction from './utils/use-action'
+import { useHook, useMutationHook } from './utils/use-hook'
+import { mutationFetcher } from './utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from './utils/types'
+import type { Provider } from '.'
-const useLogout = useAction
+export type UseLogout<
+ H extends MutationHook = MutationHook
+> = ReturnType
+
+export const fetcher: HookFetcherFn = mutationFetcher
+
+const fn = (provider: Provider) => provider.auth?.useLogout!
+
+const useLogout: UseLogout = (...args) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(...args)
+}
export default useLogout
diff --git a/framework/commerce/use-signup.tsx b/framework/commerce/use-signup.tsx
index 08ddb22c0..be3c32000 100644
--- a/framework/commerce/use-signup.tsx
+++ b/framework/commerce/use-signup.tsx
@@ -1,5 +1,19 @@
-import useAction from './utils/use-action'
+import { useHook, useMutationHook } from './utils/use-hook'
+import { mutationFetcher } from './utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from './utils/types'
+import type { Provider } from '.'
-const useSignup = useAction
+export type UseSignup<
+ H extends MutationHook = MutationHook
+> = ReturnType
+
+export const fetcher: HookFetcherFn = mutationFetcher
+
+const fn = (provider: Provider) => provider.auth?.useSignup!
+
+const useSignup: UseSignup = (...args) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(...args)
+}
export default useSignup
diff --git a/framework/commerce/utils/default-fetcher.ts b/framework/commerce/utils/default-fetcher.ts
index 8dc9def75..493a9b5f9 100644
--- a/framework/commerce/utils/default-fetcher.ts
+++ b/framework/commerce/utils/default-fetcher.ts
@@ -1,6 +1,12 @@
import type { HookFetcherFn } from './types'
-const defaultFetcher: HookFetcherFn = ({ options, fetch }) =>
+export const SWRFetcher: HookFetcherFn = ({ options, fetch }) =>
fetch(options)
-export default defaultFetcher
+export const mutationFetcher: HookFetcherFn = ({
+ input,
+ options,
+ fetch,
+}) => fetch({ ...options, body: input })
+
+export default SWRFetcher
diff --git a/framework/commerce/utils/features.ts b/framework/commerce/utils/features.ts
new file mode 100644
index 000000000..d84321967
--- /dev/null
+++ b/framework/commerce/utils/features.ts
@@ -0,0 +1,37 @@
+import commerceProviderConfig from '@framework/config.json'
+import type { CommerceProviderConfig } from '../types'
+import memo from 'lodash.memoize'
+
+type FeaturesAPI = {
+ isEnabled: (desideredFeature: string) => boolean
+}
+
+function isFeatureEnabled(config: CommerceProviderConfig) {
+ const features = config.features
+ return (desideredFeature: string) =>
+ Object.keys(features)
+ .filter((k) => features[k])
+ .includes(desideredFeature)
+}
+
+function boostrap(): FeaturesAPI {
+ const basis = {
+ isEnabled: () => false,
+ }
+
+ if (!commerceProviderConfig) {
+ console.log('No config.json found - Please add a config.json')
+ return basis
+ }
+
+ if (commerceProviderConfig.features) {
+ return {
+ ...basis,
+ isEnabled: memo(isFeatureEnabled(commerceProviderConfig)),
+ }
+ }
+
+ return basis
+}
+
+export default boostrap()
diff --git a/framework/commerce/utils/types.ts b/framework/commerce/utils/types.ts
index 1d3adef81..852afb208 100644
--- a/framework/commerce/utils/types.ts
+++ b/framework/commerce/utils/types.ts
@@ -2,13 +2,18 @@ import type { ConfigInterface } from 'swr'
import type { CommerceError } from './errors'
import type { ResponseState } from './use-data'
+/**
+ * Returns the properties in T with the properties in type K, overriding properties defined in T
+ */
export type Override = Omit & K
/**
* Returns the properties in T with the properties in type K changed from optional to required
*/
export type PickRequired = Omit &
- Required>
+ {
+ [P in K]-?: NonNullable
+ }
/**
* Core fetcher added by CommerceProvider
@@ -31,16 +36,15 @@ export type HookFetcher = (
fetch: (options: FetcherOptions) => Promise
) => Data | Promise
-export type HookFetcherFn<
- Data,
- Input = never,
- Result = any,
- Body = any
-> = (context: {
+export type HookFetcherFn = (
+ context: HookFetcherContext
+) => Data | Promise
+
+export type HookFetcherContext = {
options: HookFetcherOptions
input: Input
fetch: (options: FetcherOptions) => Promise
-}) => Data | Promise
+}
export type HookFetcherOptions = { method?: string } & (
| { query: string; url?: string }
@@ -49,13 +53,20 @@ export type HookFetcherOptions = { method?: string } & (
export type HookInputValue = string | number | boolean | undefined
-export type HookSwrInput = [string, HookInputValue][]
+export type HookSWRInput = [string, HookInputValue][]
export type HookFetchInput = { [k: string]: HookInputValue }
-export type HookInput = {}
+export type HookFunction<
+ Input extends { [k: string]: unknown } | null,
+ T
+> = keyof Input extends never
+ ? () => T
+ : Partial extends Input
+ ? (input?: Input) => T
+ : (input: Input) => T
-export type HookHandler<
+export type SWRHook<
// Data obj returned by the hook and fetch operation
Data,
// Input expected by the hook
@@ -65,58 +76,56 @@ export type HookHandler<
// Custom state added to the response object of SWR
State = {}
> = {
- useHook?(context: {
- input: Input & { swrOptions?: SwrOptions }
- useData(context?: {
- input?: HookFetchInput | HookSwrInput
- swrOptions?: SwrOptions
- }): ResponseState
- }): ResponseState & State
+ useHook(
+ context: SWRHookContext
+ ): HookFunction<
+ Input & { swrOptions?: SwrOptions },
+ ResponseState & State
+ >
fetchOptions: HookFetcherOptions
fetcher?: HookFetcherFn
}
-export type MutationHandler<
+export type SWRHookContext<
+ Data,
+ FetchInput extends { [k: string]: unknown } = {}
+> = {
+ useData(context?: {
+ input?: HookFetchInput | HookSWRInput
+ swrOptions?: SwrOptions
+ }): ResponseState
+}
+
+export type MutationHook<
// Data obj returned by the hook and fetch operation
Data,
// Input expected by the hook
Input extends { [k: string]: unknown } = {},
+ // Input expected by the action returned by the hook
+ ActionInput extends { [k: string]: unknown } = {},
// Input expected before doing a fetch operation
- FetchInput extends { [k: string]: unknown } = {}
+ FetchInput extends { [k: string]: unknown } = ActionInput
> = {
- useHook?(context: {
- input: Input
- }): (context: {
- input: FetchInput
- fetch: (context: { input: FetchInput }) => Data | Promise
- }) => Data | Promise
+ useHook(
+ context: MutationHookContext
+ ): HookFunction >>
fetchOptions: HookFetcherOptions
fetcher?: HookFetcherFn
}
+export type MutationHookContext<
+ Data,
+ FetchInput extends { [k: string]: unknown } | null = {}
+> = {
+ fetch: keyof FetchInput extends never
+ ? () => Data | Promise
+ : Partial extends FetchInput
+ ? (context?: { input?: FetchInput }) => Data | Promise
+ : (context: { input: FetchInput }) => Data | Promise
+}
+
export type SwrOptions = ConfigInterface<
Data,
CommerceError,
HookFetcher
>
-
-/**
- * Returns the property K from type T excluding nullables
- */
-export type Prop = NonNullable
-
-export type HookHandlerType =
- | HookHandler
- | MutationHandler
-
-export type UseHookParameters = Parameters<
- Prop
->
-
-export type UseHookResponse = ReturnType<
- Prop
->
-
-export type UseHookInput<
- H extends HookHandlerType
-> = UseHookParameters[0]['input']
diff --git a/framework/commerce/utils/use-action.tsx b/framework/commerce/utils/use-action.tsx
deleted file mode 100644
index 24593383f..000000000
--- a/framework/commerce/utils/use-action.tsx
+++ /dev/null
@@ -1,15 +0,0 @@
-import { useCallback } from 'react'
-import type { HookFetcher, HookFetcherOptions } from './types'
-import { useCommerce } from '..'
-
-export default function useAction(
- options: HookFetcherOptions,
- fetcher: HookFetcher
-) {
- const { fetcherRef } = useCommerce()
-
- return useCallback(
- (input: Input) => fetcher(options, input, fetcherRef.current),
- [fetcher]
- )
-}
diff --git a/framework/commerce/utils/use-data.tsx b/framework/commerce/utils/use-data.tsx
index 94679a0c6..9224b612c 100644
--- a/framework/commerce/utils/use-data.tsx
+++ b/framework/commerce/utils/use-data.tsx
@@ -1,11 +1,11 @@
import useSWR, { responseInterface } from 'swr'
import type {
- HookHandler,
- HookSwrInput,
+ HookSWRInput,
HookFetchInput,
- PickRequired,
Fetcher,
SwrOptions,
+ HookFetcherOptions,
+ HookFetcherFn,
} from './types'
import defineProperty from './define-property'
import { CommerceError } from './errors'
@@ -14,13 +14,12 @@ export type ResponseState = responseInterface & {
isLoading: boolean
}
-export type UseData = <
- Data = any,
- Input extends { [k: string]: unknown } = {},
- FetchInput extends HookFetchInput = {}
->(
- options: PickRequired, 'fetcher'>,
- input: HookFetchInput | HookSwrInput,
+export type UseData = (
+ options: {
+ fetchOptions: HookFetcherOptions
+ fetcher: HookFetcherFn
+ },
+ input: HookFetchInput | HookSWRInput,
fetcherFn: Fetcher,
swrOptions?: SwrOptions
) => ResponseState
diff --git a/framework/commerce/utils/use-hook.ts b/framework/commerce/utils/use-hook.ts
new file mode 100644
index 000000000..da3431e3c
--- /dev/null
+++ b/framework/commerce/utils/use-hook.ts
@@ -0,0 +1,50 @@
+import { useCallback } from 'react'
+import { Provider, useCommerce } from '..'
+import type { MutationHook, PickRequired, SWRHook } from './types'
+import useData from './use-data'
+
+export function useFetcher() {
+ const { providerRef, fetcherRef } = useCommerce()
+ return providerRef.current.fetcher ?? fetcherRef.current
+}
+
+export function useHook<
+ P extends Provider,
+ H extends MutationHook | SWRHook
+>(fn: (provider: P) => H) {
+ const { providerRef } = useCommerce()
+ const provider = providerRef.current
+ return fn(provider)
+}
+
+export function useSWRHook>(
+ hook: PickRequired
+) {
+ const fetcher = useFetcher()
+
+ return hook.useHook({
+ useData(ctx) {
+ const response = useData(hook, ctx?.input ?? [], fetcher, ctx?.swrOptions)
+ return response
+ },
+ })
+}
+
+export function useMutationHook>(
+ hook: PickRequired
+) {
+ const fetcher = useFetcher()
+
+ return hook.useHook({
+ fetch: useCallback(
+ ({ input } = {}) => {
+ return hook.fetcher({
+ input,
+ options: hook.fetchOptions,
+ fetch: fetcher,
+ })
+ },
+ [fetcher, hook.fetchOptions]
+ ),
+ })
+}
diff --git a/framework/commerce/utils/use-response.tsx b/framework/commerce/utils/use-response.tsx
deleted file mode 100644
index de1b5088c..000000000
--- a/framework/commerce/utils/use-response.tsx
+++ /dev/null
@@ -1,40 +0,0 @@
-import { useMemo } from 'react'
-import { responseInterface } from 'swr'
-import { CommerceError } from './errors'
-import { Override } from './types'
-
-export type UseResponseOptions<
- D,
- R extends responseInterface
-> = {
- descriptors?: PropertyDescriptorMap
- normalizer?: (data: R['data']) => D
-}
-
-export type UseResponse = >(
- response: R,
- options: UseResponseOptions
-) => D extends object ? Override : R
-
-const useResponse: UseResponse = (response, { descriptors, normalizer }) => {
- const memoizedResponse = useMemo(
- () =>
- Object.create(response, {
- ...descriptors,
- ...(normalizer
- ? {
- data: {
- get() {
- return response.data && normalizer(response.data)
- },
- enumerable: true,
- },
- }
- : {}),
- }),
- [response]
- )
- return memoizedResponse
-}
-
-export default useResponse
diff --git a/framework/commerce/wishlist/use-add-item.tsx b/framework/commerce/wishlist/use-add-item.tsx
index d9b513694..11c8cc241 100644
--- a/framework/commerce/wishlist/use-add-item.tsx
+++ b/framework/commerce/wishlist/use-add-item.tsx
@@ -1,12 +1,19 @@
-import useAction from '../utils/use-action'
-import type { CartItemBody } from '../types'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { MutationHook } from '../utils/types'
+import type { Provider } from '..'
-// Input expected by the action returned by the `useAddItem` hook
-// export interface AddItemInput {
-// includeProducts?: boolean
-// }
-export type AddItemInput = T
+export type UseAddItem<
+ H extends MutationHook = MutationHook
+> = ReturnType
-const useAddItem = useAction
+export const fetcher = mutationFetcher
+
+const fn = (provider: Provider) => provider.wishlist?.useAddItem!
+
+const useAddItem: UseAddItem = (...args) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(...args)
+}
export default useAddItem
diff --git a/framework/commerce/wishlist/use-remove-item.tsx b/framework/commerce/wishlist/use-remove-item.tsx
index dfa60c363..c8c34a5af 100644
--- a/framework/commerce/wishlist/use-remove-item.tsx
+++ b/framework/commerce/wishlist/use-remove-item.tsx
@@ -1,5 +1,28 @@
-import useAction from '../utils/use-action'
+import { useHook, useMutationHook } from '../utils/use-hook'
+import { mutationFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, MutationHook } from '../utils/types'
+import type { Provider } from '..'
-const useRemoveItem = useAction
+export type RemoveItemInput = {
+ id: string | number
+}
+
+export type UseRemoveItem<
+ H extends MutationHook = MutationHook<
+ any | null,
+ { wishlist?: any },
+ RemoveItemInput,
+ {}
+ >
+> = ReturnType
+
+export const fetcher: HookFetcherFn = mutationFetcher
+
+const fn = (provider: Provider) => provider.wishlist?.useRemoveItem!
+
+const useRemoveItem: UseRemoveItem = (input) => {
+ const hook = useHook(fn)
+ return useMutationHook({ fetcher, ...hook })(input)
+}
export default useRemoveItem
diff --git a/framework/commerce/wishlist/use-wishlist.tsx b/framework/commerce/wishlist/use-wishlist.tsx
index dc912bc98..7a93b20b1 100644
--- a/framework/commerce/wishlist/use-wishlist.tsx
+++ b/framework/commerce/wishlist/use-wishlist.tsx
@@ -1,56 +1,25 @@
+import { useHook, useSWRHook } from '../utils/use-hook'
+import { SWRFetcher } from '../utils/default-fetcher'
+import type { HookFetcherFn, SWRHook } from '../utils/types'
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'
-import { Provider, useCommerce } from '..'
+import type { Provider } from '..'
-export type UseWishlistHandler = Prop<
- Prop
,
- 'useWishlist'
->
+export type UseWishlist<
+ H extends SWRHook = SWRHook<
+ Wishlist | null,
+ { includeProducts?: boolean },
+ { customerId?: number; includeProducts: boolean },
+ { isEmpty?: boolean }
+ >
+> = ReturnType
-export type UseWishlistInput = UseHookInput<
- UseWishlistHandler
->
+export const fetcher: HookFetcherFn = SWRFetcher
-export type WishlistResponse = UseHookResponse<
- UseWishlistHandler
->
+const fn = (provider: Provider) => provider.wishlist?.useWishlist!
-export type UseWishlist
= Partial<
- UseWishlistInput
-> extends UseWishlistInput
- ? (input?: UseWishlistInput
) => WishlistResponse
- : (input: UseWishlistInput
) => WishlistResponse
-
-export const fetcher = defaultFetcher as HookFetcherFn
-
-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
- },
- })
+const useWishlist: UseWishlist = (input) => {
+ const hook = useHook(fn)
+ return useSWRHook({ fetcher, ...hook })(input)
}
+
+export default useWishlist
diff --git a/package.json b/package.json
index ff492a35e..2d8e32772 100644
--- a/package.json
+++ b/package.json
@@ -21,7 +21,7 @@
},
"dependencies": {
"@reach/portal": "^0.11.2",
- "@tailwindcss/ui": "^0.6.2",
+ "@types/lodash.memoize": "^4.1.6",
"@vercel/fetch": "^6.1.0",
"body-scroll-lock": "^3.1.5",
"bowser": "^2.11.0",
@@ -33,20 +33,21 @@
"js-cookie": "^2.2.1",
"keen-slider": "^5.2.4",
"lodash.debounce": "^4.0.8",
+ "lodash.memoize": "^4.1.2",
"lodash.random": "^3.2.0",
"lodash.throttle": "^4.1.1",
- "next": "^10.0.5",
+ "next": "^10.0.7-canary.3",
"next-seo": "^4.11.0",
"next-themes": "^0.0.4",
- "normalizr": "^3.6.1",
+ "postcss": "^8.2.4",
"postcss-nesting": "^7.0.1",
- "react": "^16.14.0",
- "react-dom": "^16.14.0",
+ "react": "^17.0.1",
+ "react-dom": "^17.0.1",
"react-merge-refs": "^1.1.0",
"react-ticker": "^1.2.2",
"swr": "^0.4.0",
"tabbable": "^5.1.5",
- "tailwindcss": "^1.9"
+ "tailwindcss": "^2.0.2"
},
"devDependencies": {
"@graphql-codegen/cli": "^1.20.0",
@@ -56,8 +57,6 @@
"@manifoldco/swagger-to-ts": "^2.1.0",
"@next/bundle-analyzer": "^10.0.1",
"@types/body-scroll-lock": "^2.6.1",
- "@types/bunyan": "^1.8.6",
- "@types/bunyan-prettystream": "^0.1.31",
"@types/classnames": "^2.2.10",
"@types/cookie": "^0.4.0",
"@types/js-cookie": "^2.2.6",
@@ -66,8 +65,6 @@
"@types/lodash.throttle": "^4.1.6",
"@types/node": "^14.14.16",
"@types/react": "^17.0.0",
- "bunyan": "^1.8.14",
- "bunyan-prettystream": "^0.1.3",
"graphql": "^15.4.0",
"husky": "^4.3.8",
"lint-staged": "^10.5.3",
diff --git a/pages/_app.tsx b/pages/_app.tsx
index 132ce5f18..dae0311b4 100644
--- a/pages/_app.tsx
+++ b/pages/_app.tsx
@@ -1,12 +1,11 @@
import '@assets/main.css'
-import 'keen-slider/keen-slider.min.css'
import '@assets/chrome-bug.css'
+import 'keen-slider/keen-slider.min.css'
import { FC, useEffect } from 'react'
import type { AppProps } from 'next/app'
-
-import { ManagedUIContext } from '@components/ui/context'
import { Head } from '@components/common'
+import { ManagedUIContext } from '@components/ui/context'
const Noop: FC = ({ children }) => <>{children}>
diff --git a/pages/index.tsx b/pages/index.tsx
index 4ddf561aa..8f6ce5f2a 100644
--- a/pages/index.tsx
+++ b/pages/index.tsx
@@ -1,13 +1,14 @@
import { Layout } from '@components/common'
import { Grid, Marquee, Hero } from '@components/ui'
import { ProductCard } from '@components/product'
-import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
+// import HomeAllProductsGrid from '@components/common/HomeAllProductsGrid'
import type { GetStaticPropsContext, InferGetStaticPropsType } from 'next'
import { getConfig } from '@framework/api'
import getAllProducts from '@framework/product/get-all-products'
import getSiteInfo from '@framework/common/get-site-info'
import getAllPages from '@framework/common/get-all-pages'
+import Features from '@commerce/utils/features'
export async function getStaticProps({
preview,
@@ -23,6 +24,7 @@ export async function getStaticProps({
const { categories, brands } = await getSiteInfo({ config, preview })
const { pages } = await getAllPages({ config, preview })
+ const isWishlistEnabled = Features.isEnabled('wishlist')
return {
props: {
@@ -30,6 +32,9 @@ export async function getStaticProps({
categories,
brands,
pages,
+ commerceFeatures: {
+ wishlist: isWishlistEnabled,
+ },
},
revalidate: 1440,
}
@@ -39,6 +44,7 @@ export default function Home({
products,
brands,
categories,
+ commerceFeatures,
}: InferGetStaticPropsType) {
return (
<>
@@ -51,6 +57,7 @@ export default function Home({
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
+ wishlist={commerceFeatures.wishlist}
/>
))}
@@ -64,6 +71,7 @@ export default function Home({
width: 320,
height: 320,
}}
+ wishlist={commerceFeatures.wishlist}
/>
))}
@@ -86,6 +94,7 @@ export default function Home({
width: i === 0 ? 1080 : 540,
height: i === 0 ? 1080 : 540,
}}
+ wishlist={commerceFeatures.wishlist}
/>
))}
@@ -99,6 +108,7 @@ export default function Home({
width: 320,
height: 320,
}}
+ wishlist={commerceFeatures.wishlist}
/>
))}
diff --git a/pages/orders.tsx b/pages/orders.tsx
index 08e32c2b2..db4ab55b2 100644
--- a/pages/orders.tsx
+++ b/pages/orders.tsx
@@ -1,9 +1,9 @@
import type { GetStaticPropsContext } from 'next'
-import { getConfig } from '@framework/api'
-import getAllPages from '@framework/common/get-all-pages'
+import { Bag } from '@components/icons'
import { Layout } from '@components/common'
import { Container, Text } from '@components/ui'
-import { Bag } from '@components/icons'
+import { getConfig } from '@framework/api'
+import getAllPages from '@framework/common/get-all-pages'
export async function getStaticProps({
preview,
diff --git a/pages/product/[slug].tsx b/pages/product/[slug].tsx
index 83aeaa54c..a705c001b 100644
--- a/pages/product/[slug].tsx
+++ b/pages/product/[slug].tsx
@@ -11,14 +11,15 @@ import { getConfig } from '@framework/api'
import getProduct from '@framework/product/get-product'
import getAllPages from '@framework/common/get-all-pages'
import getAllProductPaths from '@framework/product/get-all-product-paths'
+import Features from '@commerce/utils/features'
export async function getStaticProps({
params,
locale,
preview,
}: GetStaticPropsContext<{ slug: string }>) {
+ const isWishlistEnabled = Features.isEnabled('wishlist')
const config = getConfig({ locale })
-
const { pages } = await getAllPages({ config, preview })
const { product } = await getProduct({
variables: { slug: params!.slug },
@@ -31,7 +32,13 @@ export async function getStaticProps({
}
return {
- props: { pages, product },
+ props: {
+ pages,
+ product,
+ commerceFeatures: {
+ wishlist: isWishlistEnabled,
+ },
+ },
revalidate: 200,
}
}
@@ -55,13 +62,17 @@ export async function getStaticPaths({ locales }: GetStaticPathsContext) {
export default function Slug({
product,
+ commerceFeatures,
}: InferGetStaticPropsType) {
const router = useRouter()
return router.isFallback ? (
Loading... // TODO (BC) Add Skeleton Views
) : (
-
+
)
}
diff --git a/pages/search.tsx b/pages/search.tsx
index 97bee34d4..7c0a4e140 100644
--- a/pages/search.tsx
+++ b/pages/search.tsx
@@ -26,6 +26,8 @@ const SORT = Object.entries({
'price-desc': 'Price: High to low',
})
+import Features from '@commerce/utils/features'
+
import {
filterQuery,
getCategoryPath,
@@ -40,14 +42,23 @@ export async function getStaticProps({
const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview })
const { categories, brands } = await getSiteInfo({ config, preview })
+ const isWishlistEnabled = Features.isEnabled('wishlist')
return {
- props: { pages, categories, brands },
+ props: {
+ pages,
+ categories,
+ brands,
+ commerceFeatures: {
+ wishlist: isWishlistEnabled,
+ },
+ },
}
}
export default function Search({
categories,
brands,
+ commerceFeatures: { wishlist },
}: InferGetStaticPropsType) {
const [activeFilter, setActiveFilter] = useState('')
const [toggleFilter, setToggleFilter] = useState(false)
@@ -337,7 +348,7 @@ export default function Search({
{data ? (
- {data.products.map((product) => (
+ {data.products.map((product: Product) => (
))}
diff --git a/pages/wishlist.tsx b/pages/wishlist.tsx
index 6de798411..ca11152f4 100644
--- a/pages/wishlist.tsx
+++ b/pages/wishlist.tsx
@@ -1,28 +1,43 @@
+import { useEffect } from 'react'
+import { useRouter } from 'next/router'
import type { GetStaticPropsContext } from 'next'
-import { getConfig } from '@framework/api'
-import getAllPages from '@framework/common/get-all-pages'
-import useWishlist from '@framework/wishlist/use-wishlist'
-import { Layout } from '@components/common'
+
import { Heart } from '@components/icons'
+import { Layout } from '@components/common'
import { Text, Container } from '@components/ui'
-import { WishlistCard } from '@components/wishlist'
import { defaultPageProps } from '@lib/defaults'
+import { getConfig } from '@framework/api'
import { useCustomer } from '@framework/customer'
+import { WishlistCard } from '@components/wishlist'
+import useWishlist from '@framework/wishlist/use-wishlist'
+import getAllPages from '@framework/common/get-all-pages'
+import Features from '@commerce/utils/features'
export async function getStaticProps({
preview,
locale,
}: GetStaticPropsContext) {
+ // Disabling page if Feature is not available
+ if (Features.isEnabled('wishlist')) {
+ return {
+ notFound: true,
+ }
+ }
+
const config = getConfig({ locale })
const { pages } = await getAllPages({ config, preview })
return {
- props: { ...defaultPageProps, pages },
+ props: {
+ pages,
+ ...defaultPageProps,
+ },
}
}
export default function Wishlist() {
const { data: customer } = useCustomer()
const { data, isLoading, isEmpty } = useWishlist()
+ const router = useRouter()
return (
diff --git a/tsconfig.json b/tsconfig.json
index 4b4389c44..7aec4729c 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -26,6 +26,6 @@
"@framework": ["framework/shopify"]
}
},
- "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
- "exclude": ["node_modules", "components/wishlist"]
+ "include": ["next-env.d.ts", "**/*.d.ts", "**/*.ts", "**/*.tsx", "**/*.js"],
+ "exclude": ["node_modules"]
}
diff --git a/yarn.lock b/yarn.lock
index 3cdabc5b4..aad5af97d 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1060,6 +1060,13 @@
dependencies:
"@types/lodash" "*"
+"@types/lodash.memoize@^4.1.6":
+ version "4.1.6"
+ resolved "https://registry.yarnpkg.com/@types/lodash.memoize/-/lodash.memoize-4.1.6.tgz#3221f981790a415cab1a239f25c17efd8b604c23"
+ integrity sha512-mYxjKiKzRadRJVClLKxS4wb3Iy9kzwJ1CkbyKiadVxejnswnRByyofmPMscFKscmYpl36BEEhCMPuWhA1R/1ZQ==
+ dependencies:
+ "@types/lodash" "*"
+
"@types/lodash.random@^3.2.6":
version "3.2.6"
resolved "https://registry.yarnpkg.com/@types/lodash.random/-/lodash.random-3.2.6.tgz#64b08abad168dca39c778ed40cce75b2f9e168eb"
@@ -4232,6 +4239,11 @@ lodash.isstring@^4.0.1:
resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451"
integrity sha1-1SfftUVuynzJu5XV2ur4i6VKVFE=
+lodash.memoize@^4.1.2:
+ version "4.1.2"
+ resolved "https://registry.yarnpkg.com/lodash.memoize/-/lodash.memoize-4.1.2.tgz#bcc6c49a42a2840ed997f323eada5ecd182e0bfe"
+ integrity sha1-vMbEmkKihA7Zl/Mj6tpezRguC/4=
+
lodash.once@^4.0.0:
version "4.1.1"
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"