mirror of
https://github.com/vercel/commerce.git
synced 2025-05-18 15:36:58 +00:00
* Adding multiple initial files * Updated the default cart endpoint * Fixes * Updated CommerceAPI class for better usage * Adding more migration changes * Taking multiple steps into better API types * Adding more experimental types * Removed many testing types * Adding types, fixes and other updates * Updated commerce types * Updated types for hooks now using the API * Updated mutation types * Simplified cart types for the provider * Updated cart hooks * Remove normalizers from the hooks * Updated cart endpoint * Removed cart handlers * bug fixes * Improve quantity input behavior in cart item * Removed endpoints folder * Making progress on api operations * Moved method * Moved types * Changed the way ops are created * Added customer endpoint * Login endpoint * Added logout endpoint * Add missing logout files * Added signup endpoint * Removed customers old endpoints * Moved endpoints to nested folder * Removed old customer endpoint builders * Updated login operation * Updated login operation * Added getAllPages operation * Renamed endpoint operations to handlers * Changed import * Renamed operations to handlers in usage * Moved getAllPages everywhere * Moved getPage * Updated getPage usage * Moved getSiteInfo * Added def types for product * Updated type * moved products catalog endpoint * removed old catalog endpoint * Moved wishlist * Removed commerce.endpoint * Replaced references to commerce.endpoint * Updated catalog products * Moved checkout api * Added the get customer wishlist operation * Removed old wishlist stuff * Added getAllProductPaths operation * updated reference to operation * Moved getAllProducts * Updated getProduct operation * Removed old getConfig and references * Removed is-allowed-method from BC * Updated types for auth hooks * Updated useCustomer and core types * Updated useData and util hooks * Updated useSearch hook * Updated types for useWishlist * Added index for types * Fixes * Updated urls to the API * Renamed fetchInput to fetcherInput * Updated fetch type * Fixes in search hook * Updated Shopify Provider Structure (#340) * Add codegen, update fragments & schemas * Update checkout-create.ts * Update checkout-create.ts * Update README.md * Update product mutations & queries * Uptate customer fetch types * Update schemas * Start updates * Moved Page, AllPages & Site Info * Moved product, all products (paths) * Add translations, update operations & fixes * Update api endpoints, types & fixes * Add api checkout endpoint * Updates * Fixes * Update commerce.config.json Co-authored-by: B <curciobelen@gmail.com> * Added category type and normalizer * updated init script to exclude other providers * Excluded swell and venture temporarily * Fix category & color normalization * Fixed category normalizer in shopify * Don't use getSlug for category on /search * Update colors.ts Co-authored-by: cond0r <pinte_catalin@yahoo.com> Co-authored-by: B <curciobelen@gmail.com>
240 lines
6.9 KiB
Markdown
240 lines
6.9 KiB
Markdown
# Adding a new Commerce Provider
|
|
|
|
A commerce provider is a headless e-commerce platform that integrates with the [Commerce Framework](./README.md). Right now we have the following providers:
|
|
|
|
- BigCommerce ([framework/bigcommerce](../bigcommerce))
|
|
- Shopify ([framework/shopify](../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](./README.md#commerce-hooks) and `api/index.ts` exports a Node.js provider for the [Commerce API](./README.md#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:
|
|
|
|
```tsx
|
|
import type { ReactNode } from 'react'
|
|
import {
|
|
CommerceConfig,
|
|
CommerceProvider as CoreCommerceProvider,
|
|
useCommerce as useCoreCommerce,
|
|
} from '@commerce'
|
|
import { bigcommerceProvider, 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:
|
|
|
|
```tsx
|
|
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](./index.ts).
|
|
|
|
A hook handler, like `useCart`, looks like this:
|
|
|
|
```tsx
|
|
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`:
|
|
|
|
```tsx
|
|
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.
|