From 936f149fccdb5c0840600b6239b39c6e9380461c Mon Sep 17 00:00:00 2001 From: Luis Alvarez D Date: Thu, 8 Apr 2021 15:42:59 -0500 Subject: [PATCH] Update provider and commerce docs (#256) * Updating the docs for framework/commerce * Added more docs * Updated cart hooks docs * Updated docs for wishlist and Node.js * Added a note * Updated table of contents * Adding new provider docs * Updated core docs, main repo docs, and new provider docs * Updated bigcommerce docs * Updated shopify docs --- README.md | 59 +---- framework/bigcommerce/.env.template | 2 + framework/bigcommerce/README.md | 397 +++------------------------- framework/commerce/README.md | 334 +++++++++++++++++++++++ framework/commerce/new-provider.md | 239 +++++++++++++++++ framework/shopify/.env.template | 2 + framework/shopify/README.md | 163 +----------- 7 files changed, 640 insertions(+), 556 deletions(-) create mode 100644 framework/commerce/README.md create mode 100644 framework/commerce/new-provider.md diff --git a/README.md b/README.md index cd4351911..941b1699b 100644 --- a/README.md +++ b/README.md @@ -29,43 +29,10 @@ Next.js Commerce integrates out-of-the-box with BigCommerce and Shopify. We plan ## Considerations - `framework/commerce` contains all types, helpers and functions to be used as base to build a new **provider**. -- **Providers** live under `framework`'s root folder and they will extend Next.js Commerce types and functionality. -- **Features API** is to ensure feature parity between the UI and the Provider. The UI should update accordingly and no extra code should be bundled. All extra configuration for features will live under `features` in `commerce.config.json` and if needed it can also be accessed programatically. +- **Providers** live under `framework`'s root folder and they will extend Next.js Commerce types and functionality (`framework/commerce`). +- We have a **Features API** to ensure feature parity between the UI and the Provider. The UI should update accordingly and no extra code should be bundled. All extra configuration for features will live under `features` in `commerce.config.json` and if needed it can also be accessed programatically. - Each **provider** should add its corresponding `next.config.js` and `commerce.config.json` adding specific data related to the provider. For example in case of BigCommerce, the images CDN and additional API routes. - **Providers don't depend on anything that's specific to the application they're used in**. They only depend on `framework/commerce`, on their own framework folder and on some dependencies included in `package.json` -- We recommend that each **provider** ships with an `env.template` file and a `[readme.md](http://readme.md)` file. - -## Provider Structure - -Next.js Commerce provides a set of utilities and functions to create new providers. This is how a provider structure looks like. - -- `product` - - usePrice - - useSearch - - getProduct - - getAllProducts -- `wishlist` - - useWishlist - - useAddItem - - useRemoveItem -- `auth` - - useLogin - - useLogout - - useSignup -- `customer` - - useCustomer - - getCustomerId - - getCustomerWistlist -- `cart` - - useCart - - useAddItem - - useRemoveItem - - useUpdateItem -- `env.template` -- `provider.ts` -- `commerce.config.json` -- `next.config.js` -- `README.md` ## Configuration @@ -95,15 +62,9 @@ Every provider defines the features that it supports under `framework/{provider} ### How to create a new provider -We'd recommend to duplicate a provider folder and push your providers SDK. +Follow our docs for [Adding a new Commerce Provider](framework/commerce/new-provider.md). -If you succeeded building a provider, submit a PR so we can all enjoy it. - -## Work in progress - -We're using Github Projects to keep track of issues in progress and todo's. Here is our [Board](https://github.com/vercel/commerce/projects/1) - -People actively working on this project: @okbel & @lfades. +If you succeeded building a provider, submit a PR with a valid demo and we'll review it asap. ## Contribute @@ -113,11 +74,15 @@ Our commitment to Open Source can be found [here](https://vercel.com/oss). 2. Create a new branch `git checkout -b MY_BRANCH_NAME` 3. Install yarn: `npm install -g yarn` 4. Install the dependencies: `yarn` -5. Duplicate `.env.template` and rename it to `.env.local`. -6. Add proper store values to `.env.local`. +5. Duplicate `.env.template` and rename it to `.env.local` +6. Add proper store values to `.env.local` 7. Run `yarn dev` to build and watch for code changes -8. The development branch is `canary` (this is the branch pull requests should be made against). - On a release, `canary` branch is rebased into `master`. + +## Work in progress + +We're using Github Projects to keep track of issues in progress and todo's. Here is our [Board](https://github.com/vercel/commerce/projects/1) + +People actively working on this project: @okbel & @lfades. ## Troubleshoot diff --git a/framework/bigcommerce/.env.template b/framework/bigcommerce/.env.template index 43e85c046..2b91bc095 100644 --- a/framework/bigcommerce/.env.template +++ b/framework/bigcommerce/.env.template @@ -1,3 +1,5 @@ +COMMERCE_PROVIDER=bigcommerce + BIGCOMMERCE_STOREFRONT_API_URL= BIGCOMMERCE_STOREFRONT_API_TOKEN= BIGCOMMERCE_STORE_API_URL= diff --git a/framework/bigcommerce/README.md b/framework/bigcommerce/README.md index 2609b1544..7f62a5f3f 100644 --- a/framework/bigcommerce/README.md +++ b/framework/bigcommerce/README.md @@ -1,45 +1,34 @@ -# Table of Contents +# Bigcommerce Provider -- [BigCommerce Storefront Data Hooks](#bigcommerce-storefront-data-hooks) - - [Installation](#installation) - - [General Usage](#general-usage) - - [CommerceProvider](#commerceprovider) - - [useLogin hook](#uselogin-hook) - - [useLogout](#uselogout) - - [useCustomer](#usecustomer) - - [useSignup](#usesignup) - - [usePrice](#useprice) - - [Cart Hooks](#cart-hooks) - - [useCart](#usecart) - - [useAddItem](#useadditem) - - [useUpdateItem](#useupdateitem) - - [useRemoveItem](#useremoveitem) - - [Wishlist Hooks](#wishlist-hooks) - - [Product Hooks and API](#product-hooks-and-api) - - [useSearch](#usesearch) - - [getAllProducts](#getallproducts) - - [getProduct](#getproduct) - - [More](#more) +**Demo:** https://bigcommerce.demo.vercel.store/ -# BigCommerce Storefront Data Hooks +With the deploy button below you'll be able to have a [BigCommerce](https://www.bigcommerce.com/) account and a store that works with this starter: -> This project is under active development, new features and updates will be continuously added over time +[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/new/git/external?repository-url=https%3A%2F%2Fgithub.com%2Fvercel%2Fcommerce&project-name=commerce&repo-name=commerce&demo-title=Next.js%20Commerce&demo-description=An%20all-in-one%20starter%20kit%20for%20high-performance%20e-commerce%20sites.&demo-url=https%3A%2F%2Fdemo.vercel.store&demo-image=https%3A%2F%2Fbigcommerce-demo-asset-ksvtgfvnd.vercel.app%2Fbigcommerce.png&integration-ids=oac_MuWZiE4jtmQ2ejZQaQ7ncuDT) -UI hooks and data fetching methods built from the ground up for e-commerce applications written in React, that use BigCommerce as a headless e-commerce platform. The package provides: +If you already have a BigCommerce account and want to use your current store, then copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git): -- Code splitted hooks for data fetching using [SWR](https://swr.vercel.app/), and to handle common user actions -- Code splitted data fetching methods for initial data population and static generation of content -- Helpers to create the API endpoints that connect to the hooks, very well suited for Next.js applications - -## Installation - -To install: - -``` -yarn add storefront-data-hooks +```bash +cp framework/bigcommerce/.env.template .env.local ``` -After install, the first thing you do is: set your environment variables in `.env.local` +Then, set the environment variables in `.env.local` to match the ones from your store. + +## Contribute + +Our commitment to Open Source can be found [here](https://vercel.com/oss). + +If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues). + +## Troubleshoot + +
+I already own a BigCommerce store. What should I do? +
+First thing you do is: set your environment variables +
+
+.env.local ```sh BIGCOMMERCE_STOREFRONT_API_URL=<> @@ -50,331 +39,21 @@ BIGCOMMERCE_STORE_API_CLIENT_ID=<> BIGCOMMERCE_CHANNEL_ID=<> ``` -## General Usage +If your project was started with a "Deploy with Vercel" button, you can use Vercel's CLI to retrieve these credentials. -### CommerceProvider +1. Install Vercel CLI: `npm i -g vercel` +2. Link local instance with Vercel and Github accounts (creates .vercel file): `vercel link` +3. Download your environment variables: `vercel env pull .env.local` -This component is a provider pattern component that creates commerce context for it's children. It takes config values for the locale and an optional `fetcherRef` object for data fetching. +Next, you're free to customize the starter. More updates coming soon. Stay tuned. -```jsx -... -import { CommerceProvider } from '@bigcommerce/storefront-data-hooks' +
-const App = ({ locale = 'en-US', children }) => { - return ( - - {children} - - ) -} -... -``` - -### useLogin hook - -Hook for bigcommerce user login functionality, returns `login` function to handle user login. - -```jsx -... -import useLogin from '@bigcommerce/storefront-data-hooks/use-login' - -const LoginView = () => { - const login = useLogin() - - const handleLogin = async () => { - await login({ - email, - password, - }) - } - - return ( -
- {children} -
- ) -} -... -``` - -### useLogout - -Hook to logout user. - -```jsx -... -import useLogout from '@bigcommerce/storefront-data-hooks/use-logout' - -const LogoutLink = () => { - const logout = useLogout() - return ( - logout()}> - Logout - - ) -} -``` - -### useCustomer - -Hook for getting logged in customer data, and fetching customer info. - -```jsx -... -import useCustomer from '@bigcommerce/storefront-data-hooks/use-customer' -... - -const Profile = () => { - const { data } = useCustomer() - - if (!data) { - return null - } - - return ( -
Hello, {data.firstName}
- ) -} -``` - -### useSignup - -Hook for bigcommerce user signup, returns `signup` function to handle user signups. - -```jsx -... -import useSignup from '@bigcommerce/storefront-data-hooks/use-login' - -const SignupView = () => { - const signup = useSignup() - - const handleSignup = async () => { - await signup({ - email, - firstName, - lastName, - password, - }) - } - - return ( -
- {children} -
- ) -} -... -``` - -### usePrice - -Helper hook to format price according to commerce locale, and return discount if available. - -```jsx -import usePrice from '@bigcommerce/storefront-data-hooks/use-price' -... - const { price, discount, basePrice } = usePrice( - data && { - amount: data.cart_amount, - currencyCode: data.currency.code, - } - ) -... -``` - -## Cart Hooks - -### useCart - -Returns the current cart data for use - -```jsx -... -import useCart from '@bigcommerce/storefront-data-hooks/cart/use-cart' - -const countItem = (count: number, item: LineItem) => count + item.quantity - -const CartNumber = () => { - const { data } = useCart() - const itemsCount = data?.lineItems.reduce(countItem, 0) ?? 0 - - return itemsCount > 0 ? {itemsCount} : null -} -``` - -### useAddItem - -```jsx -... -import useAddItem from '@bigcommerce/storefront-data-hooks/cart/use-add-item' - -const AddToCartButton = ({ productId, variantId }) => { - const addItem = useAddItem() - - const addToCart = async () => { - await addItem({ - productId, - variantId, - }) - } - - return -} -... -``` - -### useUpdateItem - -```jsx -... -import useUpdateItem from '@bigcommerce/storefront-data-hooks/cart/use-update-item' - -const CartItem = ({ item }) => { - const [quantity, setQuantity] = useState(item.quantity) - const updateItem = useUpdateItem(item) - - const updateQuantity = async (e) => { - const val = e.target.value - await updateItem({ quantity: val }) - } - - return ( - - ) -} -... -``` - -### useRemoveItem - -Provided with a cartItemId, will remove an item from the cart: - -```jsx -... -import useRemoveItem from '@bigcommerce/storefront-data-hooks/cart/use-remove-item' - -const RemoveButton = ({ item }) => { - const removeItem = useRemoveItem() - - const handleRemove = async () => { - await removeItem({ id: item.id }) - } - - return -} -... -``` - -## Wishlist Hooks - -Wishlist hooks are similar to cart hooks. See the below example for how to use `useWishlist`, `useAddItem`, and `useRemoveItem`. - -```jsx -import useAddItem from '@bigcommerce/storefront-data-hooks/wishlist/use-add-item' -import useRemoveItem from '@bigcommerce/storefront-data-hooks/wishlist/use-remove-item' -import useWishlist from '@bigcommerce/storefront-data-hooks/wishlist/use-wishlist' - -const WishlistButton = ({ productId, variant }) => { - const addItem = useAddItem() - const removeItem = useRemoveItem() - const { data } = useWishlist() - const { data: customer } = useCustomer() - const itemInWishlist = data?.items?.find( - (item) => - item.product_id === productId && - item.variant_id === variant?.node.entityId - ) - - const handleWishlistChange = async (e) => { - e.preventDefault() - - if (!customer) { - return - } - - if (itemInWishlist) { - await removeItem({ id: itemInWishlist.id! }) - } else { - await addItem({ - productId, - variantId: variant?.node.entityId!, - }) - } - } - - return ( - - ) -} -``` - -## Product Hooks and API - -### useSearch - -`useSearch` handles searching the bigcommerce storefront product catalog by catalog, brand, and query string. - -```jsx -... -import useSearch from '@bigcommerce/storefront-data-hooks/products/use-search' - -const SearchPage = ({ searchString, category, brand, sortStr }) => { - const { data } = useSearch({ - search: searchString || '', - categoryId: category?.entityId, - brandId: brand?.entityId, - sort: sortStr || '', - }) - - return ( - - {data.products.map(({ node }) => ( - - ))} - - ) -} -``` - -### getAllProducts - -API function to retrieve a product list. - -```js -import { getConfig } from '@bigcommerce/storefront-data-hooks/api' -import getAllProducts from '@bigcommerce/storefront-data-hooks/api/operations/get-all-products' - -const { products } = await getAllProducts({ - variables: { field: 'featuredProducts', first: 6 }, - config, - preview, -}) -``` - -### getProduct - -API product to retrieve a single product when provided with the product -slug string. - -```js -import { getConfig } from '@bigcommerce/storefront-data-hooks/api' -import getProduct from '@bigcommerce/storefront-data-hooks/api/operations/get-product' - -const { product } = await getProduct({ - variables: { slug }, - config, - preview, -}) -``` - -## More - -Feel free to read through the source for more usage, and check the commerce vercel demo and commerce repo for usage examples: ([demo.vercel.store](https://demo.vercel.store/)) ([repo](https://github.com/vercel/commerce)) +
+BigCommerce shows a Coming Soon page and requests a Preview Code +
+After Email confirmation, Checkout should be manually enabled through BigCommerce platform. Look for "Review & test your store" section through BigCommerce's dashboard. +
+
+BigCommerce team has been notified and they plan to add more detailed about this subject. +
diff --git a/framework/commerce/README.md b/framework/commerce/README.md new file mode 100644 index 000000000..ecdebb8c0 --- /dev/null +++ b/framework/commerce/README.md @@ -0,0 +1,334 @@ +# Commerce Framework + +- [Commerce Framework](#commerce-framework) + - [Commerce Hooks](#commerce-hooks) + - [CommerceProvider](#commerceprovider) + - [Authentication Hooks](#authentication-hooks) + - [useSignup](#usesignup) + - [useLogin](#uselogin) + - [useLogout](#uselogout) + - [Customer Hooks](#customer-hooks) + - [useCustomer](#usecustomer) + - [Product Hooks](#product-hooks) + - [usePrice](#useprice) + - [useSearch](#usesearch) + - [Cart Hooks](#cart-hooks) + - [useCart](#usecart) + - [useAddItem](#useadditem) + - [useUpdateItem](#useupdateitem) + - [useRemoveItem](#useremoveitem) + - [Wishlist Hooks](#wishlist-hooks) + - [Commerce API](#commerce-api) + - [More](#more) + +The commerce framework ships multiple hooks and a Node.js API, both using an underlying headless e-commerce platform, which we call commerce providers. + +The core features are: + +- Code splitted hooks for data fetching using [SWR](https://swr.vercel.app/), and to handle common user actions +- A Node.js API for initial data population, static generation of content and for creating the API endpoints that connect to the hooks, if required. + +> 👩‍🔬 If you would like to contribute a new provider, check the docs for [Adding a new Commerce Provider](./new-provider.md). + +> 🚧 The core commerce framework is under active development, new features and updates will be continuously added over time. Breaking changes are expected while we finish the API. + +## Commerce Hooks + +A commerce hook is a [React hook](https://reactjs.org/docs/hooks-intro.html) that's connected to a commerce provider. They focus on user actions and data fetching of data that wasn't statically generated. + +Data fetching hooks use [SWR](https://swr.vercel.app/) underneath and you're welcome to use any of its [return values](https://swr.vercel.app/docs/options#return-values) and [options](https://swr.vercel.app/docs/options#options). For example, using the `useCustomer` hook: + +```jsx +const { data, isLoading, error } = useCustomer({ + swrOptions: { + revalidateOnFocus: true, + }, +}) +``` + +### CommerceProvider + +This component adds the provider config and handlers to the context of your React tree for it's children. You can optionally pass the `locale` to it: + +```jsx +import { CommerceProvider } from '@framework' + +const App = ({ locale = 'en-US', children }) => { + return {children} +} +``` + +## Authentication Hooks + +### useSignup + +Returns a _signup_ function that can be used to sign up the current visitor: + +```jsx +import useSignup from '@framework/auth/use-signup' + +const SignupView = () => { + const signup = useSignup() + + const handleSignup = async () => { + await signup({ + email, + firstName, + lastName, + password, + }) + } + + return
{children}
+} +``` + +### useLogin + +Returns a _login_ function that can be used to sign in the current visitor into an existing customer: + +```jsx +import useLogin from '@framework/auth/use-login' + +const LoginView = () => { + const login = useLogin() + const handleLogin = async () => { + await login({ + email, + password, + }) + } + + return
{children}
+} +``` + +### useLogout + +Returns a _logout_ function that signs out the current customer when called. + +```jsx +import useLogout from '@framework/auth/use-logout' + +const LogoutButton = () => { + const logout = useLogout() + return ( + + ) +} +``` + +## Customer Hooks + +### useCustomer + +Fetches and returns the data of the signed in customer: + +```jsx +import useCustomer from '@framework/customer/use-customer' + +const Profile = () => { + const { data, isLoading, error } = useCustomer() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (!data) return null + + return
Hello, {data.firstName}
+} +``` + +## Product Hooks + +### usePrice + +Helper hook to format price according to the commerce locale and currency code. It also handles discounts: + +```jsx +import useCart from '@framework/cart/use-cart' +import usePrice from '@framework/product/use-price' + +// ... +const { data } = useCart() +const { price, discount, basePrice } = usePrice( + data && { + amount: data.subtotalPrice, + currencyCode: data.currency.code, + // If `baseAmount` is used, a discount will be calculated + // baseAmount: number, + } +) +// ... +``` + +### useSearch + +Fetches and returns the products that match a set of filters: + +```jsx +import useSearch from '@framework/product/use-search' + +const SearchPage = ({ searchString, category, brand, sortStr }) => { + const { data } = useSearch({ + search: searchString || '', + categoryId: category?.entityId, + brandId: brand?.entityId, + sort: sortStr, + }) + + return ( + + {data.products.map((product) => ( + + ))} + + ) +} +``` + +## Cart Hooks + +### useCart + +Fetches and returns the data of the current cart: + +```jsx +import useCart from '@framework/cart/use-cart' + +const CartTotal = () => { + const { data, isLoading, isEmpty, error } = useCart() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (isEmpty) return

