docs: added mark fulfillment delivered step in digital products recipe (#12290)

* docs: add fulfillment delivered step to digital products recipe

* generate llms

* small change
This commit is contained in:
Shahed Nasser authored and GitHub committed 2025-04-24 19:08:05 +03:00
1 parent 35d7c143ea
commit 13071172b3
15 files changed
+15379 -15305

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -145,12 +145,12 @@ const { result } = await createPromotionsWorkflow(container)
attribute: "customer.group.id", attribute: "customer.group.id",
operator: "eq", operator: "eq",
values: [ values: [
"cusgrp_123" "cusgrp_123",
] ],
} },
] ],
}], }],
} },
}) })
``` ```
@@ -184,10 +184,10 @@ const promotions = await promotionModuleService.createPromotions([
attribute: "customer.group.id", attribute: "customer.group.id",
operator: "eq", operator: "eq",
values: [ values: [
"cusgrp_123" "cusgrp_123",
] ],
} },
] ],
}, },
]) ])
``` ```
@@ -60,7 +60,7 @@ class BlogModuleService extends MedusaService({
title: "My Post", title: "My Post",
content: "This is a post", content: "This is a post",
author_id: "01JSGRGQ8S61SR0Y7VCRV8FH66", author_id: "01JSGRGQ8S61SR0Y7VCRV8FH66",
} },
]) ])
return posts return posts
@@ -55,7 +55,7 @@ class BlogModuleService extends MedusaService({
@MedusaContext() sharedContext?: Context<EntityManager> @MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> { ): Promise<any> {
const deletedIds = await this.postRepository_.delete({ const deletedIds = await this.postRepository_.delete({
id id,
}) })
return deletedIds return deletedIds
@@ -105,7 +105,7 @@ class BlogModuleService extends MedusaService({
@MedusaContext() sharedContext?: Context<EntityManager> @MedusaContext() sharedContext?: Context<EntityManager>
): Promise<any> { ): Promise<any> {
const deletedIds = await this.postRepository_.delete({ const deletedIds = await this.postRepository_.delete({
title: "My Post" title: "My Post",
}) })
return deletedIds return deletedIds
@@ -227,11 +227,11 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
id id,
}, },
options: { options: {
populate: ["author"] populate: ["author"],
} },
}) })
return posts return posts
@@ -299,11 +299,11 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
id id,
}, },
options: { options: {
fields: ["title"] fields: ["title"],
} },
}) })
return posts return posts
@@ -363,12 +363,12 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
id id,
}, },
options: { options: {
limit: 10, limit: 10,
offset: 10, offset: 10,
} },
}) })
return posts return posts
@@ -424,13 +424,13 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
id id,
}, },
options: { options: {
orderBy: { orderBy: {
title: "ASC" title: "ASC",
} },
} },
}) })
return posts return posts
@@ -122,7 +122,7 @@ class BlogModuleService extends MedusaService({
return { return {
posts, posts,
count count,
} }
} }
@@ -176,7 +176,7 @@ class BlogModuleService extends MedusaService({
return { return {
posts, posts,
count count,
} }
} }
@@ -240,16 +240,16 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [posts, count] = await this.postRepository_.findAndCount({ const [posts, count] = await this.postRepository_.findAndCount({
where: { where: {
id id,
}, },
options: { options: {
populate: ["author"] populate: ["author"],
} },
}) })
return { return {
posts, posts,
count count,
} }
} }
@@ -320,16 +320,16 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [posts, count] = await this.postRepository_.findAndCount({ const [posts, count] = await this.postRepository_.findAndCount({
where: { where: {
id id,
}, },
options: { options: {
fields: ["title"] fields: ["title"],
} },
}) })
return { return {
posts, posts,
count count,
} }
} }
@@ -392,12 +392,12 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [posts, count] = await this.postRepository_.findAndCount({ const [posts, count] = await this.postRepository_.findAndCount({
where: { where: {
id id,
}, },
options: { options: {
limit: 10, limit: 10,
offset: 10, offset: 10,
} },
}) })
return posts return posts
@@ -453,18 +453,18 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [posts, count] = await this.postRepository_.findAndCount({ const [posts, count] = await this.postRepository_.findAndCount({
where: { where: {
id id,
}, },
options: { options: {
orderBy: { orderBy: {
title: "ASC" title: "ASC",
} },
} },
}) })
return { return {
posts, posts,
count count,
} }
} }
@@ -58,7 +58,7 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
restoredPosts, restoredPosts,
restoredEntities restoredEntities,
] = await this.postRepository_.restore(id) ] = await this.postRepository_.restore(id)
return restoredPosts return restoredPosts
@@ -154,7 +154,7 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
restoredPosts, restoredPosts,
restoredEntities restoredEntities,
] = await this.postRepository_.restore(ids) ] = await this.postRepository_.restore(ids)
return restoredPosts return restoredPosts
@@ -250,9 +250,9 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
restoredPosts, restoredPosts,
restoredEntities restoredEntities,
] = await this.postRepository_.restore({ ] = await this.postRepository_.restore({
title: "My Post" title: "My Post",
}) })
return restoredPosts return restoredPosts
@@ -60,7 +60,7 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
deletedPosts, deletedPosts,
deletedEntities deletedEntities,
] = await this.postRepository_.softDelete(id) ] = await this.postRepository_.softDelete(id)
return deletedPosts return deletedPosts
@@ -156,7 +156,7 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
deletedPosts, deletedPosts,
deletedEntities deletedEntities,
] = await this.postRepository_.softDelete(ids) ] = await this.postRepository_.softDelete(ids)
return deletedPosts return deletedPosts
@@ -252,9 +252,9 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const [ const [
deletedPosts, deletedPosts,
deletedEntities deletedEntities,
] = await this.postRepository_.softDelete({ ] = await this.postRepository_.softDelete({
title: "My Post" title: "My Post",
}) })
return deletedPosts return deletedPosts
@@ -71,17 +71,17 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const existingPost = await this.postRepository_.find({ const existingPost = await this.postRepository_.find({
where: { where: {
id id,
} },
}) })
const posts = await this.postRepository_.update([ const posts = await this.postRepository_.update([
{ {
entity: existingPost[0], entity: existingPost[0],
update: { update: {
title: "My Post Updated" title: "My Post Updated",
} },
} },
]) ])
return posts return posts
@@ -61,7 +61,7 @@ class BlogModuleService extends MedusaService({
}, },
{ {
title: "My New Post", title: "My New Post",
} },
]) ])
return posts return posts
@@ -62,22 +62,22 @@ class BlogModuleService extends MedusaService({
): Promise<any> { ): Promise<any> {
const { const {
entities, entities,
performedActions performedActions,
} = await this.postRepository_.upsertWithReplace([ } = await this.postRepository_.upsertWithReplace([
{ {
id: "01JSHAW6Z7KW4X6E8MFPGNEKHC", id: "01JSHAW6Z7KW4X6E8MFPGNEKHC",
title: "My Old Post", title: "My Old Post",
author_id: null author_id: null,
}, },
{ {
id: "123", id: "123",
title: "My New Post", title: "My New Post",
} },
]) ])
return { return {
entities, entities,
performedActions performedActions,
} }
} }
@@ -66,7 +66,7 @@ class BlogModuleService extends MedusaService({
protected postRepository_: DAL.RepositoryService<Post> protected postRepository_: DAL.RepositoryService<Post>
constructor({ constructor({
postRepository postRepository,
}: InjectedDependencies) { }: InjectedDependencies) {
super(...arguments) super(...arguments)
this.postRepository_ = postRepository this.postRepository_ = postRepository
@@ -80,7 +80,7 @@ While the examples in this reference are based on the `find` method, the same fi
```ts ```ts
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
title: "My Post" title: "My Post",
}, },
}) })
``` ```
@@ -98,7 +98,7 @@ const posts = await this.postRepository_.find({
where: { where: {
title: { title: {
$ne: "My Post", $ne: "My Post",
} },
}, },
}) })
``` ```
@@ -134,8 +134,8 @@ In the example above, only posts having either `50` or `100` views are retrieved
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
title: { title: {
$nin: ["My Post", "My Post 2"] $nin: ["My Post", "My Post 2"],
} },
}, },
}) })
``` ```
@@ -158,8 +158,8 @@ This filter only applies to text-like properties, including `text`, `id`, and `e
const posts = await this.postRepository_.find({ const posts = await this.postRepository_.find({
where: { where: {
title: { title: {
$like: "My%" $like: "My%",
} },
}, },
}) })
``` ```
@@ -65,7 +65,7 @@ Start by installing the Medusa application on your machine with the following co
npx create-medusa-app@latest npx create-medusa-app@latest
``` ```
You'll first be asked for the project's name. You can also optionally choose to install the [Next.js starter storefront](../../../../nextjs-starter/page.mdx). You'll first be asked for the project's name. Then, when asked whether you want to install the [Next.js Starter Storefront](../../../../nextjs-starter/page.mdx), choose Yes.
Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name. Afterwards, the installation process will start, which will install the Medusa application in a directory with your project's name. If you chose to install the Next.js starter, it'll be installed in a separate directory with the `{project-name}-storefront` name.
@@ -1400,6 +1400,7 @@ fetch(`/admin/digital-products`, {
options: { options: {
Default: "default", Default: "default",
}, },
manage_inventory: false,
// delegate setting the prices to the // delegate setting the prices to the
// product's page. // product's page.
prices: [], prices: [],
@@ -1498,8 +1499,7 @@ To use this digital product in later steps (such as to create an order), you mus
1. Change the status to published. 1. Change the status to published.
2. Add it to the default sales channel. 2. Add it to the default sales channel.
3. Disable manage inventory of the variant. 3. Add prices to the variant.
4. Add prices to the variant.
</Note> </Note>
@@ -1572,7 +1572,7 @@ Then, you use Query to retrieve the digital products associated with the product
Finally, you return the IDs of the digital products to delete. Finally, you return the IDs of the digital products to delete.
## deleteDigitalProductsSteps ### deleteDigitalProductsSteps
Next, you'll implement the step that deletes those digital products. Next, you'll implement the step that deletes those digital products.
@@ -1837,11 +1837,11 @@ This is necessary to use the fulfillment provider's shipping option during check
--- ---
## Step 13: Customize Cart Completion ## Step 13: Create Cart Completion Flow for Digital Products
In this step, you’ll customize the cart completion flow to not only create a Medusa order, but also create a digital product order. In this step, you’ll create a new cart completion flow that not only creates a Medusa order, but also create a digital product order.
To customize the cart completion flow, you’ll create a workflow and then use that workflow in an API route defined at `src/api/store/carts/[id]/complete/route.ts`. To create the cart completion flow, you’ll create a workflow and then use that workflow in an API route defined at `src/api/store/carts/[id]/complete-digital/route.ts`.
```mermaid ```mermaid
graph TD graph TD
@@ -1945,12 +1945,12 @@ Create the file `src/workflows/create-digital-product-order/index.ts` with the f
export const createDpoWorkflowHighlights = [ export const createDpoWorkflowHighlights = [
["27", "completeCartWorkflow", "Create an order for the cart."], ["27", "completeCartWorkflow", "Create an order for the cart."],
["33", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."], ["33", "useQueryGraphStep", "Retrieve the order's items and their associated variants and linked digital products."],
["57", "when", "Check whether the order has any digital products."], ["58", "when", "Check whether the order has any digital products."],
["63", "then", "Perform the callback function if an order has digital products."], ["64", "then", "Perform the callback function if an order has digital products."],
["66", "createDigitalProductOrderStep", "Create the digital product order."], ["67", "createDigitalProductOrderStep", "Create the digital product order."],
["70", "createRemoteLinkStep", "Link the digital product order to the Medusa order."], ["71", "createRemoteLinkStep", "Link the digital product order to the Medusa order."],
["79", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."], ["80", "createOrderFulfillmentWorkflow", "Create a fulfillment for the digital products in the order."],
["93", "emitEventStep", "Emit the `digital_product_order.created` event."] ["94", "emitEventStep", "Emit the `digital_product_order.created` event."]
] ]
```ts title="src/workflows/create-digital-product-order/index.ts" highlights={createDpoWorkflowHighlights} collapsibleLines="1-17" expandMoreLabel="Show Imports" ```ts title="src/workflows/create-digital-product-order/index.ts" highlights={createDpoWorkflowHighlights} collapsibleLines="1-17" expandMoreLabel="Show Imports"
@@ -1993,6 +1993,7 @@ const createDigitalProductOrderWorkflow = createWorkflow(
"items.*", "items.*",
"items.variant.*", "items.variant.*",
"items.variant.digital_product.*", "items.variant.digital_product.*",
"shipping_address.*",
], ],
filters: { filters: {
id, id,
@@ -2080,9 +2081,9 @@ The workflow returns the Medusa order and the digital product order, if created.
### Cart Completion API Route ### Cart Completion API Route
Next, create the file `src/api/store/carts/[id]/complete/route.ts` with the following content: Next, create the file `src/api/store/carts/[id]/complete-digital/route.ts` with the following content:
```ts title="src/api/store/carts/[id]/complete/route.ts" ```ts title="src/api/store/carts/[id]/complete-digital/route.ts"
import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import createDigitalProductOrderWorkflow from "../../../../../workflows/create-digital-product-order" import createDigitalProductOrderWorkflow from "../../../../../workflows/create-digital-product-order"
@@ -2104,11 +2105,62 @@ export const POST = async (
} }
``` ```
This overrides the Cart Completion API route. In the route handler, you execute the `createDigitalProductOrderWorkflow` and return the created order in the response. Since you export a `POST` function, you expose a `POST` API route at `/store/carts/[id]/complete-digital`.
### Test Cart Completion In the route handler, you execute the `createDigitalProductOrderWorkflow` and return the created order in the response.
To test out the cart completion, it’s recommended to use the [Next.js Starter storefront](../../../../nextjs-starter/page.mdx) to place an order. ### Test Cart Completion: Customize Next.js Starter Storefront
To test out the cart completion, you'll customize the [Next.js Starter Storefront](../../../../nextjs-starter/page.mdx) that you installed in the first step to use the new cart completion route to place an order.
<Note title="Reminder" forceMultiline>
The Next.js Starter Storefront was installed in a separate directory from Medusa. The directory's name is `{your-project}-storefront`.
So, if your Medusa application's directory is `medusa-digital-product`, you can find the storefront by going back to the parent directory and changing to the `medusa-digital-product-storefront` directory:
```bash
cd ../medusa-digital-product-storefront # change based on your project name
```
</Note>
In the Next.js Starter Storefront, open the file `src/lib/data/cart.ts` and find the following lines in the `placeOrder` function:
```ts title="src/lib/data/cart.ts" badgeLabel="Storefront" badgeColor="blue"
const cartRes = await sdk.store.cart
.complete(id, {}, headers)
```
Replace these lines with the following:
```ts title="src/lib/data/cart.ts" badgeLabel="Storefront" badgeColor="blue"
const cartRes = await sdk.client.fetch<HttpTypes.StoreCompleteCartResponse>(
`/store/carts/${id}/complete-digital`,
{
method: "POST",
headers,
}
)
```
This will send a `POST` request to the new cart completion route you created to complete the cart.
Then, run the following command in the Medusa application directory to start the Medusa application:
```bash npm2yarn badgeLabel="Medusa application" badgeColor="green"
npm run start
```
And run the following command in the Next.js Starter Storefront directory to start the Next.js application:
```bash npm2yarn badgeLabel="Storefront" badgeColor="blue"
npm run dev
```
Open the storefront in your browser at `http://localhost:8000` and add a digital product to the cart.
Then, go through the checkout process. Make sure to choose the shipping option you created in the previous step for shipping.
Once you place the order, the cart completion route you added above will run, creating the order and digital product order, if the order has digital products. Once you place the order, the cart completion route you added above will run, creating the order and digital product order, if the order has digital products.
@@ -2128,6 +2180,7 @@ The workflow has the following steps:
1. Retrieve the digital product order's details. For this, you'll use `useQueryGraphStep` from Medusa's core workflows. 1. Retrieve the digital product order's details. For this, you'll use `useQueryGraphStep` from Medusa's core workflows.
2. Send a notification to the customer with the digital products to download. 2. Send a notification to the customer with the digital products to download.
3. Mark the Medusa order's fulfillment as delivered. For this, you'll use `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows.
So, you only need to implement the second step. So, you only need to implement the second step.
@@ -2243,8 +2296,9 @@ You use the `createNotifications` method of the Notification Module's main servi
Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`: Create the workflow in the file `src/workflows/fulfill-digital-order/index.ts`:
export const fulfillWorkflowHighlights = [ export const fulfillWorkflowHighlights = [
["17", "useQueryGraphStep", "Retrieve the digital product order's details."], ["18", "useQueryGraphStep", "Retrieve the digital product order's details."],
["33", "sendDigitalOrderNotificationStep", "Send a notification to the customer."] ["35", "sendDigitalOrderNotificationStep", "Send a notification to the customer."],
["39", "markOrderFulfillmentAsDeliveredWorkflow", "Mark the order's fulfillment as delivered."],
] ]
```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports" ```ts title="src/workflows/fulfill-digital-order/index.ts" highlights={fulfillWorkflowHighlights} collapsibleLines="1-10" expandMoreLabel="Show Imports"
@@ -2253,6 +2307,7 @@ import {
WorkflowResponse, WorkflowResponse,
} from "@medusajs/framework/workflows-sdk" } from "@medusajs/framework/workflows-sdk"
import { import {
markOrderFulfillmentAsDeliveredWorkflow,
useQueryGraphStep, useQueryGraphStep,
} from "@medusajs/medusa/core-flows" } from "@medusajs/medusa/core-flows"
import { sendDigitalOrderNotificationStep } from "./steps/send-digital-order-notification" import { sendDigitalOrderNotificationStep } from "./steps/send-digital-order-notification"
@@ -2271,6 +2326,7 @@ export const fulfillDigitalOrderWorkflow = createWorkflow(
"products.*", "products.*",
"products.medias.*", "products.medias.*",
"order.*", "order.*",
"order.fulfillments.*",
], ],
filters: { filters: {
id, id,
@@ -2284,6 +2340,13 @@ export const fulfillDigitalOrderWorkflow = createWorkflow(
digital_product_order: digitalProductOrders[0], digital_product_order: digitalProductOrders[0],
}) })
markOrderFulfillmentAsDeliveredWorkflow.runAsStep({
input: {
orderId: digitalProductOrders[0].order.id,
fulfillmentId: digitalProductOrders[0].order.fulfillments[0].id,
},
})
return new WorkflowResponse( return new WorkflowResponse(
digitalProductOrders[0] digitalProductOrders[0]
) )
@@ -2295,6 +2358,7 @@ In the workflow, you:
1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows. 1. Retrieve the digital product order's details using `useQueryGraphStep` from Medusa's core workflows.
2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`. 2. Send a notification to the customer with the digital product download links using the `sendDigitalOrderNotificationStep`.
3. Mark the order's fulfillment as delivered using `markOrderFulfillmentAsDeliveredWorkflow` from Medusa's core workflows.
### Configure Notification Module Provider ### Configure Notification Module Provider
@@ -2609,13 +2673,23 @@ In this section, you’ll customize the [Next.js Starter storefront](../../../..
2. Add a new tab in the customer’s dashboard to view their purchased digital products. 2. Add a new tab in the customer’s dashboard to view their purchased digital products.
3. Allow customers to download the digital products through the new page in the dashboard. 3. Allow customers to download the digital products through the new page in the dashboard.
If you haven't installed the Next.js Starter storefront in the first step, refer to [this guide](../../../../nextjs-starter/page.mdx#approach-2-install-separately) to learn how to install it. <Note title="Reminder" forceMultiline>
The Next.js Starter Storefront was installed in a separate directory from Medusa. The directory's name is `{your-project}-storefront`.
So, if your Medusa application's directory is `medusa-digital-product`, you can find the storefront by going back to the parent directory and changing to the `medusa-digital-product-storefront` directory:
```bash
cd ../medusa-digital-product-storefront # change based on your project name
```
</Note>
### Add Types ### Add Types
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" badgeLabel="Storefront" badgeColor="blue"
import { import {
// other imports... // other imports...
StoreProductVariant, StoreProductVariant,
@@ -2655,7 +2729,7 @@ export const fieldHighlights = [
["24"] ["24"]
] ]
```ts title="src/lib/data/products.ts" highlights={fieldHighlights} ```ts title="src/lib/data/products.ts" highlights={fieldHighlights} badgeLabel="Storefront" badgeColor="blue"
export const listProducts = async ({ export const listProducts = async ({
pageParam = 1, pageParam = 1,
queryParams, queryParams,
@@ -2693,7 +2767,7 @@ When a customer views a product’s details page, digital products linked to var
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`: 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" badgeLabel="Storefront" badgeColor="blue"
import { DigitalProductPreview } from "../../types/global" import { DigitalProductPreview } from "../../types/global"
``` ```
@@ -2735,7 +2809,7 @@ This function uses the API route you created in the previous section to get the
To add a button that shows the customer the preview media of a digital product, first, in `src/modules/products/components/product-actions/index.tsx`, cast the `selectedVariant` variable in the component to the `VariantWithDigitalProduct` type you created earlier: To add a button that shows the customer the preview media of a digital product, first, in `src/modules/products/components/product-actions/index.tsx`, cast the `selectedVariant` variable in the component to the `VariantWithDigitalProduct` type you created earlier:
```tsx title="src/modules/products/components/product-actions/index.tsx" ```tsx title="src/modules/products/components/product-actions/index.tsx" badgeLabel="Storefront" badgeColor="blue"
// other imports... // other imports...
import { VariantWithDigitalProduct } from "../../../../types/global" import { VariantWithDigitalProduct } from "../../../../types/global"
@@ -2757,7 +2831,7 @@ export default function ProductActions({
Then, add the following function in the component: Then, add the following function in the component:
```tsx title="src/modules/products/components/product-actions/index.tsx" ```tsx title="src/modules/products/components/product-actions/index.tsx" badgeLabel="Storefront" badgeColor="blue"
// other imports... // other imports...
import { getDigitalProductPreview } from "../../../../lib/data/products" import { getDigitalProductPreview } from "../../../../lib/data/products"
@@ -2790,7 +2864,7 @@ This function uses the `getDigitalProductPreview` function you created earlier t
Finally, in the `return` statement, add a new button above the add-to-cart button: Finally, in the `return` statement, add a new button above the add-to-cart button:
```tsx title="src/modules/products/components/product-actions/index.tsx" ```tsx title="src/modules/products/components/product-actions/index.tsx" badgeLabel="Storefront" badgeColor="blue"
return ( return (
<div> <div>
{/* Before add to cart */} {/* Before add to cart */}
@@ -2819,7 +2893,7 @@ You’ll now create the page customers can view their purchased digital product
Start by creating the file `src/lib/data/digital-products.ts` with the following content: Start by creating the file `src/lib/data/digital-products.ts` with the following content:
```ts title="src/lib/data/digital-products.ts" ```ts title="src/lib/data/digital-products.ts" badgeLabel="Storefront" badgeColor="blue"
"use server" "use server"
import { DigitalProduct } from "../../types/global" import { DigitalProduct } from "../../types/global"
@@ -2851,7 +2925,7 @@ The `getCustomerDigitalProducts` retrieves the logged-in customer’s purchased
Then, create the file `src/modules/account/components/digital-products-list/index.tsx` with the following content: Then, create the file `src/modules/account/components/digital-products-list/index.tsx` with the following content:
```tsx title="src/modules/account/components/digital-products-list/index.tsx" ```tsx title="src/modules/account/components/digital-products-list/index.tsx" badgeLabel="Storefront" badgeColor="blue"
"use client" "use client"
import { Table } from "@medusajs/ui" import { Table } from "@medusajs/ui"
@@ -2905,7 +2979,7 @@ This adds a `DigitalProductsList` component that receives a list of digital prod
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:
```tsx title="src/app/[countryCode]/(main)/account/@dashboard/digital-products/page.tsx" ```tsx title="src/app/[countryCode]/(main)/account/@dashboard/digital-products/page.tsx" badgeLabel="Storefront" badgeColor="blue"
import { Metadata } from "next" import { Metadata } from "next"
import { getCustomerDigitalProducts } from "../../../../../../lib/data/digital-products" import { getCustomerDigitalProducts } from "../../../../../../lib/data/digital-products"
@@ -2941,7 +3015,7 @@ In the route, you retrieve the digital’s products using the `getCustomerDigita
Finally, to add a tab in the customer’s account dashboard that links to this page, add it in the `src/modules/account/components/account-nav/index.tsx` file: Finally, to add a tab in the customer’s account dashboard that links to this page, add it in the `src/modules/account/components/account-nav/index.tsx` file:
```tsx title="src/modules/account/components/account-nav/index.tsx" ```tsx title="src/modules/account/components/account-nav/index.tsx" badgeLabel="Storefront" badgeColor="blue"
// other imports... // other imports...
import { Photo } from "@medusajs/icons" import { Photo } from "@medusajs/icons"
@@ -3003,7 +3077,7 @@ Then, go to the customer’s account page and click on the new Digital Products
To add a download link for the purchased digital products’ medias, first, add a new function to `src/lib/data/digital-products.ts`: To add a download link for the purchased digital products’ medias, first, add a new function to `src/lib/data/digital-products.ts`:
```ts title="src/lib/data/digital-products.ts" ```ts title="src/lib/data/digital-products.ts" badgeLabel="Storefront" badgeColor="blue"
export const getDigitalMediaDownloadLink = async (mediaId: string) => { export const getDigitalMediaDownloadLink = async (mediaId: string) => {
const headers = { const headers = {
...(await getAuthHeaders()), ...(await getAuthHeaders()),
@@ -3029,7 +3103,7 @@ In this function, you send a request to the download API route you created earli
Then, in `src/modules/account/components/digital-products-list/index.tsx`, import the `getDigitalMediaDownloadLink` at the top of the file: 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" ```tsx title="src/modules/account/components/digital-products-list/index.tsx" badgeLabel="Storefront" badgeColor="blue"
import { getDigitalMediaDownloadLink } from "../../../../lib/data/digital-products" import { getDigitalMediaDownloadLink } from "../../../../lib/data/digital-products"
``` ```
+1 -1
View File
@@ -111,7 +111,7 @@ export const generatedEditDates = {
"app/nextjs-starter/page.mdx": "2025-02-26T11:37:47.137Z", "app/nextjs-starter/page.mdx": "2025-02-26T11:37:47.137Z",
"app/recipes/b2b/page.mdx": "2025-04-17T08:48:38.369Z", "app/recipes/b2b/page.mdx": "2025-04-17T08:48:38.369Z",
"app/recipes/commerce-automation/page.mdx": "2025-04-17T08:48:37.663Z", "app/recipes/commerce-automation/page.mdx": "2025-04-17T08:48:37.663Z",
"app/recipes/digital-products/examples/standard/page.mdx": "2025-04-17T08:48:36.289Z", "app/recipes/digital-products/examples/standard/page.mdx": "2025-04-24T15:41:05.364Z",
"app/recipes/digital-products/page.mdx": "2025-04-17T08:29:01.230Z", "app/recipes/digital-products/page.mdx": "2025-04-17T08:29:01.230Z",
"app/recipes/ecommerce/page.mdx": "2025-02-26T12:20:52.092Z", "app/recipes/ecommerce/page.mdx": "2025-02-26T12:20:52.092Z",
"app/recipes/marketplace/examples/vendors/page.mdx": "2025-03-18T15:28:32.122Z", "app/recipes/marketplace/examples/vendors/page.mdx": "2025-03-18T15:28:32.122Z",