From a5e8a55d631126e72fa22683cbaaf95d78e095d8 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Mon, 9 Sep 2024 10:02:59 +0300 Subject: [PATCH] docs: added storefront development guide on retrieving variant inventory details (#9032) * docs: added storefront development guide on retrieving variant inventory details * fix vale error --- .../products/inventory/page.mdx | 178 ++++++++++++++++++ www/apps/resources/generated/edit-dates.mjs | 1 + www/apps/resources/generated/files-map.mjs | 4 + www/apps/resources/generated/sidebar.mjs | 8 + www/apps/resources/sidebar.mjs | 5 + 5 files changed, 196 insertions(+) create mode 100644 www/apps/resources/app/storefront-development/products/inventory/page.mdx diff --git a/www/apps/resources/app/storefront-development/products/inventory/page.mdx b/www/apps/resources/app/storefront-development/products/inventory/page.mdx new file mode 100644 index 0000000000..4322c720d3 --- /dev/null +++ b/www/apps/resources/app/storefront-development/products/inventory/page.mdx @@ -0,0 +1,178 @@ +import { CodeTabs, CodeTab } from "docs-ui" + +export const metadata = { + title: `Retrieve Product Variant's Inventory in Storefront`, +} + +# {metadata.title} + +To retrieve variants' inventory quantity using either the [List Products](!api!/store#products_getproducts) or [Retrieve Products](!api!/store#products_getproductsid) API routes: + +1. Pass the publishable API key in the header of the request. The retrieved inventory quantity is in the locations associated with the key's sales channels. +2. Pass in the `fields` query parameter the value `+variants.inventory_quantity`. + +For example: + +export const fetchHighlights = [ + ["2", "fields", "Pass `+variants.inventory_quantity` in the fields to retrieve."], + ["8", "process.env.NEXT_PUBLIC_PAK", "Pass the Publishable API key to retrieve the inventory quantity based on the associated sales channels' stock locations."], + ["14", "isInStock", "Consider the variant in stock either if its `manage_inventory` property is disabled, or the `inventory_quantity` is greater than `0`."] +] + +```ts highlights={fetchHighlights} +const queryParams = new URLSearchParams({ + fields: `*variants.calculated_price,+variants.inventory_quantity`, +}) + +fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { + credentials: "include", + headers: { + "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", + }, +}) +.then((res) => res.json()) +.then(({ product }) => { + product.variants?.forEach((variant) => { + const isInStock = variant.manage_inventory === false || + variant.inventory_quantity > 0 + + // ... + }) +}) +``` + + + +If you're also passing `*variants.calculated_price` in `fields` to get the product variants' prices, make sure to include it in the beginning of the list of fields. For example, `?fields=*variants.calculated_price,+variants.inventory_quantity`. + + + +### When is a Variant in Stock? + +A variant is in stock if: + +1. Its `manage_inventory`'s value is `false`, meaning that Medusa doesn't keep track of its inventory. +2. If its `inventory_quantity`'s value is greater than `0`. This property is only available on variants whose `manage_inventory` is `false`. + +--- + +## Full React Example + +export const reactHighlights = [ + ["12", "{ params: { id } }: Params", "This is based on Next.js which passes the path parameters as a prop."], + ["25", "fields", "Pass `+variants.inventory_quantity` in the fields to retrieve."], + ["31", "process.env.NEXT_PUBLIC_PAK", "Pass the Publishable API key to retrieve the inventory quantity based on the associated sales channels' stock locations."], + ["55", "isInStock", "Consider the selected variant in stock either if its `manage_inventory` property is disabled, or the `inventory_quantity` is greater than `0`."], + ["96", "isInStock", "Show whether the selected variant is in stock."] +] + +```tsx title="React Storefront" highlights={reactHighlights} +"use client" // include with Next.js 13+ + +import { useEffect, useMemo, useState } from "react" +import { HttpTypes } from "@medusajs/types" + +type Params = { + params: { + id: string + } +} + +export default function Product({ params: { id } }: Params) { + const [loading, setLoading] = useState(true) + const [product, setProduct] = useState< + HttpTypes.StoreProduct | undefined + >() + const [selectedOptions, setSelectedOptions] = useState>({}) + + useEffect(() => { + if (!loading) { + return + } + + const queryParams = new URLSearchParams({ + fields: `+variants.inventory_quantity`, + }) + + fetch(`http://localhost:9000/store/products/${id}?${queryParams.toString()}`, { + credentials: "include", + headers: { + "x-publishable-api-key": process.env.NEXT_PUBLIC_PAK || "temp", + }, + }) + .then((res) => res.json()) + .then(({ product: dataProduct }) => { + setProduct(dataProduct) + setLoading(false) + }) + }, [loading]) + + const selectedVariant = useMemo(() => { + if ( + !product?.variants || + !product.options || + Object.keys(selectedOptions).length !== product.options?.length + ) { + return + } + + return product.variants.find((variant) => variant.options?.every( + (optionValue) => optionValue.value === selectedOptions[optionValue.option_id!] + )) + }, [selectedOptions, product]) + + const isInStock = useMemo(() => { + if (!selectedVariant) { + return undefined + } + + return selectedVariant.manage_inventory === false || selectedVariant.inventory_quantity > 0 + }, [selectedVariant]) + + return ( +
+ {loading && Loading...} + {product && ( + <> +

{product.title}

+ {(product.options?.length || 0) > 0 && ( +
    + {product.options!.map((option) => ( +
  • + {option.title} + {option.values?.map((optionValue) => ( + + ))} +
  • + ))} +
+ )} + {selectedVariant && ( + Selected Variant: {selectedVariant.id} + )} + {isInStock !== undefined && ( + + {isInStock && "In Stock"} + {!isInStock && "Out of Stock"} + + )} + + )} +
+ ) +} +``` + +In this example, you show whether the selected variant is in or out of stock. \ No newline at end of file diff --git a/www/apps/resources/generated/edit-dates.mjs b/www/apps/resources/generated/edit-dates.mjs index 10c6075e32..a02960f65c 100644 --- a/www/apps/resources/generated/edit-dates.mjs +++ b/www/apps/resources/generated/edit-dates.mjs @@ -969,6 +969,7 @@ export const generatedEditDates = { "references/promotion/interfaces/promotion.IPromotionModuleService/page.mdx": "2024-09-06T00:11:38.306Z", "references/types/EventBusTypes/interfaces/types.EventBusTypes.IEventBusService/page.mdx": "2024-09-06T00:11:07.298Z", "references/types/TransactionBaseTypes/interfaces/types.TransactionBaseTypes.ITransactionBaseService/page.mdx": "2024-09-06T00:11:08.494Z", + "app/storefront-development/products/inventory/page.mdx": "2024-09-06T10:24:49.161Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateAuthIdentities/page.mdx": "2024-09-06T11:11:37.710Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateProvider/page.mdx": "2024-09-06T11:11:37.686Z", "references/auth/IAuthModuleService/methods/auth.IAuthModuleService.updateProviderIdentities/page.mdx": "2024-09-06T11:11:37.726Z", diff --git a/www/apps/resources/generated/files-map.mjs b/www/apps/resources/generated/files-map.mjs index 5ae90aa5e2..43bdcf6225 100644 --- a/www/apps/resources/generated/files-map.mjs +++ b/www/apps/resources/generated/files-map.mjs @@ -967,6 +967,10 @@ export const filesMap = [ "filePath": "/www/apps/resources/app/storefront-development/products/collections/retrieve/page.mdx", "pathname": "/storefront-development/products/collections/retrieve" }, + { + "filePath": "/www/apps/resources/app/storefront-development/products/inventory/page.mdx", + "pathname": "/storefront-development/products/inventory" + }, { "filePath": "/www/apps/resources/app/storefront-development/products/list/page.mdx", "pathname": "/storefront-development/products/list" diff --git a/www/apps/resources/generated/sidebar.mjs b/www/apps/resources/generated/sidebar.mjs index 98917362c8..d31208c710 100644 --- a/www/apps/resources/generated/sidebar.mjs +++ b/www/apps/resources/generated/sidebar.mjs @@ -8196,6 +8196,14 @@ export const generatedSidebar = [ } ] }, + { + "loaded": true, + "isPathHref": true, + "type": "link", + "path": "/storefront-development/products/inventory", + "title": "Retrieve Variant Inventory", + "children": [] + }, { "loaded": true, "isPathHref": true, diff --git a/www/apps/resources/sidebar.mjs b/www/apps/resources/sidebar.mjs index 2636746127..dcdda3ceef 100644 --- a/www/apps/resources/sidebar.mjs +++ b/www/apps/resources/sidebar.mjs @@ -1930,6 +1930,11 @@ export const sidebar = sidebarAttachHrefCommonOptions([ title: "Retrieve Variant Prices", autogenerate_path: "storefront-development/products/price/examples", }, + { + type: "link", + path: "/storefront-development/products/inventory", + title: "Retrieve Variant Inventory", + }, { type: "link", path: "/storefront-development/products/categories",