Sfoglia il codice sorgente

docs: Add storefront guides for PDP, active order

Michael Bromley 2 anni fa
parent
commit
39c5f285ef

+ 180 - 0
docs/docs/guides/core-concepts/money/index.mdx

@@ -0,0 +1,180 @@
+---
+title: "Money & Currency"
+---
+
+In Vendure, monetary values are stored as **integers** using the **minor unit** of the selected currency.
+For example, if the currency is set to USD, then the integer value `100` would represent $1.00.
+This is a common practice in financial applications, as it avoids the rounding errors that can occur when using floating-point numbers.
+
+For example, here's the response from a query for a product's variant prices:
+
+```json
+{
+  "data": {
+    "product": {
+      "id": "42",
+      "variants": [
+        {
+          "id": "74",
+          "name": "Bonsai Tree",
+          "currencyCode": "USD",
+          // highlight-start
+          "price": 1999,
+          "priceWithTax": 2399,
+          // highlight-end
+        }
+      ]
+    }
+  }
+}
+```
+
+In this example, the tax-inclusive price of the variant is `$23.99`.
+
+:::info
+To illustrate the problem with storing money as decimals, imagine that we want to add the price of two items:
+
+- Product A: `$1.21`
+- Product B: `$1.22`
+
+We should expect the sum of these two amounts to equal `$2.43`. However, if we perform this addition in JavaScript (and the same
+holds true for most common programming languages), we will instead get `$2.4299999999999997`!
+
+For a more in-depth explanation of this issue, see [this StackOverflow answer](https://stackoverflow.com/a/3730040/772859)
+:::
+
+## Displaying monetary values
+
+When you are building your storefront, or any other client that needs to display monetary values in a human-readable form,
+you need to divide by 100 to convert to the major currency unit and then format with the correct decimal & grouping dividers.
+
+In JavaScript environments such as browsers & Node.js, we can take advantage of the excellent [`Intl.NumberFormat` API](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat).
+
+Here's a function you can use in your projects:
+
+```ts title="src/utils/format-currency.ts"
+export function formatCurrency(value: number, currencyCode: string, locale?: string) {
+    const majorUnits = value / 100;
+    try {
+        // Note: if no `locale` is provided, the browser's default
+        // locale will be used.
+        return new Intl.NumberFormat(locale, {
+            style: 'currency',
+            currency: currencyCode,
+        }).format(majorUnits);
+    } catch (e: any) {
+        // A fallback in case the NumberFormat fails for any reason
+        return majorUnits.toFixed(2);
+    }
+}
+```
+
+If you are building an Admin UI extension, you can use the built-in [`LocaleCurrencyPipe`](/reference/admin-ui-api/pipes/locale-currency-pipe/):
+
+```html title="src/plugins/my-plugin/ui/components/my-component/my.component.html"
+<div>
+    Variant price: {{ variant.price | localeCurrency : variant.currencyCode }}
+</div>
+```
+
+
+## The GraphQL `Money` scalar
+
+In the GraphQL APIs, we use a custom [`Money` scalar type](/reference/graphql-api/admin/object-types/#money) to represent
+all monetary values. We do this for two reasons:
+
+1. The built-in `Int` type is that the GraphQL spec imposes an upper limit of
+`2147483647`, which in some cases (especially currencies with very large amounts) is not enough.
+2. Very advanced use-cases might demand more precision than is possible with an integer type. Using our own custom
+scalar gives us the possibility of supporting more precision.
+
+Here's how the `Money` scalar is used in the `ShippingLine` type:
+
+```graphql
+type ShippingLine {
+    id: ID!
+    shippingMethod: ShippingMethod!
+    // highlight-start
+    price: Money!
+    priceWithTax: Money!
+    discountedPrice: Money!
+    discountedPriceWithTax: Money!
+    // highlight-end
+    discounts: [Discount!]!
+}
+```
+
+If you are defining custom GraphQL types, or adding fields to existing types (see the [Extending the GraphQL API doc](/guides/how-to/extend-graphql-api/)),
+then you should also use the `Money` scalar for any monetary values.
+
+
+## The `@Money()` decorator
+
+When [defining new database entities](/guides/how-to/database-entity/), if you need to store a monetary value, then rather than using the TypeORM `@Column()`
+decorator, you should use Vendure's [`@Money()` decorator](/reference/typescript-api/money/money-decorator).
+
+Using this decorator allows Vendure to correctly store the value in the database according to the configured `MoneyStrategy` (see below).
+
+```ts title="src/plugins/quote/entities/quote.entity.ts"
+import { DeepPartial } from '@vendure/common/lib/shared-types';
+import { VendureEntity, Order, EntityId, Money, CurrencyCode, ID } from '@vendure/core';
+import { Column, Entity, ManyToOne } from 'typeorm';
+
+@Entity()
+class Quote extends VendureEntity {
+    constructor(input?: DeepPartial<Quote>) {
+        super(input);
+    }
+
+    @ManyToOne(type => Order)
+    order: Order;
+
+    @EntityId()
+    orderId: ID;
+
+    @Column()
+    text: string;
+
+    // highlight-start
+    @Money()
+    value: number;
+    // highlight-end
+
+    // Whenever you store a monetary value, it's a good idea to also
+    // explicitly store the currency code too. This makes it possible
+    // to support multiple currencies and correctly format the amount
+    // when displaying the value.
+    @Column('varchar')
+    currencyCode: CurrencyCode;
+
+    @Column()
+    approved: boolean;
+}
+```
+
+## Advanced configuration: MoneyStrategy
+
+For advanced use-cases, it is possible to configure aspects of how Vendure handles monetary values internally by defining
+a custom [`MoneyStrategy`](/reference/typescript-api/money/money-strategy/).
+
+The `MoneyStrategy` allows you to define:
+
+- How the value is stored and retrieved from the database
+- How rounding is applied internally
+
+For example, in addition to the [`DefaultMoneyStrategy`](/reference/typescript-api/money/default-money-strategy), Vendure
+also provides the [`BigIntMoneyStrategy`](/reference/typescript-api/money/big-int-money-strategy) which stores monetary values
+using the `bigint` data type, allowing much larger amounts to be stored.
+
+Here's how you would configure your server to use this strategy:
+
+```ts title="src/vendure-config.ts"
+import { VendureConfig, BigIntMoneyStrategy } from '@vendure/core';
+
+export const config: VendureConfig = {
+    // ...
+    entityOptions: {
+        moneyStrategy: new BigIntMoneyStrategy(),
+    }
+}
+```

+ 1 - 1
docs/docs/guides/how-to/database-entity/index.md

@@ -108,7 +108,7 @@ In addition to the decorators described above, there are many other decorators p
 
 There is also another Vendure-specific decorator for representing monetary values specifically:
 
-- [`@Money()`](/reference/typescript-api/money/money-decorator): This works together with the [`MoneyStrategy`](/reference/typescript-api/money/money-strategy) to allow configurable control over how monetary values are stored in the database.
+- [`@Money()`](/reference/typescript-api/money/money-decorator): This works together with the [`MoneyStrategy`](/reference/typescript-api/money/money-strategy) to allow configurable control over how monetary values are stored in the database. For more information see the [Money & Currency guide](/guides/core-concepts/money/#the-money-decorator).
 
 :::info
 The full list of TypeORM decorators can be found in the [TypeORM decorator reference](https://typeorm.io/decorator-reference)

+ 205 - 0
docs/docs/guides/storefront/active-order/index.mdx

@@ -0,0 +1,205 @@
+---
+title: "Managing the Active Order"
+---
+
+import Stackblitz from '@site/src/components/Stackblitz';
+
+The "active order" is what is also known as the "cart" - it is the order that is currently being worked on by the customer.
+
+An order remains active until it is completed, and during this time it can be modified by the customer in various ways:
+
+- Adding an item
+- Removing an item
+- Changing the quantity of items
+- Applying a coupon
+- Removing a coupon
+
+This guide will cover how to manage the active order.
+
+## Define an Order fragment
+
+Since all the mutations that we will be using in this guide require an `Order` object, we will define a fragment that we can
+reuse in all of our mutations.
+
+```ts title="src/fragments.ts"
+const ACTIVE_ORDER_FRAGMENT = /*GraphQL*/`
+fragment ActiveOrder on Order {
+  __typename
+  id
+  code
+  couponCodes
+  state
+  currencyCode
+  totalQuantity
+  subTotalWithTax
+  shippingWithTax
+  totalWithTax
+  discounts {
+    description
+    amountWithTax
+  }
+  lines {
+    id
+    unitPriceWithTax
+    quantity
+    linePriceWithTax
+    productVariant {
+      id
+      name
+      sku
+    }
+    featuredAsset {
+      id
+      preview
+    }
+  }
+  shippingLines {
+    shippingMethod {
+      description
+    }
+    priceWithTax
+  }
+}`
+```
+
+:::note
+The `__typename` field is used to determine the type of the object returned by the GraphQL server. In this case, it will always be `'Order'`.
+
+Some GraphQL clients such as Apollo Client will automatically add the `__typename` field to all queries and mutations, but if you are using a different client you may need to add it manually.
+:::
+
+This fragment can then be used in subsequent queries and mutations by using the `...` spread operator in the place where an `Order` object is expected.
+You can then embed the fragment in the query or mutation by using the `${ACTIVE_ORDER_FRAGMENT}` syntax:
+
+```ts title="src/queries.ts"
+import { ACTIVE_ORDER_FRAGMENT } from './fragments';
+
+export const GET_ACTIVE_ORDER = /*GraphQL*/`
+  query GetActiveOrder {
+    activeOrder {
+      // highlight-next-line
+      ...ActiveOrder
+    }
+  }
+  // highlight-next-line
+  ${ACTIVE_ORDER_FRAGMENT}
+`;
+```
+
+For the remainder of this guide, we will list just the body of the query or mutation, and assume that the fragment is defined and imported as above.
+
+## Get the active order
+
+This fragment can then be used in subsequent queries and mutations. Let's start with a query to get the active order using the [`activeOrder` query](/reference/graphql-api/shop/queries#activeorder):
+
+```graphql
+query GetActiveOrder {
+  activeOrder {
+    ...ActiveOrder
+  }
+}
+```
+
+## Add an item
+
+To add an item to the active order, we use the [`addItemToOrder` mutation](/reference/graphql-api/shop/mutations/#additemtoorder), as we have seen in the [Product Detail Page guide](/guides/storefront/product-detail/).
+
+```graphql
+mutation AddItemToOrder($productVariantId: ID!, $quantity: Int!) {
+  addItemToOrder(productVariantId: $productVariantId, quantity: $quantity) {
+    ...ActiveOrder
+    ... on ErrorResult {
+      errorCode
+      message
+    }
+    ... on InsufficientStockError {
+      quantityAvailable
+      order {
+        ...ActiveOrder
+      }
+    }
+  }
+}
+```
+
+:::info
+If you have defined any custom fields on the `OrderLine` entity, you will be able to pass them as a `customFields` argument to the `addItemToOrder` mutation.
+See the [Configurable Products guide](/guides/how-to/configurable-products/) for more information.
+:::
+
+## Remove an item
+
+To remove an item from the active order, we use the [`removeOrderLine` mutation](/reference/graphql-api/shop/mutations/#removeorderline),
+and pass the `id` of the `OrderLine` to remove.
+
+```graphql
+mutation RemoveItemFromOrder($orderLineId: ID!) {
+  removeOrderLine(orderLineId: $orderLineId) {
+    ...ActiveOrder
+    ... on ErrorResult {
+      errorCode
+      message
+    }
+  }
+}
+```
+
+## Change the quantity of an item
+
+To change the quantity of an item in the active order, we use the [`adjustOrderLine` mutation](/reference/graphql-api/shop/mutations/#adjustorderline).
+
+```graphql
+mutation AdjustOrderLine($orderLineId: ID!, $quantity: Int!) {
+  adjustOrderLine(orderLineId: $orderLineId, quantity: $quantity) {
+    ...ActiveOrder
+    ... on ErrorResult {
+        errorCode
+        message
+    }
+  }
+}
+```
+
+:::info
+If you have defined any custom fields on the `OrderLine` entity, you will be able to update their values by passing a `customFields` argument to the `adjustOrderLine` mutation.
+See the [Configurable Products guide](/guides/how-to/configurable-products/) for more information.
+:::
+
+## Applying a coupon code
+
+If you have defined any [Promotions](/guides/core-concepts/promotions/) which use coupon codes, you can apply the a coupon code to the active order
+using the [`applyCouponCode` mutation](/reference/graphql-api/shop/mutations/#applycouponcode).
+
+```graphql
+mutation ApplyCouponCode($couponCode: String!) {
+  applyCouponCode(couponCode: $couponCode) {
+    ...ActiveOrder
+    ... on ErrorResult {
+      errorCode
+      message
+    }
+  }
+}
+```
+
+## Removing a coupon code
+
+To remove a coupon code from the active order, we use the [`removeCouponCode` mutation](/reference/graphql-api/shop/mutations/#removecouponcode).
+
+```graphql
+mutation RemoveCouponCode($couponCode: String!) {
+  removeCouponCode(couponCode: $couponCode) {
+    ...ActiveOrder
+    ... on ErrorResult {
+      errorCode
+      message
+    }
+  }
+}
+```
+
+## Live example
+
+Here is a live example which demonstrates adding, updating and removing items from the active order:
+
+<Stackblitz id='vendure-docs-active-order' />

+ 0 - 32
docs/docs/guides/storefront/building-a-storefront/index.md

@@ -1,32 +0,0 @@
----
-title: "Building a Storefront"
-weight: 0
-showtoc: true
----
-
-# Building a Storefront
-
-The storefront is the application that customers use to buy things from your store.
-
-One of the benefits of Vendure's headless architecture is that you can build your storefront using any technology you like, and in the future you can update your storefront without requiring any changes to the Vendure server itself!
-
-## Storefront starters
-
-To get you up and running with your storefront implementation, we offer a number of integrations with popular front-end frameworks such as Remix, Angular & Qwik. See all of our [storefront integrations](https://demo.vendure.io/).
-
-## Custom-building
-
-If you'd prefer to build your storefront from scratch, here are the main points you'll need to cover at a minimum:
-
-- Displaying navigation based on Collections using the `collections` query.
-- Listing products. Use the `search` query for this - it will let you filter by collection and also implements faceted filtering.
-- Product detail view with variant selection & add to cart functionality.
-- A cart view which allows items to be removed or quantity to be modified.
-- A checkout flow including shipping address and payment.
-- Login page with forgotten password flow & account creation flow
-- Customer account dashboard
-- Customer order history
-- Customer password reset flow
-- Customer email address change flow
-
-Some of these aspects are covered in more detail in this section, but we plan to create guides for each of these.

+ 1 - 1
docs/docs/guides/storefront/connect-api/index.mdx

@@ -208,7 +208,7 @@ export function query(document: string, variables: Record<string, any> = {}) {
     // highlight-start
     return fetch(endpoint, {
         method: 'POST',
-        headers: { 'content-type': 'application/json' },
+        headers,
         credentials: 'include',
         body: JSON.stringify({
             query: document,

+ 10 - 0
docs/docs/guides/storefront/listing-products/index.mdx

@@ -699,3 +699,13 @@ To filter by "Nikkon" **or** "Sony", we would use:
 Here's a live example of faceted search. Try searching for terms like "shoe", "plant" or "ball".
 
 <Stackblitz id="vendure-docs-faceted-search"></Stackblitz>
+
+## Listing custom product data
+
+If you have defined custom fields on the `Product` or `ProductVariant` entity, you might want to include these in the
+search results. With the [`DefaultSearchPlugin`](/reference/typescript-api/core-plugins/default-search-plugin/) this is
+not possible, as this plugin is designed to be a minimal and simple search implementation.
+
+Instead, you can use the [`ElasticsearchPlugin`](/reference/typescript-api/core-plugins/elasticsearch-plugin/) which
+provides advanced features which allow you to index custom data. The Elasticsearch plugin is designed as a drop-in
+replacement for the `DefaultSearchPlugin`, so you can simply swap out the plugins in your `vendure-config.ts` file.

+ 290 - 0
docs/docs/guides/storefront/product-detail/index.mdx

@@ -0,0 +1,290 @@
+---
+title: "Product Detail Page"
+---
+
+import Tabs from '@theme/Tabs';
+import TabItem from '@theme/TabItem';
+import Stackblitz from '@site/src/components/Stackblitz';
+
+The product detail page (often abbreviated to PDP) is the page that shows the details of a product and allows the user to add it to their cart.
+
+Typically, the PDP should include:
+
+- Product name
+- Product description
+- Available product variants
+- Images of the product and its variants
+- Price information
+- Stock information
+- Add to cart button
+
+## Fetching product data
+
+Let's create a query to fetch the required data. You should have either the product's `slug` or `id` available from the
+url. We'll use the `slug` in this example.
+
+<Tabs>
+<TabItem value="Query" label="Query" default>
+
+```graphql
+query GetProductDetail($slug: String!) {
+  product(slug: $slug) {
+    id
+    name
+    description
+    featuredAsset {
+      id
+      preview
+    }
+    assets {
+      id
+      preview
+    }
+    variants {
+      id
+      name
+      sku
+      stockLevel
+      currencyCode
+      price
+      priceWithTax
+      featuredAsset {
+        id
+        preview
+      }
+      assets {
+        id
+        preview
+      }
+    }
+  }
+}
+```
+
+</TabItem>
+<TabItem value="Variables" label="Variables">
+
+```json
+{
+  "slug": "laptop"
+}
+```
+
+</TabItem>
+<TabItem value="Response" label="Response" >
+
+```json
+{
+  "data": {
+    "product": {
+      "id": "1",
+      "name": "Laptop",
+      "description": "Now equipped with seventh-generation Intel Core processors, Laptop is snappier than ever. From daily tasks like launching apps and opening files to more advanced computing, you can power through your day thanks to faster SSDs and Turbo Boost processing up to 3.6GHz.",
+      "featuredAsset": {
+        "id": "1",
+        "preview": "https://demo.vendure.io/assets/preview/71/derick-david-409858-unsplash__preview.jpg"
+      },
+      "assets": [
+        {
+          "id": "1",
+          "preview": "https://demo.vendure.io/assets/preview/71/derick-david-409858-unsplash__preview.jpg"
+        }
+      ],
+      "variants": [
+        {
+          "id": "1",
+          "name": "Laptop 13 inch 8GB",
+          "sku": "L2201308",
+          "stockLevel": "IN_STOCK",
+          "currencyCode": "USD",
+          "price": 129900,
+          "priceWithTax": 155880,
+          "featuredAsset": null,
+          "assets": []
+        },
+        {
+          "id": "2",
+          "name": "Laptop 15 inch 8GB",
+          "sku": "L2201508",
+          "stockLevel": "IN_STOCK",
+          "currencyCode": "USD",
+          "price": 139900,
+          "priceWithTax": 167880,
+          "featuredAsset": null,
+          "assets": []
+        },
+        {
+          "id": "3",
+          "name": "Laptop 13 inch 16GB",
+          "sku": "L2201316",
+          "stockLevel": "IN_STOCK",
+          "currencyCode": "USD",
+          "price": 219900,
+          "priceWithTax": 263880,
+          "featuredAsset": null,
+          "assets": []
+        },
+        {
+          "id": "4",
+          "name": "Laptop 15 inch 16GB",
+          "sku": "L2201516",
+          "stockLevel": "IN_STOCK",
+          "currencyCode": "USD",
+          "price": 229900,
+          "priceWithTax": 275880,
+          "featuredAsset": null,
+          "assets": []
+        }
+      ]
+    }
+  }
+}
+```
+
+
+</TabItem>
+</Tabs>
+
+This single query provides all the data we need to display our PDP.
+
+## Formatting prices
+
+As explained in the [Money & Currency guide](/guides/core-concepts/money/), the prices are returned as integers in the
+smallest unit of the currency (e.g. cents for USD). Therefore, when we display the price, we need to divide by 100 and
+format it according to the currency's formatting rules.
+
+In the demo at the end of this guide, we'll use the [`formatCurrency` function](/guides/core-concepts/money/#displaying-monetary-values)
+which makes use of the browser's `Intl` API to format the price according to the user's locale.
+
+## Displaying images
+
+If we are using the [`AssetServerPlugin`](/reference/typescript-api/core-plugins/asset-server-plugin/) to serve our product images (as is the default), then we can take advantage
+of the dynamic image transformation abilities in order to display the product images in the correct size and in
+and optimized format such as WebP.
+
+This is done by appending a query string to the image URL. For example, if we want to use the `'large'` size preset (800 x 800)
+and convert the format to WebP, we'd use a url like this:
+
+```tsx
+<img src={product.featuredAsset.preview + '?preset=large&format=webp'} />
+```
+
+An even more sophisticated approach would be to make use of the HTML [`<picture>` element](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/picture) to provide multiple image sources
+so that the browser can select the optimal format. This can be wrapped in a component to make it easier to use. For example:
+
+```tsx title="src/components/VendureAsset.tsx"
+interface VendureAssetProps {
+    preview: string;
+    preset: 'tiny' | 'thumb' | 'small' | 'medium' | 'large';
+    alt: string;
+}
+
+export function VendureAsset({ preview, preset, alt }: VendureAssetProps) {
+    return (
+        <picture>
+            <source type="image/avif" srcSet={preview + `?preset=${preset}&format=avif`} />
+            <source type="image/webp" srcSet={preview + `?preset=${preset}&format=webp`} />
+            <img src={preview + `?preset=${preset}&format=jpg`} alt={alt} />
+        </picture>
+    );
+}
+```
+
+## Adding to the order
+
+To add a particular product variant to the order, we need to call the [`addItemToOrder`](/reference/graphql-api/shop/mutations/#additemtoorder) mutation.
+This mutation takes the `productVariantId` and the `quantity` as arguments.
+
+
+<Tabs>
+<TabItem value="Mutation" label="Mutation" default>
+
+```graphql
+mutation AddItemToOrder($variantId: ID!, $quantity: Int!) {
+  addItemToOrder(productVariantId: $variantId, quantity: $quantity) {
+    __typename
+    ...UpdatedOrder
+    ... on ErrorResult {
+      errorCode
+      message
+    }
+    ... on InsufficientStockError {
+      quantityAvailable
+      order {
+        ...UpdatedOrder
+      }
+    }
+  }
+}
+
+fragment UpdatedOrder on Order {
+  id
+  code
+  state
+  totalQuantity
+  totalWithTax
+  lines {
+    id
+    unitPriceWithTax
+    quantity
+    linePriceWithTax
+  }
+}
+```
+
+</TabItem>
+<TabItem value="Variables" label="Variables">
+
+```json
+{
+  "variantId": "4",
+  "quantity": 1
+}
+```
+
+</TabItem>
+<TabItem value="Response" label="Response" >
+
+```json
+{
+  "data": {
+    "addItemToOrder": {
+      "__typename": "Order",
+      "id": "5",
+      "code": "KE5FJPVV3Y3LX134",
+      "state": "AddingItems",
+      "totalQuantity": 1,
+      "totalWithTax": 275880,
+      "lines": [
+        {
+          "id": "14",
+          "unitPriceWithTax": 275880,
+          "quantity": 1,
+          "linePriceWithTax": 275880
+        }
+      ]
+    }
+  }
+}
+```
+
+</TabItem>
+</Tabs>
+
+There are some important things to note about this mutation:
+
+- Because the `addItemToOrder` mutation returns a union type, we need to use a [fragment](/guides/core-concepts/graphql/#fragments) to specify the fields we want to return.
+In this case we have defined a fragment called `UpdatedOrder` which contains the fields we are interested in.
+- If any [expected errors](/guides/advanced-topics/error-handling) occur, the mutation will return an `ErrorResult` object. We'll be able to
+see the `errorCode` and `message` fields in the response, so that we can display a meaningful error message to the user.
+- In the special case of the `InsufficientStockError`, in addition to the `errorCode` and `message` fields, we also get the `quantityAvailable` field
+which tells us how many of the requested quantity are available (and have been added to the order). This is useful information to display to the user.
+The `InsufficientStockError` object also embeds the updated `Order` object, which we can use to update the UI.
+- The `__typename` field can be used by the client to determine which type of object has been returned. Its value will equal the name
+of the returned type. This means that we can check whether `__typename === 'Order'` in order to determine whether the mutation was successful.
+
+## Live example
+
+Here's an example that brings together all of the above concepts:
+
+<Stackblitz id="vendure-docs-product-detail-page" />

+ 1 - 2
docs/docs/guides/storefront/storefront-starters/index.mdx

@@ -1,6 +1,5 @@
 ---
-title: "Storefront starters"
-sidebar_position: 1
+title: "Storefront Starters"
 ---
 
 Since building an entire Storefront from scratch can be a daunting task, we have prepared a few starter projects that you can use as a base for your own storefront.

+ 2 - 0
docs/sidebars.js

@@ -98,6 +98,8 @@ const sidebars = {
                 'guides/storefront/connect-api/index',
                 'guides/storefront/navigation-menu/index',
                 'guides/storefront/listing-products/index',
+                'guides/storefront/product-detail/index',
+                'guides/storefront/active-order/index',
             ],
         },
         {