diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index d8d7d7cf30..3545069f0e 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -5497,40 +5497,101 @@ If you run the Medusa application and go to `localhost:9000/app/custom/123`, you To build admin customizations that match the Medusa Admin's designs and layouts, refer to [this guide](undefined/resources/admin-components) to find common components. -# Environment Variables in Admin Customizations +# Admin Development Tips -In this chapter, you'll learn how to use environment variables in your admin customizations. +In this chapter, you'll find some tips for your admin development. To learn how envirnment variables are generally loaded in Medusa based on your application's environment, check out [this chapter](undefined/learn/fundamentals/environment-variables). -## How to Set Environment Variables +To send a request to an API route in the Medusa Application, use Medusa's [JS SDK](undefined/resources/js-sdk) with [Tanstack Query](https://tanstack.com/query/latest). Both of these tools are installed in your project by default. -The Medusa Admin is built on top of [Vite](https://vite.dev/). To set an environment variable that you want to use in a widget or UI route, prefix the environment variable with `VITE_`. +Do not install Tanstack Query as that will cause unexpected errors in your development. If you prefer installing it for better auto-completion in your code editor, make sure to install `v5.64.2` as a development dependency. -For example: +First, create the file `src/admin/lib/config.ts` to setup the SDK for use in your customizations: -```plain -VITE_MY_API_KEY=sk_123 +```ts +import Medusa from "@medusajs/js-sdk" + +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "session", + }, +}) ``` -*** +Notice that you use `import.meta.env` to access environment variables in your customizations, as explained in [this chapter](undefined/learn/fundamentals/admin/environment-variables). -## How to Use Environment Variables +Learn more about the JS SDK's configurations [this documentation](undefined/resources/js-sdk#js-sdk-configurations). -To access or use an environment variable starting with `VITE_`, use the `import.meta.env` object. +Then, use the configured SDK with the `useQuery` Tanstack Query hook to send `GET` requests, and `useMutation` hook to send `POST` or `DELETE` requests. For example: -```tsx highlights={[["8"]]} +### Query + +```tsx title="src/admin/widgets/product-widget.ts" highlights={queryHighlights} import { defineWidgetConfig } from "@medusajs/admin-sdk" -import { Container, Heading } from "@medusajs/ui" +import { Button, Container } from "@medusajs/ui" +import { useQuery } from "@tanstack/react-query" +import { sdk } from "../lib/config" +import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" const ProductWidget = () => { + const { data, isLoading } = useQuery({ + queryFn: () => sdk.admin.product.list(), + queryKey: ["products"], + }) + return ( -
- API Key: {import.meta.env.VITE_MY_API_KEY} -
+ {isLoading && Loading...} + {data?.products && ( + + )} +
+ ) +} + +export const config = defineWidgetConfig({ + zone: "product.list.before", +}) + +export default ProductWidget +``` + +### Mutation + +```tsx title="src/admin/widgets/product-widget.ts" highlights={mutationHighlights} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Button, Container } from "@medusajs/ui" +import { useMutation } from "@tanstack/react-query" +import { sdk } from "../lib/config" +import { DetailWidgetProps, HttpTypes } from "@medusajs/framework/types" + +const ProductWidget = ({ + data: productData, +}: DetailWidgetProps) => { + const { mutateAsync } = useMutation({ + mutationFn: (payload: HttpTypes.AdminUpdateProduct) => + sdk.admin.product.update(productData.id, payload), + onSuccess: () => alert("updated product"), + }) + + const handleUpdate = () => { + mutateAsync({ + title: "New Product Title", + }) + } + + return ( + + ) } @@ -5542,28 +5603,49 @@ export const config = defineWidgetConfig({ export default ProductWidget ``` -In this example, you display the API key in a widget using `import.meta.env.VITE_MY_API_KEY`. - -### Type Error on import.meta.env - -If you receive a type error on `import.meta.env`, create the file `src/admin/vite-env.d.ts` with the following content: - -```ts title="src/admin/vite-env.d.ts" -/// -``` - -This file tells TypeScript to recognize the `import.meta.env` object and enhances the types of your custom environment variables. +You can also send requests to custom routes as explained in the [JS SDK reference](undefined/resources/js-sdk). *** -## Check Node Environment in Admin Customizations +## Routing Functionalities -To check the current environment, Vite exposes two variables: +To navigate or link to other pages, or perform other routing functionalities, use the [react-router-dom](https://reactrouter.com/en/main) package. It's installed in your project through the Medusa Admin. -- `import.meta.env.DEV`: Returns `true` if the current environment is development. -- `import.meta.env.PROD`: Returns `true` if the current environment is production. +For example: -Learn more about other Vite environment variables in the [Vite documentation](https://vite.dev/guide/env-and-mode). +```tsx title="src/admin/widgets/product-widget.tsx" highlights={highlights} +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container } from "@medusajs/ui" +import { Link } from "react-router-dom" + +// The widget +const ProductWidget = () => { + return ( + + View Orders + + ) +} + +// The widget's configurations +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +This adds a widget in a product's details page with a link to the Orders page. The link's path must be without the `/app` prefix. + +Refer to [react-router-dom’s documentation](https://reactrouter.com/en/main) for other available components and hooks. + +*** + +## Admin Translations + +The Medusa Admin dashboard can be displayed in languages other than English, which is the default. Other languages are added through community contributions. + +Learn how to add a new language translation for the Medusa Admin in [this guide](undefined/resources/contribution-guidelines/admin-translations). # Admin Widgets @@ -7440,75 +7522,6 @@ npx medusa db:migrate The first command generates the migration under the `migrations` directory of your module's directory, and the second reflects it on the database. -# Configure Data Model Properties - -In this chapter, you’ll learn how to configure data model properties. - -## Property’s Default Value - -Use the `default` method on a property's definition to specify the default value of a property. - -For example: - -```ts highlights={defaultHighlights} -import { model } from "@medusajs/framework/utils" - -const MyCustom = model.define("my_custom", { - color: model - .enum(["black", "white"]) - .default("black"), - age: model - .number() - .default(0), - // ... -}) - -export default MyCustom -``` - -In this example, you set the default value of the `color` enum property to `black`, and that of the `age` number property to `0`. - -*** - -## Nullable Property - -Use the `nullable` method to indicate that a property’s value can be `null`. - -For example: - -```ts highlights={nullableHighlights} -import { model } from "@medusajs/framework/utils" - -const MyCustom = model.define("my_custom", { - price: model.bigNumber().nullable(), - // ... -}) - -export default MyCustom -``` - -*** - -## Unique Property - -The `unique` method indicates that a property’s value must be unique in the database through a unique index. - -For example: - -```ts highlights={uniqueHighlights} -import { model } from "@medusajs/framework/utils" - -const User = model.define("user", { - email: model.text().unique(), - // ... -}) - -export default User -``` - -In this example, multiple users can’t have the same email. - - # Data Model Default Properties In this chapter, you'll learn about the properties available by default in your data model. @@ -8439,6 +8452,50 @@ The `cascades` method accepts an object. Its key is the operation’s name, such In the example above, when a store is deleted, its associated products are also deleted. +# Searchable Data Model Property + +In this chapter, you'll learn what a searchable property is and how to define it. + +## What is a Searchable Property? + +Methods generated by the [service factory](undefined/learn/fundamentals/modules/service-factory) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters. + +When the `q` filter is passed, the data model's searchable properties are queried to find matching records. + +*** + +## Define a Searchable Property + +Use the `searchable` method on a `text` property to indicate that it's searchable. + +For example: + +```ts highlights={searchableHighlights} +import { model } from "@medusajs/framework/utils" + +const MyCustom = model.define("my_custom", { + name: model.text().searchable(), + // ... +}) + +export default MyCustom +``` + +In this example, the `name` property is searchable. + +### Search Example + +If you pass a `q` filter to the `listMyCustoms` method: + +```ts +const myCustoms = await helloModuleService.listMyCustoms({ + q: "John", +}) +``` + +This retrieves records that include `John` in their `name` property. + + # Write Migration In this chapter, you'll learn how to create a migration and write it manually. @@ -8517,48 +8574,217 @@ This rolls back the last ran migration on the Hello Module. To learn more about the Medusa CLI's database commands, refer to [this CLI reference](undefined/resources/medusa-cli/commands/db). -# Searchable Data Model Property +# Event Data Payload -In this chapter, you'll learn what a searchable property is and how to define it. +In this chapter, you'll learn how subscribers receive an event's data payload. -## What is a Searchable Property? +## Access Event's Data Payload Methods generated by the [service factory](undefined/learn/fundamentals/modules/service-factory) that accept filters, such as `list{ModelName}s`, accept a `q` property as part of the filters. -When the `q` filter is passed, the data model's searchable properties are queried to find matching records. - -*** - -## Define a Searchable Property - -Use the `searchable` method on a `text` property to indicate that it's searchable. +The object that the subscriber function receives as a parameter has an `event` property, which is an object holding the event payload in a `data` property with additional context. For example: -```ts highlights={searchableHighlights} -import { model } from "@medusajs/framework/utils" +```ts title="src/subscribers/product-created.ts" highlights={highlights} collapsibleLines="1-5" expandButtonLabel="Show Imports" +import type { + SubscriberArgs, + SubscriberConfig, +} from "@medusajs/framework" -const MyCustom = model.define("my_custom", { - name: model.text().searchable(), +export default async function productCreateHandler({ + event, +}: SubscriberArgs<{ id: string }>) { + const productId = event.data.id + console.log(`The product ${productId} was created`) +} + +export const config: SubscriberConfig = { + event: "product.created", +} +``` + +The `event` object has the following properties: + +- data: (\`object\`) The data payload of the event. Its properties are different for each event. +- name: (string) The name of the triggered event. +- metadata: (\`object\`) Additional data and context of the emitted event. + +This logs the product ID received in the `product.created` event’s data payload to the console. + +{/* --- + +## List of Events with Data Payload + +Refer to [this reference](!resources!/events-reference) for a full list of events emitted by Medusa and their data payloads. */} + + +# Emit Workflow and Service Events + +In this chapter, you'll learn about event types and how to emit an event in a service or workflow. + +## Event Types + +In your customization, you can emit an event, then listen to it in a subscriber and perform an asynchronus action, such as send a notification or data to a third-party system. + +There are two types of events in Medusa: + +1. Workflow event: an event that's emitted in a workflow after a commerce feature is performed. For example, Medusa emits the `order.placed` event after a cart is completed. +2. Service event: an event that's emitted to track, trace, or debug processes under the hood. For example, you can emit an event with an audit trail. + +### Which Event Type to Use? + +**Workflow events** are the most common event type in development, as most custom features and customizations are built around workflows. + +Some examples of workflow events: + +1. When a user creates a blog post and you're emitting an event to send a newsletter email. +2. When you finish syncing products to a third-party system and you want to notify the admin user of new products added. +3. When a customer purchases a digital product and you want to generate and send it to them. + +You should only go for a **service event** if you're emitting an event for processes under the hood that don't directly affect front-facing features. + +Some examples of service events: + +1. When you're tracing data manipulation and changes, and you want to track every time some custom data is changed. +2. When you're syncing data with a search engine. + +*** + +## Emit Event in a Workflow + +To emit a workflow event, use the `emitEventStep` helper step provided in the `@medusajs/medusa/core-flows` package. + +For example: + +```ts highlights={highlights} +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + emitEventStep, +} from "@medusajs/medusa/core-flows" + +const helloWorldWorkflow = createWorkflow( + "hello-world", + () => { + // ... + + emitEventStep({ + eventName: "custom.created", + data: { + id: "123", + // other data payload + }, + }) + } +) +``` + +The `emitEventStep` accepts an object having the following properties: + +- `eventName`: The event's name. +- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. + +In this example, you emit the event `custom.created` and pass in the data payload an ID property. + +### Test it Out + +If you execute the workflow, the event is emitted and you can see it in your application's logs. + +Any subscribers listening to the event are executed. + +*** + +## Emit Event in a Service + +To emit a service event: + +1. Resolve `event_bus` from the module's container in your service's constructor: + +### Extending Service Factory + +```ts title="src/modules/hello/service.ts" highlights={["9"]} +import { IEventBusService } from "@medusajs/framework/types" +import { MedusaService } from "@medusajs/framework/utils" + +class HelloModuleService extends MedusaService({ + MyCustom, +}){ + protected eventBusService_: AbstractEventBusModuleService + + constructor({ event_bus }) { + super(...arguments) + this.eventBusService_ = event_bus + } +} +``` + +### Without Service Factory + +```ts title="src/modules/hello/service.ts" highlights={["6"]} +import { IEventBusService } from "@medusajs/framework/types" + +class HelloModuleService { + protected eventBusService_: AbstractEventBusModuleService + + constructor({ event_bus }) { + this.eventBusService_ = event_bus + } +} +``` + +2. Use the event bus service's `emit` method in the service's methods to emit an event: + +```ts title="src/modules/hello/service.ts" highlights={serviceHighlights} +class HelloModuleService { // ... -}) + performAction() { + // TODO perform action -export default MyCustom + this.eventBusService_.emit({ + name: "custom.event", + data: { + id: "123", + // other data payload + }, + }) + } +} ``` -In this example, the `name` property is searchable. +The method accepts an object having the following properties: -### Search Example +- `name`: The event's name. +- `data`: The data payload as an object. You can pass any properties in the object, and subscribers listening to the event will receive this data in the event's payload. -If you pass a `q` filter to the `listMyCustoms` method: +3. By default, the Event Module's service isn't injected into your module's container. To add it to the container, pass it in the module's registration object in `medusa-config.ts` in the `dependencies` property: -```ts -const myCustoms = await helloModuleService.listMyCustoms({ - q: "John", +```ts title="medusa-config.ts" highlights={depsHighlight} +import { Modules } from "@medusajs/framework/utils" + +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "./src/modules/hello", + dependencies: [ + Modules.EVENT_BUS, + ], + }, + ], }) ``` -This retrieves records that include `John` in their `name` property. +The `dependencies` property accepts an array of module registration keys. The specified modules' main services are injected into the module's container. + +That's how you can resolve it in your module's main service's constructor. + +### Test it Out + +If you execute the `performAction` method of your service, the event is emitted and you can see it in your application's logs. + +Any subscribers listening to the event are also executed. # Event Data Payload @@ -9776,6 +10002,37 @@ export const countProductsStep = createStep( Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. +# Architectural Modules + +In this chapter, you’ll learn about architectural modules. + +## What is an Architectural Module? + +An architectural module implements features and mechanisms related to the Medusa application’s architecture and infrastructure. + +Since modules are interchangeable, you have more control over Medusa’s architecture. For example, you can choose to use Memcached for event handling instead of Redis. + +*** + +## Architectural Module Types + +There are different architectural module types including: + +![Diagram illustrating how the modules connect to third-party services](https://res.cloudinary.com/dza7lstvk/image/upload/v1727095814/Medusa%20Book/architectural-modules_bj9bb9.jpg) + +- Cache Module: Defines the caching mechanism or logic to cache computational results. +- Event Module: Integrates a pub/sub service to handle subscribing to and emitting events. +- Workflow Engine Module: Integrates a service to store and track workflow executions and steps. +- File Module: Integrates a storage service to handle uploading and managing files. +- Notification Module: Integrates a third-party service or defines custom logic to send notifications to users and customers. + +*** + +## Architectural Modules List + +Refer to the [Architectural Modules reference](undefined/resources/architectural-modules) for a list of Medusa’s architectural modules, available modules to install, and how to create an architectural module. + + # Module Container In this chapter, you'll learn about the module's container and how to resolve resources in that container. @@ -10087,103 +10344,47 @@ You can now resolve the MongoDB Module's main service in your customizations to # Module Isolation -In this chapter, you'll learn how modules are isolated, and what that means for your custom development. +In this chapter, you'll learn about Medusa's commerce modules. - Modules can't access resources, such as services or data models, from other modules. - Use Medusa's linking concepts, as explained in the [Module Links chapters](undefined/learn/fundamentals/module-links), to extend a module's data models and retrieve data across modules. -## How are Modules Isolated? +Commerce modules are built-in [modules](undefined/learn/fundamentals/modules) of Medusa that provide core commerce logic specific to domains like Products, Orders, Customers, Fulfillment, and much more. -A module is unaware of any resources other than its own, such as services or data models. This means it can't access these resources if they're implemented in another module. +Medusa's commerce modules are used to form Medusa's default [workflows](undefined/resources/medusa-workflows-reference) and [APIs](undefined/api/store). For example, when you call the add to cart endpoint. the add to cart workflow runs which uses the Product Module to check if the product exists, the Inventory Module to ensure the product is available in the inventory, and the Cart Module to finally add the product to the cart. -For example, your custom module can't resolve the Product Module's main service or have direct relationships from its data model to the Product Module's data models. +You'll find the details and steps of the add-to-cart workflow in [this workflow reference](undefined/resources/references/medusa-workflows/addToCartWorkflow) + +The core commerce logic contained in Commerce Modules is also available directly when you are building customizations. This granular access to commerce functionality is unique and expands what's possible to build with Medusa drastically. + +### List of Medusa's Commerce Modules + +Refer to [this reference](undefined/resources/commerce-modules) for a full list of commerce modules in Medusa. *** -## Why are Modules Isolated +## Use Commerce Modules in Custom Flows -Some of the module isolation's benefits include: +Similar to your [custom modules](undefined/learn/fundamentals/modules), the Medusa application registers a commerce module's service in the [container](undefined/learn/fundamentals/medusa-container). So, you can resolve it in your custom flows. This is useful as you build unique requirements extending core commerce features. -- Integrate your module into any Medusa application without side-effects to your setup. -- Replace existing modules with your custom implementation, if your use case is drastically different. -- Use modules in other environments, such as Edge functions and Next.js apps. +For example, consider you have a [workflow](undefined/learn/fundamentals/workflows) (a special function that performs a task in a series of steps with rollback mechanism) that needs a step to retrieve the total number of products. You can create a step in the workflow that resolves the Product Module's service from the container to use its methods: -*** +```ts highlights={highlights} +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" -## How to Extend Data Model of Another Module? +export const countProductsStep = createStep( + "count-products", + async ({ }, { container }) => { + const productModuleService = container.resolve("product") To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](undefined/learn/fundamentals/module-links). -*** - -## How to Use Services of Other Modules? - -If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities. - -Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output. - -### Example - -For example, consider you have two modules: - -1. A module that stores and manages brands in your application. -2. A module that integrates a third-party Content Management System (CMS). - -To sync brands from your application to the third-party system, create the following steps: - -```ts title="Example Steps" highlights={stepsHighlights} -const retrieveBrandsStep = createStep( - "retrieve-brands", - async (_, { container }) => { - const brandModuleService = container.resolve( - "brandModuleService" - ) - - const brands = await brandModuleService.listBrands() - - return new StepResponse(brands) - } -) - -const createBrandsInCmsStep = createStep( - "create-brands-in-cms", - async ({ brands }, { container }) => { - const cmsModuleService = container.resolve( - "cmsModuleService" - ) - - const cmsBrands = await cmsModuleService.createBrands(brands) - - return new StepResponse(cmsBrands, cmsBrands) - }, - async (brands, { container }) => { - const cmsModuleService = container.resolve( - "cmsModuleService" - ) - - await cmsModuleService.deleteBrands( - brands.map((brand) => brand.id) - ) + return new StepResponse(count) } ) ``` -The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module. - -Then, create the following workflow that uses these steps: - -```ts title="Example Workflow" -export const syncBrandsWorkflow = createWorkflow( - "sync-brands", - () => { - const brands = retrieveBrandsStep() - - createBrandsInCmsStep({ brands }) - } -) -``` - -You can then use this workflow in an API route, scheduled job, or other resources that use this functionality. +Your workflow can use services of both custom and commerce modules, supporting you in building custom flows without having to re-build core commerce features. # Perform Database Operations in a Service @@ -10607,31 +10808,99 @@ class HelloModuleService { ``` -# Modules Directory Structure +# Module Isolation -In this document, you'll learn about the expected files and directories in your module. +In this chapter, you'll learn how modules are isolated, and what that means for your custom development. -![Module Directory Structure Example](https://res.cloudinary.com/dza7lstvk/image/upload/v1714379976/Medusa%20Book/modules-dir-overview_nqq7ne.jpg) +- Modules can't access resources, such as services or data models, from other modules. +- Use Medusa's linking concepts, as explained in the [Module Links chapters](undefined/learn/fundamentals/module-links), to extend a module's data models and retrieve data across modules. -## index.ts +## How are Modules Isolated? The `index.ts` file in the root of your module's directory is the only required file. It must export the module's definition as explained in a [previous chapter](undefined/learn/fundamentals/modules). *** -## service.ts +## Why are Modules Isolated A module must have a main service. It's created in the `service.ts` file at the root of your module directory as explained in a [previous chapter](undefined/learn/fundamentals/modules). *** -## Other Directories +## How to Extend Data Model of Another Module? -The following directories are optional and their content are explained more in the following chapters: +To extend the data model of another module, such as the `product` data model of the Product Module, use Medusa's linking concepts as explained in the [Module Links chapters](undefined/learn/fundamentals/module-links). -- `models`: Holds the data models representing tables in the database. -- `migrations`: Holds the migration files used to reflect changes on the database. -- `loaders`: Holds the scripts to run on the Medusa application's start-up. +*** + +## How to Use Services of Other Modules? + +If you're building a feature that uses functionalities from different modules, use a workflow whose steps resolve the modules' services to perform these functionalities. + +Workflows ensure data consistency through their roll-back mechanism and tracking of each execution's status, steps, input, and output. + +### Example + +For example, consider you have two modules: + +1. A module that stores and manages brands in your application. +2. A module that integrates a third-party Content Management System (CMS). + +To sync brands from your application to the third-party system, create the following steps: + +```ts title="Example Steps" highlights={stepsHighlights} +const retrieveBrandsStep = createStep( + "retrieve-brands", + async (_, { container }) => { + const brandModuleService = container.resolve( + "brandModuleService" + ) + + const brands = await brandModuleService.listBrands() + + return new StepResponse(brands) + } +) + +const createBrandsInCmsStep = createStep( + "create-brands-in-cms", + async ({ brands }, { container }) => { + const cmsModuleService = container.resolve( + "cmsModuleService" + ) + + const cmsBrands = await cmsModuleService.createBrands(brands) + + return new StepResponse(cmsBrands, cmsBrands) + }, + async (brands, { container }) => { + const cmsModuleService = container.resolve( + "cmsModuleService" + ) + + await cmsModuleService.deleteBrands( + brands.map((brand) => brand.id) + ) + } +) +``` + +The `retrieveBrandsStep` retrieves the brands from a brand module, and the `createBrandsInCmsStep` creates the brands in a third-party system using a CMS module. + +Then, create the following workflow that uses these steps: + +```ts title="Example Workflow" +export const syncBrandsWorkflow = createWorkflow( + "sync-brands", + () => { + const brands = retrieveBrandsStep() + + createBrandsInCmsStep({ brands }) + } +) +``` + +You can then use this workflow in an API route, scheduled job, or other resources that use this functionality. # Multiple Services in a Module @@ -12804,76 +13073,132 @@ In this example, you use when-then to run the `createProductsWorkflow` only if ` # Multiple Step Usage in Workflow -In this chapter, you'll learn how to use a step multiple times in a workflow. +In this chapter, you'll learn how to execute a workflow in another. -## Problem Reusing a Step in a Workflow +## Execute in a Workflow -In some cases, you may need to use a step multiple times in the same workflow. +To execute a workflow in another, use the `runAsStep` method that every workflow has. -The most common example is using the `useQueryGraphStep` multiple times in a workflow to retrieve multiple unrelated data, such as customers and products. +For example: -Each workflow step must have a unique ID, which is the ID passed as a first parameter when creating the step: +```ts highlights={workflowsHighlights} collapsibleLines="1-7" expandMoreButton="Show Imports" +import { + createWorkflow, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" -```ts -const useQueryGraphStep = createStep( - "use-query-graph" - // ... -) -``` - -This causes an error when you use the same step multiple times in a workflow, as it's registered in the workflow as two steps having the same ID: - -```ts -const helloWorkflow = createWorkflow( - "hello", - () => { - const { data: products } = useQueryGraphStep({ - entity: "product", - fields: ["id"], +const workflow = createWorkflow( + "hello-world", + async (input) => { + const products = createProductsWorkflow.runAsStep({ + input: { + products: [ + // ... + ], + }, }) - // ERROR OCCURS HERE: A STEP HAS THE SAME ID AS ANOTHER IN THE WORKFLOW - const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: ["id"], - }) + // ... } ) ``` -The next section explains how to fix this issue to use the same step multiple times in a workflow. +Instead of invoking the workflow and passing it the container, you use its `runAsStep` method and pass it an object as a parameter. + +The object has an `input` property to pass input to the workflow. *** -## How to Use a Step Multiple Times in a Workflow? +## Preparing Input Data -When you execute a step in a workflow, you can chain a `config` method to it to change the step's config. +If you need to perform some data manipulation to prepare the other workflow's input data, use `transform` from the Workflows SDK. -Use the `config` method to change a step's ID for a single execution. +Learn about transform in [this chapter](undefined/learn/fundamentals/workflows/variable-manipulation). -So, this is the correct way to write the example above: +For example: -```ts highlights={highlights} -const helloWorkflow = createWorkflow( - "hello", - () => { - const { data: products } = useQueryGraphStep({ - entity: "product", - fields: ["id"], +```ts highlights={transformHighlights} collapsibleLines="1-12" +import { + createWorkflow, + transform, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" + +type WorkflowInput = { + title: string +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const createProductsData = transform({ + input, + }, (data) => [ + { + title: `Hello ${data.input.title}`, + }, + ]) + + const products = createProductsWorkflow.runAsStep({ + input: { + products: createProductsData, + }, }) - // ✓ No error occurs, the step has a different ID. - const { data: customers } = useQueryGraphStep({ - entity: "customer", - fields: ["id"], - }).config({ name: "fetch-customers" }) + // ... } ) ``` -The `config` method accepts an object with a `name` property. Its value is a new ID of the step to use for this execution only. +In this example, you use the `transform` function to prepend `Hello` to the title of the product. Then, you pass the result as an input to the `createProductsWorkflow`. -The first `useQueryGraphStep` usage has the ID `use-query-graph`, and the second `useQueryGraphStep` usage has the ID `fetch-customers`. +*** + +## Run Workflow Conditionally + +To run a workflow in another based on a condition, use when-then from the Workflows SDK. + +Learn about when-then in [this chapter](undefined/learn/fundamentals/workflows/conditions). + +For example: + +```ts highlights={whenHighlights} collapsibleLines="1-16" +import { + createWorkflow, + when, +} from "@medusajs/framework/workflows-sdk" +import { + createProductsWorkflow, +} from "@medusajs/medusa/core-flows" +import { + CreateProductWorkflowInputDTO, +} from "@medusajs/framework/types" + +type WorkflowInput = { + product?: CreateProductWorkflowInputDTO + should_create?: boolean +} + +const workflow = createWorkflow( + "hello-product", + async (input: WorkflowInput) => { + const product = when(input, ({ should_create }) => should_create) + .then(() => { + return createProductsWorkflow.runAsStep({ + input: { + products: [input.product], + }, + }) + }) + } +) +``` + +In this example, you use when-then to run the `createProductsWorkflow` only if `should_create` (passed in the `input`) is enabled. # Run Workflow Steps in Parallel @@ -12930,6 +13255,92 @@ It returns an array of the steps' results in the same order they're passed to th So, `prices` is the result of `createPricesStep`, and `productSalesChannel` is the result of `attachProductToSalesChannelStep`. +# Retry Failed Steps + +In this chapter, you’ll learn how to configure steps to allow retrial on failure. + +## Configure a Step’s Retrial + +By default, when an error occurs in a step, the step and the workflow fail, and the execution stops. + +You can configure the step to retry on failure. The `createStep` function can accept a configuration object instead of the step’s name as a first parameter. + +For example: + +```ts title="src/workflows/hello-world.ts" highlights={[["10"]]} collapsibleLines="1-6" expandButtonLabel="Show Imports" +import { + createStep, + createWorkflow, + WorkflowResponse, +} from "@medusajs/framework/workflows-sdk" + +const step1 = createStep( + { + name: "step-1", + maxRetries: 2, + }, + async () => { + console.log("Executing step 1") + + throw new Error("Oops! Something happened.") + } +) + +const myWorkflow = createWorkflow( + "hello-world", + function () { + const str1 = step1() + + return new WorkflowResponse({ + message: str1, + }) +}) + +export default myWorkflow +``` + +The step’s configuration object accepts a `maxRetries` property, which is a number indicating the number of times a step can be retried when it fails. + +When you execute the above workflow, you’ll see the following result in the terminal: + +```bash +Executing step 1 +Executing step 1 +Executing step 1 +error: Oops! Something happened. +Error: Oops! Something happened. +``` + +The first line indicates the first time the step was executed, and the next two lines indicate the times the step was retried. After that, the step and workflow fail. + +*** + +## Step Retry Intervals + +By default, a step is retried immediately after it fails. To specify a wait time before a step is retried, pass a `retryInterval` property to the step's configuration object. Its value is a number of seconds to wait before retrying the step. + +For example: + +```ts title="src/workflows/hello-world.ts" highlights={[["5"]]} +const step1 = createStep( + { + name: "step-1", + maxRetries: 2, + retryInterval: 2, // 2 seconds + }, + async () => { + // ... + } +) +``` + +### Interval Changes Workflow to Long-Running + +By setting `retryInterval` on a step, a workflow becomes a [long-running workflow](undefined/learn/fundamentals/workflows/long-running-workflow) that runs asynchronously in the background. So, you won't receive its result or errors immediately when you execute the workflow. + +Instead, you must subscribe to the workflow's execution using the Workflow Engine Module Service. Learn more about it in [this chapter](undefined/learn/fundamentals/workflows/long-running-workflow#access-long-running-workflow-status-and-result). + + # Store Workflow Executions In this chapter, you'll learn how to store workflow executions in the database and access them later. @@ -29903,6 +30314,98 @@ export default ProductWidget This shows the JSON section at the top of the product page, passing it the object `{ name: "John" }`. +# Section Row - Admin Components + +The Medusa Admin often shows information in rows of label-values, such as when showing a product's details. + +![Example of a section row in the Medusa Admin](https://res.cloudinary.com/dza7lstvk/image/upload/v1728292781/Medusa%20Resources/section-row_kknbnw.png) + +To create a component that shows information in the same structure, create the file `src/admin/components/section-row.tsx` with the following content: + +```tsx title="src/admin/components/section-row.tsx" +import { Text, clx } from "@medusajs/ui" + +export type SectionRowProps = { + title: string + value?: React.ReactNode | string | null + actions?: React.ReactNode +} + +export const SectionRow = ({ title, value, actions }: SectionRowProps) => { + const isValueString = typeof value === "string" || !value + + return ( +
+ + {title} + + + {isValueString ? ( + + {value ?? "-"} + + ) : ( +
{value}
+ )} + + {actions &&
{actions}
} +
+ ) +} +``` + +The `SectionRow` component shows a title and a value in the same row. + +It accepts the following props: + +- title: (\`string\`) The title to show on the left side. +- value: (\`React.ReactNode\` \\| \`string\` \\| \`null\`) The value to show on the right side. +- actions: (\`React.ReactNode\`) The actions to show at the end of the row. + +*** + +## Example + +Use the `SectionRow` component in any widget or UI route. + +For example, create the widget `src/admin/widgets/product-widget.tsx` with the following content: + +```tsx title="src/admin/widgets/product-widget.tsx" +import { defineWidgetConfig } from "@medusajs/admin-sdk" +import { Container } from "../components/container" +import { Header } from "../components/header" +import { SectionRow } from "../components/section-row" + +const ProductWidget = () => { + return ( + +
+ + + ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.before", +}) + +export default ProductWidget +``` + +This widget also uses the [Container](undefined/home/runner/work/medusa/medusa/www/apps/resources/app/admin-components/components/container) and [Header](undefined/home/runner/work/medusa/medusa/www/apps/resources/app/admin-components/components/header) custom component. + + # Table - Admin Components If you're using [Medusa v2.4.0+](https://github.com/medusajs/medusa/releases/tag/v2.4.0), it's recommended to use the [Data Table](undefined/home/runner/work/medusa/medusa/www/apps/resources/app/admin-components/components/data-table) component instead. diff --git a/www/apps/user-guide/app/globals.css b/www/apps/user-guide/app/globals.css index 1d4eb3275c..4a75d63add 100644 --- a/www/apps/user-guide/app/globals.css +++ b/www/apps/user-guide/app/globals.css @@ -17,10 +17,14 @@ @apply bg-medusa-bg-highlight; } - * { + *:not(.code-block-elm) { scrollbar-color: var(--docs-border-base) var(--docs-bg-base); } + .code-block-elm { + scrollbar-color: var(--docs-contrast-border-base) transparent; + } + aside * { scrollbar-color: var(--docs-border-base) var(--docs-bg-subtle); } diff --git a/www/apps/user-guide/app/layout.tsx b/www/apps/user-guide/app/layout.tsx index 62fabfc5db..a5024adfe9 100644 --- a/www/apps/user-guide/app/layout.tsx +++ b/www/apps/user-guide/app/layout.tsx @@ -1,7 +1,7 @@ import type { Metadata } from "next" import { Inter, Roboto_Mono } from "next/font/google" import Providers from "@/providers" -import "../css/globals.css" +import "./globals.css" import { BareboneLayout, TightLayout } from "docs-ui" import { config } from "@/config" import clsx from "clsx" diff --git a/www/apps/user-guide/app/page.mdx b/www/apps/user-guide/app/page.mdx index 605b6d0442..d16df61544 100644 --- a/www/apps/user-guide/app/page.mdx +++ b/www/apps/user-guide/app/page.mdx @@ -6,21 +6,31 @@ export const metadata = { # {metadata.title} -This user guide is intended to help users learn how they can use the Medusa Admin to perform different operational and ecommerce tasks. +Welcome to the Medusa Admin user guide. This user guide is intended to help admin users learn how they can use the Medusa Admin to perform commerce operations and manage their store. -{/* TODO change image */} +{/* TODO update photo */} -![Medusa Admin Overview](https://res.cloudinary.com/dza7lstvk/image/upload/v1710228410/User%20Guide/Screenshot_2024-03-12_at_9.24.45_AM_wllqvq.png) +![Medusa Admin Overview](https://res.cloudinary.com/dza7lstvk/image/upload/v1739530936/User%20Guide/Screenshot_2025-02-14_at_1.01.35_PM_ecyltl.png) + +## Who is this guide for? + +This guide is for Medusa Admin users looking to master the Medusa Admin and manage their store effectively. You don't need any technical knowledge to follow these guides. + +You can also follow this guide if you're exploring Medusa and its operational features. + + + +For technical users, learn how to build and customize Medusa in [this documentation](!docs!/learn). + + ## Access the Medusa Admin -To access the admin panel of your ecommerce store, open the URL of your local or deployed website. +To access the admin panel of your ecommerce store, open the URL of your local or deployed website. You’ll be asked to log in. -You’ll be asked to log in. +![Log In Page](https://res.cloudinary.com/dza7lstvk/image/upload/v1739526611/User%20Guide/Screenshot_2025-02-14_at_11.45.35_AM_j1gepy.png) -![Log In Page](https://res.cloudinary.com/dza7lstvk/image/upload/v1710228558/User%20Guide/Screenshot_2024-03-12_at_9.29.08_AM_cqjgq3.png) - -You must use your user’s email and password to log in. If you’re unsure what your email and password are, please contact the technical personnel that deployed your Medusa application and admin. +You must use your user’s email and password to log in. If you’re unsure what your email and password are, please contact the technical team that deployed your Medusa application. @@ -30,35 +40,42 @@ Refer to [this documentation](!resources!/medusa-cli#user) on how to create a us ## Tour of Medusa Admin -After you log into your Medusa Admin, you see a sidebar menu to the left, a search bar at the top, some icons at the top right, and in the middle the content of the page you’re currently on. +After you log into your Medusa Admin, you see a sidebar menu to the left with a search bar at its top, a at the top right, and in the middle the content of the page you’re currently on. ### Sidebar Menu The sidebar menu includes the main navigation of your Medusa Admin. You can use it to navigate between different pages, such as Products or Orders pages. -{/* TODO update image */} - -![Sidebar Menu](https://res.cloudinary.com/dza7lstvk/image/upload/v1697793775/Medusa%20Docs/User%20Guide/Screenshot_2023-10-20_at_12.22.43_PM_shbr0e.png) +![Sidebar Menu](https://res.cloudinary.com/dza7lstvk/image/upload/v1739529281/User%20Guide/Screenshot_2025-02-14_at_12.34.22_PM_oeyaol.png) You can hide the sidebar by clicking the icon at the top next to the sidebar. Clicking it again shows the sidebar. +### Sidebar Shortcuts + +Sidebar items have shortcuts. For example, the Products page has a G then P shortcut. To view the shortcut of an item, hover your mouse for a few seconds and a tooltip will appear with the shortcut. + +![Sidebar Shortcuts](https://res.cloudinary.com/dza7lstvk/image/upload/v1739530172/User%20Guide/Screenshot_2025-02-14_at_12.48.19_PM_moyjtv.png) + +You can also find all shortcuts by clicking on your profile item at the bottom of the sidebar and choosing "Shortcuts". + +### Settings and Quick Actions + +At the bottom of the sidebar you'll find a Settings item, which takes you to further settings of your store such as Region and Sales Channel management. You'll also find your avatar and name. + +![Settings and profile](https://res.cloudinary.com/dza7lstvk/image/upload/v1739530277/User%20Guide/Screenshot_2025-02-14_at_12.51.06_PM_lq0ehf.png) + +By clicking on your avatar and name, you'll view quick actions such as going to profile settings, view shortcuts, or changing the admin theme. + +![Profile Quick Actions](https://res.cloudinary.com/dza7lstvk/image/upload/v1739530277/User%20Guide/Screenshot_2025-02-14_at_12.50.50_PM_qqmw54.png) + ### Search Bar -You’ll find at the top of the page a search bar. Use this search bar to search orders, products, customers, and more information available in your Medusa Admin. +At the top of the sidebar, you'll find a search item. You can also open the search window using the shortcuts: - +- Windows and Linux: CTRL + K +- Mac OS: + K -You can open the search window using the shortcuts: - -Windows and Linux: CTRL + K - -Mac OS: + K - - - -{/* TODO check if image needs updating */} - -![Search Window](https://res.cloudinary.com/dza7lstvk/image/upload/v1710228839/User%20Guide/Screenshot_2024-03-12_at_9.33.46_AM_ywdvpb.png) +![Search Window](https://res.cloudinary.com/dza7lstvk/image/upload/v1739529553/User%20Guide/Screenshot_2025-02-14_at_12.36.53_PM_vps6cm.png) You can select a result with your mouse, or use the up and down arrows on your keyboard to navigate between the results, then choose a result by hitting the Enter key. @@ -66,9 +83,7 @@ You can select a result with your mouse, or use the up and down arrows on your k At the top right you’ll find a icon. Clicking this icon opens a side window to view any notifications you might have. Use this to view notifications such as completed product exports. -{/* TODO change images */} - -![Notifications Window](https://res.cloudinary.com/dza7lstvk/image/upload/v1710243446/User%20Guide/XS_from_Figma_mkn3i2.jpg) +![Notifications Window](https://res.cloudinary.com/dza7lstvk/image/upload/v1739529955/User%20Guide/Screenshot_2025-02-14_at_12.45.44_PM_ljdvof.png) ### Change Language @@ -76,20 +91,10 @@ The Medusa Admin supports multiple-languages, with English being the default one Learn how to change the language in [this guide](./settings/profile/page.mdx#change-admin-language). -### Quick Actions +### Change Admin Theme -At the top right in the sidebar, you’ll find an avatar icon with your email or name. By clicking this icon, you’ll see a dropdown. Use this dropdown to access settings or sign out. +The Medusa Admin comes in light and dark themes. You can change the theme by clicking the profile item at the bottom of the sidebar -> Theme -> Light, Dark, or System, defaulting to your Operating System or Browser theme. -{/* TODO check if image needs updating */} +{/* TODO add dark theme */} -![Quick Actions Dropdown](https://res.cloudinary.com/dza7lstvk/image/upload/v1710229778/User%20Guide/Screenshot_2024-03-12_at_9.49.26_AM_j3f1pu.png) - -### Change Theme - -The Medusa Admin supports both light and dark modes. - -![Dark mode](https://res.cloudinary.com/dza7lstvk/image/upload/v1710323255/User%20Guide/Screenshot_2024-03-13_at_11.46.52_AM_atvw1e.png) - -You can switch the theme by opening the quick actions dropdown, choosing theme, then choosing the prefered mode. - -![Switch modes](https://res.cloudinary.com/dza7lstvk/image/upload/v1710323157/User%20Guide/Screenshot_2024-03-13_at_11.43.01_AM_giyhto.png) \ No newline at end of file +![Dark Theme](https://res.cloudinary.com/dza7lstvk/image/upload/v1739530936/User%20Guide/Screenshot_2025-02-14_at_1.01.56_PM_fdcgmz.png) diff --git a/www/apps/user-guide/css/globals.css b/www/apps/user-guide/css/globals.css deleted file mode 100644 index e47e019b64..0000000000 --- a/www/apps/user-guide/css/globals.css +++ /dev/null @@ -1,22 +0,0 @@ -@tailwind base; -@tailwind components; -@tailwind utilities; - -@layer base { - - html { - -webkit-font-smoothing: antialiased; - } - - body { - @apply overflow-x-hidden; - } - - *::selection { - @apply bg-medusa-bg-highlight; - } - - body[data-modal="opened"] { - @apply !overflow-hidden; - } -} \ No newline at end of file diff --git a/www/apps/user-guide/eslint.config.mjs b/www/apps/user-guide/eslint.config.mjs index b0e70103ab..21838f2aa2 100644 --- a/www/apps/user-guide/eslint.config.mjs +++ b/www/apps/user-guide/eslint.config.mjs @@ -28,6 +28,7 @@ export default [ "**/node_modules", "**/public", "**/.eslintrc.js", + "generated", ], }, ...compat.extends( diff --git a/www/apps/user-guide/generated/edit-dates.mjs b/www/apps/user-guide/generated/edit-dates.mjs new file mode 100644 index 0000000000..dee818dba9 --- /dev/null +++ b/www/apps/user-guide/generated/edit-dates.mjs @@ -0,0 +1,60 @@ +export const generatedEditDates = { + "app/tips/languages/page.mdx": "2024-05-03T17:36:38+03:00", + "app/tips/bulk-editor/page.mdx": "2024-05-03T17:36:38+03:00", + "app/tips/lists/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/sales-channels/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/users/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/regions/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/taxes/tax-rates/page.mdx": "2024-05-03T17:36:38+03:00", + "app/page.mdx": "2025-02-14T11:18:46.636Z", + "app/settings/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/export/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/return-reasons/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/regions/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/regions/shipping-options/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/users/invites/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/taxes/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/developer/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/taxes/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/profile/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/regions/countries/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/store/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/gift-cards/product-gift-card/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/import/page.mdx": "2024-05-03T17:36:38+03:00", + "app/pricing/create/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/regions/providers/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/locations/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/developer/api-key-management/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/categories/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/exchange/page.mdx": "2024-05-03T17:36:38+03:00", + "app/inventory/reservations/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/developer/api-key-management/sales-channels/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/gift-cards/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/gift-cards/customer-gift-card/page.mdx": "2024-05-03T17:36:38+03:00", + "app/pricing/page.mdx": "2024-05-03T17:36:38+03:00", + "app/pricing/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/discounts/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/pricing/_import/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/sales-channels/products/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/developer/executions/page.mdx": "2024-05-03T17:36:38+03:00", + "app/inventory/inventory/page.mdx": "2024-05-03T17:36:38+03:00", + "app/customers/groups/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/returns/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/export/page.mdx": "2024-05-03T17:36:38+03:00", + "app/inventory/page.mdx": "2024-05-03T17:36:38+03:00", + "app/settings/sales-channels/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/claims/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/fulfillments/page.mdx": "2024-05-03T17:36:38+03:00", + "app/customers/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/edit/page.mdx": "2024-05-03T17:36:38+03:00", + "app/products/collections/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/drafts/page.mdx": "2024-05-03T17:36:38+03:00", + "app/customers/manage/page.mdx": "2024-05-03T17:36:38+03:00", + "app/discounts/create/page.mdx": "2024-05-03T17:36:38+03:00", + "app/orders/payments/page.mdx": "2024-05-03T17:36:38+03:00", + "app/discounts/page.mdx": "2024-05-03T17:36:38+03:00" +} \ No newline at end of file diff --git a/www/apps/user-guide/generated/sidebar.mjs b/www/apps/user-guide/generated/sidebar.mjs index d1e1380ec8..0f671eb520 100644 --- a/www/apps/user-guide/generated/sidebar.mjs +++ b/www/apps/user-guide/generated/sidebar.mjs @@ -24,6 +24,7 @@ export const generatedSidebar = [ "type": "link", "path": "/tips/bulk-editor", "title": "Bulk Editor", + "description": "", "children": [] }, { @@ -32,6 +33,7 @@ export const generatedSidebar = [ "type": "link", "path": "/tips/languages", "title": "Admin Languages", + "description": "", "children": [] }, { @@ -40,6 +42,7 @@ export const generatedSidebar = [ "type": "link", "path": "/tips/lists", "title": "Lists", + "description": "", "children": [] } ] @@ -59,6 +62,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/manage", "title": "Manage Details", + "description": "", "children": [] }, { @@ -67,6 +71,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/payments", "title": "Manage Payments", + "description": "", "children": [] }, { @@ -75,6 +80,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/fulfillments", "title": "Manage Fulfillments", + "description": "", "children": [] }, { @@ -83,6 +89,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/edit", "title": "Edit an Order’s Items", + "description": "", "children": [] }, { @@ -91,6 +98,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/drafts", "title": "Manage Draft Orders", + "description": "", "children": [] }, { @@ -99,6 +107,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/returns", "title": "Manage Returns", + "description": "", "children": [] }, { @@ -107,6 +116,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/exchange", "title": "Manage Exchanges", + "description": "", "children": [] }, { @@ -115,6 +125,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/claims", "title": "Manage Order's Claims", + "description": "", "children": [] }, { @@ -123,6 +134,7 @@ export const generatedSidebar = [ "type": "link", "path": "/orders/export", "title": "Export Orders", + "description": "", "children": [] } ] @@ -142,6 +154,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/manage", "title": "Manage Products", + "description": "", "children": [] }, { @@ -150,6 +163,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/collections", "title": "Manage Product Collections", + "description": "", "children": [] }, { @@ -158,6 +172,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/categories", "title": "Manage Product Categories", + "description": "", "children": [] }, { @@ -166,6 +181,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/gift-cards", "title": "Gift Cards", + "description": "", "children": [ { "loaded": true, @@ -173,6 +189,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/gift-cards/product-gift-card", "title": "Manage a Product Gift Card", + "description": "", "children": [], "sidebar_position": 1 }, @@ -182,6 +199,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/gift-cards/customer-gift-card", "title": "Manage a Customer Gift Card", + "description": "", "children": [], "sidebar_position": 2 } @@ -193,6 +211,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/import", "title": "Import Products", + "description": "", "children": [] }, { @@ -201,6 +220,7 @@ export const generatedSidebar = [ "type": "link", "path": "/products/export", "title": "Export Products", + "description": "", "children": [] } ] @@ -220,6 +240,7 @@ export const generatedSidebar = [ "type": "link", "path": "/inventory/inventory", "title": "Manage Inventory", + "description": "", "children": [] }, { @@ -228,6 +249,7 @@ export const generatedSidebar = [ "type": "link", "path": "/inventory/reservations", "title": "Manage Reservations", + "description": "", "children": [] } ] @@ -247,6 +269,7 @@ export const generatedSidebar = [ "type": "link", "path": "/customers/manage", "title": "Manage Customers", + "description": "", "children": [] }, { @@ -255,6 +278,7 @@ export const generatedSidebar = [ "type": "link", "path": "/customers/groups", "title": "Manage Customer Groups", + "description": "", "children": [] } ] @@ -274,6 +298,7 @@ export const generatedSidebar = [ "type": "link", "path": "/discounts/create", "title": "Create a Discount", + "description": "", "children": [] }, { @@ -282,6 +307,7 @@ export const generatedSidebar = [ "type": "link", "path": "/discounts/manage", "title": "Manage Discounts", + "description": "", "children": [] } ] @@ -301,6 +327,7 @@ export const generatedSidebar = [ "type": "link", "path": "/pricing/create", "title": "Create a Price List", + "description": "", "children": [] }, { @@ -309,6 +336,7 @@ export const generatedSidebar = [ "type": "link", "path": "/pricing/manage", "title": "Manage Price Lists", + "description": "", "children": [] } ] @@ -328,6 +356,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/profile", "title": "Profile", + "description": "", "children": [] }, { @@ -336,6 +365,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/store", "title": "Store", + "description": "", "children": [] }, { @@ -344,6 +374,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/users", "title": "Users", + "description": "", "children": [ { "loaded": true, @@ -351,6 +382,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/users/invites", "title": "Manage Invites", + "description": "", "children": [], "sidebar_position": 1 } @@ -362,6 +394,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/regions", "title": "Regions", + "description": "", "children": [ { "loaded": true, @@ -369,6 +402,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/regions/manage", "title": "Manage Regions", + "description": "", "children": [], "sidebar_position": 1 }, @@ -378,6 +412,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/regions/countries", "title": "Manage Countries", + "description": "", "children": [], "sidebar_position": 2 }, @@ -387,6 +422,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/regions/providers", "title": "Manage Providers", + "description": "", "children": [], "sidebar_position": 3 }, @@ -396,6 +432,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/regions/shipping-options", "title": "Manage Shipping Options", + "description": "", "children": [], "sidebar_position": 4 } @@ -407,6 +444,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/return-reasons", "title": "Return Reasons", + "description": "", "children": [] }, { @@ -415,6 +453,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/taxes", "title": "Taxes", + "description": "", "children": [ { "loaded": true, @@ -422,6 +461,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/taxes/manage", "title": "Manage Taxes", + "description": "", "children": [], "sidebar_position": 1 }, @@ -431,6 +471,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/taxes/tax-rates", "title": "Manage Tax Rates", + "description": "", "children": [], "sidebar_position": 2 } @@ -442,6 +483,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/locations", "title": "Locations", + "description": "", "children": [] }, { @@ -450,6 +492,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/sales-channels", "title": "Sales Channels", + "description": "", "children": [ { "loaded": true, @@ -457,6 +500,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/sales-channels/manage", "title": "Manage Sales Channels", + "description": "", "children": [], "sidebar_position": 1 }, @@ -466,6 +510,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/sales-channels/products", "title": "Manage Products", + "description": "", "children": [], "sidebar_position": 2 } @@ -477,6 +522,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/developer", "title": "Developer", + "description": "", "children": [ { "loaded": true, @@ -484,6 +530,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/developer/api-key-management", "title": "API Key Management", + "description": "", "children": [ { "loaded": true, @@ -491,6 +538,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/developer/api-key-management/sales-channels", "title": "Manage Sales Channels", + "description": "", "children": [], "sidebar_position": 1 } @@ -503,6 +551,7 @@ export const generatedSidebar = [ "type": "link", "path": "/settings/developer/executions", "title": "Executions", + "description": "", "children": [], "sidebar_position": 2 } diff --git a/www/apps/user-guide/providers/index.tsx b/www/apps/user-guide/providers/index.tsx index 84a0dd0837..6ec76414bc 100644 --- a/www/apps/user-guide/providers/index.tsx +++ b/www/apps/user-guide/providers/index.tsx @@ -53,6 +53,7 @@ const Providers = ({ children }: ProvidersProps) => { {children} diff --git a/www/apps/user-guide/providers/main-nav.tsx b/www/apps/user-guide/providers/main-nav.tsx index cdb5a18040..78a51f4bd2 100644 --- a/www/apps/user-guide/providers/main-nav.tsx +++ b/www/apps/user-guide/providers/main-nav.tsx @@ -6,12 +6,15 @@ import { } from "docs-ui" import { useMemo } from "react" import { config } from "../config" +import { generatedEditDates } from "../generated/edit-dates.mjs" +import { usePathname } from "next/navigation" type MainNavProviderProps = { children?: React.ReactNode } export const MainNavProvider = ({ children }: MainNavProviderProps) => { + const pathname = usePathname() const navigationDropdownItems = useMemo( () => getNavDropdownItems({ @@ -20,8 +23,16 @@ export const MainNavProvider = ({ children }: MainNavProviderProps) => { [] ) + const editDate = useMemo( + () => + (generatedEditDates as Record)[ + `app${pathname.replace(/\/$/, "")}/page.mdx` + ], + [pathname] + ) + return ( - + {children} ) diff --git a/www/apps/user-guide/scripts/prepare.mjs b/www/apps/user-guide/scripts/prepare.mjs index c477b13ab7..325404f5a8 100644 --- a/www/apps/user-guide/scripts/prepare.mjs +++ b/www/apps/user-guide/scripts/prepare.mjs @@ -1,8 +1,9 @@ -import { generateSidebar } from "build-scripts" +import { generateEditedDates, generateSidebar } from "build-scripts" import { sidebar } from "../sidebar.mjs" async function main() { - generateSidebar(sidebar) + await generateSidebar(sidebar) + await generateEditedDates() } void main() diff --git a/www/packages/docs-ui/src/components/AiAssistant/ChatWindow/Footer/index.tsx b/www/packages/docs-ui/src/components/AiAssistant/ChatWindow/Footer/index.tsx index 34a2d79372..6ce54c4827 100644 --- a/www/packages/docs-ui/src/components/AiAssistant/ChatWindow/Footer/index.tsx +++ b/www/packages/docs-ui/src/components/AiAssistant/ChatWindow/Footer/index.tsx @@ -16,10 +16,10 @@ export const AiAssistantChatWindowFooter = () => {
Line break
- + - +
diff --git a/www/packages/docs-ui/src/components/InlineIcon/index.tsx b/www/packages/docs-ui/src/components/InlineIcon/index.tsx index 472b429162..ab6347f1d1 100644 --- a/www/packages/docs-ui/src/components/InlineIcon/index.tsx +++ b/www/packages/docs-ui/src/components/InlineIcon/index.tsx @@ -11,7 +11,10 @@ export const InlineIcon = ({ Icon, alt, ...props }: InlineIconProps) => { return ( ) diff --git a/www/packages/docs-ui/src/components/Kbd/index.tsx b/www/packages/docs-ui/src/components/Kbd/index.tsx index 327f757ca8..bf2d7be960 100644 --- a/www/packages/docs-ui/src/components/Kbd/index.tsx +++ b/www/packages/docs-ui/src/components/Kbd/index.tsx @@ -16,7 +16,7 @@ export const Kbd = ({ className={clsx( "rounded-docs_xs border-solid border border-medusa-border-base", "inline-flex items-center justify-center", - "p-0", + "px-docs_0.25", "bg-medusa-bg-field", "text-medusa-fg-base", "font-base shadow-none",