diff --git a/www/apps/resources/app/architectural-modules/locking/page.mdx b/www/apps/resources/app/architectural-modules/locking/page.mdx
index 0dd6c0447a..c14a9bbb2a 100644
--- a/www/apps/resources/app/architectural-modules/locking/page.mdx
+++ b/www/apps/resources/app/architectural-modules/locking/page.mdx
@@ -92,10 +92,10 @@ module.exports = defineConfig({
options: {
providers: [
// add providers here...
- ]
- }
- }
- ]
+ ],
+ },
+ },
+ ],
})
```
diff --git a/www/apps/resources/app/architectural-modules/locking/postgres/page.mdx b/www/apps/resources/app/architectural-modules/locking/postgres/page.mdx
index a80941e39e..03815a4a36 100644
--- a/www/apps/resources/app/architectural-modules/locking/postgres/page.mdx
+++ b/www/apps/resources/app/architectural-modules/locking/postgres/page.mdx
@@ -35,10 +35,10 @@ module.exports = defineConfig({
// and you have other Locking Module Providers registered.
is_default: true,
},
- ]
- }
- }
- ]
+ ],
+ },
+ },
+ ],
})
```
@@ -75,10 +75,10 @@ module.exports = defineConfig({
id: "locking-postgres",
is_default: true,
},
- ]
- }
- }
- ]
+ ],
+ },
+ },
+ ],
})
```
diff --git a/www/apps/resources/app/architectural-modules/locking/redis/page.mdx b/www/apps/resources/app/architectural-modules/locking/redis/page.mdx
index 71a6550495..a9461d9b6e 100644
--- a/www/apps/resources/app/architectural-modules/locking/redis/page.mdx
+++ b/www/apps/resources/app/architectural-modules/locking/redis/page.mdx
@@ -41,12 +41,12 @@ module.exports = defineConfig({
is_default: true,
options: {
redisUrl: process.env.LOCKING_REDIS_URL,
- }
+ },
},
- ]
- }
- }
- ]
+ ],
+ },
+ },
+ ],
})
```
@@ -257,12 +257,12 @@ module.exports = defineConfig({
is_default: true,
options: {
// ...
- }
+ },
},
- ]
- }
- }
- ]
+ ],
+ },
+ },
+ ],
})
```
diff --git a/www/apps/resources/app/nextjs-starter/guides/revalidate-cache/page.mdx b/www/apps/resources/app/nextjs-starter/guides/revalidate-cache/page.mdx
new file mode 100644
index 0000000000..5f78cd3af8
--- /dev/null
+++ b/www/apps/resources/app/nextjs-starter/guides/revalidate-cache/page.mdx
@@ -0,0 +1,88 @@
+export const metadata = {
+ title: `Revalidate Cache in Next.js Starter Storefront`,
+}
+
+# {metadata.title}
+
+In this guide, you'll learn about the general approach to revalidating cache in the Next.js Starter Storefront when data is updated in the Medusa application.
+
+## Approach Overview
+
+By default, the data that the Next.js Starter Storefront retrieves from the Medusa application is cached in the browser. This cache is used to improve the performance and speed of the storefront.
+
+In some cases, you may need to revalidate the cache in the storefront when data is updated in the Medusa application. For example, when a product variant's price is updated in the Medusa application, you may want to revalidate the cache in the storefront to reflect the updated price.
+
+You're free to choose the approach that works for your use case, custom requirements, and tech stack. The approach that Medusa recommends is:
+
+1. Create a [subscriber](!docs!/learn/fundamentals/events-and-subscribers) in the Medusa application that listens for the event that triggers the data update. For example, you can listen to the `product.updated` event.
+2. In the subscriber, send a request to a custom endpoint in the Next.js Starter Storefront to trigger the cache revalidation.
+3. Create the custom endpoint in the Next.js Starter Storefront that listens for the request from the subscriber and revalidates the cache.
+
+
+
+Refer to the [Events Reference](../../../events-reference/page.mdx) for a full list of events that the Medusa application emits.
+
+
+
+---
+
+## Example: Revalidating Cache for Product Update
+
+Consider you want to revalidate the cache in the Next.js Starter Storefront whenever a product is updated.
+
+Start by creating the following subscriber in the Medusa application:
+
+```ts
+import type {
+ SubscriberArgs,
+ SubscriberConfig,
+} from "@medusajs/framework"
+
+export default async function productUpdatedHandler({
+ event: { data },
+ container,
+}: SubscriberArgs<{ id: string }>) {
+ // send request to next.js storefront to revalidate cache
+ await fetch(`${process.env.STOREFRONT_URL}/api/revalidate?tags=products`)
+}
+
+export const config: SubscriberConfig = {
+ event: "product.updated",
+}
+```
+
+In the subscriber, you send a request to the custom endpoint `/api/revalidate` in the Next.js Starter Storefront. The request includes the query parameter `tags=product-${data.id}` to specify the cache that needs to be revalidated.
+
+
+
+Make sure to set the `STOREFRONT_URL` environment variable in the Medusa application to the URL of the Next.js Starter Storefront.
+
+
+
+Then, create in the Next.js Starter Storefront the custom endpoint that listens for the request and revalidates the cache:
+
+```ts title="src/app/api/revalidate/route.ts"
+import { NextRequest, NextResponse } from "next/server"
+import { revalidateTag } from "next/cache"
+import { getCacheTag } from "../../../lib/data/cookies"
+
+export async function GET(req: NextRequest) {
+ const searchParams = req.nextUrl.searchParams
+ const tags = searchParams.get("tags") as string
+
+ if (!tags) {
+ return NextResponse.json({ error: "No tags provided" }, { status: 400 })
+ }
+
+ const tagsArray = tags.split(",")
+ await Promise.all(
+ tagsArray.map(async (tag) => {
+ const cacheTag = await getCacheTag(tag)
+ // revalidate cache for the tag
+ revalidateTag(cacheTag)
+ })
+ )
+
+ return NextResponse.json({ message: "Revalidated" }, { status: 200 })
+}
+```
diff --git a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx
index 79b2b8314a..5f0878a789 100644
--- a/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx
+++ b/www/apps/resources/app/recipes/marketplace/examples/vendors/page.mdx
@@ -585,7 +585,7 @@ export const vendorWorkflowHighlights = [
```ts title="src/workflows/marketplace/create-vendor/index.ts" highlights={vendorWorkflowHighlights}
import {
createWorkflow,
- WorkflowResponse
+ WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import {
setAuthAppMetadataStep,
@@ -617,7 +617,7 @@ const createVendorWorkflow = createWorkflow(
const vendorAdminData = transform({
input,
- vendor
+ vendor,
}, (data) => {
return {
...data.input.admin,
@@ -701,13 +701,13 @@ export const vendorRouteSchemaHighlights = [
```ts title="src/api/vendors/route.ts" highlights={vendorRouteSchemaHighlights}
import {
AuthenticatedMedusaRequest,
- MedusaResponse
+ MedusaResponse,
} from "@medusajs/framework/http"
import { MedusaError } from "@medusajs/framework/utils"
import { z } from "zod"
import createVendorWorkflow, {
- CreateVendorWorkflowInput
-} from "../../workflows/marketplace/create-vendor";
+ CreateVendorWorkflowInput,
+} from "../../workflows/marketplace/create-vendor"
export const PostVendorCreateSchema = z.object({
name: z.string(),
@@ -716,8 +716,8 @@ export const PostVendorCreateSchema = z.object({
admin: z.object({
email: z.string(),
first_name: z.string().optional(),
- last_name: z.string().optional()
- }).strict()
+ last_name: z.string().optional(),
+ }).strict(),
}).strict()
type RequestBody = z.infer
@@ -750,7 +750,7 @@ export const POST = async (
input: {
...vendorData,
authIdentityId: req.auth_context.auth_identity_id,
- } as CreateVendorWorkflowInput
+ } as CreateVendorWorkflowInput,
})
res.json({
@@ -788,7 +788,7 @@ You define middlewares in Medusa in the `src/api/middlewares.ts` special file. S
import {
defineMiddlewares,
authenticate,
- validateAndTransformBody
+ validateAndTransformBody,
} from "@medusajs/framework/http"
import { PostVendorCreateSchema } from "./vendors/route"
@@ -960,12 +960,12 @@ import { CreateProductWorkflowInputDTO } from "@medusajs/framework/types"
import {
createWorkflow,
transform,
- WorkflowResponse
+ WorkflowResponse,
} from "@medusajs/framework/workflows-sdk"
import {
createProductsWorkflow,
createRemoteLinkStep,
- useQueryGraphStep
+ useQueryGraphStep,
} from "@medusajs/medusa/core-flows"
import { MARKETPLACE_MODULE } from "../../../modules/marketplace"
import { Modules } from "@medusajs/framework/utils"
@@ -988,22 +988,22 @@ const createVendorProductWorkflow = createWorkflow(
const productData = transform({
input,
- stores
+ stores,
}, (data) => {
return {
products: [{
...data.input.product,
sales_channels: [
{
- id: data.stores[0].default_sales_channel_id
- }
- ]
- }]
+ id: data.stores[0].default_sales_channel_id,
+ },
+ ],
+ }],
}
})
const createdProducts = createProductsWorkflow.runAsStep({
- input: productData
+ input: productData,
})
// TODO link vendor and products
@@ -1029,23 +1029,23 @@ const { data: vendorAdmins } = useQueryGraphStep({
entity: "vendor_admin",
fields: ["vendor.id"],
filters: {
- id: input.vendor_admin_id
- }
+ id: input.vendor_admin_id,
+ },
}).config({ name: "retrieve-vendor-admins" })
const linksToCreate = transform({
input,
createdProducts,
- vendorAdmins
+ vendorAdmins,
}, (data) => {
return data.createdProducts.map((product) => {
return {
[MARKETPLACE_MODULE]: {
- vendor_id: data.vendorAdmins[0].vendor.id
+ vendor_id: data.vendorAdmins[0].vendor.id,
},
[Modules.PRODUCT]: {
- product_id: product.id
- }
+ product_id: product.id,
+ },
}
})
})
@@ -1056,12 +1056,12 @@ const { data: products } = useQueryGraphStep({
entity: "product",
fields: ["*", "variants.*"],
filters: {
- id: createdProducts[0].id
- }
+ id: createdProducts[0].id,
+ },
}).config({ name: "retrieve-products" })
return new WorkflowResponse({
- product: products[0]
+ product: products[0],
})
```
@@ -1086,8 +1086,8 @@ Create the file `src/api/vendors/products/route.ts` with the following content:
```ts title="src/api/vendors/products/route.ts"
import {
AuthenticatedMedusaRequest,
- MedusaResponse
-} from "@medusajs/framework/http";
+ MedusaResponse,
+} from "@medusajs/framework/http"
import {
HttpTypes,
} from "@medusajs/framework/types"
@@ -1101,12 +1101,12 @@ export const POST = async (
.run({
input: {
vendor_admin_id: req.auth_context.actor_id,
- product: req.validatedBody
- }
+ product: req.validatedBody,
+ },
})
res.json({
- product: result.product
+ product: result.product,
})
}
```
@@ -1133,9 +1133,9 @@ export default defineMiddlewares({
method: ["POST"],
middlewares: [
validateAndTransformBody(AdminCreateProduct),
- ]
- }
- ]
+ ],
+ },
+ ],
})
```
@@ -1202,7 +1202,7 @@ To create the API route that retrieves the vendor’s products, add the followin
```ts title="src/api/vendors/products/route.ts"
// other imports...
import {
- ContainerRegistrationKeys
+ ContainerRegistrationKeys,
} from "@medusajs/framework/utils"
export const GET = async (
@@ -1217,13 +1217,13 @@ export const GET = async (
filters: {
id: [
// ID of the authenticated vendor admin
- req.auth_context.actor_id
+ req.auth_context.actor_id,
],
},
})
res.json({
- products: vendorAdmin.vendor.products
+ products: vendorAdmin.vendor.products,
})
}
```
@@ -1339,8 +1339,8 @@ const groupVendorItemsStep = createStep(
entity: "product",
fields: ["vendor.*"],
filters: {
- id: [item.product_id]
- }
+ id: [item.product_id],
+ },
})
const vendorId = product.vendor?.id
@@ -1350,12 +1350,12 @@ const groupVendorItemsStep = createStep(
}
vendorsItems[vendorId] = [
...(vendorsItems[vendorId] || []),
- item
+ item,
]
}))
return new StepResponse({
- vendorsItems
+ vendorsItems,
})
}
)
@@ -1815,7 +1815,7 @@ export const getOrderHighlights = [
]
```ts title="src/api/vendors/orders/route.ts" highlights={getOrderHighlights}
-import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http";
+import { AuthenticatedMedusaRequest, MedusaResponse } from "@medusajs/framework/http"
import { ContainerRegistrationKeys } from "@medusajs/framework/utils"
import { getOrdersListWorkflow } from "@medusajs/medusa/core-flows"
@@ -1829,8 +1829,8 @@ export const GET = async (
entity: "vendor_admin",
fields: ["vendor.orders.*"],
filters: {
- id: [req.auth_context.actor_id]
- }
+ id: [req.auth_context.actor_id],
+ },
})
const { result: orders } = await getOrdersListWorkflow(req.scope)
@@ -1854,14 +1854,14 @@ export const GET = async (
],
variables: {
filters: {
- id: vendorAdmin.vendor.orders.map((order) => order.id)
- }
- }
- }
+ id: vendorAdmin.vendor.orders.map((order) => order.id),
+ },
+ },
+ },
})
res.json({
- orders
+ orders,
})
}
```
diff --git a/www/apps/resources/app/storefront-development/cart/totals/page.mdx b/www/apps/resources/app/storefront-development/cart/totals/page.mdx
new file mode 100644
index 0000000000..8e7aab4c54
--- /dev/null
+++ b/www/apps/resources/app/storefront-development/cart/totals/page.mdx
@@ -0,0 +1,140 @@
+---
+tags:
+ - cart
+ - storefront
+---
+
+import { CodeTabs, CodeTab, Table } from "docs-ui"
+
+export const metadata = {
+ title: `Show Cart Totals`,
+}
+
+# {metadata.title}
+
+In this guide, you'll learn how to show the cart totals in the checkout flow. This is usually shown as part of the checkout and cart pages.
+
+## Cart Total Fields
+
+The `Cart` object has various fields related to its totals, which you can check out in the [Store API reference](!api!/store#carts_cart_schema).
+
+The fields that are most commonly used are:
+
+
+
+
+ Field
+ Description
+
+
+
+
+
+ `subtotal`
+
+
+ The cart's subtotal excluding taxes and shipping, and including discounts.
+
+
+
+
+ `discount_total`
+
+
+ The total discounts or promotions applied to the cart.
+
+
+
+
+ `shipping_total`
+
+
+ The total shipping cost.
+
+
+
+
+ `tax_total`
+
+
+ The total tax amount.
+
+
+
+
+ `total`
+
+
+ The total amount of the cart including all taxes, shipping, and discounts.
+
+
+
+
+
+---
+
+## Example: React Storefront
+
+Here's an example of how you can show the cart totals in a React component:
+
+export const highlights = [
+ ["3", "useCart", "The `useCart` hook was defined in the Cart React Context documentation."],
+ ["8", "formatPrice", "A function to format a price using the `Intl.NumberFormat` API."],
+ ["23", "formatPrice", "Show the cart's subtotal"],
+ ["27", "formatPrice", "Show the total discounts"],
+ ["31", "formatPrice", "Show the shipping total"],
+ ["35", "formatPrice", "Show the tax total"],
+ ["39", "formatPrice", "Show the total amount"],
+]
+
+```tsx highlights={highlights}
+"use client" // include with Next.js 13+
+
+import { useCart } from "../../../providers/cart"
+
+export default function CartTotals() {
+ const { cart } = useCart()
+
+ const formatPrice = (amount: number): string => {
+ return new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: cart?.currency_code,
+ })
+ .format(amount)
+ }
+
+ return (
+
+ )
+}
+```
+
+In the example, you first retrieve the cart using the [Cart Context](../context/page.mdx). Then, you define the [formatPrice](../retrieve/page.mdx#format-prices) function to format the total amounts.
+
+Finally, you render the cart totals in a list, showing the subtotal, discounts, shipping, taxes, and the total amount.
diff --git a/www/apps/resources/app/storefront-development/checkout/order-confirmation/page.mdx b/www/apps/resources/app/storefront-development/checkout/order-confirmation/page.mdx
new file mode 100644
index 0000000000..cccc8cab10
--- /dev/null
+++ b/www/apps/resources/app/storefront-development/checkout/order-confirmation/page.mdx
@@ -0,0 +1,248 @@
+---
+tags:
+ - order
+ - storefront
+---
+
+import { CodeTabs, CodeTab, Table } from "docs-ui"
+
+export const metadata = {
+ title: `Order Confirmation in Storefront`,
+}
+
+# {metadata.title}
+
+After the customer completes the checkout process and places an order, you can show an order confirmation page to display the order details.
+
+In this guide, you'll learn how to show the different order details on the order confirmation page.
+
+## Retrieve Order Details
+
+To show the order details, you need to retrieve the order by sending a request to the [Get an Order API route](!api!store#orders_getordersid).
+
+You need the order's ID to retrieve the order. You can pass it from the [complete cart step](../complete-cart/page.mdx) or store it in the `localStorage`.
+
+The following example assumes you already have the order ID:
+
+
+
+
+```ts
+// orderId is the order ID which you can get from the complete cart step
+fetch(`http://localhost:9000/store/orders/${orderId}`, {
+ credentials: "include",
+ headers: {
+ "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
+ },
+})
+.then((res) => res.json())
+.then(({ order }) => {
+ // use order...
+ console.log(order)
+})
+```
+
+
+
+
+```tsx
+"use client" // include with Next.js 13+
+
+import { HttpTypes } from "@medusajs/types"
+import { useEffect } from "react"
+import { useState } from "react"
+
+export function OrderConfirmation({ id }: { id: string }) {
+ const [order, setOrder] = useState()
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ fetch(`http://localhost:9000/store/orders/${id}`, {
+ credentials: "include",
+ headers: {
+ "x-publishable-api-key": process.env.NEXT_PUBLIC_MEDUSA_PUBLISHABLE_KEY || "temp",
+ },
+ })
+ .then((res) => res.json())
+ .then(({ order: dataOrder }) => {
+ setOrder(dataOrder)
+ setLoading(false)
+ })
+ }, [id])
+
+ return (
+
+ )
+}
+```
+
+
+
+
+In the above example, you retrieve the order's details from the [Get an Order API route](!api!store#orders_getordersid). Then, in the React example, you show the order details like the order ID, order date, and customer email.
+
+The rest of this guide will expand on the React example to show more order details.
+
+
+
+Refer to the [Order schema in the API reference](!api!/store#orders_order_schema) for all the available order fields.
+
+
+
+---
+
+## Show Order Items
+
+An order has an `items` field that contains the order items. You can show the order items on the order confirmation page.
+
+For example, add to the React component a `formatPrice` function to format prices with the order's currency:
+
+```tsx
+const formatPrice = (amount: number): string => {
+ return new Intl.NumberFormat("en-US", {
+ style: "currency",
+ currency: order?.currency_code,
+ })
+ .format(amount)
+}
+```
+
+Since this is the same function used to format the prices of products and cart totals, you can define the function in one place and re-use it where necessary. In that case, make sure to pass the currency code as a parameter.
+
+Then, you can show the order items in a list:
+
+```tsx
+return (
+
+ {item.title} - {item.quantity} x {formatPrice(item.unit_price)}
+
+ ))}
+
+
+ {/* TODO show more details */}
+
+ )}
+
+)
+```
+
+In the above example, you show the order items in a list, displaying the item's title, quantity, and unit price formatted with the `formatPrice` function.
+
+---
+
+## Show Order Totals
+
+An order has various fields for the order totals, which you can check out in the [Order schema in the Store API reference](https://docs.medusajs.com/api/store#orders_order_schema). The most commonly used fields are:
+
+
+
+
+ Field
+ Description
+
+
+
+
+
+ `subtotal`
+
+
+ The order's subtotal excluding taxes and shipping, and including discounts.
+
+
+
+
+ `discount_total`
+
+
+ The total discounts or promotions applied to the order.
+
+
+
+
+ `shipping_total`
+
+
+ The total shipping cost.
+
+
+
+
+ `tax_total`
+
+
+ The total tax amount.
+
+
+
+
+ `total`
+
+
+ The total amount of the order including all taxes, shipping, and discounts.
+
+
+
+
+
+You can show these totals on the order confirmation page. For example:
+
+```tsx
+return (
+