diff --git a/www/apps/api-reference/components/Navbar/index.tsx b/www/apps/api-reference/components/Navbar/index.tsx
index 4d23a33937..b07a28e905 100644
--- a/www/apps/api-reference/components/Navbar/index.tsx
+++ b/www/apps/api-reference/components/Navbar/index.tsx
@@ -41,6 +41,7 @@ const Navbar = () => {
}}
additionalActionsBefore={}
additionalActionsAfter={}
+ showSearchOpener
isLoading={isLoading}
/>
)
diff --git a/www/apps/api-reference/next.config.mjs b/www/apps/api-reference/next.config.mjs
index 98453f84fe..0cd5c80730 100644
--- a/www/apps/api-reference/next.config.mjs
+++ b/www/apps/api-reference/next.config.mjs
@@ -12,6 +12,7 @@ const nextConfig = {
destination: `${
process.env.NEXT_PUBLIC_DOCS_URL || "https://localhost:3001"
}/:path*`,
+ basePath: false,
},
],
}
diff --git a/www/apps/book/app/advanced-development/data-models/index/page.mdx b/www/apps/book/app/advanced-development/data-models/index/page.mdx
index 2f392f78a4..3fc7bf7c3a 100644
--- a/www/apps/book/app/advanced-development/data-models/index/page.mdx
+++ b/www/apps/book/app/advanced-development/data-models/index/page.mdx
@@ -53,7 +53,7 @@ import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id().primaryKey(),
name: model.text(),
- age: model.number()
+ age: model.number(),
}).indexes([
{
on: ["name", "age"],
@@ -82,13 +82,13 @@ import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id().primaryKey(),
name: model.text(),
- age: model.number()
+ age: model.number(),
}).indexes([
{
on: ["name", "age"],
where: {
- age: 30
- }
+ age: 30,
+ },
},
])
@@ -111,15 +111,15 @@ import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id().primaryKey(),
name: model.text(),
- age: model.number().nullable()
+ age: model.number().nullable(),
}).indexes([
{
on: ["name", "age"],
where: {
age: {
- $ne: null
- }
- }
+ $ne: null,
+ },
+ },
},
])
@@ -146,11 +146,11 @@ import { model } from "@medusajs/utils"
const MyCustom = model.define("my_custom", {
id: model.id().primaryKey(),
name: model.text(),
- age: model.number()
+ age: model.number(),
}).indexes([
{
on: ["name", "age"],
- unique: true
+ unique: true,
},
])
diff --git a/www/apps/book/app/basics/events-and-subscribers/page.mdx b/www/apps/book/app/basics/events-and-subscribers/page.mdx
index dd22178390..a9c8300ed9 100644
--- a/www/apps/book/app/basics/events-and-subscribers/page.mdx
+++ b/www/apps/book/app/basics/events-and-subscribers/page.mdx
@@ -41,25 +41,6 @@ A subscriber file must export:
The above subscriber listens to the `product.created` event. Whenever the event is emitted, it logs in the terminal `A product is created`.
-{/* TODO add when we have the admin dashboard to use with V2 for easy testing. */}
-
-{/\* ### Test Subscriber
-
-To test the subscriber, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Then, create a product using the Medusa Admin dashboard. Afterward, you’ll see the following messages logged in the terminal:
-
-```bash
-info: Processing product.created which has 1 subscribers
-A product is created
-```
-
-The first message indicates that the `product.created` event was emitted, and the second one is the message logged from the subscriber. \*/}
-
---
## When to Use Subscribers
diff --git a/www/apps/book/components/Navbar/index.tsx b/www/apps/book/components/Navbar/index.tsx
index f6550b9dba..bd19bdbfc7 100644
--- a/www/apps/book/components/Navbar/index.tsx
+++ b/www/apps/book/components/Navbar/index.tsx
@@ -31,6 +31,7 @@ const Navbar = () => {
mobileSidebarOpen,
}}
isLoading={false}
+ showSearchOpener
/>
)
}
diff --git a/www/apps/book/next.config.mjs b/www/apps/book/next.config.mjs
index b7f82a6899..f23fd45380 100644
--- a/www/apps/book/next.config.mjs
+++ b/www/apps/book/next.config.mjs
@@ -108,6 +108,13 @@ const nextConfig = {
}/v2/resources/:path*`,
basePath: false,
},
+ {
+ source: "/v2/api/:path*",
+ destination: `${
+ process.env.NEXT_PUBLIC_API_URL || "https://localhost:3001"
+ }/v2/api/:path*`,
+ basePath: false,
+ },
// TODO comment out once we have the user guide published
// {
// source: "/user-guide",
@@ -122,7 +129,7 @@ const nextConfig = {
{
source: "/:path*",
destination: `${
- process.env.NEXT_PUBLIC_DOCS_V1_URL || "https://localhost:3000"
+ process.env.NEXT_PUBLIC_API_V1_URL || "https://localhost:3001"
}/:path*`,
basePath: false,
},
diff --git a/www/apps/resources/app/events-reference/page.mdx b/www/apps/resources/app/_events-reference/page.mdx
similarity index 100%
rename from www/apps/resources/app/events-reference/page.mdx
rename to www/apps/resources/app/_events-reference/page.mdx
diff --git a/www/apps/resources/app/architectural-modules/notification/send-notification/page.mdx b/www/apps/resources/app/architectural-modules/notification/send-notification/page.mdx
index 49a1f13a2f..b35b7f3d6b 100644
--- a/www/apps/resources/app/architectural-modules/notification/send-notification/page.mdx
+++ b/www/apps/resources/app/architectural-modules/notification/send-notification/page.mdx
@@ -91,6 +91,4 @@ The `create` method accepts an object or an array of objects having the followin
sectionTitle="Use the Create Method"
/>
-{/* TODO add once reference for type once generated */}
-
-{/* For a full list of properties accepted, refer to [this guide](/references/notification-provider-module#create). */}
+For a full list of properties accepted, refer to [this guide](/references/notification-provider-module#create).
diff --git a/www/apps/resources/app/commerce-modules/api-key/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/api-key/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/api-key/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/api-key/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/api-key/events/page.mdx b/www/apps/resources/app/commerce-modules/api-key/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/api-key/events/page.mdx
rename to www/apps/resources/app/commerce-modules/api-key/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/auth/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/auth/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/auth/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/auth/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/auth/events/page.mdx b/www/apps/resources/app/commerce-modules/auth/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/auth/events/page.mdx
rename to www/apps/resources/app/commerce-modules/auth/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/cart/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/cart/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/cart/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/cart/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/cart/events/page.mdx b/www/apps/resources/app/commerce-modules/cart/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/cart/events/page.mdx
rename to www/apps/resources/app/commerce-modules/cart/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/currency/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/currency/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/currency/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/currency/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/currency/events/page.mdx b/www/apps/resources/app/commerce-modules/currency/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/currency/events/page.mdx
rename to www/apps/resources/app/commerce-modules/currency/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/customer/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/customer/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/customer/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/customer/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/customer/events/page.mdx b/www/apps/resources/app/commerce-modules/customer/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/customer/events/page.mdx
rename to www/apps/resources/app/commerce-modules/customer/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/fulfillment/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/fulfillment/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/fulfillment/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/fulfillment/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/fulfillment/events/page.mdx b/www/apps/resources/app/commerce-modules/fulfillment/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/fulfillment/events/page.mdx
rename to www/apps/resources/app/commerce-modules/fulfillment/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/inventory/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/inventory/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/inventory/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/inventory/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/inventory/events/page.mdx b/www/apps/resources/app/commerce-modules/inventory/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/inventory/events/page.mdx
rename to www/apps/resources/app/commerce-modules/inventory/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/order/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/order/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/order/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/order/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/order/events/page.mdx b/www/apps/resources/app/commerce-modules/order/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/order/events/page.mdx
rename to www/apps/resources/app/commerce-modules/order/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/payment/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/payment/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/payment/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/payment/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/payment/events/page.mdx b/www/apps/resources/app/commerce-modules/payment/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/payment/events/page.mdx
rename to www/apps/resources/app/commerce-modules/payment/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/pricing/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/pricing/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/pricing/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/pricing/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/pricing/events/page.mdx b/www/apps/resources/app/commerce-modules/pricing/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/pricing/events/page.mdx
rename to www/apps/resources/app/commerce-modules/pricing/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/product/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/product/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/product/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/product/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/product/events/page.mdx b/www/apps/resources/app/commerce-modules/product/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/product/events/page.mdx
rename to www/apps/resources/app/commerce-modules/product/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/promotion/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/promotion/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/promotion/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/promotion/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/promotion/events/page.mdx b/www/apps/resources/app/commerce-modules/promotion/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/promotion/events/page.mdx
rename to www/apps/resources/app/commerce-modules/promotion/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/region/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/region/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/region/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/region/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/region/events/page.mdx b/www/apps/resources/app/commerce-modules/region/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/region/events/page.mdx
rename to www/apps/resources/app/commerce-modules/region/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/sales-channel/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/sales-channel/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/sales-channel/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/sales-channel/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/sales-channel/events/page.mdx b/www/apps/resources/app/commerce-modules/sales-channel/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/sales-channel/events/page.mdx
rename to www/apps/resources/app/commerce-modules/sales-channel/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/stock-location/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/stock-location/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/stock-location/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/stock-location/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/stock-location/events/page.mdx b/www/apps/resources/app/commerce-modules/stock-location/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/stock-location/events/page.mdx
rename to www/apps/resources/app/commerce-modules/stock-location/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/store/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/store/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/store/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/store/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/store/events/page.mdx b/www/apps/resources/app/commerce-modules/store/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/store/events/page.mdx
rename to www/apps/resources/app/commerce-modules/store/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/tax/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/tax/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/tax/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/tax/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/tax/events/page.mdx b/www/apps/resources/app/commerce-modules/tax/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/tax/events/page.mdx
rename to www/apps/resources/app/commerce-modules/tax/_events/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/user/events/_events-table/page.mdx b/www/apps/resources/app/commerce-modules/user/_events/_events-table/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/user/events/_events-table/page.mdx
rename to www/apps/resources/app/commerce-modules/user/_events/_events-table/page.mdx
diff --git a/www/apps/resources/app/commerce-modules/user/events/page.mdx b/www/apps/resources/app/commerce-modules/user/_events/page.mdx
similarity index 100%
rename from www/apps/resources/app/commerce-modules/user/events/page.mdx
rename to www/apps/resources/app/commerce-modules/user/_events/page.mdx
diff --git a/www/apps/resources/app/recipes/b2b/page.mdx b/www/apps/resources/app/recipes/b2b/page.mdx
index ca0037ab7d..d958d4af94 100644
--- a/www/apps/resources/app/recipes/b2b/page.mdx
+++ b/www/apps/resources/app/recipes/b2b/page.mdx
@@ -36,28 +36,26 @@ You can create a sales channel through the Medusa Admin or Admin REST APIs.
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#sales-channels_postsaleschannels",
- title: "Option 2: Using the REST APIs",
- text: "Create the sales channel using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#sales-channels_postsaleschannels",
+ title: "Option 2: Using the REST APIs",
+ text: "Create the sales channel using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -76,55 +74,51 @@ You can create a publishable API key through the Medusa Admin or the Admin REST
### Create Key
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#api-keys_postapikeys",
- title: "Option 2: Using the REST APIs",
- text: "Create the publishable API key using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#api-keys_postapikeys",
+ title: "Option 2: Using the REST APIs",
+ text: "Create the publishable API key using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
### Associate Key with Sales Channel
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "#",
- title: "Option 2: Using the REST APIs",
- text: "Associate the publishable API key with the B2B sales channel using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "#",
+ title: "Option 2: Using the REST APIs",
+ text: "Associate the publishable API key with the B2B sales channel using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -136,55 +130,51 @@ You can create new products or add existing ones to the B2B sales channel using
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#products_postproducts",
- title: "Using REST APIs",
- text: "Create products using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#products_postproducts",
+ title: "Using REST APIs",
+ text: "Create products using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
### Add Products to Sales Channel
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#sales-channels_postsaleschannelsidproductsbatchadd",
- title: "Using REST APIs",
- text: "Add the products to the B2B sales channel using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#sales-channels_postsaleschannelsidproductsbatchadd",
+ title: "Using REST APIs",
+ text: "Add the products to the B2B sales channel using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -202,308 +192,316 @@ Module Relationships is coming soon.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/data-models",
- title: "Create Data Models",
- text: "Learn how to create data models.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/data-models",
+ title: "Create Data Models",
+ text: "Learn how to create data models.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-{/_ }
-showLinkIcon={false}
-className="mt-1"
-/> _/}
+{/* }
+ showLinkIcon={false}
+ className="mt-1"
+/> */}
-{/\*
-In this section, you'll create a B2B module that has a `Company` data model. The `Company` data model has a relationship to the `CustomerGroup` data model of the Customer Module.
+{/*
+ In this section, you'll create a B2B module that has a `Company` data model. The `Company` data model has a relationship to the `CustomerGroup` data model of the Customer Module.
-Start by creating the `src/modules/b2b` directory.
+ Start by creating the `src/modules/b2b` directory.
-Then, create the file `src/modules/b2b/models/company.ts` with the following content:
+ Then, create the file `src/modules/b2b/models/company.ts` with the following content:
-```ts title="src/modules/b2b/models/company.ts" highlights={[["8", "", "The property will be used to create a relationship to customer groups."]]}
-import { model } from "@medusajs/utils"
+ ```ts title="src/modules/b2b/models/company.ts" highlights={[["8", "", "The property will be used to create a relationship to customer groups."]]}
+ import { model } from "@medusajs/utils"
-const Company = model.define("company", {
- id: model.id().primaryKey(),
- name: model.text(),
- city: model.text(),
- country_code: model.text(),
- customer_group_id: model.text().nullable(),
-})
+ const Company = model.define("company", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ city: model.text(),
+ country_code: model.text(),
+ customer_group_id: model.text().nullable(),
+ })
-export default Company
-```
+ export default Company
+ ```
-This creates a `Company` data model with some relevant properties. Most importantly, it has a `customer_group_id` property. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
+ This creates a `Company` data model with some relevant properties. Most importantly, it has a `customer_group_id` property. It'll later be used when creating the relationship to the `CustomerGroup` data model in the Customer Module.
-Next, create the migration in the file `src/modules/b2b/migrations/Migration20240516081502.ts` with the following content:
+ Next, create the migration in the file `src/modules/b2b/migrations/Migration20240516081502.ts` with the following content:
-```ts title="src/modules/b2b/migrations/Migration20240516081502.ts"
-import { Migration } from "@mikro-orm/migrations"
+ ```ts title="src/modules/b2b/migrations/Migration20240516081502.ts"
+ import { Migration } from "@mikro-orm/migrations"
+
+ export class Migration20240516081502 extends Migration {
-export class Migration20240516081502 extends Migration {
- async up(): Promise {
- this.addSql(
- 'create table if not exists "company" ("id" text not null, "name" text not null, "city" text not null, "country_code" text not null, "customer_group_id" text not null, constraint "company_pkey" primary key ("id"));'
- )
+ async up(): Promise {
+ this.addSql("create table if not exists \"company\" (\"id\" text not null, \"name\" text not null, \"city\" text not null, \"country_code\" text not null, \"customer_group_id\" text not null, constraint \"company_pkey\" primary key (\"id\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"company\" cascade;")
+ }
+ }
+ ```
+
+ You'll run the migration to reflect the data model in the database after finishing the module definition.
+
+ Then, create the module's main service at `src/modules/b2b/service.ts` with the following content:
+
+ ```ts title="src/modules/b2b/service.ts"
+ import { MedusaService } from "@medusajs/utils"
+ import Company from "./models/company"
+
+ class B2bModuleService extends MedusaService({
+ Company,
+ }){
+ // TODO add custom methods
}
- async down(): Promise {
- this.addSql('drop table if exists "company" cascade;')
- }
-}
-```
+ export default B2bModuleService
+ ```
-You'll run the migration to reflect the data model in the database after finishing the module definition.
+ This creates a `B2bModuleService` that extends the service factory, which generates data-management functionalities for the `Company` data model.
-Then, create the module's main service at `src/modules/b2b/service.ts` with the following content:
+ Next, create the module definition at `src/modules/b2b/index.ts` with the following content:
-```ts title="src/modules/b2b/service.ts"
-import { MedusaService } from "@medusajs/utils"
-import Company from "./models/company"
+ ```ts title="src/modules/b2b/index.ts"
+ import B2bModuleService from "./service"
+ import { Module } from "@medusajs/utils"
-class B2bModuleService extends MedusaService({
- Company,
-}) {
- // TODO add custom methods
-}
+ export default Module("b2b", {
+ service: B2bModuleService,
+ })
+ ```
-export default B2bModuleService
-```
+ Finally, add the module to the `modules` object in `medusa-config.js`:
-This creates a `B2bModuleService` that extends the service factory, which generates data-management functionalities for the `Company` data model.
-
-Next, create the module definition at `src/modules/b2b/index.ts` with the following content:
-
-```ts title="src/modules/b2b/index.ts"
-import B2bModuleService from "./service"
-
-export default {
- service: B2bModuleService,
-}
-```
-
-Finally, add the module to the `modules` object in `medusa-config.js`:
-
-```js title="medusa-config.js"
-module.exports = defineConfig({
- // ...
- modules: {
- b2bModuleService: {
- resolve: "./modules/b2b",
- definition: {
- isQueryable: true,
+ ```js title="medusa-config.js"
+ module.exports = defineConfig({
+ // ...
+ modules: {
+ b2bModuleService: {
+ resolve: "./modules/b2b",
+ definition: {
+ isQueryable: true,
+ },
},
},
- },
-})
-```
+ })
+ ```
-You can now run migrations with the following commands:
+ You can now run migrations with the following commands:
-```bash npm2yarn
-npx medusa migrations run
-```
+ ```bash npm2yarn
+ npx medusa migrations run
+ ```
-### Add Create Company API Route
+ ### Add Create Company API Route
-To test out using the B2B Module, you'll add an API route to create a company.
+ To test out using the B2B Module, you'll add an API route to create a company.
-Start by creating the file `src/types/b2b/index.ts` with some helper types:
+ Start by creating the file `src/types/b2b/index.ts` with some helper types:
-```ts title="src/types/b2b/index.ts"
-import { CustomerGroupDTO } from "@medusajs/types"
+ ```ts title="src/types/b2b/index.ts"
+ import { CustomerGroupDTO } from "@medusajs/types"
-export type CompanyDTO = {
- id: string
- name: string
- city: string
- country_code: string
- customer_group_id?: string
- customer_group?: CustomerGroupDTO
-}
+ export type CompanyDTO = {
+ id: string
+ name: string
+ city: string
+ country_code: string
+ customer_group_id?: string
+ customer_group?: CustomerGroupDTO
+ }
-export type CreateCompanyDTO = {
- name: string
- city: string
- country_code: string
- customer_group_id?: string
-}
-```
+ export type CreateCompanyDTO = {
+ name: string
+ city: string
+ country_code: string
+ customer_group_id?: string
+ }
-Then, create the file `src/workflows/create-company.ts` with the following content:
+ ```
+
+ Then, create the file `src/workflows/create-company.ts` with the following content:
export const workflowHighlights = [
- [
- "23",
- "tryToCreateCustomerGroupStep",
- "This step creates the customer group if its data is passed in the `customer_group` property.",
- ],
- [
- "36",
- "createCustomerGroupsWorkflow",
- "Use the `createCustomerGroupsWorkflow` defined by Medusa to create the customer group.",
- ],
- [
- "44",
- "",
- "Set the ID of the new customer group in the `customer_group_id` property so that it's added to the created company.",
- ],
+ ["23", "tryToCreateCustomerGroupStep", "This step creates the customer group if its data is passed in the `customer_group` property."],
+ ["36", "createCustomerGroupsWorkflow", "Use the `createCustomerGroupsWorkflow` defined by Medusa to create the customer group."],
+ ["44", "", "Set the ID of the new customer group in the `customer_group_id` property so that it's added to the created company."],
["54", "createCompanyStep", "This step creates the company."],
]
-```ts title="src/workflows/create-company.ts" highlights={workflowHighlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import {
- StepResponse,
- createStep,
- createWorkflow,
-} from "@medusajs/workflows-sdk"
-import { createCustomerGroupsWorkflow } from "@medusajs/core-flows"
-import { CreateCustomerGroupDTO } from "@medusajs/types"
-import { CompanyDTO, CreateCompanyDTO } from "../types/b2b"
-import B2bModuleService from "../modules/b2b/service"
+ ```ts title="src/workflows/create-company.ts" highlights={workflowHighlights} collapsibleLines="1-12" expandButtonLabel="Show Imports"
+ import {
+ StepResponse,
+ createStep,
+ createWorkflow,
+ } from "@medusajs/workflows-sdk"
+ import {
+ createCustomerGroupsWorkflow,
+ } from "@medusajs/core-flows"
+ import { CreateCustomerGroupDTO } from "@medusajs/types"
+ import { CompanyDTO, CreateCompanyDTO } from "../types/b2b"
+ import B2bModuleService from "../modules/b2b/service"
-export type CreateCompanyWorkflowInput = CreateCompanyDTO & {
- customer_group?: CreateCustomerGroupDTO
-}
+ export type CreateCompanyWorkflowInput = CreateCompanyDTO & {
+ customer_group?: CreateCustomerGroupDTO
+ }
-type CreateCompanyWorkflowOutput = {
- company: CompanyDTO
-}
+ type CreateCompanyWorkflowOutput = {
+ company: CompanyDTO
+ }
-type CreateCustomerGroupStepInput = CreateCompanyWorkflowInput
+ type CreateCustomerGroupStepInput = CreateCompanyWorkflowInput
+
+ const tryToCreateCustomerGroupStep = createStep(
+ "try-to-create-customer-group-step",
+ async (
+ {
+ customer_group,
+ ...company
+ }: CreateCustomerGroupStepInput,
+ { container }) => {
+ if (!customer_group) {
+ return new StepResponse({ company })
+ }
+
+ // create customer group
+ const { result } = await createCustomerGroupsWorkflow(
+ container
+ ).run({
+ input: {
+ customersData: [customer_group],
+ },
+ })
+
+ company.customer_group_id = result[0].id
+
+ return new StepResponse({ company })
+ }
+ )
+
+ export type CreateCompanyStep = {
+ companyData: CreateCompanyDTO
+ }
+
+ const createCompanyStep = createStep(
+ "create-company-step",
+ async (
+ { companyData }: CreateCompanyStep,
+ { container }) => {
+ const b2bModuleService: B2bModuleService = container
+ .resolve(
+ "b2bModuleService"
+ )
+
+ const company = await b2bModuleService.createCompany(
+ companyData
+ )
-const tryToCreateCustomerGroupStep = createStep(
- "try-to-create-customer-group-step",
- async (
- { customer_group, ...company }: CreateCustomerGroupStepInput,
- { container }
- ) => {
- if (!customer_group) {
return new StepResponse({ company })
}
+ )
- // create customer group
- const { result } = await createCustomerGroupsWorkflow(container).run({
- input: {
- customersData: [customer_group],
- },
- })
+ export const createCompanyWorkflow = createWorkflow<
+ CreateCompanyWorkflowInput,
+ CreateCompanyWorkflowOutput
+ >(
+ "create-company",
+ function (input) {
+ const {
+ company: companyData,
+ } = tryToCreateCustomerGroupStep(input)
- company.customer_group_id = result[0].id
+ const company = createCompanyStep({ companyData })
- return new StepResponse({ company })
- }
-)
-
-export type CreateCompanyStep = {
- companyData: CreateCompanyDTO
-}
-
-const createCompanyStep = createStep(
- "create-company-step",
- async ({ companyData }: CreateCompanyStep, { container }) => {
- const b2bModuleService: B2bModuleService =
- container.resolve("b2bModuleService")
-
- const company = await b2bModuleService.createCompany(companyData)
-
- return new StepResponse({ company })
- }
-)
-
-export const createCompanyWorkflow = createWorkflow<
- CreateCompanyWorkflowInput,
- CreateCompanyWorkflowOutput
->("create-company", function (input) {
- const { company: companyData } = tryToCreateCustomerGroupStep(input)
-
- const company = createCompanyStep({ companyData })
-
- return company
-})
-```
-
-You create a workflow with two steps:
-
-1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` property.
-2. The second one creates the company.
-
-Finally, create the file `src/api/admin/b2b/company/route.ts` with the following content:
-
-```ts title="src/api/admin/b2b/company/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
-import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import {
- CreateCompanyWorkflowInput,
- createCompanyWorkflow,
-} from "../../../../workflows/create-company"
-
-type CreateCompanyReq = CreateCompanyWorkflowInput
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const { result } = await createCompanyWorkflow(req.scope).run({
- input: req.body,
- })
-
- res.json({
- company: result.company,
- })
-}
-```
-
-The API route uses the workflow to create the company. It passes the request body as the workflow's input.
-
-### Test API Route
-
-To test the API route, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Next, make sure you authenticate as an admin user as explained in [this Authentication guide](!api!/api/admin#authentication).
-
-Then, send a `POST` request to the `/admin/b2b/company` API route:
-
-```bash
-curl -X POST 'localhost:9000/admin/b2b/company' \
---header 'Content-Type: application/json' \
---header 'Authorization: Bearer {jwt_token}' \
---data '{
- "name": "Acme",
- "city": "London",
- "country_code": "gb",
- "customer_group": {
- "name": "B2B"
+ return company
}
-}'
-```
+ )
+ ```
-This creates a company and its associated customer group.
+ You create a workflow with two steps:
+
+ 1. The first one tries to create a customer group if its data is provided in the `customer_group` property and sets its value in the `customer_group_id` property.
+ 2. The second one creates the company.
+
+ Finally, create the file `src/api/admin/b2b/company/route.ts` with the following content:
+
+ ```ts title="src/api/admin/b2b/company/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
+ import type {
+ MedusaRequest,
+ MedusaResponse,
+ } from "@medusajs/medusa"
+ import {
+ CreateCompanyWorkflowInput,
+ createCompanyWorkflow,
+ } from "../../../../workflows/create-company"
+
+ type CreateCompanyReq = CreateCompanyWorkflowInput
+
+ export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+ ) {
+ const { result } = await createCompanyWorkflow(req.scope)
+ .run({
+ input: req.body,
+ })
+
+ res.json({
+ company: result.company,
+ })
+ }
+ ```
+
+ The API route uses the workflow to create the company. It passes the request body as the workflow's input.
+
+ ### Test API Route
+
+ To test the API route, start the Medusa application:
+
+ ```bash npm2yarn
+ npm run dev
+ ```
+
+ Next, make sure you authenticate as an admin user as explained in [this Authentication guide](!api!/api/admin#authentication).
+
+ Then, send a `POST` request to the `/admin/b2b/company` API route:
+
+ ```bash
+ curl -X POST 'localhost:9000/admin/b2b/company' \
+ --header 'Content-Type: application/json' \
+ --header 'Authorization: Bearer {jwt_token}' \
+ --data '{
+ "name": "Acme",
+ "city": "London",
+ "country_code": "gb",
+ "customer_group": {
+ "name": "B2B"
+ }
+ }'
+ ```
+
+ This creates a company and its associated customer group.
-You can alternatively pass a `customer_group_id` to use an existing customer group.
+ You can alternatively pass a `customer_group_id` to use an existing customer group.
@@ -519,55 +517,51 @@ You can do that through the Medusa Admin or Admin REST APIs.
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#customers_postcustomers",
- title: "Option 2: Using the REST APIs",
- text: "Create the customers using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#customers_postcustomers",
+ title: "Option 2: Using the REST APIs",
+ text: "Create the customers using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
### Assign Customers to Groups
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#customer-groups_postcustomergroupsidcustomersbatch",
- title: "Option 2: Using the REST APIs",
- text: "Assign the customers to the B2B customer group using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#customer-groups_postcustomergroupsidcustomersbatch",
+ title: "Option 2: Using the REST APIs",
+ text: "Assign the customers to the B2B customer group using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -579,28 +573,26 @@ You can create a price list using the Medusa Admin or the Admin REST APIs. Make
{/* TODO add links */}
-,
- showLinkIcon: false,
- badge: {
- variant: "blue",
- children: "Guide Soon",
- },
- },
- {
- href: "!api!/api/admin#price-lists_postpricelists",
- title: "Using REST APIs",
- text: "Create the price list using the REST APIs.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false,
+ badge: {
+ variant: "blue",
+ children: "Guide Soon"
+ }
+ },
+ {
+ href: "!api!/api/admin#price-lists_postpricelists",
+ title: "Using REST APIs",
+ text: "Create the price list using the REST APIs.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -608,7 +600,7 @@ You can create a price list using the Medusa Admin or the Admin REST APIs. Make
To implement a more advanced B2B sales flow, add custom data models such as `Company`, `Employee`, `Admin`, and `Buyer` to your B2B module.
-This provides more granular control of your B2B sales and allows you to build features like privileges, limits, and more.
+This provides more granular control of your B2B sales and allows you to build features like privileges, limits, and more.
-{/\*
+{/*
-For example, create the API route `src/api/store/b2b/check-customer/route.ts` with the following content:
+ For example, create the API route `src/api/store/b2b/check-customer/route.ts` with the following content:
export const checkCustomerHighlights = [
["19", "retrieveCustomer", "Retrieve the customer along with its groups."],
- [
- "26",
- "listCompanies",
- "List the companies that have a customer group ID matching any of the customer's group IDs.",
- ],
- [
- "31",
- "",
- "Return whether there are any companies associated with the customer's groups.",
- ],
+ ["26", "listCompanies", "List the companies that have a customer group ID matching any of the customer's group IDs."],
+ ["31", "", "Return whether there are any companies associated with the customer's groups."]
]
-```ts title="src/api/store/b2b/check-customer/route.ts" highlights={checkCustomerHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
-import type {
- AuthenticatedMedusaRequest,
- MedusaResponse,
-} from "@medusajs/medusa"
-import { ModuleRegistrationName } from "@medusajs/utils"
-import { ICustomerModuleService } from "@medusajs/types"
-import B2bModuleService from "../../../../modules/b2b/service"
+ ```ts title="src/api/store/b2b/check-customer/route.ts" highlights={checkCustomerHighlights} collapsibleLines="1-5" expandButtonLabel="Show Imports"
+ import type {
+ AuthenticatedMedusaRequest,
+ MedusaResponse,
+ } from "@medusajs/medusa"
+ import { ModuleRegistrationName } from "@medusajs/utils"
+ import { ICustomerModuleService } from "@medusajs/types"
+ import B2bModuleService from "../../../../modules/b2b/service"
+
+ export async function GET(
+ req: AuthenticatedMedusaRequest,
+ res: MedusaResponse
+ ) {
+ const customerModuleService: ICustomerModuleService = req
+ .scope.resolve(ModuleRegistrationName.CUSTOMER)
+ const b2bModuleService: B2bModuleService = req.scope.resolve(
+ "b2bModuleService"
+ )
-export async function GET(
- req: AuthenticatedMedusaRequest,
- res: MedusaResponse
-) {
- const customerModuleService: ICustomerModuleService = req.scope.resolve(
- ModuleRegistrationName.CUSTOMER
- )
- const b2bModuleService: B2bModuleService =
- req.scope.resolve("b2bModuleService")
+ const customer = await customerModuleService.retrieveCustomer(
+ req.auth_context.actor_id,
+ {
+ relations: ["groups"],
+ }
+ )
- const customer = await customerModuleService.retrieveCustomer(
- req.auth_context.actor_id,
- {
- relations: ["groups"],
- }
- )
+ const companies = await b2bModuleService.listCompanies({
+ customer_group_id: customer.groups.map((group) => group.id),
+ })
- const companies = await b2bModuleService.listCompanies({
- customer_group_id: customer.groups.map((group) => group.id),
- })
+ res.json({
+ is_b2b: companies.length > 0,
+ })
+ }
+ ```
- res.json({
- is_b2b: companies.length > 0,
- })
-}
-```
+ This creates a `GET` API Route at `/store/b2b/check-customer` that:
-This creates a `GET` API Route at `/store/b2b/check-customer` that:
+ 1. Retrieves the customer along with its groups using the Customer Module's main service.
+ 2. Lists the companies that have a customer group ID matching any of the customer's group IDs.
+ 3. Return an `is_b2b` field whose value is `true` if there are any companies associated with the customer's groups.
-1. Retrieves the customer along with its groups using the Customer Module's main service.
-2. Lists the companies that have a customer group ID matching any of the customer's group IDs.
-3. Return an `is_b2b` field whose value is `true` if there are any companies associated with the customer's groups.
+ Before using the API route, create the file `src/api/middlewares.ts` with the following content:
-Before using the API route, create the file `src/api/middlewares.ts` with the following content:
+ ```ts title="src/api/middlewares.ts"
+ import {
+ MiddlewaresConfig,
+ authenticate,
+ } from "@medusajs/medusa"
-```ts title="src/api/middlewares.ts"
-import { MiddlewaresConfig, authenticate } from "@medusajs/medusa"
+ export const config: MiddlewaresConfig = {
+ routes: [
+ {
+ matcher: "/store/b2b*",
+ middlewares: [
+ authenticate("store", ["bearer", "session"]),
+ ],
+ },
+ ],
+ }
+ ```
-export const config: MiddlewaresConfig = {
- routes: [
- {
- matcher: "/store/b2b*",
- middlewares: [authenticate("store", ["bearer", "session"])],
- },
- ],
-}
-```
+ This ensures that only logged-in customers can use the API route.
-This ensures that only logged-in customers can use the API route.
+ ### Test API Route
-### Test API Route
+ To test out the API route:
-To test out the API route:
+ 1. Start the Medusa application.
+ 2. Obtain an authentication JWT token for a new customer. Do that by sending a `POST` request to the `/auth/store/emailpass` API Route:
-1. Start the Medusa application.
-2. Obtain an authentication JWT token for a new customer. Do that by sending a `POST` request to the `/auth/store/emailpass` API Route:
+ ```bash
+ curl -X POST 'http://localhost:9000/auth/store/emailpass' \
+ -H 'Content-Type: application/json' \
+ --data-raw '{
+ "email": "test@medusajs.com",
+ "password": "supersecret"
+ }'
+ ```
+
+ 3. Send a `POST` request to the `/store/customers` API route that registers the customer. Make sure to pass the authentication JWT token from the previous token in the header:
-```bash
-curl -X POST 'http://localhost:9000/auth/store/emailpass' \
--H 'Content-Type: application/json' \
---data-raw '{
- "email": "test@medusajs.com",
- "password": "supersecret"
-}'
-```
+ ```bash
+ curl -X POST 'http://localhost:9000/store/customers' \
+ -H 'Content-Type: application/json' \
+ -H 'Authorization: Bearer {jwt_token}' \
+ --data-raw '{
+ "email": "test@medusajs.com",
+ "password": "supersecret"
+ }'
+ ```
-3. Send a `POST` request to the `/store/customers` API route that registers the customer. Make sure to pass the authentication JWT token from the previous token in the header:
+ 4. Add the customer to the B2B group as explained in a [previous section](#add-b2b-customers).
+ 5. Send a `GET` request to the `/store/b2b/check-customer` API route you created in this section:
-```bash
-curl -X POST 'http://localhost:9000/store/customers' \
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {jwt_token}' \
---data-raw '{
- "email": "test@medusajs.com",
- "password": "supersecret"
-}'
-```
+ ```bash
+ curl 'http://localhost:9000/store/b2b/check-customer' \
+ --header 'Authorization: Bearer {jwt_token}'
+ ```
-4. Add the customer to the B2B group as explained in a [previous section](#add-b2b-customers).
-5. Send a `GET` request to the `/store/b2b/check-customer` API route you created in this section:
+ You'll receive a JSON response as the following:
-```bash
-curl 'http://localhost:9000/store/b2b/check-customer' \
---header 'Authorization: Bearer {jwt_token}'
-```
-
-You'll receive a JSON response as the following:
-
-```json
-{
- "is_b2b": true
-}
-```
+ ```json
+ {
+ "is_b2b": true
+ }
+ ```
*/}
@@ -765,31 +754,29 @@ Based on your use case, you may need to customize the Medusa Admin to add new wi
The Medusa Admin plugin can be extended to add widgets, new pages, and setting pages.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/advanced-development/admin/ui-routes",
- title: "Create Admin UI Routes",
- text: "Learn how to add new pages to your Medusa Admin.",
- startIcon: ,
- showLinkIcon: false,
- },
- {
- href: "!docs!/advanced-development/admin/setting-pages",
- title: "Create Admin Setting Page",
- text: "Learn how to add new page to the Medusa Admin settings.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/advanced-development/admin/ui-routes",
+ title: "Create Admin UI Routes",
+ text: "Learn how to add new pages to your Medusa Admin.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/advanced-development/admin/setting-pages",
+ title: "Create Admin Setting Page",
+ text: "Learn how to add new page to the Medusa Admin settings.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -799,24 +786,22 @@ Medusa provides a Next.js storefront to use with your application. You can eithe
Use the publishable API key you associated with your B2B sales channel in the storefront to ensure only B2B products are retrieved.
-,
- showLinkIcon: false,
- },
- {
- href: "/storefront-development",
- title: "Storefront Development",
- text: "Find guides for your storefront development.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "/storefront-development",
+ title: "Storefront Development",
+ text: "Find guides for your storefront development.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
}
showLinkIcon={false}
className="mt-1"
-/>
+/>
\ No newline at end of file
diff --git a/www/apps/resources/app/recipes/commerce-automation/page.mdx b/www/apps/resources/app/recipes/commerce-automation/page.mdx
index 0e1fc58fe0..432f3f8061 100644
--- a/www/apps/resources/app/recipes/commerce-automation/page.mdx
+++ b/www/apps/resources/app/recipes/commerce-automation/page.mdx
@@ -36,323 +36,271 @@ To implement sending restock notifications, you can:
-The `inventory-item.updated` event is currently not emitted.
+The `inventory-item.updated` event is currently not emitted.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/data-models",
- title: "Create a Data Model",
- text: "Learn how to create a data model.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/data-models",
+ title: "Create a Data Model",
+ text: "Learn how to create a data model.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/events-and-subscribers",
- title: "Create a Subscriber",
- text: "Learn how to create a subscriber in Medusa.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
- className="mt-1"
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/events-and-subscribers",
+ title: "Create a Subscriber",
+ text: "Learn how to create a subscriber in Medusa.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} className="mt-1" />
-In this example, you'll create a Restock Notification Module with the features explained above.
+ In this example, you'll create a Restock Notification Module with the features explained above.
-### Create Restock Notification Module
+ ### Create Restock Notification Module
-Start by creating the `src/modules/restock-notification` directory.
+ Start by creating the `src/modules/restock-notification` directory.
-Then, create the file `src/modules/restock-notification/models/restock-notification.ts` with the following content:
+ Then, create the file `src/modules/restock-notification/models/restock-notification.ts` with the following content:
export const restockModelHighlights = [
- [
- "5",
- "email",
- "The email of the customer to send the notification to when the item is restocked.",
- ],
+ ["5", "email", "The email of the customer to send the notification to when the item is restocked."],
["6", "variant_id", "The ID of the variant the customer is subscribed to."],
- [
- "7",
- "sales_channel_id",
- "The ID of the sales channel the customer is viewing the product variant from.",
- ],
+ ["7", "sales_channel_id", "The ID of the sales channel the customer is viewing the product variant from."]
]
-```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights}
-import { model } from "@medusajs/utils"
+ ```ts title="src/modules/restock-notification/models/restock-notification.ts" highlights={restockModelHighlights}
+ import { model } from "@medusajs/utils"
-const RestockNotification = model.define("restock_notification", {
- id: model.id().primaryKey(),
- email: model.text(),
- variant_id: model.text(),
- sales_channel_id: model.text(),
-})
+ const RestockNotification = model.define("restock_notification", {
+ id: model.id().primaryKey(),
+ email: model.text(),
+ variant_id: model.text(),
+ sales_channel_id: model.text(),
+ })
-export default RestockNotification
-```
+ export default RestockNotification
+ ```
-This creates a `RestockNotification` data model with the following properties:
+ This creates a `RestockNotification` data model with the following properties:
-- `id`: An automatically generated ID.
-- `email`: The email of the customer to send the notification to when the item is restocked.
-- `variant_id`: The ID of the variant the customer is subscribed to. This will later be used to form a relationship with the `ProductVariant` data model of the Product Module.
-- `sales_channel_id`: The ID of the sales channel the customer is viewing the product variant from. This will later be used to form a relationship with the `SalesChannel` data model of the Sales Channel Module.
+ - `id`: An automatically generated ID.
+ - `email`: The email of the customer to send the notification to when the item is restocked.
+ - `variant_id`: The ID of the variant the customer is subscribed to. This will later be used to form a relationship with the `ProductVariant` data model of the Product Module.
+ - `sales_channel_id`: The ID of the sales channel the customer is viewing the product variant from. This will later be used to form a relationship with the `SalesChannel` data model of the Sales Channel Module.
-Since a variant's inventory is managed based on the locations of each sales channel, you have to specify which sales channel to check stock quantity in.
+ Since a variant's inventory is managed based on the locations of each sales channel, you have to specify which sales channel to check stock quantity in.
-Next, create the file `src/modules/restock-notification/migrations/Migration20240516140616.ts` with the following content:
+ Next, create the file `src/modules/restock-notification/migrations/Migration20240516140616.ts` with the following content:
-```ts title="src/modules/restock-notification/migrations/Migration20240516140616.ts"
-import { Migration } from "@mikro-orm/migrations"
+ ```ts title="src/modules/restock-notification/migrations/Migration20240516140616.ts"
+ import { Migration } from "@mikro-orm/migrations"
+
+ export class Migration20240516140616 extends Migration {
-export class Migration20240516140616 extends Migration {
- async up(): Promise {
- this.addSql(
- 'create table if not exists "restock_notification" ("id" text not null, "email" text not null, "variant_id" text not null, "sales_channel_id" text not null, constraint "restock_notification_pkey" primary key ("id"));'
- )
+ async up(): Promise {
+ this.addSql("create table if not exists \"restock_notification\" (\"id\" text not null, \"email\" text not null, \"variant_id\" text not null, \"sales_channel_id\" text not null, constraint \"restock_notification_pkey\" primary key (\"id\"));")
+ }
+
+ async down(): Promise {
+ this.addSql("drop table if exists \"restock_notification\" cascade;")
+ }
+
+ }
+ ```
+
+ You'll run the migration to reflect the changes on the database after finishing the module's definition.
+
+ Then, create the module's main service at `src/modules/restock-notification/service.ts` with the following content:
+
+ ```ts title="src/modules/restock-notification/service.ts"
+ import { MedusaService } from "@medusajs/utils"
+ import RestockNotification from "./models/restock-notification"
+
+ class RestockNotificationModuleService extends MedusaService({
+ RestockNotification,
+ }){
+ // TODO add custom methods
}
- async down(): Promise {
- this.addSql('drop table if exists "restock_notification" cascade;')
- }
-}
-```
+ export default RestockNotificationModuleService
+ ```
-You'll run the migration to reflect the changes on the database after finishing the module's definition.
+ The module's main service extends the service factory which generates basic management features for the `RestockNotification` data model.
-Then, create the module's main service at `src/modules/restock-notification/service.ts` with the following content:
+ Next, create the module's definition file `src/modules/restock-notification/index.ts` with the following content:
-```ts title="src/modules/restock-notification/service.ts"
-import { MedusaService } from "@medusajs/utils"
-import RestockNotification from "./models/restock-notification"
+ ```ts title="src/modules/restock-notification/index.ts"
+ import RestockNotificationModuleService from "./service"
+ import { Module } from "@medusajs/utils"
-class RestockNotificationModuleService extends MedusaService({
- RestockNotification,
-}) {
- // TODO add custom methods
-}
+ export default Module("restock-notification", {
+ service: RestockNotificationModuleService,
+ })
+ ```
-export default RestockNotificationModuleService
-```
+ Finally, add the module to the `modules` object in `medusa-config.js`:
-The module's main service extends the service factory which generates basic management features for the `RestockNotification` data model.
-
-Next, create the module's definition file `src/modules/restock-notification/index.ts` with the following content:
-
-```ts title="src/modules/restock-notification/index.ts"
-import RestockNotificationModuleService from "./service"
-
-export default {
- service: RestockNotificationModuleService,
-}
-```
-
-Finally, add the module to the `modules` object in `medusa-config.js`:
-
-```js title="medusa-config.js"
-module.exports = defineConfig({
- // ...
- modules: {
- restockNotificationModuleService: {
- resolve: "./modules/restock-notification",
- definition: {
- isQueryable: true,
+ ```js title="medusa-config.js"
+ module.exports = defineConfig({
+ // ...
+ modules: {
+ "restockNotificationModuleService": {
+ resolve: "./modules/restock-notification",
+ definition: {
+ isQueryable: true,
+ },
},
},
- },
-})
-```
-
-You can now run the module's migrations with the following command:
-
-```bash npm2yarn
-npx medusa migrations run
-```
-
-### Create Restock Notification API Route
-
-Create the file `src/api/store/restock-notification/route.ts` with the following content:
-
-```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-13" expandButtonLabel="Show Imports"
-import type { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import RestockNotificationModuleService from "../../../modules/restock-notification/service"
-
-type RestockNotificationReq = {
- email: string
- variant_id: string
- sales_channel_id: string
-}
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- const restockNotificationModuleService: RestockNotificationModuleService =
- req.scope.resolve("restockNotificationModuleService")
-
- await restockNotificationModuleService.createRestockNotifications(req.body)
-
- res.json({
- success: true,
})
-}
-```
+ ```
-This creates a `POST` API route at `/store/restock-notification`. It accepts the `email`, `variant_id`, and `sales_channel_id` request body parameters and creates a restock notification.
+ You can now run the module's migrations with the following command:
-### Create Inventory Item Updated Subscriber
+ ```bash npm2yarn
+ npx medusa migrations run
+ ```
-To handle the sending of the restock notifications, create a subscriber that listens to the `inventory-item.updated` event, then sends a notification using the Notification Module to subscribed emails.
+ ### Create Restock Notification API Route
+
+ Create the file `src/api/store/restock-notification/route.ts` with the following content:
+
+ ```ts title="src/api/store/restock-notification/route.ts" collapsibleLines="1-13" expandButtonLabel="Show Imports"
+ import type {
+ MedusaRequest,
+ MedusaResponse,
+ } from "@medusajs/medusa"
+ import RestockNotificationModuleService
+ from "../../../modules/restock-notification/service"
+
+ type RestockNotificationReq = {
+ email: string
+ variant_id: string
+ sales_channel_id: string
+ }
+
+ export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+ ) {
+ const restockNotificationModuleService:
+ RestockNotificationModuleService = req.scope.resolve(
+ "restockNotificationModuleService"
+ )
+
+ await restockNotificationModuleService.createRestockNotifications(
+ req.body
+ )
+
+ res.json({
+ success: true,
+ })
+ }
+ ```
+
+ This creates a `POST` API route at `/store/restock-notification`. It accepts the `email`, `variant_id`, and `sales_channel_id` request body parameters and creates a restock notification.
+
+ ### Create Inventory Item Updated Subscriber
+
+ To handle the sending of the restock notifications, create a subscriber that listens to the `inventory-item.updated` event, then sends a notification using the Notification Module to subscribed emails.
-The `inventory-item.updated` event is currently not emitted.
+ The `inventory-item.updated` event is currently not emitted.
-{/* To handle the sending of the restock notifications, create the file `src/subscribers/inventory-item-update.ts` with the following content: */}
+ {/* To handle the sending of the restock notifications, create the file `src/subscribers/inventory-item-update.ts` with the following content: */}
export const subscriberHighlights = [
- [
- "48",
- "inventoryVariantLinkService",
- "Retrieve an instance of the link service for the product-variant-inventory-item link module.",
- ],
- [
- "55",
- "inventoryVariantItems",
- "Retrieve the variants linked to the updated inventory item.",
- ],
- [
- "68",
- "restockQuery",
- "Assemble the query to retrieve the restock notifications with their associated variants.",
- ],
- [
- "81",
- "restockNotifications",
- "Retrieve the restock notifications using the query.",
- ],
- [
- "84",
- "salesChannelLocationService",
- "Retrieve an instance of the link service for the sales-channel-stock-location link module.",
- ],
- [
- "93",
- "salesChannelLocations",
- "Retrieve the stock locations linked to the restock notification's sales channel.",
- ],
- [
- "107",
- "availableQuantity",
- "Retrieve the available quantity of the variant in the retrieved stock locations.",
- ],
- [
- "116",
- "continue",
- "Only send the notification if the available quantity is greater than `0`",
- ],
- [
- "119",
- "createNotifications",
- "Send the notification to the customer using the Notification Module.",
- ],
- [
- "122",
- '"test_template"',
- "Replace with the actual template used for sending the email.",
- ],
- [
- "123",
- "data",
- "The data to send along to the third-party service sending the notification.",
- ],
- [
- "131",
- "deleteRestockNotifications",
- "Delete the restock notification to not send the notification again.",
- ],
+ ["48", "inventoryVariantLinkService", "Retrieve an instance of the link service for the product-variant-inventory-item link module."],
+ ["55", "inventoryVariantItems", "Retrieve the variants linked to the updated inventory item."],
+ ["68", "restockQuery", "Assemble the query to retrieve the restock notifications with their associated variants."],
+ ["81", "restockNotifications", "Retrieve the restock notifications using the query."],
+ ["84", "salesChannelLocationService", "Retrieve an instance of the link service for the sales-channel-stock-location link module."],
+ ["93", "salesChannelLocations", "Retrieve the stock locations linked to the restock notification's sales channel."],
+ ["107", "availableQuantity", "Retrieve the available quantity of the variant in the retrieved stock locations."],
+ ["116", "continue", "Only send the notification if the available quantity is greater than `0`"],
+ ["119", "createNotifications", "Send the notification to the customer using the Notification Module."],
+ ["122", '"test_template"', "Replace with the actual template used for sending the email."],
+ ["123", "data", "The data to send along to the third-party service sending the notification."],
+ ["131", "deleteRestockNotifications", "Delete the restock notification to not send the notification again."]
]
-{/\* ```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-20" expandButtonLabel="Show Imports"
-import type {
-SubscriberArgs,
-SubscriberConfig,
-} from "@medusajs/medusa"
-import {
-IInventoryService,
-INotificationModuleService,
-RemoteQueryFunction,
-} from "@medusajs/types"
-import {
-ContainerRegistrationKeys,
-Modules,
-remoteQueryObjectFromString,
-} from "@medusajs/utils"
-import {
-RemoteLink,
-} from "@medusajs/modules-sdk"
-import RestockNotificationModuleService
-from "../modules/restock-notification/service"
+ {/* ```ts title="src/subscribers/inventory-item-update.ts" highlights={subscriberHighlights} collapsibleLines="1-20" expandButtonLabel="Show Imports"
+ import type {
+ SubscriberArgs,
+ SubscriberConfig,
+ } from "@medusajs/medusa"
+ import {
+ IInventoryService,
+ INotificationModuleService,
+ RemoteQueryFunction,
+ } from "@medusajs/types"
+ import {
+ ContainerRegistrationKeys,
+ Modules,
+ remoteQueryObjectFromString,
+ } from "@medusajs/utils"
+ import {
+ RemoteLink,
+ } from "@medusajs/modules-sdk"
+ import RestockNotificationModuleService
+ from "../modules/restock-notification/service"
-// subscriber function
-export default async function inventoryItemUpdateHandler({
-data,
-container,
-}: SubscriberArgs<{ id: string }>) {
-const remoteQuery: RemoteQueryFunction = container.resolve(
-ContainerRegistrationKeys.REMOTE_QUERY
-)
-const remoteLink: RemoteLink = container.resolve(
-ContainerRegistrationKeys.REMOTE_LINK
-)
-const restockNotificationModuleService:
-RestockNotificationModuleService = container.resolve(
-"restockNotificationModuleService"
-)
-const inventoryModuleService: IInventoryService =
-container.resolve(Modules.INVENTORY)
-const notificationModuleService: INotificationModuleService =
-container.resolve(
-Modules.NOTIFICATION
-)
+ // subscriber function
+ export default async function inventoryItemUpdateHandler({
+ data,
+ container,
+ }: SubscriberArgs<{ id: string }>) {
+ const remoteQuery: RemoteQueryFunction = container.resolve(
+ ContainerRegistrationKeys.REMOTE_QUERY
+ )
+ const remoteLink: RemoteLink = container.resolve(
+ ContainerRegistrationKeys.REMOTE_LINK
+ )
+ const restockNotificationModuleService:
+ RestockNotificationModuleService = container.resolve(
+ "restockNotificationModuleService"
+ )
+ const inventoryModuleService: IInventoryService =
+ container.resolve(Modules.INVENTORY)
+ const notificationModuleService: INotificationModuleService =
+ container.resolve(
+ Modules.NOTIFICATION
+ )
const inventoryItemId = "data" in data ? data.data.id : data.id
const inventoryVariantLinkService = remoteLink.getLinkModule(
- Modules.PRODUCT,
- "variant_id",
- Modules.INVENTORY,
+ Modules.PRODUCT,
+ "variant_id",
+ Modules.INVENTORY,
"inventory_item_id"
)
- const inventoryVariantItems =
+ const inventoryVariantItems =
await inventoryVariantLinkService.list({
inventory_item_id: [inventoryItemId],
}) as {
@@ -378,7 +326,7 @@ Modules.NOTIFICATION
},
})
- const restockNotifications =
+ const restockNotifications =
await remoteQuery(restockQuery)
const salesChannelLocationService = remoteLink.getLinkModule(
@@ -389,7 +337,7 @@ Modules.NOTIFICATION
)
for (const restockNotification of restockNotifications) {
- const salesChannelLocations =
+ const salesChannelLocations =
await salesChannelLocationService.list({
sales_channel_id: [
restockNotification.sales_channel_id,
@@ -407,7 +355,7 @@ Modules.NOTIFICATION
.retrieveAvailableQuantity(
inventoryItemId,
salesChannelLocations.map(
- (salesChannelLocation) =>
+ (salesChannelLocation) =>
salesChannelLocation.stock_location_id
)
)
@@ -415,7 +363,7 @@ Modules.NOTIFICATION
if (availableQuantity === 0) {
continue
}
-
+
notificationModuleService.createNotifications({
to: restockNotification.email,
channel: "email",
@@ -431,24 +379,22 @@ Modules.NOTIFICATION
await restockNotificationModuleService
.deleteRestockNotifications(restockNotification.id)
}
+ }
-}
+ // subscriber config
+ export const config: SubscriberConfig = {
+ event: "inventory-item.updated",
+ }
+ ```
-// subscriber config
-export const config: SubscriberConfig = {
-event: "inventory-item.updated",
-}
+ This adds a subscriber to the `inventory-item.updated` event. In the subscriber handler function, you:
-````
-
-This adds a subscriber to the `inventory-item.updated` event. In the subscriber handler function, you:
-
-- Retrieve an instance of the link service for the product-variant-inventory-item link module.
-- Retrieve the variants linked to the updated inventory item.
-- Retrieve the restock notifications of those variants.
-- For each restock notification, you:
- - Retrieve its quantity based on the stock location associated with the restock notification's sales channel.
- - If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification. */}
+ - Retrieve an instance of the link service for the product-variant-inventory-item link module.
+ - Retrieve the variants linked to the updated inventory item.
+ - Retrieve the restock notifications of those variants.
+ - For each restock notification, you:
+ - Retrieve its quantity based on the stock location associated with the restock notification's sales channel.
+ - If the quantity is greater than `0`, you send a notification using the Notification Module and delete the restock notification. */}
@@ -469,20 +415,20 @@ The [Events reference](../../events-reference/page.mdx) shows an extensive list
Medusa also provides Notification Module Providers that integrate with third-party services, such as SendGrid.
,
- showLinkIcon: false
-},
-{
- href: "!docs!/basics/events-and-subscribers",
- title: "Create Subscriber",
- text: "Learn how to create a subscriber to handle events.",
- startIcon: ,
- showLinkIcon: false
-},
+ {
+ href: "/architectural-modules/notification",
+ title: "Notification Module",
+ text: "Learn about the Notification Module.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/events-and-subscribers",
+ title: "Create Subscriber",
+ text: "Learn how to create a subscriber to handle events.",
+ startIcon: ,
+ showLinkIcon: false
+ },
]} />
---
@@ -497,212 +443,217 @@ To implement that:
- Create a scheduled job that executes the workflow automatically at the specified time pattern.
}
-showLinkIcon={false}
+ href="!docs!/basics/scheduled-jobs"
+ title="Create a Scheduled Job"
+ text="Learn how to create a scheduled job in Medusa."
+ startIcon={}
+ showLinkIcon={false}
/>
-For example, create the file `src/workflows/sync-products.ts` with the following content:
+ For example, create the file `src/workflows/sync-products.ts` with the following content:
export const syncProductsWorkflowHighlight = [
-["20", "retrieveStoreStep", "A step that retrieves the store by its ID."],
-["36", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
-["56", "syncProductsStep", "A step to sync the product with a third-party service."],
-["59", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
-["63", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
-["68", "sync", "Sync the product data in the third-party service."],
-["72", "", "Pass products data before sync to compensation function."],
-["75", "", "A compensation function to revert the sync when an error occurs."],
-["81", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
-["90", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
-["96", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
-["98", "update", "Update the `last_sync_date` of the store."],
-["106", "", "Pass previous last sync date to compensation function."],
-["109", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
-["125", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
+ ["20", "retrieveStoreStep", "A step that retrieves the store by its ID."],
+ ["36", "retrieveProductsToUpdateStep", "A step that retrieves the products to update based on a last update date."],
+ ["56", "syncProductsStep", "A step to sync the product with a third-party service."],
+ ["59", "productSyncModuleService", "Assuming this is a custom module's main service that provides connection to the third-party service."],
+ ["63", "productsBeforeSync", "Retrieve old product data from third-party service for compensation function."],
+ ["68", "sync", "Sync the product data in the third-party service."],
+ ["72", "", "Pass products data before sync to compensation function."],
+ ["75", "", "A compensation function to revert the sync when an error occurs."],
+ ["81", "sync", "Revert the product's data in the third-party service to its old data before the synchronization."],
+ ["90", "updateStoreLastSyncStep", "A step to update the `last_sync_data` of the store."],
+ ["96", "prevLastSyncDate", "Retrieve the previous value of `last_sync_date` to pass it to compensation function."],
+ ["98", "update", "Update the `last_sync_date` of the store."],
+ ["106", "", "Pass previous last sync date to compensation function."],
+ ["109", "", "A compensation function to revert the update of `last_sync_data` if an error occurs."],
+ ["125", "syncProductsWorkflow", "Define the workflow that uses the above steps."]
]
-```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
-import {
- ModuleRegistrationName
-} from "@medusajs/modules-sdk"
-import {
- IProductModuleService,
- IStoreModuleService,
- ProductDTO,
- StoreDTO
-} from "@medusajs/types"
-import {
- StepResponse,
- createStep,
- createWorkflow
-} from "@medusajs/workflows-sdk"
+ ```ts title="src/workflows/sync-products.ts" highlights={syncProductsWorkflowHighlight} collapsibleLines="1-16" expandButtonLabel="Show Imports"
+ import {
+ ModuleRegistrationName
+ } from "@medusajs/modules-sdk"
+ import {
+ IProductModuleService,
+ IStoreModuleService,
+ ProductDTO,
+ StoreDTO
+ } from "@medusajs/types"
+ import {
+ StepResponse,
+ createStep,
+ createWorkflow
+ } from "@medusajs/workflows-sdk"
-type RetrieveStoreStepInput = {
- id: string
-}
-
-const retrieveStoreStep = createStep(
- "retrieve-store-step",
- async ({ id }: RetrieveStoreStepInput, { container }) => {
- const storeModuleService: IStoreModuleService =
- container.resolve(ModuleRegistrationName.STORE)
-
- const store = await storeModuleService.retrieveStore(id)
-
- return new StepResponse({ store })
+ type RetrieveStoreStepInput = {
+ id: string
}
-)
-type RetrieveProductsToUpdateStepInput = {
- last_sync_date?: string
-}
+ const retrieveStoreStep = createStep(
+ "retrieve-store-step",
+ async ({ id }: RetrieveStoreStepInput, { container }) => {
+ const storeModuleService: IStoreModuleService =
+ container.resolve(ModuleRegistrationName.STORE)
-const retrieveProductsToUpdateStep = createStep(
- "retrieve-products-to-update-step",
- async ({ last_sync_date }: RetrieveProductsToUpdateStepInput, { container }) => {
- const productModuleService: IProductModuleService =
- container.resolve(ModuleRegistrationName.PRODUCT)
+ const store = await storeModuleService.retrieveStore(id)
- const products = await productModuleService.listProducts({
- updated_at: {
- $gt: last_sync_date,
- },
- })
-
- return new StepResponse({ products })
- }
-)
-
-type SyncProductsStepInput = {
- products: ProductDTO[]
-}
-
-const syncProductsStep = createStep(
- "sync-products-step",
- async ({ products }: SyncProductsStepInput, { container }) => {
- const productSyncModuleService = container.resolve(
- "productSyncModuleService"
- )
-
- const productsBeforeSync = await productSyncModuleService.listProductSyncs({
- id: products.map((product) => product.id),
- })
-
- for (const product of products) {
- await productSyncModuleService.sync(product)
+ return new StepResponse({ store })
}
+ )
- return new StepResponse({}, {
- products: productsBeforeSync,
- })
- },
- async ({ products }, { container }) => {
- const productSyncModuleService = container.resolve(
- "productSyncModuleService"
- )
+ type RetrieveProductsToUpdateStepInput = {
+ last_sync_date?: string
+ }
- for (const product of products) {
- await productSyncModuleService.sync(product)
+ const retrieveProductsToUpdateStep = createStep(
+ "retrieve-products-to-update-step",
+ async ({ last_sync_date }: RetrieveProductsToUpdateStepInput, { container }) => {
+ const productModuleService: IProductModuleService =
+ container.resolve(ModuleRegistrationName.PRODUCT)
+
+ const products = await productModuleService.listProducts({
+ updated_at: {
+ $gt: last_sync_date,
+ },
+ })
+
+ return new StepResponse({ products })
}
+ )
+
+ type SyncProductsStepInput = {
+ products: ProductDTO[]
}
-)
-type UpdateStoreLastSyncStepInput = {
- store: StoreDTO
-}
+ const syncProductsStep = createStep(
+ "sync-products-step",
+ async ({ products }: SyncProductsStepInput, { container }) => {
+ const productSyncModuleService = container.resolve(
+ "productSyncModuleService"
+ )
-const updateStoreLastSyncStep = createStep(
- "update-store-last-sync-step",
- async ({ store }: UpdateStoreLastSyncStepInput, { container }) => {
- const storeModuleService: IStoreModuleService =
- container.resolve(ModuleRegistrationName.STORE)
+ const productsBeforeSync = await productSyncModuleService.listProductSyncs({
+ id: products.map((product) => product.id),
+ })
- const prevLastSyncDate = store.metadata.last_sync_date
+ for (const product of products) {
+ await productSyncModuleService.sync(product)
+ }
- await storeModuleService.updateStores(store.id, {
- metadata: {
- last_sync_date: (new Date()).toString(),
- },
- })
+ return new StepResponse({}, {
+ products: productsBeforeSync,
+ })
+ },
+ async ({ products }, { container }) => {
+ const productSyncModuleService = container.resolve(
+ "productSyncModuleService"
+ )
- return new StepResponse({}, {
- id: store.id,
- last_sync_date: prevLastSyncDate,
- })
- },
- async ({ id, last_sync_date }, { container }) => {
- const storeModuleService: IStoreModuleService =
- container.resolve(ModuleRegistrationName.STORE)
+ for (const product of products) {
+ await productSyncModuleService.sync(product)
+ }
+ }
+ )
- await storeModuleService.updateStores(id, {
- metadata: {
- last_sync_date,
- },
- })
+ type UpdateStoreLastSyncStepInput = {
+ store: StoreDTO
}
-)
-type SyncProductsWorkflowInput = {
- store_id: string
-}
+ const updateStoreLastSyncStep = createStep(
+ "update-store-last-sync-step",
+ async ({ store }: UpdateStoreLastSyncStepInput, { container }) => {
+ const storeModuleService: IStoreModuleService =
+ container.resolve(ModuleRegistrationName.STORE)
-export const syncProductsWorkflow = createWorkflow<
- SyncProductsWorkflowInput, {}
- >(
- "sync-products-workflow",
- function ({ store_id }: SyncProductsWorkflowInput) {
- const { store } = retrieveStoreStep({
- id: store_id,
+ const prevLastSyncDate = store.metadata.last_sync_date
+
+ await storeModuleService.updateStores(store.id, {
+ metadata: {
+ last_sync_date: (new Date()).toString(),
+ },
})
- const { products } = retrieveProductsToUpdateStep({
- last_sync_date: store.metadata.last_sync_date,
+ return new StepResponse({}, {
+ id: store.id,
+ last_sync_date: prevLastSyncDate,
})
+ },
+ async ({ id, last_sync_date }, { container }) => {
+ const storeModuleService: IStoreModuleService =
+ container.resolve(ModuleRegistrationName.STORE)
- syncProductsStep({
- products,
- })
-
- updateStoreLastSyncStep({
- store,
+ await storeModuleService.updateStores(id, {
+ metadata: {
+ last_sync_date,
+ },
})
}
)
-````
-This creates a workflow with the following steps:
+ type SyncProductsWorkflowInput = {
+ store_id: string
+ }
-1. Retrieve the store by its ID.
-2. Retrieve products to update based on the last date and time the products were synced. The last sync date is retrieved from the store's `metadata` property.
-3. Sync the retrieved products with a third-party service. It's assumed that the connection to the third-party service is implemented within a custom module's main service.
-4. Update the last sync date of the store to the current date.
+ export const syncProductsWorkflow = createWorkflow<
+ SyncProductsWorkflowInput, {}
+ >(
+ "sync-products-workflow",
+ function ({ store_id }: SyncProductsWorkflowInput) {
+ const { store } = retrieveStoreStep({
+ id: store_id,
+ })
-Then, create a scheduled job at `src/jobs/sync-products.ts` that executes the workflow at the specified interval:
+ const { products } = retrieveProductsToUpdateStep({
+ last_sync_date: store.metadata.last_sync_date,
+ })
-```ts
-import { MedusaContainer } from "@medusajs/types"
-import { syncProductsWorkflow } from "../workflows/sync-products"
+ syncProductsStep({
+ products,
+ })
-export default async function syncProductsJob(container: MedusaContainer) {
- await syncProductsWorkflow(container).run({
- input: {
- name: "John",
- },
- })
-}
+ updateStoreLastSyncStep({
+ store,
+ })
+ }
+ )
+ ```
-export const config = {
- name: "sync-products",
- // execute every minute
- schedule: "0 0 * * *",
- numberOfExecutions: 3,
-}
-```
+ This creates a workflow with the following steps:
+
+ 1. Retrieve the store by its ID.
+ 2. Retrieve products to update based on the last date and time the products were synced. The last sync date is retrieved from the store's `metadata` property.
+ 3. Sync the retrieved products with a third-party service. It's assumed that the connection to the third-party service is implemented within a custom module's main service.
+ 4. Update the last sync date of the store to the current date.
+
+ Then, create a scheduled job at `src/jobs/sync-products.ts` that executes the workflow at the specified interval:
+
+ ```ts
+ import { MedusaContainer } from "@medusajs/types"
+ import {
+ syncProductsWorkflow,
+ } from "../workflows/sync-products"
+
+ export default async function syncProductsJob(
+ container: MedusaContainer
+ ) {
+ await syncProductsWorkflow(container)
+ .run({
+ input: {
+ name: "John",
+ },
+ })
+ }
+
+ export const config = {
+ name: "sync-products",
+ // execute every minute
+ schedule: "0 0 * * *",
+ numberOfExecutions: 3,
+ }
+ ```
@@ -728,22 +679,22 @@ The `order.placed` event is currently not emitted.
showLinkIcon={false}
/>
-{/_ ,
-showLinkIcon: false
-},
-{
-href: "/events-reference",
-title: "Events Reference",
-text: "Check out triggered events by each commerce module.",
-startIcon: ,
-showLinkIcon: false
-},
-]} /> _/}
+{/* ,
+ showLinkIcon: false
+ },
+ {
+ href: "/events-reference",
+ title: "Events Reference",
+ text: "Check out triggered events by each commerce module.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} /> */}
---
@@ -757,24 +708,22 @@ Medusa's commerce features are geared towards automating RMA flows and ensuring
- Merchants can make order changes and request the customer's approval for them. The customer can also send any additional payment if necessary.
- Every order-related action triggers an event, which you can listen to with a subscriber. This allows you to handle order events to automate actions.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/events-and-subscribers",
- title: "Create a Subscriber",
- text: "Learn how to create a subscriber in Medusa.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/events-and-subscribers",
+ title: "Create a Subscriber",
+ text: "Learn how to create a subscriber in Medusa.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -798,110 +747,112 @@ The `order.placed` event is currently not emitted.
-,
- showLinkIcon: false,
- },
- {
- href: "/commerce-modules/pricing",
- title: "Pricing Module",
- text: "Learn about the Pricing Module and its features.",
- startIcon: ,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/events-and-subscribers",
- title: "Create a Subscriber",
- text: "Learn how to create a subscriber in Medusa.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "/commerce-modules/pricing",
+ title: "Pricing Module",
+ text: "Learn about the Pricing Module and its features.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/events-and-subscribers",
+ title: "Create a Subscriber",
+ text: "Learn how to create a subscriber in Medusa.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-Here’s an example of a subscriber that listens to the `order.placed` event and checks whether the customer should be added to the VIP customer group based on their number of orders:
+ Here’s an example of a subscriber that listens to the `order.placed` event and checks whether the customer should be added to the VIP customer group based on their number of orders:
-The `order.placed` event is currently not emitted.
+ The `order.placed` event is currently not emitted.
-```ts title="src/subscribers/add-custom-to-vip.ts" collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import type { SubscriberArgs, SubscriberConfig } from "@medusajs/medusa"
-import { ModuleRegistrationName } from "@medusajs/utils"
-import { ICustomerModuleService, IOrderModuleService } from "@medusajs/types"
+ ```ts title="src/subscribers/add-custom-to-vip.ts" collapsibleLines="1-12" expandButtonLabel="Show Imports"
+ import type {
+ SubscriberArgs,
+ SubscriberConfig,
+ } from "@medusajs/medusa"
+ import {
+ ModuleRegistrationName,
+ } from "@medusajs/modules-sdk"
+ import {
+ ICustomerModuleService,
+ IOrderModuleService,
+ } from "@medusajs/types"
-export default async function orderCreatedHandler({
- data,
- container,
-}: SubscriberArgs<{ id: string }>) {
- const orderId = "data" in data ? data.data.id : data.id
+ export default async function orderCreatedHandler({
+ data,
+ container,
+ }: SubscriberArgs<{ id: string }>) {
+ const orderId = "data" in data ? data.data.id : data.id
- const orderModuleService: IOrderModuleService = container.resolve(
- ModuleRegistrationName.ORDER
- )
+ const orderModuleService: IOrderModuleService = container
+ .resolve(ModuleRegistrationName.ORDER)
- const customerModuleService: ICustomerModuleService = container.resolve(
- ModuleRegistrationName.CUSTOMER
- )
+ const customerModuleService:
+ ICustomerModuleService = container.resolve(
+ ModuleRegistrationName.CUSTOMER
+ )
- // check if VIP group exists
- const vipGroup = await customerModuleService.listCustomerGroups(
- {
- name: "VIP",
- },
- {
- relations: ["customers"],
+ // check if VIP group exists
+ const vipGroup = await customerModuleService
+ .listCustomerGroups({
+ name: "VIP",
+ }, {
+ relations: ["customers"],
+ })
+
+ if (!vipGroup.length) {
+ return
}
- )
- if (!vipGroup.length) {
- return
+ // retrieve the order
+ const order = await orderModuleService.retrieveOrder(orderId)
+
+ if (!order ||
+ !order.customer_id ||
+ vipGroup[0].customers.find(
+ (customer) => customer.id === order.customer_id
+ ) !== undefined) {
+ return
+ }
+
+ const [, count] = await orderModuleService.listAndCountOrders({
+ customer_id: order.customer_id,
+ })
+
+ if (count < 20) {
+ return
+ }
+
+ // add customer to VIP group
+ await customerModuleService.addCustomerToGroup({
+ customer_group_id: vipGroup[0].id,
+ customer_id: order.customer_id,
+ })
}
- // retrieve the order
- const order = await orderModuleService.retrieveOrder(orderId)
-
- if (
- !order ||
- !order.customer_id ||
- vipGroup[0].customers.find(
- (customer) => customer.id === order.customer_id
- ) !== undefined
- ) {
- return
+ export const config: SubscriberConfig = {
+ event: "order.placed",
}
-
- const [, count] = await orderModuleService.listAndCountOrders({
- customer_id: order.customer_id,
- })
-
- if (count < 20) {
- return
- }
-
- // add customer to VIP group
- await customerModuleService.addCustomerToGroup({
- customer_group_id: vipGroup[0].id,
- customer_id: order.customer_id,
- })
-}
-
-export const config: SubscriberConfig = {
- event: "order.placed",
-}
-```
+ ```
-
+
---
@@ -913,138 +864,116 @@ To do that, create a subscriber that listens to the `product.created`, and send
You can also create a scheduled job that checks whether the number of new products has exceeded a set threshold, then sends out the newsletter.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/basics/scheduled-jobs",
- title: "Scheduled Jobs",
- text: "Learn how to create a scheduled job in Medusa.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/basics/scheduled-jobs",
+ title: "Scheduled Jobs",
+ text: "Learn how to create a scheduled job in Medusa.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-For example, create the file `src/subscribers/send-products-newsletter.ts` with the following content:
+ For example, create the file `src/subscribers/send-products-newsletter.ts` with the following content:
export const newsletterHighlights = [
["33", "store", "Retrieve the first store in the application."],
- [
- "35",
- "products",
- "Retrieve the products created since the last newsletter send date.",
- ],
- [
- "41",
- "",
- "Check whether more than 10 products have been created before proceeding.",
- ],
- [
- "45",
- "customers",
- "Retrieve all customers, assuming they're considered subscribed.",
- ],
- [
- "47",
- "createNotifications",
- "Send a notification (newsletter) to each customer using the Notification Module.",
- ],
+ ["35", "products", "Retrieve the products created since the last newsletter send date."],
+ ["41", "", "Check whether more than 10 products have been created before proceeding."],
+ ["45", "customers", "Retrieve all customers, assuming they're considered subscribed."],
+ ["47", "createNotifications", "Send a notification (newsletter) to each customer using the Notification Module."],
["50", '"email"', "Send the notification through the email channel."],
- [
- "51",
- '"newsletter_template"',
- "Specify the template name in the third-party service (for example, SendGrid).",
- ],
+ ["51", '"newsletter_template"', "Specify the template name in the third-party service (for example, SendGrid)."],
["53", "products", "Pass the created products to the template."],
- [
- "58",
- "updateStores",
- "Update the store's `last_newsletter_send_date` property with the current date.",
- ],
+ ["58", "updateStores", "Update the store's `last_newsletter_send_date` property with the current date."]
]
-```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-14" expandButtonLabel="Show Imports"
-import type { SubscriberArgs, SubscriberConfig } from "@medusajs/medusa"
-import { ModuleRegistrationName } from "@medusajs/modules-sdk"
-import {
- ICustomerModuleService,
- IProductModuleService,
- IStoreModuleService,
- INotificationModuleService,
-} from "@medusajs/types"
+ ```ts title="src/subscribers/send-products-newsletter.ts" highlights={newsletterHighlights} collapsibleLines="1-14" expandButtonLabel="Show Imports"
+ import type {
+ SubscriberArgs,
+ SubscriberConfig,
+ } from "@medusajs/medusa"
+ import {
+ ModuleRegistrationName,
+ } from "@medusajs/modules-sdk"
+ import {
+ ICustomerModuleService,
+ IProductModuleService,
+ IStoreModuleService,
+ INotificationModuleService,
+ } from "@medusajs/types"
-export default async function productCreateHandler({
- data,
- container,
-}: SubscriberArgs<{ id: string }>) {
- const productModuleService: IProductModuleService = container.resolve(
- ModuleRegistrationName.PRODUCT
- )
+ export default async function productCreateHandler({
+ data,
+ container,
+ }: SubscriberArgs<{ id: string }>) {
+ const productModuleService: IProductModuleService =
+ container.resolve(ModuleRegistrationName.PRODUCT)
- const storeModuleService: IStoreModuleService = container.resolve(
- ModuleRegistrationName.STORE
- )
+ const storeModuleService: IStoreModuleService =
+ container.resolve(ModuleRegistrationName.STORE)
- const customerModuleService: ICustomerModuleService = container.resolve(
- ModuleRegistrationName.CUSTOMER
- )
+ const customerModuleService: ICustomerModuleService =
+ container.resolve(ModuleRegistrationName.CUSTOMER)
- const notificationModuleService: INotificationModuleService =
- container.resolve(ModuleRegistrationName.NOTIFICATION)
+ const notificationModuleService:
+ INotificationModuleService = container.resolve(
+ ModuleRegistrationName.NOTIFICATION
+ )
- const store = (await storeModuleService.listStores())[0]
+ const store = (await storeModuleService.listStores())[0]
- const products = await productModuleService.listProducts({
- created_at: {
- $gt: store.metadata.last_newsletter_send_date,
- },
- })
+ const products = await productModuleService.listProducts({
+ created_at: {
+ $gt: store.metadata.last_newsletter_send_date,
+ },
+ })
- if (products.length < 10) {
- return
+ if (products.length < 10) {
+ return
+ }
+
+ const customers = await customerModuleService.listCustomers()
+
+ await notificationModuleService.createNotifications(
+ customers.map((customer) => ({
+ to: customer.email,
+ channel: "email",
+ template: "newsletter_template",
+ data: {
+ products,
+ },
+ }))
+ )
+
+ await storeModuleService.updateStores(store.id, {
+ metadata: {
+ last_newsletter_send_date: (new Date()).toString(),
+ },
+ })
}
- const customers = await customerModuleService.listCustomers()
+ export const config: SubscriberConfig = {
+ event: "product.created",
+ }
+ ```
- await notificationModuleService.createNotifications(
- customers.map((customer) => ({
- to: customer.email,
- channel: "email",
- template: "newsletter_template",
- data: {
- products,
- },
- }))
- )
+ In the subscriber function, you:
- await storeModuleService.updateStores(store.id, {
- metadata: {
- last_newsletter_send_date: new Date().toString(),
- },
- })
-}
+ 1. Retrieve the first store in our application.
+ 2. Retrieve products created since the last time a newsletter is sent. The last send date is stored in the store's `metadata` property.
+ 3. If the count of last created products is less than 10, stop execution.
+ 4. Retrieve all customers. Here, it's assumed that all customers are considered subscribed for simplicity.
+ 5. Use the Notification Module to send a notification to the customer. This uses the Notification Module Provider configured for the `email` channel.
+ 6. Update the store's `last_newsletter_send_date` with the current date.
-export const config: SubscriberConfig = {
- event: "product.created",
-}
-```
-
-In the subscriber function, you:
-
-1. Retrieve the first store in our application.
-2. Retrieve products created since the last time a newsletter is sent. The last send date is stored in the store's `metadata` property.
-3. If the count of last created products is less than 10, stop execution.
-4. Retrieve all customers. Here, it's assumed that all customers are considered subscribed for simplicity.
-5. Use the Notification Module to send a notification to the customer. This uses the Notification Module Provider configured for the `email` channel.
-6. Update the store's `last_newsletter_send_date` with the current date.
-
-
+
\ No newline at end of file
diff --git a/www/apps/resources/app/recipes/digital-products/page.mdx b/www/apps/resources/app/recipes/digital-products/page.mdx
index 9d6268db5d..f0055d932a 100644
--- a/www/apps/resources/app/recipes/digital-products/page.mdx
+++ b/www/apps/resources/app/recipes/digital-products/page.mdx
@@ -33,24 +33,22 @@ Use a file module provider to manage your stored digital products.
During development, you can use the Local File Module Provider, which is installed by default in your store. For production, check out available file module providers or create your own.
-,
- showLinkIcon: false,
- },
- {
- href: "/references/file-provider-module",
- title: "Create a File Module Provider",
- text: "Learn how to create a file module provider.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "/references/file-provider-module",
+ title: "Create a File Module Provider",
+ text: "Learn how to create a file module provider.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
---
@@ -64,24 +62,22 @@ You can use one of Medusa’s notification module providers or create your own.
{/* TODO add links */}
-,
- showLinkIcon: false,
- },
- {
- href: "/references/notification-provider-module",
- title: "Create a Notification Module Provider",
- text: "Learn how to create a custom notification service.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "/references/notification-provider-module",
+ title: "Create a Notification Module Provider",
+ text: "Learn how to create a custom notification service.",
+ startIcon: ,
+ showLinkIcon: false,
+ },
+]} />
---
@@ -101,46 +97,47 @@ The module will hold your custom data models and the service implementing digita
-In this section, you’ll create the skeleton of the Digital Product Module. In later sections, you’ll add more resources to it.
+ In this section, you’ll create the skeleton of the Digital Product Module. In later sections, you’ll add more resources to it.
+
+ Start by creating the directory `src/modules/digital-product`.
+
+ Then, create the file `src/modules/digital-product/service.ts` with the following content:
+
+ ```ts title="src/modules/digital-product/service.ts"
+ class DigitalProductModuleService {
+ // TODO
+ }
+
+ export default DigitalProductModuleService
+ ```
+
+ A module must export a service. So, you implement a dummy service for now.
+
+ Next, create the file `src/modules/digital-product/index.ts` with the following content:
+
+ ```ts title="src/modules/digital-product/index.ts"
+ import DigitalProductModuleService from "./service"
+ import { Module } from "@medusajs/utils"
-Start by creating the directory `src/modules/digital-product`.
-
-Then, create the file `src/modules/digital-product/service.ts` with the following content:
-
-```ts title="src/modules/digital-product/service.ts"
-class DigitalProductModuleService {
- // TODO
-}
-
-export default DigitalProductModuleService
-```
-
-A module must export a service. So, you implement a dummy service for now.
-
-Next, create the file `src/modules/digital-product/index.ts` with the following content:
-
-```ts title="src/modules/digital-product/index.ts"
-import DigitalProductModuleService from "./service"
-
-export default {
- service: DigitalProductModuleService,
-}
-```
-
-This file holds the definition of the module.
-
-Finally, add the module to `medusa-config.js` into the `modules` object:
-
-```js title="medusa-config.js"
-module.exports = defineConfig({
- // ...
- modules: {
- digitalProductModuleService: {
- resolve: "./modules/digital-product",
+ export default Module("digital-product", {
+ service: DigitalProductModuleService,
+ })
+ ```
+
+ This file holds the definition of the module.
+
+ Finally, add the module to `medusa-config.js` into the `modules` object:
+
+ ```js title="medusa-config.js"
+ module.exports = defineConfig({
+ // ...
+ modules: {
+ digitalProductModuleService: {
+ resolve: "./modules/digital-product",
+ },
},
- },
-})
-```
+ })
+ ```
@@ -166,41 +163,41 @@ Module Relationships is coming soon.
showLinkIcon={false}
/>
-{/\*
+{/*
-In this section, you’ll create a `ProductMedia` data model that represents your digital products.
+ In this section, you’ll create a `ProductMedia` data model that represents your digital products.
+
+ Before creating the data model, create the file `src/types/digital-product/product-media.ts` that holds common types:
+
+ ```ts title="src/types/digital-product/product-media.ts"
+ export enum MediaType {
+ MAIN = "main",
+ PREVIEW = "preview"
+ }
+ ```
+
+ Then, create the file `src/modules/digital-product/models/product-media.ts` with the following content:
+
+ ```ts title="src/modules/digital-product/models/product-media.ts"
+ import { model } from "@medusajs/utils"
+ import { MediaType } from "../../../types/digital-product/product-media"
-Before creating the data model, create the file `src/types/digital-product/product-media.ts` that holds common types:
-
-```ts title="src/types/digital-product/product-media.ts"
-export enum MediaType {
- MAIN = "main",
- PREVIEW = "preview",
-}
-```
-
-Then, create the file `src/modules/digital-product/models/product-media.ts` with the following content:
-
-```ts title="src/modules/digital-product/models/product-media.ts"
-import { model } from "@medusajs/utils"
-import { MediaType } from "../../../types/digital-product/product-media"
-
-const ProductMedia = model.define("product_media", {
- id: model.id().primaryKey(),
- name: model.text(),
- type: model.enum(Object.values(MediaType)),
- fileKey: model.text(),
- mimeType: model.text(),
- variant_id: model.text().index("IDX_product_media_variant_id"),
-})
-
-export default ProductMedia
-```
-
-The `ProductMedia` data model has properties relevant to digital products. Most importantly, it has a `variant_id` property that will later be used for its relationship with the Product Module.
-
-To reflect the data model in the database, you must create a migration.
+ const ProductMedia = model.define("product_media", {
+ id: model.id().primaryKey(),
+ name: model.text(),
+ type: model.enum(Object.values(MediaType)),
+ fileKey: model.text(),
+ mimeType: model.text(),
+ variant_id: model.text().index("IDX_product_media_variant_id"),
+ })
+ export default ProductMedia
+ ```
+
+ The `ProductMedia` data model has properties relevant to digital products. Most importantly, it has a `variant_id` property that will later be used for its relationship with the Product Module.
+
+ To reflect the data model in the database, you must create a migration.
+
Learn how to generate a migration in [this guide](!docs!/basics/data-models#create-a-migration).
@@ -250,32 +247,32 @@ Medusa facilitates implementing data-management features by providing a service
showLinkIcon={false}
/>
-{/\*
+{/*
+
+ In this section, you’ll modify the `DigitalProductModuleService` you created earlier to provide data-management functionalities of the `ProductMedia` data model.
+
+ Change the content of `src/modules/digital-product/service.ts` to the following:
+
+ ```ts title="src/modules/digital-product/service.ts"
+ import { MedusaService } from "@medusajs/utils"
+ import ProductMedia from "./models/product-media"
-In this section, you’ll modify the `DigitalProductModuleService` you created earlier to provide data-management functionalities of the `ProductMedia` data model.
-
-Change the content of `src/modules/digital-product/service.ts` to the following:
-
-```ts title="src/modules/digital-product/service.ts"
-import { MedusaService } from "@medusajs/utils"
-import ProductMedia from "./models/product-media"
-
-class DigitalProductModuleService extends MedusaService({
- ProductMedia,
-}) {
- // TODO add custom methods
-}
-
-export default DigitalProductModuleService
-```
-
-The `DigitalProductModuleService` now extends the service factory which generates data-management methods for the `ProductMedia` data model.
+ class DigitalProductModuleService extends MedusaService({
+ ProductMedia,
+ }){
+ // TODO add custom methods
+ }
+ export default DigitalProductModuleService
+ ```
+
+ The `DigitalProductModuleService` now extends the service factory which generates data-management methods for the `ProductMedia` data model.
+
*/}
{/* --- */}
-{/\* ## Add Relationship to Product Variants
+{/* ## Add Relationship to Product Variants
As mentioned in a previous section, the product media has a `variant_id` that points to the saleable product variant.
@@ -344,8 +341,8 @@ The Medusa application resolves module relationships without creating an actual
-Learn more about the data returned in the `__joinerConfig` method here.
-
+ Learn more about the data returned in the `__joinerConfig` method here.
+
Next, change the module’s entry in the `modules` object in `medusa-config.js` to the following:
@@ -382,479 +379,530 @@ Fetching data across modules using the remote query is coming soon.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/advanced-development/modules/remote-query",
- title: "Remote Query",
- text: "Learn about what the remote query is and how to use it.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/advanced-development/modules/remote-query",
+ title: "Remote Query",
+ text: "Learn about what the remote query is and how to use it.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-{/\*
+{/*
-In this section, you’ll create a List and Create API routes to retrieve and create digital products.
+ In this section, you’ll create a List and Create API routes to retrieve and create digital products.
+
+ ### Create API Route
+
+ In the Create API route, you want to create not only the product media but also the associated product variant if no ID is specified.
+
+ You’ll implement this logic in a workflow, then use the workflow in the API route.
+
+ Start by changing the content of `src/types/digital-product/product-media.ts` to include more types:
+
+ ```ts title="src/types/digital-product/product-media.ts"
+ import {
+ ProductVariantDTO,
+ CreateProductWorkflowInputDTO,
+ } from "@medusajs/types"
+
+ export enum MediaType {
+ MAIN = "main",
+ PREVIEW = "preview"
+ }
+
+ export type ProductMediaDTO = {
+ id: string
+ name: string
+ type: MediaType
+ file_key: string
+ mime_type: string
+ variant_id: string
+ variant?: ProductVariantDTO
+ }
+
+ export type CreateProductMediaDTO = {
+ name: string
+ file_key: string
+ type: MediaType
+ mime_type: string
+ variant_id?: string
+ }
+
+ export type CreateProductMediaWorkflowInput =
+ CreateProductMediaDTO & {
+ product?: CreateProductWorkflowInputDTO
+ }
-### Create API Route
+ ```
+
+ Then, create the file `src/workflows/digital-product/create.ts` with the following content:
+
+ ```ts title="src/workflows/digital-product/create.ts" collapsibleLines="1-19" expandButtonLabel="Show Imports"
+ import {
+ createWorkflow,
+ WorkflowData,
+ createStep,
+ StepResponse,
+ } from "@medusajs/workflows-sdk"
+ import { createProductsWorkflow } from "@medusajs/core-flows"
+ import {
+ CreateProductMediaDTO,
+ CreateProductMediaWorkflowInput,
+ ProductMediaDTO,
+ } from "../../types/digital-product/product-media"
+ import DigitalProductModuleService from
+ "../../modules/digital-product/service"
+ import { RemoteQueryFunction } from "@medusajs/modules-sdk"
+ import {
+ ContainerRegistrationKeys,
+ remoteQueryObjectFromString,
+ } from "@medusajs/utils"
-In the Create API route, you want to create not only the product media but also the associated product variant if no ID is specified.
+ const tryToCreateProductVariantStep = createStep(
+ "try-to-create-product-variant-step",
+ async (input: CreateProductMediaWorkflowInput, { container }) => {
+ if (input.product && !input.variant_id) {
+ const { result, errors } = await createProductsWorkflow(container)
+ .run({
+ input: {
+ products: [input.product],
+ },
+ throwOnError: false,
+ })
-You’ll implement this logic in a workflow, then use the workflow in the API route.
+ if (errors.length) {
+ throw errors[0].error
+ }
-Start by changing the content of `src/types/digital-product/product-media.ts` to include more types:
+ input.variant_id = result[0].variants[0].id
-```ts title="src/types/digital-product/product-media.ts"
-import {
- ProductVariantDTO,
- CreateProductWorkflowInputDTO,
-} from "@medusajs/types"
+ delete input.product
+ }
-export enum MediaType {
- MAIN = "main",
- PREVIEW = "preview",
-}
+ return new StepResponse(input)
+ }
+ )
-export type ProductMediaDTO = {
- id: string
- name: string
- type: MediaType
- file_key: string
- mime_type: string
- variant_id: string
- variant?: ProductVariantDTO
-}
+ const createProductMediaStep = createStep(
+ "create-product-media-step",
+ async (input: CreateProductMediaDTO, { container }) => {
+ const digitalProductModuleService:
+ DigitalProductModuleService = container.resolve(
+ "digitalProductModuleService"
+ )
-export type CreateProductMediaDTO = {
- name: string
- file_key: string
- type: MediaType
- mime_type: string
- variant_id?: string
-}
+ const productMedia = await digitalProductModuleService
+ .createProductMedias(
+ input
+ )
-export type CreateProductMediaWorkflowInput = CreateProductMediaDTO & {
- product?: CreateProductWorkflowInputDTO
-}
-```
+ return new StepResponse(productMedia)
+ }
+ )
-Then, create the file `src/workflows/digital-product/create.ts` with the following content:
+ const retrieveProductMediaWithVariant = createStep(
+ "retrieve-product-media-with-variant-step",
+ async (input: ProductMediaDTO, { container }) => {
+ const remoteQuery: RemoteQueryFunction = container.resolve(
+ ContainerRegistrationKeys.REMOTE_QUERY
+ )
-```ts title="src/workflows/digital-product/create.ts" collapsibleLines="1-19" expandButtonLabel="Show Imports"
-import {
- createWorkflow,
- WorkflowData,
- createStep,
- StepResponse,
-} from "@medusajs/workflows-sdk"
-import { createProductsWorkflow } from "@medusajs/core-flows"
-import {
- CreateProductMediaDTO,
- CreateProductMediaWorkflowInput,
- ProductMediaDTO,
-} from "../../types/digital-product/product-media"
-import DigitalProductModuleService from "../../modules/digital-product/service"
-import { RemoteQueryFunction } from "@medusajs/modules-sdk"
-import {
- ContainerRegistrationKeys,
- remoteQueryObjectFromString,
-} from "@medusajs/utils"
+ const query = remoteQueryObjectFromString({
+ entryPoint: "product_media",
+ fields: [
+ "id",
+ "name",
+ "type",
+ "file_key",
+ "mime_type",
+ "variant.*",
+ ],
+ variables: {
+ filters: {
+ id: input.id,
+ },
+ },
+ })
-const tryToCreateProductVariantStep = createStep(
- "try-to-create-product-variant-step",
- async (input: CreateProductMediaWorkflowInput, { container }) => {
- if (input.product && !input.variant_id) {
- const { result, errors } = await createProductsWorkflow(container).run({
+ const result = await remoteQuery(query)
+
+ return new StepResponse(result[0])
+ }
+ )
+
+ type WorkflowInput = {
+ data: CreateProductMediaWorkflowInput
+ }
+
+ export const createProductMediaWorkflow = createWorkflow(
+ "create-product-media-workflow",
+ function (input: WorkflowData) {
+ // create the product variant before creating the media
+ // if variant_id isn't passed
+ const normalizedInput = tryToCreateProductVariantStep(input.data)
+
+ const productMedia = createProductMediaStep(normalizedInput)
+
+ return retrieveProductMediaWithVariant(productMedia)
+ }
+ )
+ ```
+
+ This workflow has three steps:
+
+ 1. If a `variant_id` field isn’t passed and a `product` field is passed, create the product using Medusa’s `createProductsWorkflow` and set the ID of the variant in the `variant_id` property.
+ 2. Use the `DigitalProductModuleService` to create the product media.
+ 3. Use the remote query to retrieve the product media along with the variant it references.
+
+ Finally, create the `src/api/admin/digital-products/route.ts` file with the following content:
+
+ ```ts title="src/api/admin/digital-products/route.ts" collapsibleLines="1-12" expandButtonLabel="Show Imports"
+ import {
+ MedusaRequest,
+ MedusaResponse,
+ } from "@medusajs/medusa"
+ import { MedusaError } from "@medusajs/utils"
+ import {
+ CreateProductMediaWorkflowInput,
+ } from "../../../types/digital-product/product-media"
+ import {
+ createProductMediaWorkflow,
+ } from "../../../workflows/digital-product/create"
+
+ type CreateProductMediaReq = CreateProductMediaWorkflowInput
+
+ export async function POST(
+ req: MedusaRequest,
+ res: MedusaResponse
+ ) {
+ // validation omitted for simplicity
+ const {
+ result,
+ errors,
+ } = await createProductMediaWorkflow(req.scope)
+ .run({
input: {
- products: [input.product],
+ data: {
+ ...req.body,
+ },
},
throwOnError: false,
})
-
- if (errors.length) {
- throw errors[0].error
- }
-
- input.variant_id = result[0].variants[0].id
-
- delete input.product
+
+ if (errors.length) {
+ throw new MedusaError(
+ MedusaError.Types.DB_ERROR,
+ errors[0].error
+ )
}
-
- return new StepResponse(input)
+
+ res.json({
+ product_media: result,
+ })
}
-)
-
-const createProductMediaStep = createStep(
- "create-product-media-step",
- async (input: CreateProductMediaDTO, { container }) => {
- const digitalProductModuleService: DigitalProductModuleService =
- container.resolve("digitalProductModuleService")
-
- const productMedia = await digitalProductModuleService.createProductMedias(
- input
- )
-
- return new StepResponse(productMedia)
+ ```
+
+ This adds a `POST` API route at `/admin/digital-products` that executes the `createProductMediaWorkflow` workflow.
+
+ To test it out, start the Medusa application:
+
+ ```bash npm2yarn
+ npm run dev
+ ```
+
+ Next, authenticate as an admin user as explained in the [API Reference]
+
+ Then, upload a file using the Upload API route:
+
+ ```bash
+ curl -X POST 'http://localhost:9000/admin/uploads' \
+ -H 'Authorization: Bearer {bearer_token}' \
+ --form 'files=@"/path/to/file"'
+ ```
+
+ Make sure to replace `/path/to/file` with the path to the file to upload. Copy the `id` field’s value as you’ll use it as the `file_key`'s value when creating the digital product.
+
+ Finally, send a request to the API route you created:
+
+ ```bash
+ curl -X POST 'localhost:9000/admin/digital-products' \
+ -H 'Content-Type: application/json' \
+ -H 'Authorization: Bearer {bearer_token}' \
+ --data '{
+ "name": "Harry Potter",
+ "file_key": "file.png",
+ "type": "main",
+ "mime_type": "image/png",
+ "product": {
+ "title": "Harry Potter Books",
+ "variants": [
+ {
+ "title": "Harry Potter 1"
+ }
+ ]
+ }
+ }'
+ ```
+
+ This creates a product and a variant, and a product media that references the created variant.
+
+ You’ll receive a response similar to the following:
+
+ ```json
+ {
+ "product_media": {
+ "id": "promed_01HXEFRMS79293ASYVN8YY9Y0J",
+ "name": "Harry Potter",
+ "type": "main",
+ "file_key": "file.png",
+ "mime_type": "image/png",
+ "variant_id": "variant_01HXEFRMR3B09EZJX23P1DMYFQ",
+ "variant": {
+ "id": "variant_01HXEFRMR3B09EZJX23P1DMYFQ",
+ "title": "Harry Potter 1",
+ // ...
+ }
+ }
}
-)
-
-const retrieveProductMediaWithVariant = createStep(
- "retrieve-product-media-with-variant-step",
- async (input: ProductMediaDTO, { container }) => {
- const remoteQuery: RemoteQueryFunction = container.resolve(
+ ```
+
+ ### List API Route
+
+ Next, you’ll create the List API route that returns a list of digital products.
+
+ To do that, add the following to `src/api/admin/digital-products/route.ts`:
+
+ ```ts title="src/api/admin/digital-products/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
+ // other imports...
+ import { RemoteQueryFunction } from "@medusajs/modules-sdk"
+ import {
+ ContainerRegistrationKeys,
+ remoteQueryObjectFromString,
+ } from "@medusajs/utils"
+
+ // ...
+
+ export async function GET(
+ req: MedusaRequest,
+ res: MedusaResponse
+ ) {
+ const remoteQuery: RemoteQueryFunction = req.scope.resolve(
ContainerRegistrationKeys.REMOTE_QUERY
)
-
+
const query = remoteQueryObjectFromString({
entryPoint: "product_media",
- fields: ["id", "name", "type", "file_key", "mime_type", "variant.*"],
- variables: {
- filters: {
- id: input.id,
- },
- },
+ fields: [
+ "id",
+ "name",
+ "type",
+ "file_key",
+ "mime_type",
+ "variant.*",
+ "variant.product.*",
+ ],
})
-
+
const result = await remoteQuery(query)
-
- return new StepResponse(result[0])
+
+ res.json({
+ product_medias: result,
+ })
}
-)
-
-type WorkflowInput = {
- data: CreateProductMediaWorkflowInput
-}
-
-export const createProductMediaWorkflow = createWorkflow(
- "create-product-media-workflow",
- function (input: WorkflowData) {
- // create the product variant before creating the media
- // if variant_id isn't passed
- const normalizedInput = tryToCreateProductVariantStep(input.data)
-
- const productMedia = createProductMediaStep(normalizedInput)
-
- return retrieveProductMediaWithVariant(productMedia)
- }
-)
-```
-
-This workflow has three steps:
-
-1. If a `variant_id` field isn’t passed and a `product` field is passed, create the product using Medusa’s `createProductsWorkflow` and set the ID of the variant in the `variant_id` property.
-2. Use the `DigitalProductModuleService` to create the product media.
-3. Use the remote query to retrieve the product media along with the variant it references.
-
-Finally, create the `src/api/admin/digital-products/route.ts` file with the following content:
-
-```ts title="src/api/admin/digital-products/route.ts" collapsibleLines="1-12" expandButtonLabel="Show Imports"
-import { MedusaRequest, MedusaResponse } from "@medusajs/medusa"
-import { MedusaError } from "@medusajs/utils"
-import { CreateProductMediaWorkflowInput } from "../../../types/digital-product/product-media"
-import { createProductMediaWorkflow } from "../../../workflows/digital-product/create"
-
-type CreateProductMediaReq = CreateProductMediaWorkflowInput
-
-export async function POST(
- req: MedusaRequest,
- res: MedusaResponse
-) {
- // validation omitted for simplicity
- const { result, errors } = await createProductMediaWorkflow(req.scope).run({
- input: {
- data: {
- ...req.body,
- },
- },
- throwOnError: false,
- })
-
- if (errors.length) {
- throw new MedusaError(MedusaError.Types.DB_ERROR, errors[0].error)
- }
-
- res.json({
- product_media: result,
- })
-}
-```
-
-This adds a `POST` API route at `/admin/digital-products` that executes the `createProductMediaWorkflow` workflow.
-
-To test it out, start the Medusa application:
-
-```bash npm2yarn
-npm run dev
-```
-
-Next, authenticate as an admin user as explained in the [API Reference]
-
-Then, upload a file using the Upload API route:
-
-```bash
-curl -X POST 'http://localhost:9000/admin/uploads' \
--H 'Authorization: Bearer {bearer_token}' \
---form 'files=@"/path/to/file"'
-```
-
-Make sure to replace `/path/to/file` with the path to the file to upload. Copy the `id` field’s value as you’ll use it as the `file_key`'s value when creating the digital product.
-
-Finally, send a request to the API route you created:
-
-```bash
-curl -X POST 'localhost:9000/admin/digital-products' \
--H 'Content-Type: application/json' \
--H 'Authorization: Bearer {bearer_token}' \
---data '{
- "name": "Harry Potter",
- "file_key": "file.png",
- "type": "main",
- "mime_type": "image/png",
- "product": {
- "title": "Harry Potter Books",
- "variants": [
- {
- "title": "Harry Potter 1"
- }
- ]
- }
-}'
-```
-
-This creates a product and a variant, and a product media that references the created variant.
-
-You’ll receive a response similar to the following:
-
-```json
-{
- "product_media": {
- "id": "promed_01HXEFRMS79293ASYVN8YY9Y0J",
- "name": "Harry Potter",
- "type": "main",
- "file_key": "file.png",
- "mime_type": "image/png",
- "variant_id": "variant_01HXEFRMR3B09EZJX23P1DMYFQ",
- "variant": {
- "id": "variant_01HXEFRMR3B09EZJX23P1DMYFQ",
- "title": "Harry Potter 1"
- // ...
- }
- }
-}
-```
-
-### List API Route
-
-Next, you’ll create the List API route that returns a list of digital products.
-
-To do that, add the following to `src/api/admin/digital-products/route.ts`:
-
-```ts title="src/api/admin/digital-products/route.ts" collapsibleLines="1-9" expandButtonLabel="Show Imports"
-// other imports...
-import { RemoteQueryFunction } from "@medusajs/modules-sdk"
-import {
- ContainerRegistrationKeys,
- remoteQueryObjectFromString,
-} from "@medusajs/utils"
-
-// ...
-
-export async function GET(req: MedusaRequest, res: MedusaResponse) {
- const remoteQuery: RemoteQueryFunction = req.scope.resolve(
- ContainerRegistrationKeys.REMOTE_QUERY
- )
-
- const query = remoteQueryObjectFromString({
- entryPoint: "product_media",
- fields: [
- "id",
- "name",
- "type",
- "file_key",
- "mime_type",
- "variant.*",
- "variant.product.*",
- ],
- })
-
- const result = await remoteQuery(query)
-
- res.json({
- product_medias: result,
- })
-}
-```
-
-This creates a new `GET` API route at `/admin/digital-products` that retrieves the list of digital products and their associated variants.
-
-To test it out, send a request to the API route while your Medusa application is running:
-
-```bash apiTesting testApiUrl="http://localhost:9000/admin/digital-products" testApiMethod="GET"
-curl 'localhost:9000/admin/digital-products' \
--H 'Authorization: Bearer {bearer_token}' \
-```
-
-You’ll receive a list of digital products.
+ ```
+
+ This creates a new `GET` API route at `/admin/digital-products` that retrieves the list of digital products and their associated variants.
+
+ To test it out, send a request to the API route while your Medusa application is running:
+
+ ```bash apiTesting testApiUrl="http://localhost:9000/admin/digital-products" testApiMethod="GET"
+ curl 'localhost:9000/admin/digital-products' \
+ -H 'Authorization: Bearer {bearer_token}' \
+ ```
+
+ You’ll receive a list of digital products.
*/}
-
+
---
## Customize Admin Dashboard
-You can extend the Medusa Admin to provide merchants with an interface to manage digital products. You can inject widgets into existing pages or create new pages.
+You can extend the Medusa Admin to provide merchants with an interface to manage digital products. You can inject widgets into existing pages or create new pages.
In your customizations, you send requests to the API routes you created to create and list digital products.
-,
- showLinkIcon: false,
- },
- {
- href: "!docs!/advanced-development/admin/ui-routes",
- title: "Create UI Route",
- text: "Learn how to create a UI route in the Medusa Admin.",
- startIcon: ,
- showLinkIcon: false,
- },
- ]}
-/>
+,
+ showLinkIcon: false
+ },
+ {
+ href: "!docs!/advanced-development/admin/ui-routes",
+ title: "Create UI Route",
+ text: "Learn how to create a UI route in the Medusa Admin.",
+ startIcon: ,
+ showLinkIcon: false
+ },
+]} />
-{/\*
+{/*
-In this example, you’ll add a single page that lists the digital products and allows you to create a new one. The implementation will be minimal for the purpose of simplicity, so you can elaborate on it based on your use case.
+ In this example, you’ll add a single page that lists the digital products and allows you to create a new one. The implementation will be minimal for the purpose of simplicity, so you can elaborate on it based on your use case.
-To create the UI route, create the file `src/admin/routes/product-media/page.tsx` with the following content:
+ To create the UI route, create the file `src/admin/routes/product-media/page.tsx` with the following content:
-```tsx title="src/admin/routes/product-media/page.tsx" badgeLabel="Medusa Application" collapsibleLines="1-13" expandButtonLabel="Show Imports"
-import { defineRouteConfig } from "@medusajs/admin-shared"
-import { useEffect, useState } from "react"
-import { Container, Heading, Table } from "@medusajs/ui"
-import { PhotoSolid } from "@medusajs/icons"
-import { ProductMediaDTO } from "../../../types/digital-product/product-media"
-import { Link } from "react-router-dom"
-import ProductMediaCreateForm from "../../components/product-media/CreateForm"
+ ```tsx title="src/admin/routes/product-media/page.tsx" badgeLabel="Medusa Application" collapsibleLines="1-13" expandButtonLabel="Show Imports"
+ import { defineRouteConfig } from "@medusajs/admin-shared"
+ import { useEffect, useState } from "react"
+ import {
+ Container,
+ Heading,
+ Table,
+ } from "@medusajs/ui"
+ import { PhotoSolid } from "@medusajs/icons"
+ import { ProductMediaDTO } from "../../../types/digital-product/product-media"
+ import { Link } from "react-router-dom"
+ import ProductMediaCreateForm
+ from "../../components/product-media/CreateForm"
-const ProductMediaListPage = () => {
- const [loading, setLoading] = useState(true)
- const [productMedias, setProductMedias] = useState([])
+ const ProductMediaListPage = () => {
+ const [loading, setLoading] = useState(true)
+ const [productMedias, setProductMedias] = useState([])
- useEffect(() => {
- if (!loading) {
- return
- }
+ useEffect(() => {
+ if (!loading) {
+ return
+ }
- fetch(`/admin/digital-products`, {
- credentials: "include",
- })
+ fetch(`/admin/digital-products`, {
+ credentials: "include",
+ })
.then((response) => response.json())
.then(({ product_medias }) => {
setProductMedias(product_medias)
setLoading(false)
})
- }, [loading])
+ }, [loading])
- return (
-
-