diff --git a/www/apps/book/app/advanced-development/admin/onboarding-example/page.mdx b/www/apps/book/app/advanced-development/admin/onboarding-example/page.mdx deleted file mode 100644 index b6f56519a7..0000000000 --- a/www/apps/book/app/advanced-development/admin/onboarding-example/page.mdx +++ /dev/null @@ -1,1868 +0,0 @@ -export const metadata = { - title: `${pageNumber} Example: How to Create Onboarding Widget`, -} - -# {metadata.title} - - - -Admin customizations are coming soon. - - - -In this chapter, you’ll learn how to build the onboarding widget available in the admin dashboard the first time you install a Medusa application. - - - -The onboarding widget is already implemented within the codebase of your Medusa application. This chapter is useful if you want to understand how it was implemented using Medusa Admin customizations. - - - -## What you’ll be Building - -By following this chapter, you’ll: - -- Build an onboarding flow in the admin that takes the user through creating a sample product and order. This flow has four steps and navigates the user between four pages in the admin before completing the guide. This will be implemented using widgets. -- Keep track of the current step the user has reached by creating a table in the database and an API Route that the admin widget uses to retrieve and update the current step. - - - -- Medusa React - - - ---- - -## 1. Implement Helper Resources - -The resources in this section are used for typing, layout, and design purposes, and they’re used in other essential components in this tutorial. - -Each of the collapsible elements below hold the path to the file that you should create, and the content of that file. - -
- - ```ts title="src/admin/types/icon-type.ts" - import React from "react" - - type IconProps = { - color?: string - size?: string | number - } & React.SVGAttributes - - export default IconProps - ``` - -
- -
- - ```tsx title="src/admin/components/shared/icons/get-started.tsx" - import React from "react" - import IconProps from "../../../types/icon-type" - - const GetStarted: React.FC = ({ - size = "40", - color = "currentColor", - ...attributes - }) => { - return ( - - - - - - - - - - - - ) - } - - export default GetStarted - ``` - -
- -
- - ```tsx title="src/admin/components/shared/icons/dollar-sign-icon.tsx" - import React from "react" - import IconProps from "../../../types/icon-type" - - const ActiveCircleDottedLine: React.FC = ({ - size = "24", - color = "currentColor", - ...attributes - }) => { - return ( - - - - - - - - - - - - - - - - - - - - - - - - - ) - } - - export default ActiveCircleDottedLine - ``` - -
- -
- - ```tsx title="src/admin/components/shared/accordion.tsx" - import * as AccordionPrimitive from "@radix-ui/react-accordion" - import React from "react" - import { CheckCircleSolid, CircleMiniSolid } from "@medusajs/icons" - import { Heading, Text, clx } from "@medusajs/ui" - import ActiveCircleDottedLine from "./icons/active-circle-dotted-line" - - type AccordionItemProps = AccordionPrimitive.AccordionItemProps & { - title: string; - subtitle?: string; - description?: string; - required?: boolean; - tooltip?: string; - forceMountContent?: true; - headingSize?: "small" | "medium" | "large"; - customTrigger?: React.ReactNode; - complete?: boolean; - active?: boolean; - triggerable?: boolean; - }; - - const Accordion: React.FC< - | (AccordionPrimitive.AccordionSingleProps & - React.RefAttributes) - | (AccordionPrimitive.AccordionMultipleProps & - React.RefAttributes) - > & { - Item: React.FC; - } = ({ children, ...props }) => { - return ( - {children} - ) - } - - const Item: React.FC = ({ - title, - subtitle, - description, - required, - tooltip, - children, - className, - complete, - headingSize = "large", - customTrigger = undefined, - forceMountContent = undefined, - active, - triggerable, - ...props - }) => { - return ( - - -
-
-
-
- {complete ? ( - - ) : ( - <> - {active && ( - - )} - {!active && ( - - )} - - )} -
- - {title} - -
- - {customTrigger || } - -
- {subtitle && ( - - {subtitle} - - )} -
-
- -
- {description && {description}} -
{children}
-
-
-
- ) - } - - Accordion.Item = Item - - const MorphingTrigger = () => { - return ( -
-
- - -
-
- ) - } - - export default Accordion - ``` - -
- -
- - ```tsx title="src/admin/components/shared/card.tsx" - import { Text, clx } from "@medusajs/ui" - - type CardProps = { - icon?: React.ReactNode - children?: React.ReactNode - className?: string - } - - const Card = ({ - icon, - children, - className, - }: CardProps) => { - return ( -
- {icon} - {children} -
- ) - } - - export default Card - ``` - -
- ---- - -## 2. Medusa Application Customizations - -In this step, you’ll customize the Medusa application to: - -1. Add a new table in the database that stores the current onboarding step. This requires creating a new entity, repository, and migration. -2. Add new API Routes that allow retrieving and updating the current onboarding step. This also requires creating a new service. - -### Create Data Model - -To create the data model, create the file `src/models/onboarding.ts` with the following content: - -```ts title="src/models/onboarding.ts" -import { BaseEntity } from "@medusajs/medusa" -import { Column, Entity } from "typeorm" - -@Entity() -export class OnboardingState extends BaseEntity { - @Column() - current_step: string - - @Column() - is_complete: boolean - - @Column() - product_id: string -} -``` - -### Create Migration - -To create a migration, run the following command: - -```bash -npx typeorm migration:create src/migrations/CreateOnboarding -``` - -This will create a file in the `src/migrations` directory with the name formatted as `-CreateOnboarding.ts`. - -In that file, import the `generateEntityId` utility method at the top of the file: - -```ts -import { generateEntityId } from "@medusajs/utils" -``` - -Then, replace the `up` and `down` methods in the migration class with the following content: - -```ts -export class CreateOnboarding1685715079776 implements MigrationInterface { - public async up(queryRunner: QueryRunner): Promise { - await queryRunner.query( - `CREATE TABLE "onboarding_state" ("id" character varying NOT NULL, "created_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "updated_at" TIMESTAMP WITH TIME ZONE NOT NULL DEFAULT now(), "current_step" character varying NULL, "is_complete" boolean, "product_id" character varying NULL)` - ) - - await queryRunner.query( - `INSERT INTO "onboarding_state" ("id", "current_step", "is_complete") VALUES ('${generateEntityId( - "", - "onboarding" - )}' , NULL, false)` - ) - } - - public async down(queryRunner: QueryRunner): Promise { - await queryRunner.query(`DROP TABLE "onboarding_state"`) - } -} -``` - - - -Don’t copy the name of the class in the code snippet above. Keep the name you have in the file. - - - -Finally, to reflect the migration in the database, run the `build` and `migration` commands: - -```bash npm2yarn -npm run build -npx medusa migrations run -``` - -### Create Service - -Start by creating the file `src/types/onboarding.ts` with the following content: - -```ts title="src/types/onboarding.ts" -import { OnboardingState } from "../models/onboarding" - -export type UpdateOnboardingStateInput = { - current_step?: string; - is_complete?: boolean; - product_id?: string; -}; - -export interface AdminOnboardingUpdateStateReq {} - -export type OnboardingStateRes = { - status: OnboardingState; -}; -``` - -This file holds the necessary types that will be used within the service you’ll create, and later in your onboarding flow widget. - -Then, create the file `src/services/onboarding.ts` with the following content: - -```ts title="src/services/onboarding.ts" -import { TransactionBaseService } from "@medusajs/medusa" -import OnboardingRepository from "../repositories/onboarding" -import { OnboardingState } from "../models/onboarding" -import { EntityManager, IsNull, Not } from "typeorm" -import { UpdateOnboardingStateInput } from "../types/onboarding" - -type InjectedDependencies = { - manager: EntityManager; - onboardingRepository: typeof OnboardingRepository; -}; - -class OnboardingService extends TransactionBaseService { - protected onboardingRepository_: typeof OnboardingRepository - - constructor({ onboardingRepository }: InjectedDependencies) { - super(arguments[0]) - - this.onboardingRepository_ = onboardingRepository - } - - async retrieve(): Promise { - const onboardingRepo = this.activeManager_.withRepository( - this.onboardingRepository_ - ) - - const status = await onboardingRepo.findOne({ - where: { id: Not(IsNull()) }, - }) - - return status - } - - async update( - data: UpdateOnboardingStateInput - ): Promise { - return await this.atomicPhase_( - async (transactionManager: EntityManager) => { - const onboardingRepository = - transactionManager.withRepository( - this.onboardingRepository_ - ) - - const status = await this.retrieve() - - for (const [key, value] of Object.entries(data)) { - status[key] = value - } - - return await onboardingRepository.save(status) - } - ) - } -} - -export default OnboardingService -``` - -This service class implements two methods `retrieve` to retrieve the current onboarding state, and `update` to update the current onboarding state. - -### Create API Routes - -To add the API Routes, create the file `src/api/admin/onboarding/route.ts` with the following content: - -```ts title="src/api/admin/onboarding/route.ts" -import type { - MedusaRequest, - MedusaResponse, -} from "@medusajs/medusa" -import { EntityManager } from "typeorm" - -import OnboardingService from "../../../services/onboarding" - -export async function GET( - req: MedusaRequest, - res: MedusaResponse -) { - const onboardingService: OnboardingService = - req.scope.resolve("onboardingService") - - const status = await onboardingService.retrieve() - - res.status(200).json({ status }) -} - -export async function POST( - req: MedusaRequest, - res: MedusaResponse -) { - const onboardingService: OnboardingService = - req.scope.resolve("onboardingService") - const manager: EntityManager = req.scope.resolve("manager") - - const status = await manager.transaction( - async (transactionManager) => { - return await onboardingService - .withTransaction(transactionManager) - .update(req.body) - } - ) - - res.status(200).json({ status }) -} -``` - -These API routes resolve the `OnboardingService` to use its functionalities. - ---- - -## 3. Create Onboarding Widget - -In this step, you’ll create the onboarding widget with a general implementation. Some implementation details will be added later in the chapter. - -Create the file `src/admin/widgets/onboarding-flow/onboarding-flow.tsx` with the following content: - -```tsx title="src/admin/widgets/onboarding-flow/onboarding-flow.tsx" -import { OrderDetailsWidgetProps, ProductDetailsWidgetProps, WidgetConfig, WidgetProps } from "@medusajs/admin" -import { useAdminCustomPost, useAdminCustomQuery, useMedusa } from "medusa-react" -import React, { useEffect, useState, useMemo, useCallback } from "react" -import { useNavigate, useSearchParams, useLocation } from "react-router-dom" -import { OnboardingState } from "../../../models/onboarding" -import { - AdminOnboardingUpdateStateReq, - OnboardingStateRes, - UpdateOnboardingStateInput, -} from "../../../types/onboarding" -import OrderDetailDefault from "../../components/onboarding-flow/default/orders/order-detail" -import OrdersListDefault from "../../components/onboarding-flow/default/orders/orders-list" -import ProductDetailDefault from "../../components/onboarding-flow/default/products/product-detail" -import ProductsListDefault from "../../components/onboarding-flow/default/products/products-list" -import { Button, Container, Heading, Text, clx } from "@medusajs/ui" -import Accordion from "../../components/shared/accordion" -import GetStarted from "../../components/shared/icons/get-started" -import { Order, Product } from "@medusajs/medusa" -import ProductsListNextjs from "../../components/onboarding-flow/nextjs/products/products-list" -import ProductDetailNextjs from "../../components/onboarding-flow/nextjs/products/product-detail" -import OrdersListNextjs from "../../components/onboarding-flow/nextjs/orders/orders-list" -import OrderDetailNextjs from "../../components/onboarding-flow/nextjs/orders/order-detail" - -type STEP_ID = - | "create_product" - | "preview_product" - | "create_order" - | "setup_finished" - | "create_product_nextjs" - | "preview_product_nextjs" - | "create_order_nextjs" - | "setup_finished_nextjs" - -type OnboardingWidgetProps = WidgetProps | ProductDetailsWidgetProps | OrderDetailsWidgetProps - -export type StepContentProps = OnboardingWidgetProps & { - onNext?: (data?: any) => void; - isComplete?: boolean; - data?: OnboardingState; -}; - -type Step = { - id: STEP_ID; - title: string; - component: React.FC; - onNext?: (data?: any) => void; -}; - -const QUERY_KEY = ["onboarding_state"] - -const OnboardingFlow = (props: OnboardingWidgetProps) => { - // create custom hooks for custom API Routes - const { data, isLoading } = useAdminCustomQuery< - undefined, - OnboardingStateRes - >("/onboarding", QUERY_KEY) - const { mutate } = useAdminCustomPost< - AdminOnboardingUpdateStateReq, - OnboardingStateRes - >("/onboarding", QUERY_KEY) - - const navigate = useNavigate() - const location = useLocation() - // will be used if onboarding step - // is passed as a path parameter - const { client } = useMedusa() - - // get current step from custom API Route - const currentStep: STEP_ID | undefined = useMemo(() => { - return data?.status - ?.current_step as STEP_ID - }, [data]) - - // initialize some state - const [openStep, setOpenStep] = useState(currentStep) - const [completed, setCompleted] = useState(false) - - // this method is used to move from one step to the next - const setStepComplete = ({ - step_id, - extraData, - onComplete, - }: { - step_id: STEP_ID; - extraData?: UpdateOnboardingStateInput; - onComplete?: () => void; - }) => { - const next = steps[findStepIndex(step_id) + 1] - mutate({ current_step: next.id, ...extraData }, { - onSuccess: onComplete, - }) - } - - // this is useful if you want to change the current step - // using a path parameter. It can only be changed if the passed - // step in the path parameter is the next step. - const [ searchParams ] = useSearchParams() - - // the steps are set based on the - // onboarding type - const steps: Step[] = useMemo(() => { - { - switch(process.env.MEDUSA_ADMIN_ONBOARDING_TYPE) { - case "nextjs": - return [ - { - id: "create_product_nextjs", - title: "Create Products", - component: ProductsListNextjs, - onNext: (product: Product) => { - setStepComplete({ - step_id: "create_product_nextjs", - extraData: { product_id: product.id }, - onComplete: () => { - if (!location.pathname.startsWith(`/a/products/${product.id}`)) { - navigate(`/a/products/${product.id}`) - } - }, - }) - }, - }, - { - id: "preview_product_nextjs", - title: "Preview Product in your Next.js Storefront", - component: ProductDetailNextjs, - onNext: () => { - setStepComplete({ - step_id: "preview_product_nextjs", - onComplete: () => navigate(`/a/orders`), - }) - }, - }, - { - id: "create_order_nextjs", - title: "Create an Order using your Next.js Storefront", - component: OrdersListNextjs, - onNext: (order: Order) => { - setStepComplete({ - step_id: "create_order_nextjs", - onComplete: () => { - if (!location.pathname.startsWith(`/a/orders/${order.id}`)) { - navigate(`/a/orders/${order.id}`) - } - }, - }) - }, - }, - { - id: "setup_finished_nextjs", - title: "Setup Finished: Continue Building your Ecommerce Store", - component: OrderDetailNextjs, - }, - ] - default: - return [ - { - id: "create_product", - title: "Create Product", - component: ProductsListDefault, - onNext: (product: Product) => { - setStepComplete({ - step_id: "create_product", - extraData: { product_id: product.id }, - onComplete: () => { - if (!location.pathname.startsWith(`/a/products/${product.id}`)) { - navigate(`/a/products/${product.id}`) - } - }, - }) - }, - }, - { - id: "preview_product", - title: "Preview Product", - component: ProductDetailDefault, - onNext: () => { - setStepComplete({ - step_id: "preview_product", - onComplete: () => navigate(`/a/orders`), - }) - }, - }, - { - id: "create_order", - title: "Create an Order", - component: OrdersListDefault, - onNext: (order: Order) => { - setStepComplete({ - step_id: "create_order", - onComplete: () => { - if (!location.pathname.startsWith(`/a/orders/${order.id}`)) { - navigate(`/a/orders/${order.id}`) - } - }, - }) - }, - }, - { - id: "setup_finished", - title: "Setup Finished: Start developing with Medusa", - component: OrderDetailDefault, - }, - ] - } - } - }, [location.pathname]) - - // used to retrieve the index of a step by its ID - const findStepIndex = useCallback((step_id: STEP_ID) => { - return steps.findIndex((step) => step.id === step_id) - }, [steps]) - - // used to check if a step is completed - const isStepComplete = useCallback((step_id: STEP_ID) => { - return findStepIndex(currentStep) > findStepIndex(step_id) - }, [findStepIndex, currentStep]) - - // this is used to retrieve the data necessary - // to move to the next onboarding step - const getOnboardingParamStepData = useCallback(async (onboardingStep: string, data?: { - orderId?: string, - productId?: string, - }) => { - switch (onboardingStep) { - case "setup_finished_nextjs": - case "setup_finished": - if (!data?.orderId && "order" in props) { - return props.order - } - const orderId = data?.orderId || searchParams.get("order_id") - if (orderId) { - return (await client.admin.orders.retrieve(orderId)).order - } - - throw new Error ("Required `order_id` parameter was not passed as a parameter") - case "preview_product_nextjs": - case "preview_product": - if (!data?.productId && "product" in props) { - return props.product - } - const productId = data?.productId || searchParams.get("product_id") - if (productId) { - return (await client.admin.products.retrieve(productId)).product - } - - throw new Error ("Required `product_id` parameter was not passed as a parameter") - default: - return undefined - } - }, [searchParams, props]) - - const isProductCreateStep = useMemo(() => { - return currentStep === "create_product" || - currentStep === "create_product_nextjs" - }, [currentStep]) - - const isOrderCreateStep = useMemo(() => { - return currentStep === "create_order" || - currentStep === "create_order_nextjs" - }, [currentStep]) - - // used to change the open step when the current - // step is retrieved from custom API Routes - useEffect(() => { - setOpenStep(currentStep) - - if (findStepIndex(currentStep) === steps.length - 1) {setCompleted(true)} - }, [currentStep, findStepIndex]) - - // used to check if the user created a product and has entered its details page - // the step is changed to the next one - useEffect(() => { - if (location.pathname.startsWith("/a/products/prod_") && isProductCreateStep && "product" in props) { - // change to the preview product step - const currentStepIndex = findStepIndex(currentStep) - steps[currentStepIndex].onNext?.(props.product) - } - }, [location.pathname, isProductCreateStep]) - - // used to check if the user created an order and has entered its details page - // the step is changed to the next one. - useEffect(() => { - if (location.pathname.startsWith("/a/orders/order_") && isOrderCreateStep && "order" in props) { - // change to the preview product step - const currentStepIndex = findStepIndex(currentStep) - steps[currentStepIndex].onNext?.(props.order) - } - }, [location.pathname, isOrderCreateStep]) - - // used to check if the `onboarding_step` path - // parameter is passed and, if so, moves to that step - // only if it's the next step and its necessary data is passed - useEffect(() => { - const onboardingStep = searchParams.get("onboarding_step") as STEP_ID - const onboardingStepIndex = findStepIndex(onboardingStep) - if (onboardingStep && onboardingStepIndex !== -1 && onboardingStep !== openStep) { - // change current step to the onboarding step - const openStepIndex = findStepIndex(openStep) - - if (onboardingStepIndex !== openStepIndex + 1) { - // can only go forward one step - return - } - - // retrieve necessary data and trigger the next function - getOnboardingParamStepData(onboardingStep) - .then((data) => { - steps[openStepIndex].onNext?.(data) - }) - .catch((e) => console.error(e)) - } - }, [searchParams, openStep, getOnboardingParamStepData]) - - if ( - !isLoading && - data?.status?.is_complete && - !localStorage.getItem("override_onboarding_finish") - ) - {return null} - - // a method that will be triggered when - // the setup is started - const onStart = () => { - mutate({ current_step: steps[0].id }) - navigate(`/a/products`) - } - - // a method that will be triggered when - // the setup is completed - const onComplete = () => { - setCompleted(true) - } - - // a method that will be triggered when - // the setup is closed - const onHide = () => { - mutate({ is_complete: true }) - } - - // used to get text for get started header - const getStartedText = () => { - switch(process.env.MEDUSA_ADMIN_ONBOARDING_TYPE) { - case "nextjs": - return "Learn the basics of Medusa by creating your first order using the Next.js storefront." - default: - return "Learn the basics of Medusa by creating your first order." - } - } - - return ( - <> - - setOpenStep(value as STEP_ID)} - > -
-
- -
- {!completed ? ( - <> -
- Get started - - {getStartedText()} - -
-
- {!!currentStep ? ( - <> - {currentStep === steps[steps.length - 1].id ? ( - - ) : ( - - )} - - ) : ( - <> - - - - )} -
- - ) : ( - <> -
- - Thank you for completing the setup guide! - - - This whole experience was built using our new{" "} - widgets feature. -
You can find out more details and build your own by - following{" "} - - our guide - - . -
-
-
- -
- - )} -
- { -
- {(!completed ? steps : steps.slice(-1)).map((step) => { - const isComplete = isStepComplete(step.id) - const isCurrent = currentStep === step.id - return ( - , - })} - > -
- -
-
- ) - })} -
- } -
-
- - ) -} - -export const config: WidgetConfig = { - zone: [ - "product.list.before", - "product.details.before", - "order.list.before", - "order.details.before", - ], -} - -export default OnboardingFlow -``` - - - -you'll see errors related to components not being defined. You'll create these components in upcoming sections. - - - -There are three important details to ensure that Medusa reads this file as a widget: - -1. The file is placed under the `src/admin/widget` directory. -2. The file exports a `config` object of type `WidgetConfig`, which is imported from `@medusajs/admin`. -3. The file default exports a React component, which in this case is `OnboardingFlow` - -The extension uses `react-router-dom`, which is available as a dependency of the `@medusajs/admin` package, to navigate to other pages in the dashboard. - -The `OnboardingFlow` widget also implements functionalities related to handling the steps of the onboarding flow, including navigating between them and updating the current step in the application. - -To use the custom API Routes created in a previous step, you use the `useAdminCustomQuery` and `useAdminCustomPost` hooks from the `medusa-react` package. - ---- - -## 4. Create Step Components - -In this section, you’ll create the components for each step in the onboarding flow. You’ll then update the `OnboardingFlow` widget to use these components. - -Notice that as there are two types of flows, you'll be creating the components for the default flow and for the Next.js flow. - -
- - The `ProductsListDefault` component is used in the first step of the onboarding widget's default flow. It allows the user to create a sample product. - - Create the file `src/admin/components/onboarding-flow/default/products/products-list.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/default/products/products-list.tsx" - import React, { useMemo } from "react" - import { - useAdminCreateProduct, - useAdminCreateCollection, - useMedusa, - } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, Text } from "@medusajs/ui" - import getSampleProducts from "../../../../utils/sample-products" - import prepareRegions from "../../../../utils/prepare-region" - - const ProductsListDefault = ({ onNext, isComplete }: StepContentProps) => { - const { mutateAsync: createCollection, isLoading: collectionLoading } = - useAdminCreateCollection() - const { mutateAsync: createProduct, isLoading: productLoading } = - useAdminCreateProduct() - const { client } = useMedusa() - - const isLoading = useMemo(() => - collectionLoading || productLoading, - [collectionLoading, productLoading] - ) - - const createSample = async () => { - try { - const { collection } = await createCollection({ - title: "Merch", - handle: "merch", - }) - - const regions = await prepareRegions(client) - - const sampleProducts = getSampleProducts({ - regions, - collection_id: collection.id, - }) - const { product } = await createProduct(sampleProducts[0]) - onNext(product) - } catch (e) { - console.error(e) - } - } - - return ( -
- - Create a product and set its general details such as title and - description, its price, options, variants, images, and more. You'll then - use the product to create a sample order. - - - You can create a product by clicking the "New Product" button below. - Alternatively, if you're not ready to create your own product, we can - create a sample one for you. - - {!isComplete && ( -
- -
- )} -
- ) - } - - export default ProductsListDefault - ``` - -
- -
- - The `ProductDetailDefault` component is used in the second step of the onboarding's default flow. It shows the user a code snippet to preview the product created in the first step. - - Create the file `src/admin/components/onboarding-flow/default/products/product-detail.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/default/products/product-detail.tsx" - import React, { useEffect, useMemo } from "react" - import { - useAdminPublishableApiKeys, - useAdminCreatePublishableApiKey, - } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, CodeBlock, Text } from "@medusajs/ui" - - const ProductDetailDefault = ({ onNext, isComplete, data }: StepContentProps) => { - const { publishable_api_keys: keys, isLoading, refetch } = useAdminPublishableApiKeys({ - offset: 0, - limit: 1, - }) - const createPublishableApiKey = useAdminCreatePublishableApiKey() - - const api_key = useMemo(() => keys?.[0]?.id || "", [keys]) - const backendUrl = process.env.MEDUSA_BACKEND_URL === "/" || process.env.MEDUSA_ADMIN_BACKEND_URL === "/" ? - location.origin : - process.env.MEDUSA_BACKEND_URL || process.env.MEDUSA_ADMIN_BACKEND_URL || "http://localhost:9000" - - useEffect(() => { - if (!isLoading && !keys?.length) { - createPublishableApiKey.mutate({ - "title": "Development", - }, { - onSuccess: () => { - refetch() - }, - }) - } - }, [isLoading, keys]) - - return ( -
-
- On this page, you can view your product's details and edit them. - - You can preview your product using Medusa's Store APIs. You can copy any - of the following code snippets to try it out. - -
-
- {!isLoading && ( - {\n // ...\n const productService = await initializeProductModule()\n const products = await productService.list({\n id: "${data?.product_id}",\n })\n\n console.log(products[0])\n}`, - }, - ]} className="my-6"> - - - - )} -
-
- - - - {!isComplete && ( - - )} -
-
- ) - } - - export default ProductDetailDefault - ``` - -
- -
- - The `OrdersListDefault` component is used in the third step of the onboarding's default flow. It allows the user to create a sample order. - - Create the file `src/admin/components/onboarding-flow/default/orders/orders-list.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/default/orders/orders-list.tsx" - import React from "react" - import { - useAdminProduct, - useAdminCreateDraftOrder, - useMedusa, - } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, Text } from "@medusajs/ui" - import prepareRegions from "../../../../utils/prepare-region" - import prepareShippingOptions from "../../../../utils/prepare-shipping-options" - - const OrdersListDefault = ({ onNext, isComplete, data }: StepContentProps) => { - const { product } = useAdminProduct(data.product_id) - const { mutateAsync: createDraftOrder, isLoading } = - useAdminCreateDraftOrder() - const { client } = useMedusa() - - const createOrder = async () => { - const variant = product.variants[0] ?? null - try { - // check if there is a shipping option and a region - // and if not, create demo ones - const regions = await prepareRegions(client) - const shipping_options = await prepareShippingOptions(client, regions[0]) - - const { draft_order } = await createDraftOrder({ - email: "customer@medusajs.com", - items: [ - variant - ? { - quantity: 1, - variant_id: variant?.id, - } - : { - quantity: 1, - title: product.title, - unit_price: 50, - }, - ], - shipping_methods: [ - { - option_id: shipping_options[0].id, - }, - ], - region_id: regions[0].id, - }) - - const { order } = await client.admin.draftOrders.markPaid(draft_order.id) - - onNext(order) - } catch (e) { - console.error(e) - } - } - return ( - <> -
- - The last step is to create a sample order using the product you just created. You can then view your order’s details, process its payment, fulfillment, inventory, and more. - - - By clicking the “Create a Sample Order” button, we’ll generate an order using the product you created and default configurations. - -
-
- {!isComplete && ( - - )} -
- - ) - } - - export default OrdersListDefault - ``` - -
- -
- - The `OrderDetailDefault` component is used in the fourth and final step of the onboarding's default flow. It educates the user on the next steps when developing with Medusa. - - Create the file `src/admin/components/onboarding-flow/default/orders/order-detail.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/default/orders/order-detail.tsx" - import React from "react" - import { - ComputerDesktopSolid, - CurrencyDollarSolid, - ToolsSolid, - } from "@medusajs/icons" - import { IconBadge, Heading, Text } from "@medusajs/ui" - - const OrderDetailDefault = () => { - return ( - <> - - You finished the setup guide 🎉 You now have your first order. Feel free - to play around with the order management functionalities, such as - capturing payment, creating fulfillments, and more. - - - Start developing with Medusa - - - Medusa is a completely customizable commerce solution. We've curated - some essential guides to kickstart your development with Medusa. - - -
- You can find more useful guides in{" "} - - our documentation - - . If you like Medusa, please{" "} - - star us on GitHub - - . -
- - ) - } - - export default OrderDetailDefault - - ``` - -
- -
- - The `ProductsListNextjs` component is used in the first step of the onboarding widget's Next.js flow. It allows the user to create sample products. - - Create the file `src/admin/components/onboarding-flow/nextjs/products/products-list.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/nextjs/products/products-list.tsx" - import React from "react" - import { - useAdminCreateProduct, - useAdminCreateCollection, - useMedusa, - } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, Text } from "@medusajs/ui" - import { AdminPostProductsReq, Product } from "@medusajs/medusa" - import getSampleProducts from "../../../../utils/sample-products" - import prepareRegions from "../../../../utils/prepare-region" - - const ProductsListNextjs = ({ onNext, isComplete }: StepContentProps) => { - const { mutateAsync: createCollection, isLoading: collectionLoading } = - useAdminCreateCollection() - const { mutateAsync: createProduct, isLoading: productLoading } = - useAdminCreateProduct() - const { client } = useMedusa() - - const isLoading = collectionLoading || productLoading - - const createSample = async () => { - try { - const { collection } = await createCollection({ - title: "Merch", - handle: "merch", - }) - - const regions = await prepareRegions(client) - - const tryCreateProduct = async (sampleProduct: AdminPostProductsReq): Promise => { - try { - return (await createProduct(sampleProduct)).product - } catch { - // ignore if product already exists - return null - } - } - - let product: Product - const sampleProducts = getSampleProducts({ - regions, - collection_id: collection.id, - }) - await Promise.all( - sampleProducts.map(async (sampleProduct, index) => { - const createdProduct = await tryCreateProduct(sampleProduct) - if (index === 0 && createProduct) { - product = createdProduct - } - }) - ) - onNext(product) - } catch (e) { - console.error(e) - } - } - - return ( -
- - Products in Medusa represent the products you sell. You can set their general details including a - title and description. Each product has options and variants, and you can set a price for each variant. - - - Click the button below to create sample products. - - {!isComplete && ( -
- -
- )} -
- ) - } - - export default ProductsListNextjs - ``` - -
- -
- - The `ProductDetailNextjs` component is used in the second step of the onboarding's Next.js flow. It shows the user a button to preview the product created in the first step using the Next.js starter. - - Create the file `src/admin/components/onboarding-flow/nextjs/products/product-detail.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/nextjs/products/product-detail.tsx" - import { useAdminProduct } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, Text } from "@medusajs/ui" - - const ProductDetailNextjs = ({ onNext, isComplete, data }: StepContentProps) => { - const { product, isLoading: productIsLoading } = useAdminProduct(data?.product_id) - return ( -
-
- - We have now created a few sample products in your Medusa store. You can scroll down to see what the Product Detail view looks like in the Admin dashboard. - This is also the view you use to edit existing products. - - - To view the products in your store, you can visit the Next.js Storefront that was installed with create-medusa-app. - - - The Next.js Storefront Starter is a template that helps you start building an ecommerce store with Medusa. - You control the code for the storefront and you can customize it further to fit your specific needs. - - - Click the button below to view the products in your Next.js Storefront. - - - Having trouble? Click{" "} - - here - . - -
-
- - - - {!isComplete && ( - - )} -
-
- ) - } - - export default ProductDetailNextjs - ``` - -
- -
- - The `OrdersListNextjs` component is used in the third step of the onboarding's Next.js flow. It links the user to the checkout flow in the Next.js storefront so that they can create an order. - - Create the file `src/admin/components/onboarding-flow/nextjs/orders/orders-list.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/nextjs/orders/orders-list.tsx" - import React from "react" - import { - useAdminProduct, - useCreateCart, - useMedusa, - } from "medusa-react" - import { StepContentProps } from "../../../../widgets/onboarding-flow/onboarding-flow" - import { Button, Text } from "@medusajs/ui" - import prepareRegions from "../../../../utils/prepare-region" - import prepareShippingOptions from "../../../../utils/prepare-shipping-options" - - const OrdersListNextjs = ({ isComplete, data }: StepContentProps) => { - const { product } = useAdminProduct(data.product_id) - const { mutateAsync: createCart, isLoading: cartIsLoading } = useCreateCart() - const { client } = useMedusa() - - const prepareNextjsCheckout = async () => { - const variant = product.variants[0] ?? null - try { - const regions = await prepareRegions(client) - await prepareShippingOptions(client, regions[0]) - const { cart } = await createCart({ - region_id: regions[0]?.id, - items: [ - { - variant_id: variant?.id, - quantity: 1, - }, - ], - }) - - window.open(`http://localhost:8000/checkout?cart_id=${cart?.id}&onboarding=true`, "_blank") - } catch (e) { - console.error(e) - } - } - - return ( - <> -
- - The last step is to create a sample order using one of your products. You can then view your order’s details, process its payment, fulfillment, inventory, and more. - - - You can use the button below to experience hand-first the checkout flow in the Next.js storefront. After placing the order in the storefront, you’ll be directed back here to view the order’s details. - -
-
- {!isComplete && ( - <> - - - )} -
- - ) - } - - export default OrdersListNextjs - ``` - -
- -
- - The `OrderDetailNextjs` component is used in the fourth and final step of the onboarding's default flow. It educates the user on the next steps when developing with Medusa. - - Create the file `src/admin/components/onboarding-flow/nextjs/orders/order-detail.tsx` with the following content: - - ```tsx title="src/admin/components/onboarding-flow/nextjs/orders/order-detail.tsx" - import React from "react" - import { CurrencyDollarSolid, NextJs, SquaresPlusSolid } from "@medusajs/icons" - import { IconBadge, Heading, Text } from "@medusajs/ui" - - const OrderDetailNextjs = () => { - const queryParams = `?ref=onboarding&type=${ - process.env.MEDUSA_ADMIN_ONBOARDING_TYPE || "nextjs" - }` - return ( - <> - - You finished the setup guide 🎉. You have now a complete ecommerce store - with a backend, admin, and a Next.js storefront. Feel free to play - around with each of these components to experience all commerce features - that Medusa provides. - - - Continue Building your Ecommerce Store - - - Your ecommerce store provides all basic ecommerce features you need to - start selling. You can add more functionalities, add plugins for - third-party integrations, and customize the storefront’s look and feel - to support your use case. - - -
- You can find more useful guides in{" "} - - our documentation - - . If you like Medusa, please{" "} - - star us on GitHub - - . -
- - ) - } - - export default OrderDetailNextjs - ``` - -
- ---- - -## 5. Test it Out - -You’ve now implemented everything necessary for the onboarding flow! You can test it out by building the changes and running the `develop` command: - -```bash npm2yarn -npm run develop -``` - -If you open the admin at `localhost:7001` and log in, you’ll see the onboarding widget in the Products listing page. You can try using it and see your implementation in action! \ No newline at end of file diff --git a/www/apps/book/app/advanced-development/admin/page.mdx b/www/apps/book/app/advanced-development/admin/page.mdx index 6b54677808..5e4bf62452 100644 --- a/www/apps/book/app/advanced-development/admin/page.mdx +++ b/www/apps/book/app/advanced-development/admin/page.mdx @@ -4,19 +4,12 @@ export const metadata = { # {metadata.title} - - -Admin customizations are coming soon. - - - In the next chapters, you'll learn more about possible admin customizations. You can customize the admin dashboard by: - Adding new sections to existing pages using Widgets. - Adding new pages using UI Routes. -- Adding new pages to the Settings section of the admin dashboard using Setting Pages. --- diff --git a/www/apps/book/app/advanced-development/admin/setting-pages/page.mdx b/www/apps/book/app/advanced-development/admin/setting-pages/page.mdx deleted file mode 100644 index 43f5b6d9be..0000000000 --- a/www/apps/book/app/advanced-development/admin/setting-pages/page.mdx +++ /dev/null @@ -1,152 +0,0 @@ -export const metadata = { - title: `${pageNumber} Admin Setting Pages`, -} - -# {metadata.title} - - - -Admin customizations are coming soon. - - - -In this chapter, you’ll learn how to create a setting page in the admin dashboard. - -## What is a Setting Page? - -A setting page is a React component that adds a new page to the settings panel of the admin dashboard. - -For example, you may create a setting page to manage configurations related to your custom functionalities. - ---- - -## How to Create a Setting Page? - -A setting page is created in a file named `page.tsx` under the `src/admin/settings` directory. The file’s default export must be the React component, and it must also export a configuration object. - -For example, you can create the file `src/admin/settings/custom/page.tsx` with the following content: - -```tsx title="src/admin/settings/custom/page.tsx" -import type { SettingConfig } from "@medusajs/admin" -import { ChatBubbleLeftRight } from "@medusajs/icons" - -const CustomSettingPage = () => { - return ( -
-

Custom Setting Page

-
- ) -} - -export const config: SettingConfig = { - card: { - label: "Custom", - description: "Manage your custom settings", - // optional - icon: ChatBubbleLeftRight, - }, -} - -export default CustomSettingPage -``` - -The new setting page’s path is the file’s path relative to `src/admin/settings` and prefixed with `/a/settings`. So, the above UI route is a new page added at the path `localhost:7001/a/settings/custom`. - -The setting page is shown on the Settings panel as a card. In the exported configuration object, you configure the card’s label, description, and optionally icon. In this example, you use an icon from [Medusa’s UI Icons package](https://docs.medusajs.com/ui/icons/overview). - -### Test the Setting Page - -To test the setting page, start the Medusa application: - -```bash npm2yarn -npm run dev -``` - -Then, open the Settings page of the admin dashboard. You’ll find a new card that links to your setting page. - ---- - -## Using UI Components - -Similar to other admin customizations, it’s highly recommended that you use the [Medusa UI package](https://docs.medusajs.com/ui) to match your page’s design with the rest of the Medusa Admin. - -For example, you can rewrite the above UI route to the following: - -```tsx title="src/admin/settings/custom/page.tsx" -import type { SettingConfig } from "@medusajs/admin" -import { ChatBubbleLeftRight } from "@medusajs/icons" -import { Container, Heading } from "@medusajs/ui" - -const CustomSettingPage = () => { - return ( - - Custom Setting Page - - ) -} - -export const config: SettingConfig = { - card: { - label: "Custom", - description: "Manage your custom settings", - icon: ChatBubbleLeftRight, - }, -} - -export default CustomSettingPage -``` - ---- - -## Setting Page Props - -A setting page receives a `notify` prop, which is an object having the following properties: - -- `success`: a function that shows a success toast message. -- `error`: a function that shows an error toast message. -- `warn`: a function that shows a warning toast message. -- `info`: a function that shows an info toast message. - -Each of these functions accepts two parameters: the message’s title and the message’s content. - -For example: - -```tsx title="src/admin/settings/custom/page.tsx" highlights={[["11", "success", "Show a success toast message on the click of a button."]]} -import type { RouteProps, SettingConfig } from "@medusajs/admin" -import { ChatBubbleLeftRight } from "@medusajs/icons" -import { Container, Heading, Button } from "@medusajs/ui" - -const CustomSettingPage = ({ notify }: RouteProps) => { - return ( - - Custom Setting Page - - - ) -} - -export const config: SettingConfig = { - card: { - label: "Custom", - description: "Manage your custom settings", - icon: ChatBubbleLeftRight, - }, -} - -export default CustomSettingPage -``` - -If you click the button on your setting page, a toast message will show with the title and message you specified. - - - -Admin UI Routes support [Tailwind CSS](https://tailwindcss.com/) out of the box. - - diff --git a/www/apps/book/app/advanced-development/admin/tips/page.mdx b/www/apps/book/app/advanced-development/admin/tips/page.mdx index da55e1aa01..c1f42cafe1 100644 --- a/www/apps/book/app/advanced-development/admin/tips/page.mdx +++ b/www/apps/book/app/advanced-development/admin/tips/page.mdx @@ -4,22 +4,8 @@ export const metadata = { # {metadata.title} - - -Admin customizations are coming soon. - - - In this chapter, you'll find some tips for your admin development. -## Sending Requests to API Routes - -To send requests to your API routes in your admin customizations, it’s highly recommended to use the Medusa React or Medusa JS Client packages. - -Check out their references for more details on available hooks or methods and how to use them. - ---- - ## Routing Functionalities To navigate or link to other pages, or use other routing functionalities, use the [react-router-dom](https://reactrouter.com/en/main) package: @@ -30,40 +16,34 @@ npm install react-router-dom For example: -```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["18", "Link", "Add a link to another page."]]} -import type { - WidgetConfig, - ProductDetailsWidgetProps, -} from "@medusajs/admin" -import { Container, Heading, Button } from "@medusajs/ui" +export const highlights = [ + ["9", "Link", "Add a link to another page"], + ["9", '"/orders', "Add the path without the `/app` prefix."] +] + +```tsx title="src/admin/widgets/product-widget.tsx" highlights={highlights} +import { defineWidgetConfig } from "@medusajs/admin-shared" +import { Container } from "@medusajs/ui" import { Link } from "react-router-dom" // The widget -const ProductWidget = ({ - product, -}: ProductDetailsWidgetProps) => { +const ProductWidget = () => { return ( - - Product Widget {product.title} - - + View Orders ) } // The widget's configurations -export const config: WidgetConfig = { +export const config = defineWidgetConfig({ zone: "product.details.after", -} +}) export default ProductWidget - ``` -This adds a widget in a product's details page with a link to the Orders page. +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. diff --git a/www/apps/book/app/advanced-development/admin/ui-routes/page.mdx b/www/apps/book/app/advanced-development/admin/ui-routes/page.mdx index 080671e819..8f83d26513 100644 --- a/www/apps/book/app/advanced-development/admin/ui-routes/page.mdx +++ b/www/apps/book/app/advanced-development/admin/ui-routes/page.mdx @@ -4,17 +4,11 @@ export const metadata = { # {metadata.title} - - -Admin customizations are coming soon. - - - In this chapter, you’ll learn how to create a UI route in the admin dashboard. ## What is a UI Route? -A UI route is a React Component that adds a custom new page to your admin dashboard. The UI Route can be shown in the sidebar or added as a nested page. +A UI route is a React Component that adds a new page to your admin dashboard. The UI Route can be shown in the sidebar or added as a nested page. For example, you may add a new page to manage product reviews. @@ -34,7 +28,7 @@ const CustomPage = () => { export default CustomPage ``` -The new page’s path is the file’s path relative to `src/admin/routes` and prefixed with `/a`. So, the above UI route is a new page added at the path `localhost:7001/a/custom`. +The new page’s path is the file’s path relative to `src/admin/routes`. So, the above UI route is a new page added at the path `localhost:9000/app/custom`. ### Test the UI Route @@ -44,7 +38,43 @@ To test the UI route, start the Medusa application: npm run dev ``` -Then, after logging into the admin dashboard, open the page `localhost:7001/a/custom` to see your custom page. +Then, after logging into the admin dashboard, open the page `localhost:9000/app/custom` to see your custom page. + +--- + +## Show UI Route in the Sidebar + +A UI route file can export a configuration object that indicates a new item must be added in the sidebar linking to the new UI route. + +For example: + +export const highlights = [ + ["14", "label", "The label of the UI route's sidebar item."], + ["15", "icon", "The icon of the UI route's sidebar item."] +] + +```tsx title="src/admin/routes/custom/page.tsx" highlights={[["21"], ["22"], ["23"], ["24"], ["25"], ["26"]]} +import { defineRouteConfig } from "@medusajs/admin-shared"; +import { ChatBubbleLeftRight } from "@medusajs/icons" + +const CustomPage = () => { + return
This is my custom route
+} + +export const config = defineRouteConfig({ + label: "Custom Route", + icon: ChatBubbleLeftRight, + }) + +export default CustomPage +``` + +The configuration object is creaetd by the `defineRouteConfig` function imported from `@medusajs/admin-shared`. It accepts the following properties: + +- `label`: the new sidebar item’s label. +- `icon`: an optional React component that acts as an icon in the sidebar. + +The above example adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](!ui!/icons/overview). --- @@ -55,104 +85,50 @@ Similar to Widgets, it’s highly recommended that you use the [Medusa UI packag For example, you can rewrite the above UI route to the following: ```tsx title="src/admin/routes/custom/page.tsx" +import { defineRouteConfig } from "@medusajs/admin-shared" +import { ChatBubbleLeftRight } from "@medusajs/icons" import { Container } from "@medusajs/ui" const CustomPage = () => { return This is my custom route } +export const config = defineRouteConfig({ + label: "Custom Route", + icon: ChatBubbleLeftRight, +}) + export default CustomPage ``` --- -## UI Route Props +## Create Settings Page -A UI Route receives a `notify` prop, which is an object having the following properties: - -- `success`: a function that shows a success toast message. -- `error`: a function that shows an error toast message. -- `warn`: a function that shows a warning toast message. -- `info`: a function that shows an info toast message. - -Each of these functions accepts two parameters: the message’s title and the message’s content. +To create a page under the settings section of the admin dashboard, create the UI route file under the path `src/admin/routes/settings`. For example: -```tsx title="src/admin/routes/custom/page.tsx" highlights={[["10", "success", "Show a success toast message on the click of a button."]]} -import { RouteProps } from "@medusajs/admin" -import { Container, Text, Button } from "@medusajs/ui" +```tsx title="src/admin/routes/settings/custom/page.tsx" +import { defineRouteConfig } from "@medusajs/admin-shared" +import { Container, Heading } from "@medusajs/ui" -const CustomPage = ({ notify }: RouteProps) => { +const CustomSettingPage = () => { return ( - This is my custom route - + Custom Setting Page ) } -export default CustomPage +export const config = defineRouteConfig({ + label: "Custom", +}) + +export default CustomSettingPage; ``` - - -Admin UI Routes support [Tailwind CSS](https://tailwindcss.com/) out of the box. - - - ---- - -## Show UI Route in the Sidebar - -A UI route file can export a configuration object that indicates a new item must be added in the sidebar linking to the new UI route. - -The configuration object has the property `link`, which is an object having the following properties: - -- `label`: the new sidebar item’s label. -- `icon`: an optional React component that acts as an icon in the sidebar. If not provided, a default icon is used. - -For example: - -```tsx title="src/admin/routes/custom/page.tsx" highlights={[["21"], ["22"], ["23"], ["24"], ["25"], ["26"]]} -import { RouteConfig, RouteProps } from "@medusajs/admin" -import { Container, Text, Button } from "@medusajs/ui" -import { ChatBubbleLeftRight } from "@medusajs/icons" - -const CustomPage = ({ notify }: RouteProps) => { - return ( - - This is my custom route - - - ) -} - -export const config: RouteConfig = { - link: { - label: "Custom Route", - icon: ChatBubbleLeftRight, - }, -} - -export default CustomPage -``` - -This adds a new sidebar item with the label `Custom Route` and an icon from the [Medusa UI Icons package](https://docs.medusajs.com/ui/icons/overview). +This adds a page under the path `/app/settings/custom`. An item is also added to the settings sidebar with the label `Custom`. --- @@ -179,4 +155,6 @@ const CustomPage = () => { } export default CustomPage -``` \ No newline at end of file +``` + +If you run the Medusa application and go to `localhost:9000/app/custom/123`, you'll see `123` printed in the page. diff --git a/www/apps/book/app/advanced-development/admin/widgets/page.mdx b/www/apps/book/app/advanced-development/admin/widgets/page.mdx index b00d261031..3c2e553fbf 100644 --- a/www/apps/book/app/advanced-development/admin/widgets/page.mdx +++ b/www/apps/book/app/advanced-development/admin/widgets/page.mdx @@ -6,12 +6,6 @@ export const metadata = { # {metadata.title} - - -Admin customizations are coming soon. - - - In this chapter, you’ll learn more about widgets and how to use them. ## What is an Admin Widget? @@ -28,8 +22,13 @@ A widget is created in a file under the `src/admin/widgets` directory. The file For example, create the file `src/admin/widgets/product-widget.tsx` with the following content: -```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["4", "ProductWidget", "The React component of the product widget."], ["14", "", "The zone to inject the widget to."]]} -import type { WidgetConfig } from "@medusajs/admin" +export const widgetHighlights = [ + ["4", "ProductWidget", "The React component of the product widget."], + ["14", "zone", "The zone to inject the widget to."] +] + +```tsx title="src/admin/widgets/product-widget.tsx" highlights={widgetHighlights} +import { defineWidgetConfig } from "@medusajs/admin-shared" // The widget const ProductWidget = () => { @@ -41,16 +40,20 @@ const ProductWidget = () => { } // The widget's configurations -export const config: WidgetConfig = { +export const config = defineWidgetConfig({ zone: "product.details.after", -} +}) export default ProductWidget ``` The widget only shows the heading `Product Widget`. -In the exported widget’s configurations, you must specify the zone to inject the widget into. The `zone` property can be a string or an array of strings, each being the name of the injection zone. +Use the `defineWidgetConfig` function imported from `@medusajs/admin-shared` to create and export the widget's configurations. + +The function accepts as a parameter an object with the following property: + +- `zone`: A string or an array of strings, each being the name of the zone to inject the widget into. In the example above, the widget is injected after a product’s details. @@ -66,6 +69,37 @@ Then, open a product’s details page. You’ll find your custom widget at the b --- +## Detail Widget Props + +Widgets that are injected into a details page (for example, `product.details.after`) receive a `data` prop, which is the main data of the details page (for example, the product object). + +For example: + +```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["5"]]} +import { defineWidgetConfig } from "@medusajs/admin-shared" +import { DetailWidgetProps, AdminProduct } from "@medusajs/types" + +const ProductWidget = ({ + data +}: DetailWidgetProps) => { + return ( +
+

Product Widget {data.title}

+
+ ) +} + +export const config = defineWidgetConfig({ + zone: "product.details.after", +}) + +export default ProductWidget +``` + +Notice that the type of the props is `DetailWidgetProps`, which accepts as a type argument the expected type of the data. + +--- + ## Using UI Components It’s highly recommended that you use the [Medusa UI package](https://docs.medusajs.com/ui) to match your widget’s design with the rest of the Medusa Admin. @@ -73,7 +107,7 @@ It’s highly recommended that you use the [Medusa UI package](https://docs.medu For example, you can rewrite the above component to the following: ```tsx title="src/admin/widgets/product-widget.tsx" -import type { WidgetConfig } from "@medusajs/admin" +import { defineWidgetConfig } from "@medusajs/admin-shared" import { Container, Heading } from "@medusajs/ui" const ProductWidget = () => { @@ -84,72 +118,16 @@ const ProductWidget = () => { ) } -export const config: WidgetConfig = { +export const config: WidgetConfig = defineWidgetConfig({ zone: "product.details.after", -} +}) export default ProductWidget ``` ---- - -## Widget Props - -A widget receives a `notify` prop, which is an object having the following properties: - -- `success`: a function that shows a success toast message. -- `error`: a function that shows an error toast message. -- `warn`: a function that shows a warning toast message. -- `info`: a function that shows an info toast message. - -Each of these functions accepts two parameters: the message’s title, and the message’s content. - -In addition, some injection zones provide additional props based on the page’s context. - -For example, you can rewrite the above widget to the following: - -```tsx title="src/admin/widgets/product-widget.tsx" highlights={[["14", "product.title", "Show the product's title."], ["18", "success", "Show a success toast message on the click of a button."]]} -import type { - WidgetConfig, - ProductDetailsWidgetProps, -} from "@medusajs/admin" -import { Container, Heading, Button } from "@medusajs/ui" - -const ProductWidget = ({ - notify, - product, -}: ProductDetailsWidgetProps) => { - return ( - - - Product Widget {product.title} - - - - ) -} - -export const config: WidgetConfig = { - zone: "product.details.after", -} - -export default ProductWidget -``` - -Since the widget is in the product’s details page, it receives the product as a prop. The widget now shows the title of the product in the header. - -It also shows a button that, when you click, shows a success toast message. - -Admin Widgets support [Tailwind CSS](https://tailwindcss.com/) out of the box. +Admin Widgets also support [Tailwind CSS](https://tailwindcss.com/) out of the box. diff --git a/www/apps/book/app/advanced-development/custom-cli-scripts/page.mdx b/www/apps/book/app/advanced-development/custom-cli-scripts/page.mdx index 70218a8cf3..7d4595e4bc 100644 --- a/www/apps/book/app/advanced-development/custom-cli-scripts/page.mdx +++ b/www/apps/book/app/advanced-development/custom-cli-scripts/page.mdx @@ -43,15 +43,12 @@ The function receives as a parameter an object having a `container` property, wh ## How to Run Custom CLI Script? -To run the custom CLI script, `build` your code then run the `exec` command: +To run the custom CLI script, run the Medusa CLI's `exec` command: ```bash npm2yarn -npm run build -npx medusa exec ./dist/scripts/my-script.js +npx medusa exec ./src/scripts/my-script.ts ``` -Notice that you pass the path to the file in the `dist` directory. - --- ## Custom CLI Script Arguments @@ -73,6 +70,5 @@ export default async function myScript ({ Then, pass the arguments in the `exec` command after the file path: ```bash npm2yarn -npm run build -npx medusa exec ./dist/scripts/my-script.js arg1 arg2 -``` \ No newline at end of file +npx medusa exec ./src/scripts/my-script.ts arg1 arg2 +``` diff --git a/www/apps/book/app/basics/admin-customizations/page.mdx b/www/apps/book/app/basics/admin-customizations/page.mdx index 5d95d0cab6..a25a7874bf 100644 --- a/www/apps/book/app/basics/admin-customizations/page.mdx +++ b/www/apps/book/app/basics/admin-customizations/page.mdx @@ -6,19 +6,13 @@ export const metadata = { In this chapter, you’ll learn how to customize the Medusa Admin dashboard. - - -Admin customizations are coming soon. - - - ## Overview The Medusa Admin is an admin dashboard that merchants use to manage their store's data. You can extend the Medusa Admin to add widgets and new pages. Your customizations interact with API routes to provide merchants with custom functionalities. -The Medusa Admin is installed in your Medusa application and runs at port `7001` when you start the Medusa application. +The Medusa Admin is installed in your Medusa application and runs at the path `/app` when you start the Medusa application. --- @@ -29,7 +23,7 @@ A widget is a React component that can be injected into an existing page in the For example, create the file `src/admin/widgets/product-widget.tsx` with the following content: ```tsx title="src/admin/widgets/product-widget.tsx" -import type { WidgetConfig } from "@medusajs/admin" +import { defineWidgetConfig } from "@medusajs/admin-shared" const ProductWidget = () => { return ( @@ -39,9 +33,9 @@ const ProductWidget = () => { ) } -export const config: WidgetConfig = { +export const config = defineWidgetConfig({ zone: "product.details.after", -} +}) export default ProductWidget ``` diff --git a/www/apps/book/sidebar.mjs b/www/apps/book/sidebar.mjs index 63cf33e318..f23ca461d5 100644 --- a/www/apps/book/sidebar.mjs +++ b/www/apps/book/sidebar.mjs @@ -216,18 +216,10 @@ export const sidebar = sidebarAttachHrefCommonOptions( path: "/advanced-development/admin/ui-routes", title: "Admin UI Routes", }, - { - path: "/advanced-development/admin/setting-pages", - title: "Admin Setting Pages", - }, { path: "/advanced-development/admin/tips", title: "Tips", }, - { - path: "/advanced-development/admin/onboarding-example", - title: "Example: Onboarding Widget", - }, ], }, ], diff --git a/www/apps/resources/app/admin-widget-injection-zones/page.mdx b/www/apps/resources/app/admin-widget-injection-zones/page.mdx index d6dce003e8..759710429f 100644 --- a/www/apps/resources/app/admin-widget-injection-zones/page.mdx +++ b/www/apps/resources/app/admin-widget-injection-zones/page.mdx @@ -6,14 +6,10 @@ export const metadata = { # {metadata.title} - - -Admin customizations are coming soon. - - - This documentation page includes the list of injection zones you can add Admin Widgets to. +## Login Page + @@ -57,6 +53,22 @@ This documentation page includes the list of injection zones you can add Admin W + +
+ +--- + +## Order Pages + + + + + Injection Zone Name + Description + Additional Props + + + @@ -104,11 +116,11 @@ This documentation page includes the list of injection zones you can add Admin W - Type `OrderDetailsWidgetProps` imported from `@medusajs/admin`. + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - order, // Order object + data, // Order object } ``` @@ -127,11 +139,11 @@ This documentation page includes the list of injection zones you can add Admin W - Type `OrderDetailsWidgetProps` imported from `@medusajs/admin` + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - order, // Order object + data, // Order object } ``` @@ -140,55 +152,21 @@ This documentation page includes the list of injection zones you can add Admin W - `draft_order.list.before` + `order.details.side.before` - Added at the top of the draft orders list page. - - - - - \- - - - - - - - `draft_order.list.after` - - - - - Added at the bottom of the draft orders list page. - - - - - \- - - - - - - - `draft_order.details.before` - - - - - Added at the top of the draft order details page + Added at the top of the second column in the order details page. - Type `DraftOrderDetailsWidgetProps` imported from `@medusajs/admin`. + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - draftOrder, // DraftOrder object + data, // Order object } ``` @@ -197,26 +175,42 @@ This documentation page includes the list of injection zones you can add Admin W - `draft_order.details.after` + `order.details.side.before` - Added at the bottom of the draft order details page. + Added at the end of the second column in the order details page. - Type `DraftOrderDetailsWidgetProps` imported from `@medusajs/admin`. + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - draftOrder, // DraftOrder object + data, // Order object } ``` + +
+ +--- + +## Customer Pages + + + + + Injection Zone Name + Description + Additional Props + + + @@ -264,11 +258,11 @@ This documentation page includes the list of injection zones you can add Admin W - Type `CustomerDetailsWidgetProps` imported from `@medusajs/admin`. + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - customer, // Customer object + data, // Customer object } ``` @@ -286,12 +280,12 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `CustomerDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - customer, // Customer object + data, // Customer object } ``` @@ -343,12 +337,12 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `CustomerGroupDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - customerGroup, // CustomerGroup object + data, // CustomerGroup object } ``` @@ -366,17 +360,33 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `CustomerGroupDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - customerGroup, // CustomerGroup object + data, // CustomerGroup object } ``` + +
+ +--- + +## Product Pages + + + + + Injection Zone Name + Description + Additional Props + + + @@ -423,12 +433,12 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `ProductDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - product, // Product object + data, // Product object } ``` @@ -446,12 +456,58 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `ProductDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - product, // Product object + data, // Product object + } + ``` + + + + + + + `product.details.side.before` + + + + + Added at the top of the second column in the product details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Product object + } + ``` + + + + + + + `product.details.side.after` + + + + + Added at the bottom of the second column in the product details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Product object } ``` @@ -499,16 +555,16 @@ This documentation page includes the list of injection zones you can add Admin W - Added at the top of the product collection details page + Added at the top of the product collection details page. - - Type `ProductCollectionDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - productCollection, // Collection object + data, // ProductCollection object } ``` @@ -526,17 +582,159 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `ProductCollectionDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - productCollection, // Collection object + data, // ProductCollection object } ``` + + + + `product_category.list.before` + + + + + Added at the top of the product categories list page. + + + + + \- + + + + + + + `product_category.list.after` + + + + + Added at the bottom of the product categories list page. + + + + + \- + + + + + + + `product_category.details.before` + + + + + Added at the top of the product category details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ProductCategory object + } + ``` + + + + + + + `product_category.details.after` + + + + + Added at the bottom of the product category details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ProductCategory object + } + ``` + + + + + + + `product_category.details.side.before` + + + + + Added at the top of the second column in the product category details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ProductCategory object + } + ``` + + + + + + + `product_category.details.side.after` + + + + + Added at the bottom of the second column in the product category details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ProductCategory object + } + ``` + + + + +
+ +--- + +## Pricing Pages + + + + + Injection Zone Name + Description + Additional Props + + + @@ -583,12 +781,12 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `PriceListDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - priceList, // PriceList object + data, // PriceList object } ``` @@ -606,12 +804,12 @@ This documentation page includes the list of injection zones you can add Admin W - - Type `PriceListDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - priceList, // PriceList object + data, // PriceList object } ``` @@ -620,55 +818,21 @@ This documentation page includes the list of injection zones you can add Admin W - `discount.list.before` + `price_list.details.side.before` - Added at the top of the discounts list page. + Added at the top of the second column in the price list details page. - - \- - - - - - - - `discount.list.after` - - - - - Added at the bottom of the discounts list page. - - - - - \- - - - - - - - `discount.details.before` - - - - - Added at the top of a discount's details page. - - - - - Type `DiscountDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - discount, // Discount object + data, // PriceList object } ``` @@ -677,147 +841,1351 @@ This documentation page includes the list of injection zones you can add Admin W - `discount.details.after` + `price_list.details.side.after` - Added at the bottom of a discount's details page. + Added at the bottom of the second column in the price list details page. - - Type `DiscountDetailsWidgetProps` imported from `@medusajs/admin`. + + Type `DetailWidgetProps` imported from `@medusajs/types` ```ts noCopy noReport { - discount, // Discount object - } - ``` - - - - - - - `gift_card.list.before` - - - - - Added at the top of the gift cards list page. - - - - - \- - - - - - - - `gift_card.list.after` - - - - - Added at the bottom of the gift cards list page. - - - - - \- - - - - - - - `gift_card.details.before` - - - - - Added at the top of a gift card's details page. - - - - - Type `GiftCardDetailsWidgetProps` imported from `@medusajs/admin`. - - ```ts noCopy noReport - { - giftCard, // Product object - } - ``` - - - - - - - `gift_card.details.after` - - - - - Added at the bottom of a gift card's details page. - - - - - Type `GiftCardDetailsWidgetProps` imported from `@medusajs/admin`. - - ```ts noCopy noReport - { - giftCard, // Product object - } - ``` - - - - - - - `custom_gift_card.before` - - - - - Added at the top of a custom gift card's page. - - - - - Type `GiftCardCustomWidgetProps` imported from `@medusajs/admin`. - - ```ts noCopy noReport - { - giftCard, // GiftCard object - } - ``` - - - - - - - `custom_gift_card.after` - - - - - Added at the bottom of a custom gift card's page. - - - - - Type `GiftCardCustomWidgetProps` imported from `@medusajs/admin`. - - ```ts noCopy noReport - { - giftCard, // GiftCard object + data, // PriceList object + } + ``` + + + + +
+ +--- + +## Promotion Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `promotion.list.before` + + + + + Added at the top of the promotions list page. + + + + + \- + + + + + + + `promotion.list.after` + + + + + Added at the bottom of the promotions list page. + + + + + \- + + + + + + + `promotion.details.before` + + + + + Added at the top of a promotion's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Promotion object + } + ``` + + + + + + + `promotion.details.after` + + + + + Added at the bottom of a promotion's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Promotion object + } + ``` + + + + + + + `promotion.details.side.before` + + + + + Added at the top of the second column in the promotion details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Promotion object + } + ``` + + + + + + + `promotion.details.side.after` + + + + + Added at the bottom of the second column in the promotion details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Promotion object + } + ``` + + + + + + + `campaign.list.before` + + + + + Added at the top of the campaigns list page. + + + + + \- + + + + + + + `campaign.list.after` + + + + + Added at the bottom of the campaigns list page. + + + + + \- + + + + + + + `campaign.details.before` + + + + + Added at the top of a campaign's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Campaign object + } + ``` + + + + + + + `campaign.details.after` + + + + + Added at the bottom of a campaign's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Campaign object + } + ``` + + + + + + + `campaign.details.side.before` + + + + + Added at the top of the second column in the campaign details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Campaign object + } + ``` + + + + + + + `campaign.details.side.after` + + + + + Added at the bottom of the second column in the campaign details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Campaign object + } + ``` + + + + +
+ +--- + +## Setting Pages + +### User Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `user.list.before` + + + + + Added at the top of the users list page. + + + + + \- + + + + + + + `user.list.after` + + + + + Added at the bottom of the users list page. + + + + + \- + + + + + + + `user.details.before` + + + + + Added at the top of a user's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // User object + } + ``` + + + + + + + `user.details.after` + + + + + Added at the bottom of a user's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // User object + } + ``` + + + + +
+ +### Store Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `store.details.before` + + + + + Added at the top of a store's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Store object + } + ``` + + + + + + + `store.details.after` + + + + + Added at the bottom of a store's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Store object + } + ``` + + + + +
+ +### Profile Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `profile.details.before` + + + + + Added at the top of a profile's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // User object + } + ``` + + + + + + + `profile.details.after` + + + + + Added at the bottom of a profile's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // User object + } + ``` + + + + +
+ +### Region Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `region.list.before` + + + + + Added at the top of the regions list page. + + + + + \- + + + + + + + `region.list.after` + + + + + Added at the bottom of the regions list page. + + + + + \- + + + + + + + `region.details.before` + + + + + Added at the top of a region's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Region object + } + ``` + + + + + + + `region.details.after` + + + + + Added at the bottom of a region's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // Region object + } + ``` + + + + +
+ +### Shipping Profile Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `shipping_profile.list.before` + + + + + Added at the top of the shipping profiles list page. + + + + + \- + + + + + + + `shipping_profile.list.after` + + + + + Added at the bottom of the shipping profiles list page. + + + + + \- + + + + + + + `shipping_profile.details.before` + + + + + Added at the top of a shipping profile's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ShippingProfile object + } + ``` + + + + + + + `shipping_profile.details.after` + + + + + Added at the bottom of a shipping profile's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ShippingProfile object + } + ``` + + + + +
+ +### Location Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `location.list.before` + + + + + Added at the top of the locations list page. + + + + + \- + + + + + + + `location.list.after` + + + + + Added at the bottom of the locations list page. + + + + + \- + + + + + + + `location.details.before` + + + + + Added at the top of a location's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // StockLocation object + } + ``` + + + + + + + `location.details.after` + + + + + Added at the bottom of a location's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // StockLocation object + } + ``` + + + + + + + `location.details.side.before` + + + + + Added at the top of the second column in the location details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // StockLocation object + } + ``` + + + + + + + `location.details.side.after` + + + + + Added at the bottom of the second column in the location details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // StockLocation object + } + ``` + + + + +
+ +### Sales Channel Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `sales_channel.list.before` + + + + + Added at the top of the sales channels list page. + + + + + \- + + + + + + + `sales_channel.list.after` + + + + + Added at the bottom of the sales channels list page. + + + + + \- + + + + + + + `sales_channel.details.before` + + + + + Added at the top of a sales channel's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // SalesChannel object + } + ``` + + + + + + + `sales_channel.details.after` + + + + + Added at the bottom of a sales channel's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // SalesChannel object + } + ``` + + + + +
+ +### Reservation Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `reservation.list.before` + + + + + Added at the top of the reservations list page. + + + + + \- + + + + + + + `reservation.list.after` + + + + + Added at the bottom of the reservations list page. + + + + + \- + + + + + + + `reservation.details.before` + + + + + Added at the top of a reservation item's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ReservationItem object + } + ``` + + + + + + + `reservation.details.after` + + + + + Added at the bottom of a reservation item's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ReservationItem object + } + ``` + + + + + + + `reservation.details.side.before` + + + + + Added at the top of the second column in the reservation item details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ReservationItem object + } + ``` + + + + + + + `reservation.details.side.after` + + + + + Added at the bottom of the second column in the reservation item details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ReservationItem object + } + ``` + + + + +
+ +### API Key Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `api_key.list.before` + + + + + Added at the top of the API keys list page. + + + + + \- + + + + + + + `api_key.list.after` + + + + + Added at the bottom of the API keys list page. + + + + + \- + + + + + + + `api_key.details.before` + + + + + Added at the top of a API key's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ApiKey object + } + ``` + + + + + + + `api_key.details.after` + + + + + Added at the bottom of a API key's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // ApiKey object + } + ``` + + + + +
+ +### Workflow Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `workflow.list.before` + + + + + Added at the top of the workflows list page. + + + + + \- + + + + + + + `workflow.list.after` + + + + + Added at the bottom of the workflows list page. + + + + + \- + + + + + + + `workflow.details.before` + + + + + Added at the top of a workflow execution's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // WorkflowExecution object + } + ``` + + + + + + + `workflow.details.after` + + + + + Added at the bottom of a workflow execution's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // WorkflowExecution object + } + ``` + + + + +
+ +### Tax Pages + + + + + Injection Zone Name + Description + Additional Props + + + + + + + `tax.list.before` + + + + + Added at the top of the tax regions list page. + + + + + \- + + + + + + + `tax.list.after` + + + + + Added at the bottom of the tax regions list page. + + + + + \- + + + + + + + `tax.details.before` + + + + + Added at the top of a tax region's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // TaxRegion object + } + ``` + + + + + + + `tax.details.after` + + + + + Added at the bottom of a tax region's details page. + + + + + Type `DetailWidgetProps` imported from `@medusajs/types` + + ```ts noCopy noReport + { + data, // TaxRegion object } ``` diff --git a/www/apps/resources/app/configurations/_medusa-admin/page.mdx b/www/apps/resources/app/configurations/_medusa-admin/page.mdx deleted file mode 100644 index d331449497..0000000000 --- a/www/apps/resources/app/configurations/_medusa-admin/page.mdx +++ /dev/null @@ -1,400 +0,0 @@ -import { Table, TypeList } from "docs-ui" - -export const metadata = { - title: `Medusa Admin Configurations`, -} - -# {metadata.title} - -You can configure the Medusa Admin plugin to change its PORT, the Medusa server's URL, and more. You can also extend the Webpack configurations. - -## Plugin Options - -The plugin accepts the following options: - -```js title="medusa-config.js" -const plugins = [ - // ... - { - resolve: "@medusajs/admin", - /** @type {import('@medusajs/admin').PluginOptions} */ - options: { - serve: true, - autoRebuild: true, - backend: "https://example.com", - path: "/app", - outDir: "build", - develop: { - open: true, - port: 7001, - logLevel: "error", - stats: "normal", - allowedHosts: "auto", - webSocketURL: undefined, - }, - }, - }, -] -``` - - - ---- - -## Admin CLI Commands - -The `medusa-admin` CLI tool is installed in your Medusa application. It's used within the `package.json`'s scripts to build admin assets. - -### build - -The `build` command in the admin CLI allows you to manually build the admin dashboard. For example: - -```json title="package.json" -{ - "scripts": { - // other scripts... - "build:admin": "medusa-admin build" - } -} -``` - -#### Options - -
- - - Option - Description - - - - - - - `--deployment` - - - - - Build admin assets for deployment. When this option is added, plugin options are not loaded from `medusa-config.js` anymore, and - the backend URL is loaded from the `MEDUSA_ADMIN_BACKEND_URL` environment variable. - - - - -
- -### develop - -The `develop` command in the admin CLI allows you to run the admin dashboard in development separately from the Medusa application. For example: - -```json title="package.json" -{ - "scripts": { - // other scripts... - "dev:admin": "medusa-admin develop" - } -} -``` - -#### Options - - - - - Option - Description - Default - - - - - - - `--backend `, `-b ` - - - - - The URL of the Medusa backend. - - - - - The value of the environment variable `MEDUSA_ADMIN_BACKEND_URL` - - - - - - - `--port `, `-p ` - - - - - The port to run the admin on. - - - - - `7001` - - - - -
- -## Change the Medusa Application URL - -### In Development - -To change the Medusa application's URL that the admin sends request to, disable the `serve` plugin option and set the `backend` option to the new URL. - -However, this requires you to also set-up the [develop command](#develop-command-options) as the admin will no longer start with the Medusa application. - -For example: - -```js title="medusa-config.js" -const plugins = [ - // ... - { - resolve: "@medusajs/admin", - /** @type {import('@medusajs/admin').PluginOptions} */ - options: { - serve: false, - backend: "http://localhost:9001", - // other options... - }, - }, -] -``` - -### In Production - - - -This assumes that you've deployed the admin separately and you're passing the `--deployment` option to the [build command](#build-command-options). - - - -To change the backend's URL that the admin sends request to, set the environment variable `MEDUSA_ADMIN_BACKEND_URL` to the backend's URL. - -For example: - -```bash -MEDUSA_ADMIN_BACKEND_URL=https://example.com -``` - ---- - -## Custom Environment Variables - -To set environment variables that you want to access in your admin dashboard's customizations (such as in [widgets](!docs!/advanced-development/admin/widgets) or [UI routes](!docs!/advanced-development/admin/ui-routes)), your environment variables must be prefixed with `MEDUSA_ADMIN_`. Otherwise, it won't be loaded within the admin. - -For example: - -```bash -MEDUSA_ADMIN_CUSTOM_API_KEY=123... -``` - ---- - -## Custom Webpack Configurations - - - -This is an advanced feature and requires knowledge of configuring webpack. If configured wrongly, it may lead to the admin application breaking. - - - - - -Plugins can't include webpack customizations. - - - -The Medusa Admin uses [Webpack](https://webpack.js.org/) to define the configurations for both the Medusa Admin plugin and your customizations. - -You can extend the default webpack configurations defined in the admin plugin to add your custom configurations, such as to support styling your extensions with CSS Modules. - -To do that, create the file `src/admin/webpack.config.js` that uses the `withCustomWebpackConfig` method imported from `@medusajs/admin` to export the extended configurations: - -```js title="src/admin/webpack.config.js" -import { withCustomWebpackConfig } from "@medusajs/admin" - -export default withCustomWebpackConfig((config, webpack) => { - config.plugins.push( - new webpack.DefinePlugin({ - "process.env": { - NODE_ENV: JSON.stringify("production"), - API_URL: - JSON.stringify("https://api.medusa-commerce.com"), - }, - }) - ) - - return config -}) -``` - -The method `withCustomWebpackConfig` accepts a callback function that must return an object of [webpack configuration](https://webpack.js.org/configuration/). The callback function accepts two parameters: - -1. The first parameter is an object that holds the default webpack configuration. Add your configurations to this object, then return it. Not returning the default configurations breaks the application. -2. The second parameter is the webpack instance. diff --git a/www/apps/resources/app/medusa-cli/page.mdx b/www/apps/resources/app/medusa-cli/page.mdx index c2c792d990..b60a77d589 100644 --- a/www/apps/resources/app/medusa-cli/page.mdx +++ b/www/apps/resources/app/medusa-cli/page.mdx @@ -588,7 +588,7 @@ npx medusa exec [file] [args...] - The path to a JavaScript file holding the function to execute. + The path to the TypeScript or JavaScript file holding the function to execute. diff --git a/www/apps/resources/app/troubleshooting/admin-sign-in/page.mdx b/www/apps/resources/app/troubleshooting/admin-sign-in/page.mdx deleted file mode 100644 index e0d35480d6..0000000000 --- a/www/apps/resources/app/troubleshooting/admin-sign-in/page.mdx +++ /dev/null @@ -1,18 +0,0 @@ -export const metadata = { - title: `Signing in to Medusa Admin`, -} - -# {metadata.title} - -If you've created a new Medusa backend and used the `seed` command, the default credentials are: - -```bash noReport -email: admin@medusa-test.com -password: supersecret -``` - -Alternatively, you can create your own users using the Medusa CLI tool: - -```bash -npx medusa user -e some@email.com -p somepassword -``` diff --git a/www/apps/resources/app/troubleshooting/admin-webpack-build-error/page.mdx b/www/apps/resources/app/troubleshooting/admin-webpack-build-error/page.mdx deleted file mode 100644 index e98dc4d2fb..0000000000 --- a/www/apps/resources/app/troubleshooting/admin-webpack-build-error/page.mdx +++ /dev/null @@ -1,93 +0,0 @@ -export const metadata = { - title: `Admin Webpack Build Error`, -} - -# {metadata.title} - -If you run the `build` command in your backend and you get an error message during the admin build process similar to the following: - -```bash noReport noCopy -The left-hand side of an assignment expression must be a variable or a property access. -``` - -Make sure that in your admin customizations (widget, UI route, or settings page) you're not using a type imported from `@medusajs/medusa`. - -This is often the case if you're using a type or an enum necessary for a request sent with the JS Client or Medusa React library. - -For example: - -```ts -import { - Region, - ShippingOptionPriceType, -} from "@medusajs/medusa" -import type Medusa from "@medusajs/medusa-js" - -export default async function prepareShippingOptions( - client: Medusa, - region: Region -) { - let { - shipping_options, - } = await client.admin.shippingOptions.list({ - region_id: region.id, - }) - if (!shipping_options.length) { - shipping_options = [( - await client.admin.shippingOptions.create({ - "name": "PostFake Standard", - "region_id": region.id, - "provider_id": "manual", - "data": { - "id": "manual-fulfillment", - }, - // THIS CAUSES THE ERROR - "price_type": ShippingOptionPriceType.FLAT_RATE, - "amount": 1000, - } - )).shipping_option] - } - - return shipping_options -} -``` - -In this case, you're using the `ShippingOptionPriceType` type to send a request with the JS Client. - -Instead, change it to the string value. If you get a TypeScript error, you can add `// @ts-ignore` before the line: - -```ts -import { - Region, - ShippingOptionPriceType, -} from "@medusajs/medusa" -import type Medusa from "@medusajs/medusa-js" - -export default async function prepareShippingOptions( - client: Medusa, - region: Region -) { - let { - shipping_options, - } = await client.admin.shippingOptions.list({ - region_id: region.id, - }) - if (!shipping_options.length) { - shipping_options = [( - await client.admin.shippingOptions.create({ - "name": "PostFake Standard", - "region_id": region.id, - "provider_id": "manual", - "data": { - "id": "manual-fulfillment", - }, - // @ts-expect-error can't use type from core - "price_type": "flat_rate", - "amount": 1000, - } - )).shipping_option] - } - - return shipping_options -} -``` diff --git a/www/apps/resources/sidebar.mjs b/www/apps/resources/sidebar.mjs index a80c44b690..bf28f8b5e1 100644 --- a/www/apps/resources/sidebar.mjs +++ b/www/apps/resources/sidebar.mjs @@ -1668,10 +1668,6 @@ export const sidebar = sidebarAttachHrefCommonOptions([ path: "/references/medusa-config", title: "Medusa Application", }, - // { - // path: "/configurations/medusa-admin", - // title: "Medusa Admin", - // }, ], }, { @@ -1800,20 +1796,6 @@ export const sidebar = sidebarAttachHrefCommonOptions([ }, ], }, - { - title: "Medusa Admin", - hasTitleStyling: true, - children: [ - { - path: "/troubleshooting/admin-sign-in", - title: "Signing In", - }, - { - path: "/troubleshooting/admin-custom-hooks-error", - title: "Custom Hooks Error", - }, - ], - }, ], }, ],