docs: added how-to guide for show products (#3828)
* docs: added how-to guide for show products * small fixes * fix links * renamed store directory * fixing links after renaming directory * fix links after renaming directory * fix link * fixed sidebar link * fix tab
This commit is contained in:
1 parent
95d338262b
commit
b9e0af5470
6 files changed
+631
-11
No files matched your search
@@ -681,4 +681,4 @@ The request returns the following fields:
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to use product categories in a storefront](../store/use-categories.mdx)
|
||||
- [How to use product categories in a storefront](../storefront/use-categories.mdx)
|
||||
@@ -93,4 +93,4 @@ Aside from these relations, the `mpath` attribute, which is a [Materialized Path
|
||||
## See Also
|
||||
|
||||
- [How to manage product categories using the admin APIs](./admin/manage-categories.mdx)
|
||||
- [How to use product categories in a storefront](./store/use-categories.mdx)
|
||||
- [How to use product categories in a storefront](./storefront/use-categories.mdx)
|
||||
@@ -42,12 +42,11 @@ Admins can manage unlimited amount of products and their variants. They can mana
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '#',
|
||||
href: '/modules/products/storefront/show-products',
|
||||
label: 'Storefront: Show Products',
|
||||
customProps: {
|
||||
icon: Icons['academic-cap-solid'],
|
||||
description: 'Learn how to show products in a storefront.',
|
||||
isSoon: true,
|
||||
}
|
||||
},
|
||||
]} />
|
||||
@@ -79,7 +78,7 @@ Customers can use this organization to filter products while browsing them.
|
||||
},
|
||||
{
|
||||
type: 'link',
|
||||
href: '/modules/products/store/use-categories',
|
||||
href: '/modules/products/storefront/use-categories',
|
||||
label: 'Storefront: Use Categories',
|
||||
customProps: {
|
||||
icon: Icons['server-solid'],
|
||||
|
||||
@@ -0,0 +1,624 @@
|
||||
---
|
||||
description: 'Learn how to show products in your storefront using the Store REST APIs. This includes listing products, showing the price of a product, and more.'
|
||||
addHowToData: true
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs';
|
||||
import TabItem from '@theme/TabItem';
|
||||
|
||||
# How to Show Products on the Storefront
|
||||
|
||||
In this document, you’ll learn how to show products in your storefront using the Store REST APIs.
|
||||
|
||||
## Overview
|
||||
|
||||
Using the products store REST APIs, you can display products on your storefront along with their different details.
|
||||
|
||||
### Scenario
|
||||
|
||||
You want to add or use the following storefront functionalities:
|
||||
|
||||
- List products with filters.
|
||||
- Display product prices.
|
||||
- Search products.
|
||||
- Retrieve details of a single product by ID or by handle.
|
||||
|
||||
---
|
||||
|
||||
## Prerequisites
|
||||
|
||||
### Medusa Components
|
||||
|
||||
It's assumed that you already have a Medusa backend installed and set up. If not, you can follow the [quickstart guide](../../../development/backend/install.mdx) to get started.
|
||||
|
||||
It's also assumed you already have a storefront set up. It can be a custom storefront or one of Medusa’s storefronts. If you don’t have a storefront set up, you can install the [Next.js starter storefront](../../../starters/nextjs-medusa-starter.mdx).
|
||||
|
||||
### JS Client
|
||||
|
||||
This guide includes code snippets to send requests to your Medusa backend using Medusa’s JS Client, among other methods.
|
||||
|
||||
If you follow the JS Client code blocks, it’s assumed you already have [Medusa’s JS Client installed](../../../js-client/overview.md) and have [created an instance of the client](../../../js-client/overview.md#configuration).
|
||||
|
||||
### Medusa React
|
||||
|
||||
This guide also includes code snippets to send requests to your Medusa backend using Medusa React, among other methods.
|
||||
|
||||
If you follow the Medusa React code blocks, it's assumed you already have [Medusa React installed](../../../medusa-react/overview.md) and have [used MedusaProvider higher in your component tree](../../../medusa-react/overview.md#usage).
|
||||
|
||||
---
|
||||
|
||||
## List Products
|
||||
|
||||
You can list available products using the [List Products endpoint](/api/store#tag/Products/operation/GetProducts):
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list()
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts()
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint does not require any parameters. You can pass it parameters related to pagination, filtering, and more as explained in the [API reference](/api/store#tag/Products/operation/GetProducts).
|
||||
|
||||
The request returns an array of product objects along with [pagination parameters](/api/store#section/Pagination).
|
||||
|
||||
### Filtering Retrieved Products
|
||||
|
||||
The List Products endpoint accepts different query parameters that allow you to filter through retrieved results.
|
||||
|
||||
For example, you can filter products by a category ID:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
category_id: ["cat_123"],
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
category_id: ["cat_123"],
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?category_id[]=cat_123`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?category_id[]=cat_123'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This will retrieve only products that belong to that category.
|
||||
|
||||
### Product Pricing Parameters
|
||||
|
||||
By default, the prices are retrieved based on the default currency associated with a store. You can use the following query parameters to ensure you are retrieving correct pricing based on the customer’s context:
|
||||
|
||||
- `region_id`: The ID of the customer’s region.
|
||||
- `cart_id`: The ID of the customer’s cart.
|
||||
- `currency_code`: The code of the currency to retrieve prices for.
|
||||
|
||||
It’s recommended to always include the cart and region’s IDs when you’re listing or retrieving a single product’s details, as it’ll show you the correct pricing fields as explained in the next section.
|
||||
|
||||
For example:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
cart_id,
|
||||
region_id,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
import { Product } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
cart_id,
|
||||
region_id,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && <span>No Products</span>}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<li key={product.id}>{product.title}</li>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
<!-- eslint-disable max-len -->
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?cart_id=${cartId}®ion_id=${regionId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
console.log(products.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?cart_id=<CART_ID>®ion_id=<REGION_ID>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
### Display Product Price
|
||||
|
||||
Each product object in the retrieved array has a `variants` array. Each item in the `variants` array is a product variant object.
|
||||
|
||||
Product prices are available for each variant in the product. Each variant has a `prices` array with all the available prices in the context. However, when displaying the variant’s price, you’ll use the following properties inside a variant object:
|
||||
|
||||
- `original_price`: The original price of the product variant.
|
||||
- `calculated_price`: The calculated price, which can be based on prices defined in a price list.
|
||||
- `original_tax`: The tax amount applied to the original price, if any.
|
||||
- `calculated_tax`: The tax amount applied to the calculated price, if any.
|
||||
- `original_price_incl_tax`: The price after applying the tax amount on the original price.
|
||||
- `calculated_price_incl_tax`: The price after applying the tax amount on the calculated price
|
||||
|
||||
Typically, you would display the `calculated_price_incl_tax` as the price of the product variant.
|
||||
|
||||
:::note
|
||||
|
||||
You must pass one of the [pricing parameters](#product-pricing-parameters) to the request to retrieve these values. Otherwise, their value will be `null`.
|
||||
|
||||
:::
|
||||
|
||||
Prices in Medusa are stored as the price entered by the admin multiplied by a 100. So, to show the correct price, you would need to convert it to a decimal with a method like this:
|
||||
|
||||
```ts
|
||||
const convertToDecimal = (amount: number) => {
|
||||
return Math.floor(amount) / 100
|
||||
}
|
||||
```
|
||||
|
||||
<!-- vale docs.Spacing = NO -->
|
||||
|
||||
To display it along with a currency, it’s recommended to use JavaScript’s [Intl.NumberFormat](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Intl/NumberFormat). For example:
|
||||
|
||||
<!-- vale docs.Spacing = YES -->
|
||||
|
||||
```ts
|
||||
new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
currency: "eur",
|
||||
}).format(convertToDecimal(amount))
|
||||
```
|
||||
|
||||
Ideally, you would retrieve the value of the `currency` property from the selected region’s `currency_code` attribute.
|
||||
|
||||
Medusa React provides utility methods such as `formatVariantPrice` that handles this logic for you.
|
||||
|
||||
Here’s an example of how you can calculate the price with and without Medusa React:
|
||||
|
||||
<Tabs groupId="client-preference" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Without Medusa Client" default>
|
||||
|
||||
```tsx
|
||||
import React, { useEffect, useState } from "react"
|
||||
import Medusa from "@medusajs/medusa-js"
|
||||
|
||||
const medusa = new Medusa({
|
||||
baseUrl: "<YOUR_BACKEND_URL>",
|
||||
maxRetries: 3,
|
||||
})
|
||||
|
||||
function Products() {
|
||||
const [products, setProducts] = useState([])
|
||||
|
||||
useEffect(() => {
|
||||
medusa.products.list({
|
||||
// TODO assuming region is already defined somewhere
|
||||
region_id: region.id,
|
||||
})
|
||||
.then(({ products, limit, offset, count }) => {
|
||||
// ignore pagination for sake of example
|
||||
setProducts(products)
|
||||
})
|
||||
})
|
||||
|
||||
const convertToDecimal = (amount) => {
|
||||
return Math.floor(amount) / 100
|
||||
}
|
||||
|
||||
const formatPrice = (amount) => {
|
||||
return new Intl.NumberFormat("en-US", {
|
||||
style: "currency",
|
||||
// TODO assuming region is already defined somewhere
|
||||
currency: region.currency_code,
|
||||
}).format(convertToDecimal(amount))
|
||||
}
|
||||
|
||||
return (
|
||||
<ul>
|
||||
{products.map((product) => (
|
||||
<>
|
||||
{product.variants.map((variant) => (
|
||||
<li key={variant.id}>{
|
||||
formatPrice(variant.calculated_price_incl_tax)
|
||||
}</li>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="With Medusa React">
|
||||
|
||||
```tsx
|
||||
import { formatVariantPrice, useProducts } from "medusa-react"
|
||||
import { Product, ProductVariant } from "@medusajs/medusa"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
region_id: region.id, // assuming already defined somewhere
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && (
|
||||
<span>No Products</span>
|
||||
)}
|
||||
{products && products.length > 0 && (
|
||||
<ul>
|
||||
{products.map((product: Product) => (
|
||||
<>
|
||||
{product.variants.map(
|
||||
(variant: ProductVariant) => (
|
||||
<li key={variant.id}>
|
||||
{formatVariantPrice({
|
||||
variant,
|
||||
// assuming already defined somewhere
|
||||
region,
|
||||
})}
|
||||
</li>
|
||||
))}
|
||||
</>
|
||||
))}
|
||||
</ul>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
---
|
||||
|
||||
## Search Products
|
||||
|
||||
:::note
|
||||
|
||||
The Search functionality requires either installing a [search plugin](../../../plugins/search/index.mdx) or creating a search service.
|
||||
|
||||
:::
|
||||
|
||||
You can search products using the [Search Products endpoint](/api/store#tag/Products/operation/PostProductsSearch):
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.search({
|
||||
q: "Shirt",
|
||||
})
|
||||
.then(({ hits }) => {
|
||||
console.log(hits.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products/search?q=Shirt`, {
|
||||
credentials: "include",
|
||||
method: "POST",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ hits }) => {
|
||||
console.log(hits.length)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X POST '<BACKEND_URL>/store/products/search?q=Shirt'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the query parameter `q` being the term to search products for. The search plugin or service you’re using determine how `q` will be used to search the products. It also accepts pagination parameters as explained in the [API reference](/api/store#tag/Products/operation/PostProductsSearch).
|
||||
|
||||
The request returns a `hits` array holding the result items. The structure of the items depends on the plugin you’re using.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve a Product by ID
|
||||
|
||||
You can retrieve the details of a single product by its ID using the [Get a Product endpoint](/api/store#tag/Products/operation/GetProductsProduct):
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.retrieve(productId)
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProduct } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { product, isLoading } = useProduct(productId)
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{product && <span>{product.title}</span>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products/${productId}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ product }) => {
|
||||
console.log(product.id)
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products/<PRODUCT_ID>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
This endpoint requires the product’s ID to be passed as a path parameter. You can also pass query parameters such as `cart_id` and `region_id` which are relevant for pricing as explained in the [Product Pricing Parameters section](#product-pricing-parameters). You can check the full list of accepted parameters in the [API reference](/api/store#tag/Products/operation/GetProductsProduct).
|
||||
|
||||
The request returns a product object. You can display its price as explained in the [Display Product Price](#display-product-price) section.
|
||||
|
||||
---
|
||||
|
||||
## Retrieve Product by Handle
|
||||
|
||||
On the storefront, you may use the handle of a product as its page’s path. For example, instead of displaying the product’s details on the path `/products/prod_123`, you can display it on the path `/products/shirt`, where `shirt` is the handle of the product. This type of URL is human-readable and is good for Search Engine Optimization (SEO)
|
||||
|
||||
You can retrieve the details of a product by its handle by sending a request to the List Products endpoint, passing the `handle` as a filter:
|
||||
|
||||
<Tabs groupId="request-type" wrapperClassName="code-tabs">
|
||||
<TabItem value="client" label="Medusa JS Client" default>
|
||||
|
||||
```ts
|
||||
medusa.products.list({
|
||||
handle,
|
||||
})
|
||||
.then(({ products }) => {
|
||||
if (!products.length) {
|
||||
// product does not exist
|
||||
}
|
||||
const product = products[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="medusa-react" label="Medusa React">
|
||||
|
||||
```tsx
|
||||
import { useProducts } from "medusa-react"
|
||||
|
||||
const Products = () => {
|
||||
const { products, isLoading } = useProducts({
|
||||
handle,
|
||||
})
|
||||
|
||||
return (
|
||||
<div>
|
||||
{isLoading && <span>Loading...</span>}
|
||||
{products && !products.length && (
|
||||
<span>Product does not exist</span>
|
||||
)}
|
||||
{products && products.length > 0 && products[0].title}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Products
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="fetch" label="Fetch API">
|
||||
|
||||
```ts
|
||||
fetch(`<BACKEND_URL>/store/products?handle=${handle}`, {
|
||||
credentials: "include",
|
||||
})
|
||||
.then((response) => response.json())
|
||||
.then(({ products }) => {
|
||||
if (!products.length) {
|
||||
// product does not exist
|
||||
}
|
||||
const product = products[0]
|
||||
})
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
<TabItem value="curl" label="cURL">
|
||||
|
||||
```bash
|
||||
curl -L -X GET '<BACKEND_URL>/store/products?handle=<HANDLE>'
|
||||
```
|
||||
|
||||
</TabItem>
|
||||
</Tabs>
|
||||
|
||||
As the `handle` of each product is unique, when you pass the handle as a filter you’ll either:
|
||||
|
||||
- receive an empty `products` array, meaning the product doesn’t exist;
|
||||
- or you’ll receive a `products` array with one item being the product you’re looking for. In this case, you can access the product at index `0`.
|
||||
|
||||
As explained earlier, make sure to pass the [product pricing parameters](#product-pricing-parameters) to [display the product's price](#display-product-price)
|
||||
|
||||
---
|
||||
|
||||
## See Also
|
||||
|
||||
- [How to use regions in a storefront](../../regions-and-currencies/storefront/use-regions.mdx)
|
||||
- [How to implement cart functionality](../../carts-and-checkout/storefront/implement-cart.mdx)
|
||||
File renamed without changes.
@@ -633,16 +633,13 @@ module.exports = {
|
||||
label: "Admin: Import Products",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
href: "#",
|
||||
type: "doc",
|
||||
id: "modules/products/storefront/show-products",
|
||||
label: "Storefront: Show Products",
|
||||
customProps: {
|
||||
sidebar_is_soon: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: "doc",
|
||||
id: "modules/products/store/use-categories",
|
||||
id: "modules/products/storefront/use-categories",
|
||||
label: "Storefront: Use Categories",
|
||||
},
|
||||
{
|
||||
|
||||
Reference in new issue
Block a user