docs: added re-order guide (#12363)
* docs: added re-order guide * reiteration to intro * adjustment to title * fix vale error * generate * fix vale
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,659 @@
|
||||
---
|
||||
sidebar_label: "Re-Order"
|
||||
tags:
|
||||
- name: order
|
||||
label: "Implement Re-Order"
|
||||
- server
|
||||
- tutorial
|
||||
---
|
||||
|
||||
import { Github, PlaySolid } from "@medusajs/icons"
|
||||
import { Prerequisites, WorkflowDiagram } from "docs-ui"
|
||||
|
||||
export const ogImage = "https://res.cloudinary.com/dza7lstvk/image/upload/v1742389227/Medusa%20Resources/product-reviews_zia79g.jpg"
|
||||
|
||||
export const metadata = {
|
||||
title: `Implement Quick Re-Order Functionality in Medusa`,
|
||||
openGraph: {
|
||||
images: [
|
||||
{
|
||||
url: ogImage,
|
||||
width: 1600,
|
||||
height: 836,
|
||||
type: "image/jpeg"
|
||||
}
|
||||
],
|
||||
},
|
||||
twitter: {
|
||||
images: [
|
||||
{
|
||||
url: ogImage,
|
||||
width: 1600,
|
||||
height: 836,
|
||||
type: "image/jpeg"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
# {metadata.title}
|
||||
|
||||
In this tutorial, you'll learn how to implement a re-order functionality in Medusa.
|
||||
|
||||
When you install a Medusa application, you get a fully-fledged commerce platform with a Framework for customization. The Medusa application's commerce features are built around [Commerce Modules](../../../commerce-modules/page.mdx) which are available out-of-the-box. The features include order-management features.
|
||||
|
||||
The Medusa Framework facilitates building custom features that are necessary for your business use case. In this tutorial, you'll learn how to implement a re-order functionality in Medusa. This feature is useful for businesses whose customers are likely to repeat their orders, such as B2B or food delivery businesses.
|
||||
|
||||
You can follow this guide whether you're new to Medusa or an advanced Medusa developer.
|
||||
|
||||
## Summary
|
||||
|
||||
By following this tutorial, you'll learn how to:
|
||||
|
||||
- Install and set up Medusa.
|
||||
- Define the logic to re-order an order.
|
||||
- Customize the Next.js Starter Storefront to add a re-order button.
|
||||
|
||||

|
||||
|
||||
<CardList items={[
|
||||
{
|
||||
href: "https://github.com/medusajs/examples/tree/main/re-order",
|
||||
title: "Re-Order Repository",
|
||||
text: "Find the full code for this guide in this repository.",
|
||||
icon: Github,
|
||||
},
|
||||
{
|
||||
// TODO update link
|
||||
href: "https://res.cloudinary.com/dza7lstvk/raw/upload/v1741941475/OpenApi/product-reviews_jh8ohj.yaml",
|
||||
title: "OpenApi Specs for Postman",
|
||||
text: "Import this OpenApi Specs file into tools like Postman.",
|
||||
icon: PlaySolid,
|
||||
},
|
||||
]} />
|
||||
|
||||
---
|
||||
|
||||
## Step 1: Install a Medusa Application
|
||||
|
||||
<Prerequisites items={[
|
||||
{
|
||||
text: "Node.js v20+",
|
||||
link: "https://nodejs.org/en/download"
|
||||
},
|
||||
{
|
||||
text: "Git CLI tool",
|
||||
link: "https://git-scm.com/downloads"
|
||||
},
|
||||
{
|
||||
text: "PostgreSQL",
|
||||
link: "https://www.postgresql.org/download/"
|
||||
}
|
||||
]} />
|
||||
|
||||
Start by installing the Medusa application on your machine with the following command:
|
||||
|
||||
```bash
|
||||
npx create-medusa-app@latest
|
||||
```
|
||||
|
||||
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, and the Next.js Starter Storefront in a separate directory with the `{project-name}-storefront` name.
|
||||
|
||||
<Note title="Why is the storefront installed separately?">
|
||||
|
||||
The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](!docs!/learn/fundamentals/api-routes). Learn more in [Medusa's Architecture documentation](!docs!/learn/introduction/architecture).
|
||||
|
||||
</Note>
|
||||
|
||||
Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterwards, you can log in with the new user and explore the dashboard.
|
||||
|
||||
<Note title="Ran into Errors?">
|
||||
|
||||
Check out the [troubleshooting guides](../../../troubleshooting/create-medusa-app-errors/page.mdx) for help.
|
||||
|
||||
</Note>
|
||||
|
||||
---
|
||||
|
||||
## Step 2: Implement Re-Order Workflow
|
||||
|
||||
To build custom commerce features in Medusa, you create a [workflow](!docs!/learn/fundamentals/workflows). A workflow is a series of queries and actions, called steps, that complete a task.
|
||||
|
||||
By using workflows, you can track their executions' progress, define roll-back logic, and configure other advanced features. Then, you execute the workflow from other customizations, such as in an API Route.
|
||||
|
||||
In this section, you'll implement the re-order functionality in a workflow. Later, you'll execute the workflow in a custom API route.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to the [Workflows documentation](!docs!/learn/fundamentals/workflows) to learn more.
|
||||
|
||||
</Note>
|
||||
|
||||
The workflow will have the following steps:
|
||||
|
||||
<WorkflowDiagram
|
||||
workflow={{
|
||||
name: "reorderWorkflow",
|
||||
steps: [
|
||||
{
|
||||
name: "useQueryGraphStep",
|
||||
type: "step",
|
||||
description: "Retrieve the order's details.",
|
||||
link: "/references/helper-steps/useQueryGraphStep",
|
||||
depth: 1,
|
||||
},
|
||||
{
|
||||
name: "createCartWorkflow",
|
||||
type: "step",
|
||||
description: "Create a cart for the re-order.",
|
||||
link: "/references/medusa-workflows/createCartWorkflow",
|
||||
depth: 1,
|
||||
},
|
||||
{
|
||||
name: "addShippingMethodToCartWorkflow",
|
||||
type: "step",
|
||||
description: "Add the order's shipping method(s) to the cart.",
|
||||
link: "/references/medusa-workflows/addShippingMethodToCartWorkflow",
|
||||
depth: 1,
|
||||
},
|
||||
{
|
||||
name: "useQueryGraphStep",
|
||||
type: "step",
|
||||
description: "Retrieve the cart's details.",
|
||||
link: "/references/helper-steps/useQueryGraphStep",
|
||||
depth: 1,
|
||||
}
|
||||
]
|
||||
}}
|
||||
hideLegend
|
||||
/>
|
||||
|
||||
This workflow uses steps from Medusa's `@medusajs/medusa/core-flows` package. So, you can implement the workflow without implementing custom steps.
|
||||
|
||||
### a. Create the Workflow
|
||||
|
||||
To create the workflow, create the file `src/workflows/reorder.ts` with the following content:
|
||||
|
||||
export const workflowHighlights1 = [
|
||||
["20", "useQueryGraphStep", "Retrieve the order's details."],
|
||||
]
|
||||
|
||||
```ts title="src/workflows/reorder.ts" highlights={workflowHighlights1}
|
||||
import {
|
||||
createWorkflow,
|
||||
transform,
|
||||
WorkflowResponse,
|
||||
} from "@medusajs/framework/workflows-sdk"
|
||||
import {
|
||||
addShippingMethodToCartWorkflow,
|
||||
createCartWorkflow,
|
||||
useQueryGraphStep,
|
||||
} from "@medusajs/medusa/core-flows"
|
||||
|
||||
type ReorderWorkflowInput = {
|
||||
order_id: string
|
||||
}
|
||||
|
||||
export const reorderWorkflow = createWorkflow(
|
||||
"reorder",
|
||||
({ order_id }: ReorderWorkflowInput) => {
|
||||
// @ts-ignore
|
||||
const { data: orders } = useQueryGraphStep({
|
||||
entity: "order",
|
||||
fields: [
|
||||
"*",
|
||||
"items.*",
|
||||
"shipping_address.*",
|
||||
"billing_address.*",
|
||||
"region.*",
|
||||
"sales_channel.*",
|
||||
"shipping_methods.*",
|
||||
"customer.*",
|
||||
],
|
||||
filters: {
|
||||
id: order_id,
|
||||
},
|
||||
})
|
||||
|
||||
// TODO create a cart with the order's items
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
You create a workflow using `createWorkflow` from the Workflows SDK. It accepts the workflow's unique name as a first parameter.
|
||||
|
||||
It accepts as a second parameter a constructor function, which is the workflow's implementation. The function can accept input, which in this case is an object holding the ID of the order to re-order.
|
||||
|
||||
In the workflow's constructor function, so far you use the `useQueryGraphStep` step to retrieve the order's details. This step uses [Query](!docs!/learn/fundamentals/module-links/query) under the hood, which allows you to query data across [modules](!docs!/learn/fundamentals/modules).
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to the [Query documentation](!docs!/learn/fundamentals/module-links/query) to learn more about how to use it.
|
||||
|
||||
</Note>
|
||||
|
||||
### b. Create a Cart
|
||||
|
||||
Next, you need to create a cart using the old order's details. You can use the `createCartWorkflow` step to create a cart, but you first need to prepare its input data.
|
||||
|
||||
Replace the `TODO` in the workflow with the following:
|
||||
|
||||
export const workflowHighlights2 = [
|
||||
["1", "transform", "Prepare the input for cart creation."],
|
||||
["37", "createCartWorkflow", "Create the cart."],
|
||||
]
|
||||
|
||||
```ts title="src/workflows/reorder.ts" highlights={workflowHighlights2}
|
||||
const createInput = transform({
|
||||
orders,
|
||||
}, (data) => {
|
||||
return {
|
||||
region_id: data.orders[0].region_id!,
|
||||
sales_channel_id: data.orders[0].sales_channel_id!,
|
||||
customer_id: data.orders[0].customer_id!,
|
||||
email: data.orders[0].email!,
|
||||
billing_address: {
|
||||
first_name: data.orders[0].billing_address?.first_name!,
|
||||
last_name: data.orders[0].billing_address?.last_name!,
|
||||
address_1: data.orders[0].billing_address?.address_1!,
|
||||
city: data.orders[0].billing_address?.city!,
|
||||
country_code: data.orders[0].billing_address?.country_code!,
|
||||
province: data.orders[0].billing_address?.province!,
|
||||
postal_code: data.orders[0].billing_address?.postal_code!,
|
||||
phone: data.orders[0].billing_address?.phone!,
|
||||
},
|
||||
shipping_address: {
|
||||
first_name: data.orders[0].shipping_address?.first_name!,
|
||||
last_name: data.orders[0].shipping_address?.last_name!,
|
||||
address_1: data.orders[0].shipping_address?.address_1!,
|
||||
city: data.orders[0].shipping_address?.city!,
|
||||
country_code: data.orders[0].shipping_address?.country_code!,
|
||||
province: data.orders[0].shipping_address?.province!,
|
||||
postal_code: data.orders[0].shipping_address?.postal_code!,
|
||||
phone: data.orders[0].shipping_address?.phone!,
|
||||
},
|
||||
items: data.orders[0].items?.map((item) => ({
|
||||
variant_id: item?.variant_id!,
|
||||
quantity: item?.quantity!,
|
||||
unit_price: item?.unit_price!,
|
||||
})),
|
||||
}
|
||||
})
|
||||
|
||||
const { id: cart_id } = createCartWorkflow.runAsStep({
|
||||
input: createInput,
|
||||
})
|
||||
|
||||
// TODO add the shipping method to the cart
|
||||
```
|
||||
|
||||
Data manipulation is not allowed in a workflow, as Medusa stores its definition before executing it. Instead, you can use `transform` from the Workflows SDK to manipulate the data.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn more about why you can't manipulate data in a workflow and the `transform` function in the [Data Manipulation in Workflows documentation](!docs!/learn/fundamentals/workflows/variable-manipulation).
|
||||
|
||||
</Note>
|
||||
|
||||
`transform` accepts the following parameters:
|
||||
|
||||
1. The data to use in the transformation function.
|
||||
2. A transformation function that accepts the data from the first parameter and returns the transformed data.
|
||||
|
||||
In the above code snippet, you use `transform` to create the input for the `createCartWorkflow` step. The input is an object that holds the cart's details, including its items, shipping and billing addresses, and more.
|
||||
|
||||
<Note>
|
||||
|
||||
Learn about other input parameters you can pass in the [createCartWorkflow reference](/references/medusa-workflows/createCartWorkflow).
|
||||
|
||||
</Note>
|
||||
|
||||
After that, you execute the `createCartWorkflow` passing it the transformed input. The workflow returns the cart's details, including its ID.
|
||||
|
||||
### c. Add Shipping Methods
|
||||
|
||||
Next, you need to add the order's shipping method(s) to the cart. This saves the customer from having to select a shipping method again.
|
||||
|
||||
You can use the `addShippingMethodToCartWorkflow` step to add the shipping method(s) to the cart.
|
||||
|
||||
Replace the `TODO` in the workflow with the following:
|
||||
|
||||
export const workflowHighlights3 = [
|
||||
["1", "transform", "Prepare the input for adding shipping methods to the cart."],
|
||||
["14", "addShippingMethodToCartWorkflow", "Add the shipping methods to the cart."],
|
||||
]
|
||||
|
||||
```ts title="src/workflows/reorder.ts" highlights={workflowHighlights3}
|
||||
const addShippingMethodToCartInput = transform({
|
||||
cart_id,
|
||||
orders,
|
||||
}, (data) => {
|
||||
return {
|
||||
cart_id: data.cart_id,
|
||||
options: data.orders[0].shipping_methods?.map((method) => ({
|
||||
id: method?.shipping_option_id!,
|
||||
data: method?.data!,
|
||||
})) ?? [],
|
||||
}
|
||||
})
|
||||
|
||||
addShippingMethodToCartWorkflow.runAsStep({
|
||||
input: addShippingMethodToCartInput,
|
||||
})
|
||||
|
||||
// TODO retrieve and return the cart's details
|
||||
```
|
||||
|
||||
Again, you use `transform` to prepare the input for the `addShippingMethodToCartWorkflow`. The input includes the cart's ID and the shipping method(s) to add to the cart.
|
||||
|
||||
Then, you execute the `addShippingMethodToCartWorkflow` to add the shipping method(s) to the cart.
|
||||
|
||||
### d. Retrieve and Return the Cart's Details
|
||||
|
||||
Finally, you need to retrieve the cart's details and return them as the workflow's output.
|
||||
|
||||
Replace the `TODO` in the workflow with the following:
|
||||
|
||||
export const workflowHighlights4 = [
|
||||
["2", "useQueryGraphStep", "Retrieve the cart's details."],
|
||||
["28", "WorkflowResponse", "Return the cart's details."],
|
||||
]
|
||||
|
||||
```ts title="src/workflows/reorder.ts" highlights={workflowHighlights4}
|
||||
// @ts-ignore
|
||||
const { data: carts } = useQueryGraphStep({
|
||||
entity: "cart",
|
||||
fields: [
|
||||
"*",
|
||||
"items.*",
|
||||
"shipping_methods.*",
|
||||
"shipping_address.*",
|
||||
"billing_address.*",
|
||||
"region.*",
|
||||
"sales_channel.*",
|
||||
"promotions.*",
|
||||
"currency_code",
|
||||
"subtotal",
|
||||
"item_total",
|
||||
"total",
|
||||
"item_subtotal",
|
||||
"shipping_subtotal",
|
||||
"customer.*",
|
||||
"payment_collection.*",
|
||||
|
||||
],
|
||||
filters: {
|
||||
id: cart_id,
|
||||
},
|
||||
}).config({ name: "retrieve-cart" })
|
||||
|
||||
return new WorkflowResponse(carts[0])
|
||||
```
|
||||
|
||||
You execute the `useQueryGraphStep` again to retrieve the cart's details. Since you're re-using a step, you have to rename it using the `config` method.
|
||||
|
||||
Finally, you return the cart's details. A workflow must return an instance of `WorkflowResponse`.
|
||||
|
||||
The `WorkflowResponse` constructor accepts the workflow's output as a parameter, which is the cart's details in this case.
|
||||
|
||||
In the next step, you'll create an API route that exposes the re-order functionality.
|
||||
|
||||
---
|
||||
|
||||
## Step 3: Create Re-Order API Route
|
||||
|
||||
Now that you have the logic to re-order, you need to expose it so that frontend clients, such as a storefront, can use it. You do this by creating an [API route](!docs!/learn/fundamentals/api-routes).
|
||||
|
||||
An API Route is an endpoint that exposes commerce features to external applications and clients, such as storefronts. You'll create an API route at the path `/store/customers/me/orders/:id` that executes the workflow from the previous step.
|
||||
|
||||
<Note>
|
||||
|
||||
Refer to the [API Routes documentation](!docs!/learn/fundamentals/api-routes) to learn more.
|
||||
|
||||
</Note>
|
||||
|
||||
An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory. The path of the API route is the file's path relative to `src/api`.
|
||||
|
||||
So, create the file `src/api/store/customers/me/orders/[id]/route.ts` with the following content:
|
||||
|
||||
```ts title="src/api/store/customers/me/orders/[id]/route.ts"
|
||||
import {
|
||||
AuthenticatedMedusaRequest,
|
||||
MedusaResponse,
|
||||
} from "@medusajs/framework/http"
|
||||
import { reorderWorkflow } from "../../../../../../workflows/reorder"
|
||||
|
||||
export async function POST(
|
||||
req: AuthenticatedMedusaRequest,
|
||||
res: MedusaResponse
|
||||
) {
|
||||
const { id } = req.params
|
||||
|
||||
const { result } = await reorderWorkflow(req.scope).run({
|
||||
input: {
|
||||
order_id: id,
|
||||
},
|
||||
})
|
||||
|
||||
return res.json({
|
||||
cart: result,
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
Since you export a `POST` route handler function, you expose a `POST` API route at `/store/customers/me/orders/:id`.
|
||||
|
||||
<Note>
|
||||
|
||||
API routes that start with `/store/customers/me` are protected by default, meaning that only authenticated customers can access them. Learn more in the [Protected API Routes documentation](!docs!/learn/fundamentals/api-routes/protected-routes).
|
||||
|
||||
</Note>
|
||||
|
||||
The route handler function accepts two parameters:
|
||||
|
||||
1. A request object with details and context on the request, such as path parameters or authenticated customer details.
|
||||
2. A response object to manipulate and send the response.
|
||||
|
||||
In the route handler function, you execute the `reorderWorkflow`. To execute a workflow, you:
|
||||
|
||||
- Invoke it, passing it the [Medusa container](!docs!/learn/fundamentals/medusa-container) available in the `req.scope` property.
|
||||
- The Medusa container is a registry of Framework and commerce resources that you can resolve and use in your customizations.
|
||||
- Call the `run` method, passing it an object with the workflow's input.
|
||||
|
||||
You pass the order ID from the request's path parameters as the workflow's input. Finally, you return the created cart's details in the response.
|
||||
|
||||
You'll test out this API route after you customize the Next.js Starter Storefront.
|
||||
|
||||
---
|
||||
|
||||
## Step 4: Customize the Next.js Starter Storefront
|
||||
|
||||
In this step, you'll customize the [Next.js Starter Storefront](../../../nextjs-starter/page.mdx) to add a re-order button. You installed the Next.js Starter Storefront in the first step with the Medusa application, but you can also install it separately as explained in the [Next.js Starter Storefront documentation](../../../nextjs-starter/page.mdx).
|
||||
|
||||
The Next.js Starter Storefront provides rich commerce features and a sleek design. You can use it as-is or build on top of it to tailor it for your business's unique use case, design, and customer experience.
|
||||
|
||||
<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-reorder`, you can find the storefront by going back to the parent directory and changing to the `medusa-reorder-storefront` directory:
|
||||
|
||||
```bash
|
||||
cd ../medusa-reorder-storefront # change based on your project name
|
||||
```
|
||||
|
||||
</Note>
|
||||
|
||||
To add the re-order button, you will:
|
||||
|
||||
- Add a server function that re-orders an order using the API route from the previous step.
|
||||
- Add a button to the order details page that calls the server function.
|
||||
|
||||
### a. Add the Server Function
|
||||
|
||||
You'll add the server function for the re-order functionality in the `src/lib/data/orders.ts` file.
|
||||
|
||||
First, add the following import statement to the top of the file:
|
||||
|
||||
```ts title="src/lib/data/orders.ts" badgeLabel="Storefront" badgeColor="blue"
|
||||
import { setCartId } from "./cookies"
|
||||
```
|
||||
|
||||
Then, add the function at the end of the file:
|
||||
|
||||
```ts title="src/lib/data/orders.ts" badgeLabel="Storefront" badgeColor="blue"
|
||||
export const reorder = async (id: string) => {
|
||||
const headers = await getAuthHeaders()
|
||||
|
||||
const { cart } = await sdk.client.fetch<HttpTypes.StoreCartResponse>(
|
||||
`/store/customers/me/orders/${id}`,
|
||||
{
|
||||
method: "POST",
|
||||
headers,
|
||||
}
|
||||
)
|
||||
|
||||
await setCartId(cart.id)
|
||||
|
||||
return cart
|
||||
}
|
||||
```
|
||||
|
||||
You add a function that accepts the order ID as a parameter.
|
||||
|
||||
The function uses the `client.fetch` method of the [JS SDK](../../../js-sdk/page.mdx) to send a request to the API route you created in the previous step.
|
||||
|
||||
<Note>
|
||||
|
||||
The JS SDK is already configured in the Next.js Starter Storefront. Refer to the [JS SDK documentation](../../../js-sdk/page.mdx) to learn more about it.
|
||||
|
||||
</Note>
|
||||
|
||||
Once the request succeeds, you use the `setCartId` function that's defined in the storefront to set the cart ID in a cookie. This ensures the cart is used across the storefront.
|
||||
|
||||
Finally, you return the cart's details.
|
||||
|
||||
### b. Add the Re-Order Button Component
|
||||
|
||||
Next, you'll add the component that shows the re-order button. You'll later add the component to the order details page.
|
||||
|
||||
To create the component, create the file `src/modules/order/components/reorder-action/index.tsx` with the following content:
|
||||
|
||||
export const componentHighlights = [
|
||||
["14", "handleReorder", "Re-order the order."],
|
||||
["21", "push", "Redirect to the checkout page."],
|
||||
["24", "toast.error", "Show the error message."],
|
||||
]
|
||||
|
||||
```tsx title="src/modules/order/components/reorder-action/index.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={componentHighlights}
|
||||
import { Button, toast } from "@medusajs/ui"
|
||||
import { reorder } from "../../../../lib/data/orders"
|
||||
import { useState } from "react"
|
||||
import { useRouter } from "next/navigation"
|
||||
|
||||
type ReorderActionProps = {
|
||||
orderId: string
|
||||
}
|
||||
|
||||
export default function ReorderAction({ orderId }: ReorderActionProps) {
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
const router = useRouter()
|
||||
|
||||
const handleReorder = async () => {
|
||||
setIsLoading(true)
|
||||
try {
|
||||
const cart = await reorder(orderId)
|
||||
|
||||
setIsLoading(false)
|
||||
toast.success("Prepared cart to reorder. Proceeding to checkout...")
|
||||
router.push(`/${cart.shipping_address!.country_code}/checkout?step=payment`)
|
||||
} catch (error) {
|
||||
setIsLoading(false)
|
||||
toast.error(`Error reordering: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Button
|
||||
variant="primary"
|
||||
size="small"
|
||||
onClick={handleReorder}
|
||||
disabled={isLoading}
|
||||
>
|
||||
Reorder
|
||||
</Button>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
You create a `ReorderAction` component that accepts the order ID as a prop.
|
||||
|
||||
In the component, you render a button that, when clicked, calls a `handleReorder` function. The function calls the `reorder` function you created in the previous step to re-order the order.
|
||||
|
||||
If the re-order succeeds, you redirect the user to the payment step of the checkout page. If it fails, you show an error message.
|
||||
|
||||
### c. Show Re-Order Button on Order Details Page
|
||||
|
||||
Finally, you'll show the `ReorderAction` component on the order details page.
|
||||
|
||||
In `src/modules/order/templates/order-details-template.tsx`, add the following import statement to the top of the file:
|
||||
|
||||
```tsx title="src/modules/order/templates/order-details-template.tsx" badgeLabel="Storefront" badgeColor="blue"
|
||||
import ReorderAction from "../components/reorder-action"
|
||||
```
|
||||
|
||||
Then, in the return statement of the `OrderDetailsTemplate` component, find the `OrderDetails` component and add the `ReorderAction` component below it:
|
||||
|
||||
```tsx title="src/modules/order/templates/order-details-template.tsx" badgeLabel="Storefront" badgeColor="blue"
|
||||
<ReorderAction orderId={order.id} />
|
||||
```
|
||||
|
||||
The re-order button will now be shown on the order details page.
|
||||
|
||||
### Test it Out
|
||||
|
||||
You'll now test out the re-order functionality.
|
||||
|
||||
First, to start the Medusa application, run the following command in the Medusa application's directory:
|
||||
|
||||
```bash npm2yarn badgeLabel="Medusa application" badgeColor="green"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
Then, in the Next.js Starter Storefront directory, run the following command to start the storefront:
|
||||
|
||||
```bash npm2yarn badgeLabel="Storefront" badgeColor="blue"
|
||||
npm run dev
|
||||
```
|
||||
|
||||
The storefront will be running at `http://localhost:8000`. Open it in your browser.
|
||||
|
||||
To test out the re-order functionality:
|
||||
|
||||
- Create an account in the storefront.
|
||||
- Add a product to the cart and complete the checkout process to place an order.
|
||||
- Go to Account -> Orders, and click on the "See details" button.
|
||||
|
||||

|
||||
|
||||
On the order's details page, you'll find a "Reorder" button.
|
||||
|
||||

|
||||
|
||||
When you click on the button, a new cart will be created with the order's details, and you'll be redirected to the checkout page where you can complete the purchase.
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## Next Steps
|
||||
|
||||
You now have a re-order functionality in your Medusa application and Next.js Starter Storefront. You can expand more on this feature based on your use case.
|
||||
|
||||
For example, you can add quick orders on the storefront's homepage, allowing customers to quickly re-order their last orders.
|
||||
|
||||
If you're new to Medusa, check out the [main documentation](!docs!/learn), where you'll get a more in-depth learning of all the concepts you've used in this guide and more.
|
||||
|
||||
To learn more about the commerce features that Medusa provides, check out Medusa's [Commerce Modules](../../../commerce-modules/page.mdx).
|
||||
@@ -11,7 +11,7 @@ export const metadata = {
|
||||
|
||||
The Medusa application is made up of a Node.js server and an admin dashboard. The storefront is installed and hosted separately from the Medusa application. You have the flexibility to choose the frontend tech stack that you and your team are proficient in, and implement unique design systems and user experience.
|
||||
|
||||
Our Next.js Starter is a good place to start building your storefront. The Next.js Starter storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
|
||||
Our Next.js Starter Storefront is a good place to start building your storefront. The Next.js Starter Storefront provides rich commerce features and a sleek design. Developers and businesses can use it as-is or build on top of it to tailor it for the business's unique use case, design, and customer experience.
|
||||
|
||||
<Note>
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -747,6 +747,10 @@ export const filesMap = [
|
||||
"filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/product-reviews/page.mdx",
|
||||
"pathname": "/how-to-tutorials/tutorials/product-reviews"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/re-order/page.mdx",
|
||||
"pathname": "/how-to-tutorials/tutorials/re-order"
|
||||
},
|
||||
{
|
||||
"filePath": "/www/apps/resources/app/how-to-tutorials/tutorials/saved-payment-methods/page.mdx",
|
||||
"pathname": "/how-to-tutorials/tutorials/saved-payment-methods"
|
||||
|
||||
@@ -3716,14 +3716,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/fetchShippingOptionForOrderWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -3943,14 +3935,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/deleteShippingProfilesStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -6093,6 +6077,14 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"title": "Implement Quote Management",
|
||||
"path": "https://docs.medusajs.com/resources/examples/guides/quote-management",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "Implement Re-Order",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/re-order",
|
||||
"children": []
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -11855,14 +11847,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteProductVariantsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -12066,22 +12050,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/getProductsStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "groupProductsForBatchStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/groupProductsForBatchStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -14118,14 +14086,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/exportProductsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -14209,14 +14169,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/generateProductCsvStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -14685,14 +14637,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteSalesChannelsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
@@ -14784,14 +14728,6 @@ const generatedgeneratedCommerceModulesSidebarSidebar = {
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/findSalesChannelStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "ref",
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -464,6 +464,15 @@ const generatedgeneratedHowToTutorialsSidebarSidebar = {
|
||||
"description": "Learn how to implement quote management, useful for B2B use cases.",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
"type": "link",
|
||||
"title": "Re-Order",
|
||||
"path": "/how-to-tutorials/tutorials/re-order",
|
||||
"description": "Learn how to allow customers to re-order previous orders.",
|
||||
"children": []
|
||||
},
|
||||
{
|
||||
"loaded": true,
|
||||
"isPathHref": true,
|
||||
|
||||
@@ -127,6 +127,13 @@ While tutorials show you a specific use case, they also help you understand how
|
||||
description:
|
||||
"Learn how to implement quote management, useful for B2B use cases.",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
title: "Re-Order",
|
||||
path: "/how-to-tutorials/tutorials/re-order",
|
||||
description:
|
||||
"Learn how to allow customers to re-order previous orders.",
|
||||
},
|
||||
{
|
||||
type: "link",
|
||||
title: "Saved Payment Methods",
|
||||
|
||||
@@ -271,14 +271,6 @@ export const fulfillment = [
|
||||
"title": "maybeRefreshShippingMethodsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/maybeRefreshShippingMethodsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "deleteShippingProfilesStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/deleteShippingProfilesStep"
|
||||
|
||||
@@ -43,6 +43,10 @@ export const order = [
|
||||
"title": "Implement Loyalty Points",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/loyalty-points"
|
||||
},
|
||||
{
|
||||
"title": "Implement Re-Order",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/re-order"
|
||||
},
|
||||
{
|
||||
"title": "Checkout Step 5: Complete Cart",
|
||||
"path": "https://docs.medusajs.com/resources/storefront-development/checkout/complete-cart"
|
||||
|
||||
@@ -211,14 +211,6 @@ export const product = [
|
||||
"title": "getProductsStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/getProductsStep"
|
||||
},
|
||||
{
|
||||
"title": "groupProductsForBatchStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/groupProductsForBatchStep"
|
||||
},
|
||||
{
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "updateCollectionsStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/updateCollectionsStep"
|
||||
@@ -303,10 +295,6 @@ export const product = [
|
||||
"title": "deleteProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/deleteProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "updateCollectionsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/updateCollectionsWorkflow"
|
||||
|
||||
@@ -67,18 +67,10 @@ export const region = [
|
||||
"title": "generateProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/generateProductCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "exportProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/exportProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "createRegionsStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/createRegionsStep"
|
||||
|
||||
@@ -63,14 +63,6 @@ export const salesChannel = [
|
||||
"title": "orderExchangeAddNewItemWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/orderExchangeAddNewItemWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "importProductsWorkflow",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/importProductsWorkflow"
|
||||
},
|
||||
{
|
||||
"title": "createDefaultSalesChannelStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/createDefaultSalesChannelStep"
|
||||
|
||||
@@ -55,6 +55,10 @@ export const server = [
|
||||
"title": "Product Reviews",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/product-reviews"
|
||||
},
|
||||
{
|
||||
"title": "Re-Order",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/re-order"
|
||||
},
|
||||
{
|
||||
"title": "Saved Payment Methods",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/saved-payment-methods"
|
||||
|
||||
@@ -1032,12 +1032,8 @@ export const step = [
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/getProductsStep"
|
||||
},
|
||||
{
|
||||
"title": "groupProductsForBatchStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/groupProductsForBatchStep"
|
||||
},
|
||||
{
|
||||
"title": "parseProductCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/parseProductCsvStep"
|
||||
"title": "normalizeCsvStep",
|
||||
"path": "https://docs.medusajs.com/resources/references/medusa-workflows/steps/normalizeCsvStep"
|
||||
},
|
||||
{
|
||||
"title": "updateCollectionsStep",
|
||||
|
||||
@@ -35,6 +35,10 @@ export const tutorial = [
|
||||
"title": "Product Reviews",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/product-reviews"
|
||||
},
|
||||
{
|
||||
"title": "Re-Order",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/re-order"
|
||||
},
|
||||
{
|
||||
"title": "Saved Payment Methods",
|
||||
"path": "https://docs.medusajs.com/resources/how-to-tutorials/tutorials/saved-payment-methods"
|
||||
|
||||
Reference in New Issue
Block a user