docs: fix storefront sections in digital product recipe (#10833)
This commit is contained in:
1 parent
f7ffa3540f
commit
8224466dd7
2 files changed
+133
-85
No files matched your search
@@ -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:
|
In `src/types/global.ts`, add the following types that you’ll use in your customizations:
|
||||||
|
|
||||||
```ts title="src/types/global.ts"
|
```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
|
fileId: string
|
||||||
type: "preview" | "main"
|
type: "preview" | "main"
|
||||||
mimeType: string
|
mimeType: string
|
||||||
|
digitalProduct?: DigitalProduct[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type VariantWithDigitalProduct = BaseProductVariant & {
|
export type DigitalProductPreview = DigitalProductMedia & {
|
||||||
|
url: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export type VariantWithDigitalProduct = StoreProductVariant & {
|
||||||
digital_product?: DigitalProduct
|
digital_product?: DigitalProduct
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2615,71 +2623,84 @@ export type VariantWithDigitalProduct = BaseProductVariant & {
|
|||||||
|
|
||||||
### Retrieve Digital Products with Variants
|
### 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 = [
|
export const fieldHighlights = [
|
||||||
["12"], ["27"]
|
["24"]
|
||||||
]
|
]
|
||||||
|
|
||||||
```ts title="src/lib/data/products.ts" highlights={fieldHighlights}
|
```ts title="src/lib/data/products.ts" highlights={fieldHighlights}
|
||||||
export const getProductsById = cache(async function ({
|
export const listProducts = async ({
|
||||||
ids,
|
pageParam = 1,
|
||||||
|
queryParams,
|
||||||
|
countryCode,
|
||||||
regionId,
|
regionId,
|
||||||
}: {
|
}: {
|
||||||
ids: string[]
|
pageParam?: number
|
||||||
regionId: string
|
queryParams?: HttpTypes.FindParams & HttpTypes.StoreProductParams
|
||||||
}) {
|
countryCode?: string
|
||||||
return sdk.store.product
|
regionId?: string
|
||||||
.list(
|
}): 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.
|
When a customer views a product’s details page, digital products linked to variants are also retrieved.
|
||||||
|
|
||||||
### Get Digital Product Preview Links
|
### 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"
|
```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,
|
||||||
}: {
|
}: {
|
||||||
id: string
|
id: string
|
||||||
}) {
|
}) {
|
||||||
const { previews } = await fetch(
|
const headers = {
|
||||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/digital-products/${id}/preview`, {
|
...(await getAuthHeaders()),
|
||||||
credentials: "include",
|
}
|
||||||
headers: {
|
|
||||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
const next = {
|
||||||
},
|
...(await getCacheOptions("products")),
|
||||||
}).then((res) => res.json())
|
}
|
||||||
|
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
|
// for simplicity, return only the first preview url
|
||||||
// instead you can show all the preview media to the customer
|
// instead you can show all the preview media to the customer
|
||||||
return previews.length ? previews[0].url : ""
|
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.
|
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"
|
"use server"
|
||||||
|
|
||||||
import { DigitalProduct } from "../../types/global"
|
import { DigitalProduct } from "../../types/global"
|
||||||
import { getAuthHeaders } from "./cookies"
|
import { sdk } from "../config"
|
||||||
|
import { getAuthHeaders, getCacheOptions } from "./cookies"
|
||||||
|
|
||||||
export const getCustomerDigitalProducts = async () => {
|
export const getCustomerDigitalProducts = async () => {
|
||||||
const { digital_products } = await fetch(
|
const headers = {
|
||||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/customers/me/digital-products`, {
|
...(await getAuthHeaders()),
|
||||||
credentials: "include",
|
}
|
||||||
headers: {
|
|
||||||
...getAuthHeaders(),
|
const next = {
|
||||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
...(await getCacheOptions("products")),
|
||||||
},
|
}
|
||||||
}).then((res) => res.json())
|
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[]
|
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 { Table } from "@medusajs/ui"
|
||||||
import { DigitalProduct } from "../../../../types/global"
|
import { DigitalProduct } from "../../../../types/global"
|
||||||
import { getDigitalMediaDownloadLink } from "../../../../lib/data/digital-products"
|
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
digitalProducts: DigitalProduct[]
|
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.
|
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.
|
||||||
|
|
||||||
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:
|
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 (
|
return (
|
||||||
<div>
|
<div>
|
||||||
{/* Add before log out */}
|
<div className="small:hidden">
|
||||||
<li>
|
{/* ... */}
|
||||||
<LocalizedClientLink
|
{/* Add before log out */}
|
||||||
href="/account/digital-products"
|
<li>
|
||||||
className="flex items-center justify-between py-4 border-b border-gray-200 px-8"
|
<LocalizedClientLink
|
||||||
data-testid="digital-products-link"
|
href="/account/digital-products"
|
||||||
>
|
className="flex items-center justify-between py-4 border-b border-gray-200 px-8"
|
||||||
<div className="flex items-center gap-x-2">
|
data-testid="digital-products-link"
|
||||||
<Photo />
|
>
|
||||||
<span>Digital Products</span>
|
<div className="flex items-center gap-x-2">
|
||||||
</div>
|
<Photo />
|
||||||
<ChevronDown className="transform -rotate-90" />
|
<span>Digital Products</span>
|
||||||
</LocalizedClientLink>
|
</div>
|
||||||
</li>
|
<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>
|
</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
|
### 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"
|
```ts title="src/lib/data/digital-products.ts"
|
||||||
export const getDigitalMediaDownloadLink = async (mediaId: string) => {
|
export const getDigitalMediaDownloadLink = async (mediaId: string) => {
|
||||||
const { url } = await fetch(
|
const headers = {
|
||||||
`${process.env.NEXT_PUBLIC_MEDUSA_BACKEND_URL}/store/customers/me/digital-products/${
|
...(await getAuthHeaders()),
|
||||||
mediaId
|
}
|
||||||
}/download`, {
|
|
||||||
credentials: "include",
|
const next = {
|
||||||
|
...(await getCacheOptions("products")),
|
||||||
|
}
|
||||||
|
const { url } = await sdk.client.fetch<{
|
||||||
|
url: string
|
||||||
|
}>(`/store/customers/me/digital-products/${mediaId}/download`, {
|
||||||
method: "POST",
|
method: "POST",
|
||||||
headers: {
|
headers,
|
||||||
...getAuthHeaders(),
|
next,
|
||||||
"x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY,
|
cache: "force-cache",
|
||||||
},
|
})
|
||||||
}).then((res) => res.json())
|
|
||||||
|
|
||||||
return url
|
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.
|
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"
|
```tsx title="src/modules/account/components/digital-products-list/index.tsx"
|
||||||
const handleDownload = async (
|
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.
|
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
|
## Next Steps
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ export const generatedEditDates = {
|
|||||||
"app/nextjs-starter/page.mdx": "2024-12-12T12:31:16.661Z",
|
"app/nextjs-starter/page.mdx": "2024-12-12T12:31:16.661Z",
|
||||||
"app/recipes/b2b/page.mdx": "2024-10-03T13:07:44.153Z",
|
"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/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/digital-products/page.mdx": "2024-10-03T13:07:44.147Z",
|
||||||
"app/recipes/ecommerce/page.mdx": "2024-10-22T11:01:01.218Z",
|
"app/recipes/ecommerce/page.mdx": "2024-10-22T11:01:01.218Z",
|
||||||
"app/recipes/integrate-ecommerce-stack/page.mdx": "2024-12-09T13:03:35.846Z",
|
"app/recipes/integrate-ecommerce-stack/page.mdx": "2024-12-09T13:03:35.846Z",
|
||||||
|
|||||||
Reference in new issue
Block a user