* Custom Checkout Progress * Updates to Checkout * Custom Checkout Progress * Adding tabs * Adding Collapse * Adding Collapse * Improving Sidebar Scroll * Modif footer * Changes * More design updates * sidebar cart * More design updates * More design updates * More design updates * More design updates * Types * Types * Design Updates * More changes * More changes * More changes * Changes * Changes * Changes * New tailwind required changes * Sidebar Styling issues with Mobile * Latest changes - Normalizing cart * Styling Fixes * New changes * Changes * latest * Refactor and Renaming some UI Props * Adding Quantity Component * Adding Rating Component * Rating Component * More updates * User Select disabled, plus hidding horizontal scroll bars * Changes * Adding ProductOptions Component and more helpers * Styling updates * Styling updates * Fix for slim tags * Missmatch with RightArrow * Footer updates and some styles * Latest Updates * Latest Updates * Latest Updates * Removing Portal, since it's not needed. We might add it later I'd rather not to. * Removing Portal, since it's not needed. We might add it later I'd rather not to. * Sam backdrop filter * General UI Improvements * General UI Improvements * Search now with Geist Colors * Now with Geist Colors * Changes * Scroll for Mobile on IOs devises * LoadingDots Working (: * Changes * More Changes * Perf changes * More perf changes * Fade to the Nametags in the ProductCard * changes * Search issue ui * Search issue ui * Make sure to only refresh navbar and modals when required * Index revalidate * Fixed image issue * hide album scroll on windows * Fix scrollbar * Changing * Adding 404 with Layout * Removing Toast * Adding Assets * Adding Assets * Progress with LocalProvider * New productTag * Only images for the drop * changes * Empty SWRhooks * Adding Local Provider * Working local * Working view of a LocalProvider * More updates * Changes * Removed react-ticker * default to local if no env available * default to local if no env available * add missing `@` to css import * rewrite search rewrites to multiple pages * allow requests in getStaticProps to execute in parallel * make type import explicit * add a tsconfig.js file * use local provider in tsconfig.js * avoid a circular dependency * Saleor was not in the providers list * avoid circular dependency in bigcommerce * Adding more to the Local Provider (#366) * Adding more data * Adding more data * optimize assets (#370) * Optimize assets (#372) * optimize assets * remove assets * remove assets * cart enabled * Adding saleor * Changes with Webpack * Changes Co-authored-by: Luis Alvarez <luis@vercel.com> Co-authored-by: Tobias Koppers <tobias.koppers@googlemail.com> Co-authored-by: Shu Ding <g@shud.in>
7.0 KiB
Adding a new Commerce Provider
A commerce provider is a headless e-commerce platform that integrates with the Commerce Framework. Right now we have the following providers:
- BigCommerce (framework/bigcommerce)
- Saleor (framework/saleor)
- Shopify (framework/shopify)
Adding a commerce provider means adding a new folder in framework
with a folder structure like the next one:
api
- index.ts
product
- usePrice
- useSearch
- getProduct
- getAllProducts
wishlist
- useWishlist
- useAddItem
- useRemoveItem
auth
- useLogin
- useLogout
- useSignup
customer
- useCustomer
- getCustomerId
- getCustomerWistlist
cart
- useCart
- useAddItem
- useRemoveItem
- useUpdateItem
env.template
index.ts
provider.ts
commerce.config.json
next.config.js
README.md
provider.ts
exports a provider object with handlers for the Commerce Hooks and api/index.ts
exports a Node.js provider for the Commerce API
Important: We use TypeScript for every provider and expect its usage for every new one.
The app imports from the provider directly instead of the core commerce folder (framework/commerce
), but all providers are interchangeable and to achieve it every provider always has to implement the core types and helpers.
The provider folder should only depend on framework/commerce
and dependencies in the main package.json
. In the future we'll move the framework
folder to a package that can be shared easily for multiple apps.
Adding the provider hooks
Using BigCommerce as an example. The first thing to do is export a CommerceProvider
component that includes a provider
object with all the handlers that can be used for hooks:
import type { ReactNode } from 'react'
import {
CommerceConfig,
CommerceProvider as CoreCommerceProvider,
useCommerce as useCoreCommerce,
} from '@commerce'
import { bigcommerceProvider } from './provider'
import type { BigcommerceProvider } from './provider'
export { bigcommerceProvider }
export type { BigcommerceProvider }
export const bigcommerceConfig: CommerceConfig = {
locale: 'en-us',
cartCookie: 'bc_cartId',
}
export type BigcommerceConfig = Partial<CommerceConfig>
export type BigcommerceProps = {
children?: ReactNode
locale: string
} & BigcommerceConfig
export function CommerceProvider({ children, ...config }: BigcommerceProps) {
return (
<CoreCommerceProvider
provider={bigcommerceProvider}
config={{ ...bigcommerceConfig, ...config }}
>
{children}
</CoreCommerceProvider>
)
}
export const useCommerce = () => useCoreCommerce<BigcommerceProvider>()
The exported types and components extend from the core ones exported by @commerce
, which refers to framework/commerce
.
The bigcommerceProvider
object looks like this:
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 useWishlist } from './wishlist/use-wishlist'
import { handler as useWishlistAddItem } from './wishlist/use-add-item'
import { handler as useWishlistRemoveItem } from './wishlist/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'
import fetcher from './fetcher'
export const bigcommerceProvider = {
locale: 'en-us',
cartCookie: 'bc_cartId',
fetcher,
cart: { useCart, useAddItem, useUpdateItem, useRemoveItem },
wishlist: {
useWishlist,
useAddItem: useWishlistAddItem,
useRemoveItem: useWishlistRemoveItem,
},
customer: { useCustomer },
products: { useSearch },
auth: { useLogin, useLogout, useSignup },
}
export type BigcommerceProvider = typeof bigcommerceProvider
The provider object, in this case bigcommerceProvider
, has to match the Provider
type defined in framework/commerce.
A hook handler, like useCart
, looks like this:
import { useMemo } from 'react'
import { SWRHook } from '@commerce/utils/types'
import useCart, { UseCart, FetchCartInput } from '@commerce/cart/use-cart'
import { normalizeCart } from '../lib/normalize'
import type { Cart } from '../types'
export default useCart as UseCart<typeof handler>
export const handler: SWRHook<
Cart | null,
{},
FetchCartInput,
{ isEmpty?: boolean }
> = {
fetchOptions: {
url: '/api/cart',
method: 'GET',
},
async fetcher({ input: { cartId }, options, fetch }) {
const data = cartId ? await fetch(options) : null
return data && normalizeCart(data)
},
useHook:
({ useData }) =>
(input) => {
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]
)
},
}
In the case of data fetching hooks like useCart
each handler has to implement the SWRHook
type that's defined in the core types. For mutations it's the MutationHook
, e.g for useAddItem
:
import { useCallback } from 'react'
import type { MutationHook } from '@commerce/utils/types'
import { CommerceError } from '@commerce/utils/errors'
import useAddItem, { UseAddItem } from '@commerce/cart/use-add-item'
import { normalizeCart } from '../lib/normalize'
import type {
Cart,
BigcommerceCart,
CartItemBody,
AddCartItemBody,
} from '../types'
import useCart from './use-cart'
export default useAddItem as UseAddItem<typeof handler>
export const handler: MutationHook<Cart, {}, CartItemBody> = {
fetchOptions: {
url: '/api/cart',
method: 'POST',
},
async fetcher({ input: item, options, fetch }) {
if (
item.quantity &&
(!Number.isInteger(item.quantity) || item.quantity! < 1)
) {
throw new CommerceError({
message: 'The item quantity has to be a valid integer greater than 0',
})
}
const data = await fetch<BigcommerceCart, AddCartItemBody>({
...options,
body: { item },
})
return normalizeCart(data)
},
useHook:
({ fetch }) =>
() => {
const { mutate } = useCart()
return useCallback(
async function addItem(input) {
const data = await fetch({ input })
await mutate(data, false)
return data
},
[fetch, mutate]
)
},
}
Adding the Node.js provider API
TODO
The commerce API is currently going through a refactor in https://github.com/vercel/commerce/pull/252 - We'll update the docs once the API is released.