docs: fix storefront sections in digital product recipe (#10833)
This commit is contained in:
@@ -2590,7 +2590,10 @@ If you haven't installed the Next.js Starter storefront in the first step, refer
|
||||
In `src/types/global.ts`, add the following types that you’ll use in your customizations:
|
||||
|
||||
```ts title="src/types/global.ts"
|
||||
import { BaseProductVariant } from "@medusajs/framework/types/http/product/common"
|
||||
import {
|
||||
// other imports...
|
||||
StoreProductVariant
|
||||
} from "@medusajs/types"
|
||||
|
||||
// ...
|
||||
|
||||
@@ -2605,9 +2608,14 @@ export type DigitalProductMedia = {
|
||||
fileId: string
|
||||
type: "preview" | "main"
|
||||
mimeType: string
|
||||
digitalProduct?: DigitalProduct[]
|
||||
}
|
||||
|
||||
export type VariantWithDigitalProduct = BaseProductVariant & {
|
||||
export type DigitalProductPreview = DigitalProductMedia & {
|
||||
url: string
|
||||
}
|
||||
|
||||
export type VariantWithDigitalProduct = StoreProductVariant & {
|
||||
digital_product?: DigitalProduct
|
||||
}
|
||||
|
||||
@@ -2615,71 +2623,84 @@ export type VariantWithDigitalProduct = BaseProductVariant & {
|
||||
|
||||
### Retrieve Digital Products with Variants
|
||||
|
||||
To retrieve the digital products details when retrieving a product and its variants, in the `src/lib/data/products.ts` file, change the `getProductsById` and `getProductByHandle` functions to pass the digital products in the `fields` property passed to the `sdk.store.product.list` method:
|
||||
To retrieve the digital products details when retrieving a product and its variants, in the `src/lib/data/products.ts` file, change the `listProducts` function to pass the digital products in the `fields` property passed to the `sdk.store.product.list` method:
|
||||
|
||||
export const fieldHighlights = [
|
||||
["12"], ["27"]
|
||||
["24"]
|
||||
]
|
||||
|
||||
```ts title="src/lib/data/products.ts" highlights={fieldHighlights}
|
||||
export const getProductsById = cache(async function ({
|
||||
ids,
|
||||
export const listProducts = async ({
|
||||
pageParam = 1,
|
||||
queryParams,
|
||||
countryCode,
|
||||
regionId,
|
||||
}: {
|
||||
ids: string[]
|
||||
regionId: string
|
||||
}) {
|
||||
return sdk.store.product
|
||||
.list(
|
||||
pageParam?: number
|
||||
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
|
||||
countryCode?: string
|
||||
regionId?: string
|
||||
}): Promise<{
|
||||
response: { products: HttpTypes.StoreProduct[]; count: number }
|
||||
nextPage: number | null
|
||||
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
|
||||
}> => {
|
||||
// ...
|
||||
return sdk.client
|
||||
.fetch<{ products: HttpTypes.StoreProduct[]; count: number }>(
|
||||
`/store/products`,
|
||||
{
|
||||
// ...
|
||||
fields: "*variants.calculated_price,*variants.digital_product",
|
||||
query: {
|
||||
// ...
|
||||
fields: "*variants.calculated_price,+variants.inventory_quantity,+metadata,+tags,*variants.calculated_price,*variants.digital_product",
|
||||
}
|
||||
}
|
||||
// ...
|
||||
)
|
||||
// ...
|
||||
})
|
||||
|
||||
export const getProductByHandle = cache(async function (
|
||||
handle: string,
|
||||
regionId: string
|
||||
) {
|
||||
return sdk.store.product
|
||||
.list(
|
||||
{
|
||||
// ...
|
||||
fields: "*variants.calculated_price,*variants.digital_product",
|
||||
}
|
||||
// ...
|
||||
)
|
||||
// ...
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
When a customer views a product’s details page, digital products linked to variants are also retrieved.
|
||||
|
||||
### Get Digital Product Preview Links
|
||||
|
||||
To retrieve the links of a digital product’s preview media, add in `src/lib/data/products.ts` the following function:
|
||||
To retrieve the links of a digital product’s preview media, first, add the following import at the top of `src/lib/data/products.ts`:
|
||||
|
||||
```ts title="src/lib/data/products.ts"
|
||||
export const getDigitalProductPreview = cache(async function ({
|
||||
import { DigitalProductPreview } from "../../types/global"
|
||||
```
|
||||
|
||||
Then, add the following function at the end of the file:
|
||||
|
||||
```ts title="src/lib/data/products.ts"
|
||||
export const getDigitalProductPreview = async function ({
|
||||
id,
|
||||
}: {
|
||||
id: string
|
||||
}) {
|
||||
const { previews } = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/digital-products/${id}/preview`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
const headers = {
|
||||
...(await getAuthHeaders()),
|
||||
}
|
||||
|
||||
const next = {
|
||||
...(await getCacheOptions("products")),
|
||||
}
|
||||
const { previews } = await sdk.client.fetch<{
|
||||
previews: DigitalProductPreview[]
|
||||
}>(
|
||||
`/store/digital-products/${id}/preview`,
|
||||
{
|
||||
headers,
|
||||
next,
|
||||
cache: "force-cache"
|
||||
}
|
||||
)
|
||||
|
||||
// for simplicity, return only the first preview url
|
||||
// instead you can show all the preview media to the customer
|
||||
return previews.length ? previews[0].url : ""
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
This function uses the API route you created in the previous section to get the preview links and return the first preview link.
|
||||
@@ -2776,17 +2797,25 @@ Start by creating the file `src/lib/data/digital-products.ts` with the following
|
||||
"use server"
|
||||
|
||||
import { DigitalProduct } from "../../types/global"
|
||||
import { getAuthHeaders } from "./cookies"
|
||||
import { sdk } from "../config"
|
||||
import { getAuthHeaders, getCacheOptions } from "./cookies"
|
||||
|
||||
export const getCustomerDigitalProducts = async () => {
|
||||
const { digital_products } = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/customers/me/digital-products`, {
|
||||
credentials: "include",
|
||||
headers: {
|
||||
...getAuthHeaders(),
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
const headers = {
|
||||
...(await getAuthHeaders()),
|
||||
}
|
||||
|
||||
const next = {
|
||||
...(await getCacheOptions("products")),
|
||||
}
|
||||
const { digital_products } = await sdk.client.fetch<{
|
||||
digital_products: DigitalProduct[]
|
||||
}>(`/store/customers/me/digital-products`, {
|
||||
|
||||
headers,
|
||||
next,
|
||||
cache: "force-cache",
|
||||
})
|
||||
|
||||
return digital_products as DigitalProduct[]
|
||||
}
|
||||
@@ -2801,7 +2830,6 @@ Then, create the file `src/modules/account/components/digital-products-list/inde
|
||||
|
||||
import { Table } from "@medusajs/ui"
|
||||
import { DigitalProduct } from "../../../../types/global"
|
||||
import { getDigitalMediaDownloadLink } from "../../../../lib/data/digital-products"
|
||||
|
||||
type Props = {
|
||||
digitalProducts: DigitalProduct[]
|
||||
@@ -2847,9 +2875,7 @@ export const DigitalProductsList = ({
|
||||
}
|
||||
```
|
||||
|
||||
This adds a `DigitalProductsList` component that receives a list of digital products and shows them in a table.
|
||||
|
||||
Each digital product’s media has a download link. You’ll implement its functionality afterwards.
|
||||
This adds a `DigitalProductsList` component that receives a list of digital products and shows them in a table. Each digital product’s media has a download link. You’ll implement its functionality afterwards.
|
||||
|
||||
Next, create the file `src/app/[countryCode]/(main)/account/@dashboard/digital-products/page.tsx` with the following content:
|
||||
|
||||
@@ -2902,26 +2928,44 @@ const AccountNav = ({
|
||||
|
||||
return (
|
||||
<div>
|
||||
{/* Add before log out */}
|
||||
<li>
|
||||
<LocalizedClientLink
|
||||
href="/account/digital-products"
|
||||
className="flex items-center justify-between py-4 border-b border-gray-200 px-8"
|
||||
data-testid="digital-products-link"
|
||||
>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Photo />
|
||||
<span>Digital Products</span>
|
||||
</div>
|
||||
<ChevronDown className="transform -rotate-90" />
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
<div className="small:hidden">
|
||||
{/* ... */}
|
||||
{/* Add before log out */}
|
||||
<li>
|
||||
<LocalizedClientLink
|
||||
href="/account/digital-products"
|
||||
className="flex items-center justify-between py-4 border-b border-gray-200 px-8"
|
||||
data-testid="digital-products-link"
|
||||
>
|
||||
<div className="flex items-center gap-x-2">
|
||||
<Photo />
|
||||
<span>Digital Products</span>
|
||||
</div>
|
||||
<ChevronDown className="transform -rotate-90" />
|
||||
</LocalizedClientLink>
|
||||
</li>
|
||||
{/* ... */}
|
||||
</div>
|
||||
<div className="hidden small:block">
|
||||
{/* ... */}
|
||||
{/* Add before log out */}
|
||||
<li>
|
||||
<AccountNavLink
|
||||
href="/account/digital-products"
|
||||
route={route!}
|
||||
data-testid="digital-products-link"
|
||||
>
|
||||
Digital Products
|
||||
</AccountNavLink>
|
||||
</li>
|
||||
{/* ... */}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You add a link to the new route before the log out tab.
|
||||
You add a link to the new route before the log out tab both for small and large devices.
|
||||
|
||||
### Test Purchased Digital Products Page
|
||||
|
||||
@@ -2935,17 +2979,21 @@ To add a download link for the purchased digital products’ medias, first, add
|
||||
|
||||
```ts title="src/lib/data/digital-products.ts"
|
||||
export const getDigitalMediaDownloadLink = async (mediaId: string) => {
|
||||
const { url } = await fetch(
|
||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/customers/me/digital-products/${
|
||||
mediaId
|
||||
}/download`, {
|
||||
credentials: "include",
|
||||
const headers = {
|
||||
...(await getAuthHeaders()),
|
||||
}
|
||||
|
||||
const next = {
|
||||
...(await getCacheOptions("products")),
|
||||
}
|
||||
const { url } = await sdk.client.fetch<{
|
||||
url: string
|
||||
}>(`/store/customers/me/digital-products/${mediaId}/download`, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
...getAuthHeaders(),
|
||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
||||
},
|
||||
}).then((res) => res.json())
|
||||
headers,
|
||||
next,
|
||||
cache: "force-cache",
|
||||
})
|
||||
|
||||
return url
|
||||
}
|
||||
@@ -2953,7 +3001,13 @@ export const getDigitalMediaDownloadLink = async (mediaId: string) => {
|
||||
|
||||
In this function, you send a request to the download API route you created earlier to retrieve the download URL of a purchased digital product media.
|
||||
|
||||
Then, in `src/modules/account/components/digital-products-list/index.tsx`, add a `handleDownload` function in the `DigitalProductsList` component:
|
||||
Then, in `src/modules/account/components/digital-products-list/index.tsx`, import the `getDigitalMediaDownloadLink` at the top of the file:
|
||||
|
||||
```tsx title="src/modules/account/components/digital-products-list/index.tsx"
|
||||
import { getDigitalMediaDownloadLink } from "../../../../lib/data/digital-products"
|
||||
```
|
||||
|
||||
And add a `handleDownload` function in the `DigitalProductsList` component:
|
||||
|
||||
```tsx title="src/modules/account/components/digital-products-list/index.tsx"
|
||||
const handleDownload = async (
|
||||
@@ -2982,12 +3036,6 @@ Finally, add an `onClick` handler to the digital product medias’ link in the r
|
||||
|
||||
To test the latest changes out, open the purchased digital products page and click on the Download link of any media in the table. The media’s download link will open in a new page.
|
||||
|
||||
<Note>
|
||||
|
||||
The local file module provider doesn't support private uploads, so the download link won't actually be useful. Instead, use the [S3 module provider](../../../../architectural-modules/file/s3/page.mdx) in production.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
@@ -112,7 +112,7 @@ export const generatedEditDates = {
|
||||
"app/nextjs-starter/page.mdx": "2024-12-12T12:31:16.661Z",
|
||||
"app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z",
|
||||
"app/recipes/commerce-automation/page.mdx": "2024-10-16T08:52:01.585Z",
|
||||
"app/recipes/digital-products/examples/standard/page.mdx": "2025-01-03T14:38:04.333Z",
|
||||
"app/recipes/digital-products/examples/standard/page.mdx": "2025-01-06T09:03:35.202Z",
|
||||
"app/recipes/digital-products/page.mdx": "2024-10-03T13:07:44.147Z",
|
||||
"app/recipes/ecommerce/page.mdx": "2024-10-22T11:01:01.218Z",
|
||||
"app/recipes/integrate-ecommerce-stack/page.mdx": "2024-12-09T13:03:35.846Z",
|
||||
|
||||
Reference in New Issue
Block a user