diff --git a/examples/b2b/README.md b/examples/b2b/README.md index e69de29bb2..47b9bf392b 100644 --- a/examples/b2b/README.md +++ b/examples/b2b/README.md @@ -0,0 +1,44 @@ +# Hydrogen example: B2B + +> Note: +> +> This example is currently Unstable + +This is an example implementation of a B2B storefront using Hydrogen. It includes the following high level changes. + +1. Retrieving company location data from a logged in customer using the [Customer Account API](https://shopify.dev/docs/api/customer/2024-04/queries/customer) +2. Displaying a list of company locations and setting a `companyLocationId` in session +3. Using a storefront `customerAccessToken` and `companyLocationId` to update cart and get B2B specific rules and pricing +4. Using a storefront `customerAccessToken` and `companyLocationId` to [contextualize queries](https://shopify.dev/docs/api/storefront#directives) using the `buyer` argument + +Only queries on the product display page, `app/routes/products.$handle.tsx`, were contextualized in this example. For a production storefront, all queries for product data should be contextualized. + +## Install + +Setup a new project with this example: + +```bash +npm create @shopify/hydrogen@latest -- --template b2b +``` + +## Requirements + +- Your store is on a [Shopify Plus plan](https://help.shopify.com/manual/intro-to-shopify/pricing-plans/plans-features/shopify-plus-plan). +- Your store is using [new customer accounts](https://help.shopify.com/en/manual/customers/customer-accounts/new-customer-accounts). +- You have access to a customer which has permission to order for a [B2B company](https://help.shopify.com/en/manual/b2b). + +## Key files + +This folder contains the minimal set of files needed to showcase the implementation. +Not all queries where contextualized for B2B. `app/routes/products.$handle.tsx` provides +reference on how to contextualize storefront queries.Files that aren’t included by default +with Hydrogen and that you’ll need to create are labeled with 🆕. + +| File | Description | +| ---------------------------------------------------- | ---------------------------------------------------------------------------------------------- | +| [`app/root.tsx`](app/root.tsx) | Includes a customer query to determine if the the logged in session is for a B2B buyer. Set `companyLocationId` in session if there is only one location available to buy for | +| 🆕 [`app/graphql/CustomerLocationsQuery.ts`](app/graphql/CustomerLocationsQuery.ts) | Customer query to fetch company locations | +| 🆕 [`app/components/LocationSelector.tsx`](app/components/LocationSelector.tsx) | Component to choose a Company location to buy for. Rendered if there is no `companyLocationId` set in session | +| [`app/routes/products.$handle.tsx`](app/routes/products.$handle.tsx) | Added buyer context to the product and product varient queries. Includes logic and components to display quantity rules and quantity price breaks | +| 🆕 [`app/components/PriceBreaks.tsx`](app/components/PriceBreaks.tsx) | Component rendered on the product page to highlight quantity price breaks | +| 🆕 [`app/components/QuantityRules.tsx`](app/components/QuantityRules.tsx) | Component rendered on the product page to highlight quantity rules | diff --git a/examples/b2b/app/components/Header.tsx b/examples/b2b/app/components/Header.tsx index f06ddcee23..003e3e8d00 100644 --- a/examples/b2b/app/components/Header.tsx +++ b/examples/b2b/app/components/Header.tsx @@ -4,6 +4,10 @@ import {useState, Suspense} from 'react'; import type {HeaderQuery} from 'storefrontapi.generated'; import type {LayoutProps} from './Layout'; import {useRootLoaderData} from '~/root'; +import type { + CompanyLocation, + CompanyLocationConnection, +} from '@shopify/hydrogen-react/customer-account-api-types'; /***********************************************/ /********** EXAMPLE UPDATE STARTS ************/ @@ -156,13 +160,13 @@ function CartToggle({cart}: Pick) { /********** EXAMPLE UPDATE STARTS ************/ function LocationDropdown({company}: Pick) { const locations = company?.locations?.edges - ? company.locations.edges.map((location) => { + ? company.locations.edges.map((location: CompanyLocationConnection) => { return {...location.node}; }) : []; const [selectedLocation, setSelectedLocation] = useState( - company.locations.edges[0].node.id ?? undefined, + company?.locations?.edges?.[0]?.node?.id ?? undefined, ); const setLocation = async (event: React.ChangeEvent) => { @@ -170,7 +174,7 @@ function LocationDropdown({company}: Pick) { setSelectedLocation(locationId); }; - if (locations.length === 1) return null; + if (locations.length === 1 || !company) return null; return ( @@ -187,7 +191,7 @@ function LocationDropdown({company}: Pick) { value={selectedLocation} style={{marginRight: '4px'}} > - {locations.map((location) => { + {locations.map((location: CompanyLocation) => { return (