From f614d86332ba7c599570453d8173b19d788ae706 Mon Sep 17 00:00:00 2001 From: Shahed Nasser Date: Fri, 27 Jun 2025 10:36:15 +0300 Subject: [PATCH] docs: added gift message guide (#12833) --- .../first-purchase-discounts/page.mdx | 28 +- .../tutorials/gift-message/page.mdx | 688 ++++++++++++++++++ www/apps/resources/generated/edit-dates.mjs | 3 + www/apps/resources/generated/files-map.mjs | 4 + .../generated-how-to-tutorials-sidebar.mjs | 9 + .../resources/sidebars/how-to-tutorials.mjs | 7 + www/packages/tags/src/tags/cart.ts | 4 + www/packages/tags/src/tags/file.ts | 4 - www/packages/tags/src/tags/nextjs.ts | 4 + www/packages/tags/src/tags/server.ts | 4 + www/packages/tags/src/tags/tutorial.ts | 4 + 11 files changed, 741 insertions(+), 18 deletions(-) create mode 100644 www/apps/resources/app/how-to-tutorials/tutorials/gift-message/page.mdx diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx index 9cfcd6367d..7e91f28060 100644 --- a/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx +++ b/www/apps/resources/app/how-to-tutorials/tutorials/first-purchase-discounts/page.mdx @@ -27,7 +27,7 @@ export const metadata = { In this tutorial, you'll learn how to implement first-purchase discounts 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. These features include promotion and discount management features. +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. These features include promotion and cart management features. The first-purchase discount feature encourages customers to sign up and make their first purchase by offering them a discount. In this tutorial, you'll learn how to implement this feature in Medusa. @@ -227,8 +227,8 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow( entity: "cart", fields: ["promotions.*", "customer.*", "customer.orders.*"], filters: { - id: input.cart_id - } + id: input.cart_id, + }, }) const { data: promotions } = useQueryGraphStep({ @@ -241,7 +241,7 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow( when({ carts, - promotions + promotions, }, (data) => { return data.promotions.length > 0 && !data.carts[0].promotions?.some((promo) => promo?.id === data.promotions[0].id) && @@ -252,7 +252,7 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow( updateCartPromotionsStep({ id: carts[0].id, promo_codes: [promotions[0].code!], - action: PromotionActions.ADD + action: PromotionActions.ADD, }) }) @@ -261,8 +261,8 @@ export const applyFirstPurchasePromoWorkflow = createWorkflow( entity: "cart", fields: ["*", "promotions.*"], filters: { - id: input.cart_id - } + id: input.cart_id, + }, }).config({ name: "retrieve-updated-cart" }) return new WorkflowResponse(updatedCarts[0]) @@ -319,8 +319,8 @@ export default async function cartCreatedHandler({ await applyFirstPurchasePromoWorkflow(container) .run({ input: { - cart_id: data.id - } + cart_id: data.id, + }, }) } @@ -429,8 +429,8 @@ updateCartPromotionsWorkflow.hooks.validate( entity: "customer", fields: ["orders.*", "has_account"], filters: { - id: cart.customer_id - } + id: cart.customer_id, + }, }) if (!customer.has_account || (customer?.orders?.length || 0) > 0) { @@ -474,7 +474,7 @@ In the same `src/workflows/hooks/validate-promotion.ts` file, add the following ```ts title="src/workflows/hooks/validate-promotion.ts" import { - completeCartWorkflow + completeCartWorkflow, } from "@medusajs/medusa/core-flows" ``` @@ -511,8 +511,8 @@ completeCartWorkflow.hooks.validate( entity: "customer", fields: ["orders.*", "has_account"], filters: { - id: cart.customer_id - } + id: cart.customer_id, + }, }) if (!customer.has_account || (customer?.orders?.length || 0) > 0) { diff --git a/www/apps/resources/app/how-to-tutorials/tutorials/gift-message/page.mdx b/www/apps/resources/app/how-to-tutorials/tutorials/gift-message/page.mdx new file mode 100644 index 0000000000..fe69165412 --- /dev/null +++ b/www/apps/resources/app/how-to-tutorials/tutorials/gift-message/page.mdx @@ -0,0 +1,688 @@ +--- +sidebar_label: "Add Gift Message" +tags: + - cart + - order + - server + - nextjs + - tutorial +products: + - cart + - order +--- + +import { Github } from "@medusajs/icons" +import { Prerequisites, WorkflowDiagram, CardList } from "docs-ui" + +export const metadata = { + title: `Add Gift Message to Line Items in Medusa`, +} + +# {metadata.title} + +In this tutorial, you will learn how to add a gift message to items in carts and orders 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. These features include cart and order management capabilities. + +You can customize the Medusa application and storefront to add a gift message to items in the cart. This feature allows customers to add a personalized message to their gifts, enhancing the shopping experience. + +## Summary + +By following this tutorial, you will learn how to: + +- Install and set up Medusa and the Next.js Starter Storefront. +- Customize the storefront to support gift messages on cart items during checkout. +- Customize the Medusa Admin to show gift items with messages in an order. + +You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer. + + + +--- + +## Step 1: Install a Medusa Application + + + +Start by installing the Medusa application on your machine with the following command: + +```bash +npx create-medusa-app@latest +``` + +First, you'll be asked for the project's name. Then, when prompted about installing the [Next.js Starter Storefront](../../../nextjs-starter/page.mdx), choose "Yes." + +Afterward, 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 named `{project-name}-storefront`. + + + +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). + + + +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. Afterward, you can log in with the new user and explore the dashboard. + + + +Check out the [troubleshooting guides](../../../troubleshooting/create-medusa-app-errors/page.mdx) for help. + + + +--- + +## Step 2: Add Gift Inputs to Cart Item + +In this step, you'll customize the Next.js Starter Storefront to allow customers to specify that an item is a gift and add a gift message to it. + +You'll store the gift option and message in the cart item's `metadata` property, which is a key-value `jsonb` object that can hold any additional information about the item. When the customer places the order, the `metadata` is copied to the `metadata` of the order's line items. + +So, you only need to customize the storefront to add the gift message input and update the cart item metadata. + +### a. Changes to Update Item Function + +The Next.js Starter Storefront has an `updateLineItem` function that sends a request to the Medusa server to update the cart item. However, it doesn't support updating the `metadata` property. + +So, in `src/lib/data/cart.ts`, find the `updateLineItem` function and add a `metadata` property to its object parameter: + +```ts title="src/lib/data/cart.ts" badgeLabel="Storefront" badgeColor="blue" highlights={[["4"], ["8"]]} +export async function updateLineItem({ + lineId, + quantity, + metadata, +}: { + lineId: string + quantity: number + metadata?: Record +}) { + // ... +} +``` + +Next, change the usage of `await sdk.store.cart.updateLineItem` in the function to pass the `metadata` property: + +```ts title="src/lib/data/cart.ts" badgeLabel="Storefront" badgeColor="blue" +const updateData: any = { quantity } +if (metadata) { + updateData.metadata = metadata +} + +await sdk.store.cart + .updateLineItem(cartId, lineId, updateData, {}, headers) +// ... +``` + +You pass the `metadata` property to the Medusa server, which will update the cart item with the new metadata. + +### b. Add Gift Inputs + +Next, you'll modify the cart item component that's shown in the cart and checkout pages to show two inputs: one to specify that the item is a gift and another to add a gift message. + +In `src/modules/cart/components/item/index.tsx`, add the following imports at the top of the file: + +```tsx title="src/modules/cart/components/item/index.tsx" badgeLabel="Storefront" badgeColor="blue" +import { Checkbox, Textarea, Button, Label } from "@medusajs/ui" +``` + +You import components from the [Medusa UI library](!ui!) that will be useful for the gift inputs. + +Next, in the `Item` component, add the following variables before the `changeQuantity` function: + +export const giftVarsHighlights = [ + ["1", "giftUpdating", "Track whether the gift message is being updated"], + ["2", "newGiftMessage", "Hold the new gift message input value."], + ["5", "isEditingGiftMessage", "Track whether the gift message input is being edited."], + ["7", "isGift", "Indicate whether the item is a gift."], + ["8", "giftMessage", "Current gift message from item's metadata."] +] + +```tsx title="src/modules/cart/components/item/index.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={giftVarsHighlights} +const [giftUpdating, setGiftUpdating] = useState(false) +const [newGiftMessage, setNewGiftMessage] = useState( + item.metadata?.gift_message as string || "" +) +const [isEditingGiftMessage, setIsEditingGiftMessage] = useState(false) + +const isGift = item.metadata?.is_gift === "true" +const giftMessage = item.metadata?.gift_message as string +``` + +You define the following variables: + +- `giftUpdating`: A state variable to track whether the gift message is being updated. This will be useful to handle loading and disabled states. +- `newGiftMessage`: A state variable to hold the new gift message input value. +- `isEditingGiftMessage`: A state variable to track whether the gift message input is being edited. This will be useful to show or hide the input field. +- `isGift`: A boolean indicating whether the item is a gift based on the `metadata.is_gift` property. +- `giftMessage`: The current gift message from the item's `metadata.gift_message` property. + +Next, add the following functions before the `return` statement to handle updates to the gift inputs: + +export const giftFunctionsHighlights = [ + ["1", "handleGiftToggle", "Handle the gift checkbox toggle."], + ["22", "handleSaveGiftMessage", "Handle saving the gift message."], + ["44", "handleStartEdit", "Enable editing the gift message input."], + ["48", "handleCancelEdit", "Cancel editing the gift message input."] +] + +```tsx title="src/modules/cart/components/item/index.tsx" badgeLabel="Storefront" badgeColor="blue" highlights={giftFunctionsHighlights} +const handleGiftToggle = async (checked: boolean) => { + setGiftUpdating(true) + + try { + const newMetadata = { + is_gift: checked.toString(), + gift_message: checked ? newGiftMessage : "", + } + + await updateLineItem({ + lineId: item.id, + quantity: item.quantity, + metadata: newMetadata, + }) + } catch (error) { + console.error("Error updating gift status:", error) + } finally { + setGiftUpdating(false) + } +} + +const handleSaveGiftMessage = async () => { + setGiftUpdating(true) + + try { + const newMetadata = { + is_gift: "true", + gift_message: newGiftMessage, + } + + await updateLineItem({ + lineId: item.id, + quantity: item.quantity, + metadata: newMetadata, + }) + setIsEditingGiftMessage(false) + } catch (error) { + console.error("Error updating gift message:", error) + } finally { + setGiftUpdating(false) + } +} + +const handleStartEdit = () => { + setIsEditingGiftMessage(true) +} + +const handleCancelEdit = () => { + setNewGiftMessage(giftMessage || "") + setIsEditingGiftMessage(false) +} +``` + +You define the following functions: + +- `handleGiftToggle`: Used when the gift checkbox is toggled. It updates the cart item's metadata to set the `is_gift` and `gift_message` properties based on the checkbox state. +- `handleSaveGiftMessage`: Used to save the gift message when the customer clicks the "Save" button. It updates the cart item's metadata with the new gift message. +- `handleStartEdit`: Used to start editing the gift message input by setting the `isEditingGiftMessage` state to `true`. +- `handleCancelEdit`: Used to cancel the gift message editing and reset the input value to the current gift message. + +Finally, you'll change the `return` statement to include the gift inputs. Replace the existing return statement with the following: + +```tsx title="src/modules/cart/components/item/index.tsx" badgeLabel="Storefront" badgeColor="blue" +return ( +
+
+ {/* Product Image */} +
+ + + +
+ + {/* Product Details */} +
+
+
+ + {item.product_title} + + +
+
+ + {/* Gift Options */} +
+
+ + +
+ + {isGift && ( +
+ {isEditingGiftMessage ? ( +
+
+ + Gift Message: + + (optional) +
+