The cart is empty

+ + return

The cart total is {data.totalPrice}

+} +``` + +### useAddItem + +Returns a function that adds a new item to the cart when called, if this is the first item it will create the cart: + +```jsx +import { useAddItem } from '@framework/cart' + +const AddToCartButton = ({ productId, variantId }) => { + const addItem = useAddItem() + + const addToCart = async () => { + await addItem({ + productId, + variantId, + }) + } + + return +} +``` + +### useUpdateItem + +Returns a function that updates a current item in the cart when called, usually the quantity. + +```jsx +import { useUpdateItem } from '@framework/cart' + +const CartItemQuantity = ({ item }) => { + const [quantity, setQuantity] = useState(item.quantity) + const updateItem = useUpdateItem({ item }) + + const updateQuantity = async (e) => { + const val = e.target.value + + setQuantity(val) + await updateItem({ quantity: val }) + } + + return ( + + ) +} +``` + +If the `quantity` is lower than 1 the item will be removed from the cart. + +### useRemoveItem + +Returns a function that removes an item in the cart when called: + +```jsx +import { useRemoveItem } from '@framework/cart' + +const RemoveButton = ({ item }) => { + const removeItem = useRemoveItem() + const handleRemove = async () => { + await removeItem(item) + } + + return +} +``` + +## Wishlist Hooks + +Wishlist hooks work just like [cart hooks](#cart-hooks). Feel free to check how those work first. + +The example below shows how to use the `useWishlist`, `useAddItem` and `useRemoveItem` hooks: + +```jsx +import { useWishlist, useAddItem, useRemoveItem } from '@framework/wishlist' + +const WishlistButton = ({ productId, variant }) => { + const addItem = useAddItem() + const removeItem = useRemoveItem() + const { data, isLoading, isEmpty, error } = useWishlist() + + if (isLoading) return

Loading...

+ if (error) return

{error.message}

+ if (isEmpty) return

The wihslist is empty

+ + const { data: customer } = useCustomer() + const itemInWishlist = data?.items?.find( + (item) => item.product_id === productId && item.variant_id === variant.id + ) + + const handleWishlistChange = async (e) => { + e.preventDefault() + if (!customer) return + + if (itemInWishlist) { + await removeItem({ id: itemInWishlist.id }) + } else { + await addItem({ + productId, + variantId: variant.id, + }) + } + } + + return ( + + ) +} +``` + +## Commerce API + +While commerce hooks focus on client side data fetching and interactions, the commerce API focuses on static content generation for pages and API endpoints in a Node.js context. + +> 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. + +## More + +Feel free to read through the source for more usage, and check the commerce vercel demo and commerce repo for usage examples: ([demo.vercel.store](https://demo.vercel.store/)) ([repo](https://github.com/vercel/commerce)) diff --git a/framework/commerce/new-provider.md b/framework/commerce/new-provider.md new file mode 100644 index 000000000..4051c0f01 --- /dev/null +++ b/framework/commerce/new-provider.md @@ -0,0 +1,239 @@ +# 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 + +export type BigcommerceProps = { + children?: ReactNode + locale: string +} & BigcommerceConfig + +export function CommerceProvider({ children, ...config }: BigcommerceProps) { + return ( + + {children} + + ) +} + +export const useCommerce = () => useCoreCommerce() +``` + +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 + +export const handler: SWRHook< + Cart | null, + {}, + FetchCartInput, + { isEmpty?: boolean } +> = { + fetchOptions: { + url: '/api/bigcommerce/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 + +export const handler: MutationHook = { + fetchOptions: { + url: '/api/bigcommerce/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({ + ...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. diff --git a/framework/shopify/.env.template b/framework/shopify/.env.template index 9dc3674b6..74f446835 100644 --- a/framework/shopify/.env.template +++ b/framework/shopify/.env.template @@ -1,2 +1,4 @@ +COMMERCE_PROVIDER=shopify + NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= diff --git a/framework/shopify/README.md b/framework/shopify/README.md index eeae73afc..d67111a41 100644 --- a/framework/shopify/README.md +++ b/framework/shopify/README.md @@ -1,57 +1,28 @@ -## Table of Contents +## Shopify Provider -- [Getting Started](#getting-started) - - [Modifications](#modifications) - - [Adding item to Cart](#adding-item-to-cart) - - [Proceed to Checkout](#proceed-to-checkout) -- [General Usage](#general-usage) - - [CommerceProvider](#commerceprovider) - - [useCommerce](#usecommerce) -- [Hooks](#hooks) - - [usePrice](#useprice) - - [useAddItem](#useadditem) - - [useRemoveItem](#useremoveitem) - - [useUpdateItem](#useupdateitem) -- [APIs](#apis) - - [getProduct](#getproduct) - - [getAllProducts](#getallproducts) - - [getAllCollections](#getallcollections) - - [getAllPages](#getallpages) +**Demo:** https://shopify.demo.vercel.store/ -# Shopify Storefront Data Hooks +Before getting starter, a [Shopify](https://www.shopify.com/) account and store is required before using the provider. -Collection of hooks and data fetching functions to integrate Shopify in a React application. Designed to work with [Next.js Commerce](https://demo.vercel.store/). +Next, copy the `.env.template` file in this directory to `.env.local` in the main directory (which will be ignored by Git): -## Getting Started - -1. Install dependencies: - -``` -yarn add shopify-buy -yarn add @types/shopify-buy +```bash +cp framework/shopify/.env.template .env.local ``` -3. Environment variables need to be set: +Then, set the environment variables in `.env.local` to match the ones from your store. -``` -SHOPIFY_STORE_DOMAIN= -SHOPIFY_STOREFRONT_ACCESS_TOKEN= -NEXT_PUBLIC_SHOPIFY_STORE_DOMAIN= -NEXT_PUBLIC_SHOPIFY_STOREFRONT_ACCESS_TOKEN= -``` +## Contribute -4. Point the framework to `shopify` by updating `tsconfig.json`: +Our commitment to Open Source can be found [here](https://vercel.com/oss). -``` -"@framework/*": ["framework/shopify/*"], -"@framework": ["framework/shopify"] -``` +If you find an issue with the provider or want a new feature, feel free to open a PR or [create a new issue](https://github.com/vercel/commerce/issues). -### Modifications +## Modifications These modifications are temporarily until contributions are made to remove them. -#### Adding item to Cart +### Adding item to Cart ```js // components/product/ProductView/ProductView.tsx @@ -72,7 +43,7 @@ const ProductView: FC = ({ product }) => { } ``` -#### Proceed to Checkout +### Proceed to Checkout ```js // components/cart/CartSidebarView/CartSidebarView.tsx @@ -88,114 +59,6 @@ const CartSidebarView: FC = () => { } ``` -## General Usage - -### CommerceProvider - -Provider component that creates the commerce context for children. - -```js -import { CommerceProvider } from '@framework' - -const App = ({ children }) => { - return {children} -} - -export default App -``` - -### useCommerce - -Returns the configs that are defined in the nearest `CommerceProvider`. Also provides access to Shopify's `checkout` and `shop`. - -```js -import { useCommerce } from 'nextjs-commerce-shopify' - -const { checkout, shop } = useCommerce() -``` - -- `checkout`: The information required to checkout items and pay ([Documentation](https://shopify.dev/docs/storefront-api/reference/checkouts/checkout)). -- `shop`: Represents a collection of the general settings and information about the shop ([Documentation](https://shopify.dev/docs/storefront-api/reference/online-store/shop/index)). - -## Hooks - -### usePrice - -Display the product variant price according to currency and locale. - -```js -import usePrice from '@framework/product/use-price' - -const { price } = usePrice({ - amount, -}) -``` - -Takes in either `amount` or `variant`: - -- `amount`: A price value for a particular item if the amount is known. -- `variant`: A shopify product variant. Price will be extracted from the variant. - -### useAddItem - -```js -import { useAddItem } from '@framework/cart' - -const AddToCartButton = ({ variantId, quantity }) => { - const addItem = useAddItem() - - const addToCart = async () => { - await addItem({ - variantId, - }) - } - - return -} -``` - -### useRemoveItem - -```js -import { useRemoveItem } from '@framework/cart' - -const RemoveButton = ({ item }) => { - const removeItem = useRemoveItem() - - const handleRemove = async () => { - await removeItem({ id: item.id }) - } - - return -} -``` - -### useUpdateItem - -```js -import { useUpdateItem } from '@framework/cart' - -const CartItem = ({ item }) => { - const [quantity, setQuantity] = useState(item.quantity) - const updateItem = useUpdateItem(item) - - const updateQuantity = async (e) => { - const val = e.target.value - await updateItem({ quantity: val }) - } - - return ( - - ) -} -``` - ## APIs Collections of APIs to fetch data from a Shopify store.