diff --git a/www/apps/book/public/llms-full.txt b/www/apps/book/public/llms-full.txt index 1cfa8d6aa5..6257e076ff 100644 --- a/www/apps/book/public/llms-full.txt +++ b/www/apps/book/public/llms-full.txt @@ -54476,6 +54476,2348 @@ If you encounter issues not covered in the troubleshooting guides: 2. Join the [Medusa Discord community](https://discord.gg/medusajs) for real-time support from community members. +# Generate Invoices for Orders in Medusa + +In this tutorial, you will learn how to generate invoices for orders in your Medusa application. + +When you install a Medusa application, you get a fully-fledged commerce platform with a Framework for customization. The Medusa application's commerce features are built around [Commerce Modules](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/commerce-modules/index.html.md), which are available out-of-the-box. These features include order management capabilities. + +You can extend the Medusa application to automatically generate invoices for orders, manage invoice configurations through the admin dashboard, and provide customers with easy access to their invoices through the storefront. + +## Summary + +By following this tutorial, you will learn how to: + +- Install and set up Medusa. +- Store default invoice configurations and manage them from the Medusa Admin dashboard. +- Generate PDF invoices for orders, and allow admin users and customers to download them. +- Send PDF invoices as attachments in order confirmation emails. +- Mark previously generated invoices as stale when orders are updated. + +You can follow this tutorial whether you're new to Medusa or an advanced Medusa developer. + +![Diagram illustrating the flow from the customer placing the order, them receiving the invoice, and the admin downloading the invoice](https://res.cloudinary.com/dza7lstvk/image/upload/v1753793501/Medusa%20Resources/invoices-summary_ieja9e.jpg) + +- [Full Code](https://github.com/medusajs/examples/tree/main/invoice-generator): Find the full code for this tutorial. +- [OpenAPI Specs for Postman](https://res.cloudinary.com/dza7lstvk/raw/upload/v1753801608/OpenApi/Invoice-Generator_wtft9v.yaml): Import this OpenAPI Specs file into tools like Postman. + +*** + +## Step 1: Install a Medusa Application + +### Prerequisites + +- [Node.js v20+](https://nodejs.org/en/download) +- [Git CLI tool](https://git-scm.com/downloads) +- [PostgreSQL](https://www.postgresql.org/download/) + +Start by installing the Medusa application on your machine with the following command: + +```bash +npx create-medusa-app@latest +``` + +You'll first be asked for the project's name. Then, when asked whether you want to install the [Next.js Starter Storefront](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/nextjs-starter/index.html.md), choose Yes. + +Afterward, the installation process will start, which will install the Medusa application in a directory with your project's name, and the Next.js Starter Storefront in a separate directory with the `{project-name}-storefront` name. + +The Medusa application is composed of a headless Node.js server and an admin dashboard. The storefront is installed or custom-built separately and connects to the Medusa application through its REST endpoints, called [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md). Learn more in [Medusa's Architecture documentation](https://docs.medusajs.com/docs/learn/introduction/architecture/index.html.md). + +Once the installation finishes successfully, the Medusa Admin dashboard will open with a form to create a new user. Enter the user's credentials and submit the form. Afterward, you can log in with the new user and explore the dashboard. + +Check out the [troubleshooting guides](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/troubleshooting/create-medusa-app-errors/index.html.md) for help. + +*** + +## Step 2: Create Invoice Generator Module + +In Medusa, you can build custom features in a [module](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md). A module is a reusable package with the data models and functionalities related to a single feature or domain. Medusa integrates the module into your application without implications or side effects on your setup. + +In this step, you'll build an Invoice Generator Module that defines the data models and logic to manage invoices. Later, you'll build commerce flows related to invoices around the module. + +Refer to the [Modules documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules/index.html.md) to learn more. + +### a. Create Module Directory + +Create the directory `src/modules/invoice-generator` that will hold the Invoice Generator Module's code. + +### b. Create Data Models + +A data model represents a table in the database. You create data models using Medusa's Data Model Language (DML). It simplifies defining a table's columns, relations, and indexes with straightforward methods and configurations. + +Refer to the [Data Models documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules#1-create-data-model/index.html.md) to learn more. + +For the Invoice Generator Module, you'll create a data model to store default invoice configurations and another to store generated invoices. + +#### InvoiceConfig Data Model + +To create the first data model, create the file `src/modules/invoice-generator/models/invoice-config.ts` with the following content: + +```ts title="src/modules/invoice-generator/models/invoice-config.ts" highlights={invoiceConfigHighlights} +import { model } from "@medusajs/framework/utils" + +export const InvoiceConfig = model.define("invoice_config", { + id: model.id().primaryKey(), + company_name: model.text(), + company_address: model.text(), + company_phone: model.text(), + company_email: model.text(), + company_logo: model.text().nullable(), + notes: model.text().nullable(), +}) +``` + +The `InvoiceConfig` data model has the following properties: + +- `id`: The primary key of the table. +- `company_name`: The name of the company issuing the invoice. +- `company_address`: The address of the company. +- `company_phone`: The phone number of the company. +- `company_email`: The email address of the company. +- `company_logo`: The URL of the company logo image. +- `notes`: Additional notes to include in the invoice. + +You can also add other fields as needed, such as tax information or payment terms. + +Learn more about defining data model properties in the [Property Types documentation](https://docs.medusajs.com/docs/learn/fundamentals/data-models/properties/index.html.md). + +#### Invoice Data Model + +Next, you'll create the `Invoice` data model that represents a generated invoice. + +Create the file `src/modules/invoice-generator/models/invoice.ts` with the following content: + +```ts title="src/modules/invoice-generator/models/invoice.ts" highlights={invoiceHighlights} +import { model } from "@medusajs/framework/utils" + +export enum InvoiceStatus { + LATEST = "latest", + STALE = "stale", +} + +export const Invoice = model.define("invoice", { + id: model.id().primaryKey(), + display_id: model.autoincrement(), + order_id: model.text(), + status: model.enum(InvoiceStatus).default(InvoiceStatus.LATEST), + pdfContent: model.json(), +}) +``` + +The `Invoice` data model has the following properties: + +- `id`: The primary key of the table. +- `display_id`: An auto-incrementing identifier for the invoice, which will be used to display the invoice number. +- `order_id`: The ID of the order that the invoice belongs to. +- `status`: The current status of the invoice. + - `latest` indicates the invoice is the most recent version for the order. + - `stale` indicates a previous invoice for the order that became stale after order updates. +- `pdfContent`: The content of the invoice in object format that works with `pdfmake`. You'll use this later to generate the invoice PDF. + +### c. Create Module's Service + +You can manage your module's data models in a service. + +A service is a TypeScript class that the module exports. In the service's methods, you can connect to the database, allowing you to manage your data models, or connect to a third-party service, which is useful if you're integrating with external services. + +Refer to the [Module Service documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules#2-create-service/index.html.md) to learn more. + +To create the Invoice Generator Module's service, create the file `src/modules/invoice-generator/service.ts` with the following content: + +```ts title="src/modules/invoice-generator/service.ts" +import { MedusaService } from "@medusajs/framework/utils" +import { InvoiceConfig } from "./models/invoice-config" +import { Invoice } from "./models/invoice" + +class InvoiceGeneratorService extends MedusaService({ + InvoiceConfig, + Invoice, +}) { } + +export default InvoiceGeneratorService +``` + +The `InvoiceGeneratorService` extends `MedusaService`, which generates a class with data-management methods for your module's data models. This saves you time on implementing Create, Read, Update, and Delete (CRUD) methods. + +So, the `InvoiceGeneratorService` class now has methods like `createInvoices` and `retrieveInvoice`. + +Find all methods generated by the `MedusaService` in [the Service Factory reference](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/service-factory-reference/index.html.md). + +### d. Export Module Definition + +The final piece to a module is its definition, which you export in an `index.ts` file at its root directory. This definition tells Medusa the name of the module and its service. + +So, create the file `src/modules/invoice-generator/index.ts` with the following content: + +```ts title="src/modules/invoice-generator/index.ts" +import InvoiceModuleService from "./service" +import { Module } from "@medusajs/framework/utils" + +export const INVOICE_MODULE = "invoiceGenerator" + +export default Module(INVOICE_MODULE, { + service: InvoiceModuleService, +}) +``` + +You use the `Module` function to create the module's definition. It accepts two parameters: + +1. The module's unique name. +2. An object whose `service` property is the module's service class. + +### e. Register the Module + +Once you finish building the module, add it to Medusa's configurations to start using it. + +In `medusa-config.ts`, add a `modules` property and pass an array with your custom module: + +```ts title="medusa-config.ts" +module.exports = defineConfig({ + // ... + modules: [ + { + resolve: "./src/modules/invoice-generator", + }, + ], +}) +``` + +Each object in the `modules` array has a `resolve` property, whose value is either a path to the module's directory, or an `npm` package’s name. + +### f. Generate and Run Migrations + +Since data models represent tables in the database, you define how they're created in the database with migrations. A migration is a TypeScript class that defines database changes made by a module. + +Refer to the [Migrations documentation](https://docs.medusajs.com/docs/learn/fundamentals/modules#5-generate-migrations/index.html.md) to learn more. + +Medusa's CLI tool can generate the migrations for you. To generate a migration for the Invoice Generator Module, run the following command in your Medusa application's directory: + +```bash +npx medusa db:generate invoice-generator +``` + +The `db:generate` command of the Medusa CLI accepts the name of the module to generate the migration for. You'll now have a `migrations` directory under `src/modules/invoice-generator` that holds the generated migration. + +Then, to reflect these migrations on the database, run the following command: + +```bash +npx medusa db:migrate +``` + +The tables for the `InvoiceConfig` and `Invoice` data models are now created in the database. + +*** + +## Step 3: Create Default Invoice Configurations + +In this step, you'll create default invoice configurations that admins can later manage through the Medusa Admin dashboard. + +Since you need to create the default configurations when the Medusa application starts, you'll use a [loader](https://docs.medusajs.com/docs/learn/fundamentals/modules/loaders/index.html.md). A loader is a script in your module that Medusa runs on application startup. + +### a. Create Loader + +To create the loader, create the file `src/modules/invoice-generator/loaders/create-default-config.ts` with the following content: + +```ts title="src/modules/invoice-generator/loaders/create-default-config.ts" highlights={createDefaultConfigHighlights} +import { + LoaderOptions, + IMedusaInternalService, +} from "@medusajs/framework/types" +import { InvoiceConfig } from "../models/invoice-config" + +export default async function createDefaultConfigLoader({ + container, +}: LoaderOptions) { + const service: IMedusaInternalService< + typeof InvoiceConfig + > = container.resolve("invoiceConfigService") + + const [_, count] = await service.listAndCount() + + if (count > 0) { + return + } + + await service.create({ + company_name: "Acme", + company_address: "123 Acme St, Springfield, USA", + company_phone: "+1 234 567 8900", + company_email: "admin@example.com", + }) +} +``` + +The loader function accepts an object having the [module's container](https://docs.medusajs.com/docs/learn/fundamentals/modules/container/index.html.md) as a parameter. You can use this container to resolve Framework tools and the module's resources. + +In the loader, you resolve a service that Medusa generates for the `InvoiceConfig` data model. You use that service to create default configurations if none exist. + +### b. Register the Loader + +Next, to register the loader in the module's definition, update the `Module` function usage in `src/modules/invoice-generator/index.ts`: + +```ts title="src/modules/invoice-generator/index.ts" highlights={loaderRegisterHighlights} +// other imports... +import createDefaultConfigLoader from "./loaders/create-default-config" + +// ... + +export default Module(INVOICE_MODULE, { + // ... + loaders: [createDefaultConfigLoader], +}) +``` + +You pass a `loaders` property to the `Module` function, whose value is an array of loader functions. + +Refer to the [Loaders](https://docs.medusajs.com/docs/learn/fundamentals/modules/loaders/index.html.md) documentation to learn more about loaders. + +### c. Run the Loader + +To run the loader, start the Medusa application: + +```bash npm2yarn +npm run dev +``` + +The Medusa application will run your loader to create the default invoice configurations in the database. You'll verify that it worked in the next step after adding the admin settings page. + +*** + +## Step 4: Allow Admins to Manage Invoice Configurations + +In this step, you'll customize the Medusa application to allow admin users to manage the default invoice configurations through the Medusa Admin dashboard. + +To build this feature, you need to create: + +1. A [workflow](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) with the business logic to manage invoice configurations. +2. An [API route](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md) that exposes the invoice configuration management functionality to clients. +3. A [settings page](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes#create-settings-page/index.html.md) in the Medusa Admin dashboard that allows admin users to manage the invoice configurations. + +### a. Invoice Configuration Management Workflow + +A workflow is a series of queries and actions, called steps, that complete a task. A workflow is similar to a function, but it allows you to track its executions' progress, define roll-back logic, and configure other advanced features. + +Refer to the [Workflows documentation](https://docs.medusajs.com/docs/learn/fundamentals/workflows/index.html.md) to learn more. + +The workflow that manages invoice configurations will have a single step that updates the invoice configuration. + +#### Update Invoice Configuration Step + +To create a step, create the file `src/workflows/steps/update-invoice-config.ts` with the following content: + +```ts title="src/workflows/steps/update-invoice-config.ts" highlights={updateInvoiceConfigHighlights} +import { createStep, StepResponse } from "@medusajs/framework/workflows-sdk" +import { INVOICE_MODULE } from "../../modules/invoice-generator" + +type StepInput = { + id?: string + company_name?: string + company_address?: string + company_phone?: string + company_email?: string + company_logo?: string + notes?: string +} + +export const updateInvoiceConfigStep = createStep( + "update-invoice-config", + async ({ id, ...updateData }: StepInput, { container }) => { + const invoiceGeneratorService = container.resolve(INVOICE_MODULE) + + const prevData = id ? + await invoiceGeneratorService.retrieveInvoiceConfig(id) : + (await invoiceGeneratorService.listInvoiceConfigs())[0] + + const updatedData = await invoiceGeneratorService.updateInvoiceConfigs({ + id: prevData.id, + ...updateData, + }) + + return new StepResponse(updatedData, prevData) + }, + async (prevInvoiceConfig, { container }) => { + if (!prevInvoiceConfig) { + return + } + + const invoiceGeneratorService = container.resolve(INVOICE_MODULE) + + await invoiceGeneratorService.updateInvoiceConfigs({ + id: prevInvoiceConfig.id, + company_name: prevInvoiceConfig.company_name, + company_address: prevInvoiceConfig.company_address, + company_phone: prevInvoiceConfig.company_phone, + company_email: prevInvoiceConfig.company_email, + company_logo: prevInvoiceConfig.company_logo, + }) + } +) +``` + +You create a step with the `createStep` function. It accepts three parameters: + +1. The step's unique name. +2. An async function that receives two parameters: + - The step's input, which is an object having the data to update in the invoice configuration. + - An object that has properties including the [Medusa container](https://docs.medusajs.com/docs/learn/fundamentals/medusa-container/index.html.md), which is a registry of Framework and commerce tools that you can access in the step. +3. An async compensation function that undoes the actions performed by the step function. This function is only executed if an error occurs during the workflow's execution. + +In the step function, you resolve the Invoice Generator Module's service from the Medusa container. Then, you either retrieve the invoice configuration by its ID, or list all invoice configurations and use the first one. + +Next, you update the invoice configuration with the data passed to the step function. + +Finally, a step function must return a `StepResponse` instance. The `StepResponse` constructor accepts two parameters: + +1. The step's output, which is the updated invoice configuration. +2. Data to pass to the step's compensation function. + +In the compensation function, you undo the invoice configuration updates if an error occurs during the workflow's execution. + +#### Update Invoice Configuration Workflow + +Next, you'll create a workflow that uses the `updateInvoiceConfigStep` step to update the invoice configuration. + +Create the file `src/workflows/update-invoice-config.ts` with the following content: + +```ts title="src/workflows/update-invoice-config.ts" +import { createWorkflow, WorkflowResponse } from "@medusajs/framework/workflows-sdk" +import { updateInvoiceConfigStep } from "./steps/update-invoice-config" + +type WorkflowInput = { + id?: string + company_name?: string + company_address?: string + company_phone?: string + company_email?: string + company_logo?: string + notes?: string +} + +export const updateInvoiceConfigWorkflow = createWorkflow( + "update-invoice-config", + (input: WorkflowInput) => { + const invoiceConfig = updateInvoiceConfigStep(input) + + return new WorkflowResponse({ + invoice_config: invoiceConfig, + }) + } +) +``` + +You create a workflow using the `createWorkflow` function. It accepts the workflow's unique name as a first parameter. + +It accepts a second parameter: a constructor function that holds the workflow's implementation. The function accepts an input object with the invoice configuration data to update. + +In the workflow, you update the invoice configuration using the `updateInvoiceConfigStep` step. + +A workflow must return an instance of `WorkflowResponse` that accepts the data to return to the workflow's executor. + +### b. Invoice Configuration API Routes + +Next, you'll create two API routes: + +1. An API route to retrieve the default invoice configurations. This is useful to display the current configurations on the settings page. +2. An API route to update the default invoice configurations. This is useful to allow admin users to update the configurations from the settings page. + +#### Retrieve Invoice Configurations API Route + +An API route is created in a `route.ts` file under a sub-directory of the `src/api` directory. The path of the API route is the file's path relative to `src/api`. + +Refer to the [API routes](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/index.html.md) to learn more about them. + +Create the file `src/api/admin/invoice-config/route.ts` with the following content: + +```ts title="src/api/admin/invoice-config/route.ts" highlights={retrieveInvoiceConfigHighlights} +import { MedusaRequest, MedusaResponse } from "@medusajs/framework/http" + +export async function GET( + req: MedusaRequest, + res: MedusaResponse +) { + const query = req.scope.resolve("query") + + const { data: [invoiceConfig] } = await query.graph({ + entity: "invoice_config", + fields: ["*"], + }) + + res.json({ + invoice_config: invoiceConfig, + }) +} +``` + +Since you export a `GET` route handler function, you expose a `GET` API route at `/admin/invoice-config`. + +In the route handler, you resolve [Query](https://docs.medusajs.com/docs/learn/fundamentals/module-links/query/index.html.md) from the Medusa container. It allows you to retrieve data across modules. + +You retrieve the default invoice configuration and return it in the response. + +#### Update Invoice Configurations API Route + +Next, you'll add the API route to update the invoice configurations. + +In the same file, add the following imports at the top of the file: + +```ts title="src/api/admin/invoice-config/route.ts" +import { z } from "zod" +import { + updateInvoiceConfigWorkflow, +} from "../../../workflows/update-invoice-config" +``` + +Then, add the following at the end of the file: + +```ts title="src/api/admin/invoice-config/route.ts" highlights={postInvoiceConfigHighlights} +export const PostInvoiceConfigSchema = z.object({ + company_name: z.string().optional(), + company_address: z.string().optional(), + company_phone: z.string().optional(), + company_email: z.string().optional(), + company_logo: z.string().optional(), + notes: z.string().optional(), +}) + +type PostInvoiceConfig = z.infer + +export async function POST( + req: MedusaRequest, + res: MedusaResponse +) { + const { result: { invoice_config } } = await updateInvoiceConfigWorkflow( + req.scope + ).run({ + input: req.validatedBody, + }) + + res.json({ + invoice_config, + }) +} +``` + +You define a validation schema with [Zod](https://zod.dev/) to validate incoming request bodies. The schema defines the fields that can be updated in the invoice configuration. + +Next, since you export a `POST` route handler function, you expose a `POST` API route at `/admin/invoice-config`. + +In the route handler, you execute the `updateInvoiceConfigWorkflow` by invoking it, passing it the Medusa container, then executing its `run` method. + +You return the updated invoice configuration in the response. + +#### Add Validation Middleware + +To validate the body parameters of requests sent to the API route, you need to apply a [middleware](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md). + +To apply a middleware to a route, create the file `src/api/middlewares.ts` with the following content: + +```ts title="src/api/middlewares.ts" +import { defineMiddlewares, validateAndTransformBody } from "@medusajs/framework/http" +import { PostInvoiceConfigSchema } from "./admin/invoice-config/route" + +export default defineMiddlewares({ + routes: [ + { + matcher: "/admin/invoice-config", + methods: ["POST"], + middlewares: [ + validateAndTransformBody(PostInvoiceConfigSchema), + ], + }, + ], +}) +``` + +You apply Medusa's `validateAndTransformBody` middleware to `POST` requests sent to the `/admin/invoice-config` API route. + +The middleware function accepts a Zod schema, which you created in the API route's file. + +Refer to the [Middlewares](https://docs.medusajs.com/docs/learn/fundamentals/api-routes/middlewares/index.html.md) documentation to learn more. + +### c. Create Settings Page + +Next, you'll create a settings page in the Medusa Admin dashboard that allows admin users to manage the invoice configurations. + +#### Initialize JS SDK + +To send requests to the Medusa server, you'll use the [JS SDK](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md). It's already installed in your Medusa project, but you need to initialize it before using it in your customizations. + +Create the file `src/admin/lib/sdk.ts` with the following content: + +```ts title="src/admin/lib/sdk.ts" +import Medusa from "@medusajs/js-sdk" + +export const sdk = new Medusa({ + baseUrl: import.meta.env.VITE_BACKEND_URL || "/", + debug: import.meta.env.DEV, + auth: { + type: "session", + }, +}) +``` + +Learn more about the initialization options in the [JS SDK](https://docs.medusajs.com/Users/shahednasser/medusa/www/apps/resources/app/js-sdk/index.html.md) reference. + +#### Create Settings Page Component + +Settings pages are [UI routes](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes#create-settings-page/index.html.md) created under the `/src/admin/routes/settings` directory. Medusa will then add the settings page under the Settings section of the Medusa Admin dashboard. + +Refer to the [UI Routes](https://docs.medusajs.com/docs/learn/fundamentals/admin/ui-routes/index.html.md) documentation to learn more. + +Create the file `src/admin/routes/settings/invoice-config/page.tsx` with the following content: + +```tsx title="src/admin/routes/settings/invoice-config/page.tsx" highlights={invoiceConfigPageHighlights} +import { defineRouteConfig } from "@medusajs/admin-sdk" +import { Container, Heading, Button, Input, Label, Textarea, toast } from "@medusajs/ui" +import { useMutation, useQuery } from "@tanstack/react-query" +import { sdk } from "../../../lib/sdk" +import { useForm } from "react-hook-form" +import * as zod from "zod" +import { + FormProvider, + Controller, +} from "react-hook-form" +import { useCallback, useEffect } from "react" + +type InvoiceConfig = { + id: string; + company_name: string; + company_address: string; + company_phone: string; + company_email: string; + company_logo?: string; + notes?: string; +} + +const schema = zod.object({ + company_name: zod.string().optional(), + company_address: zod.string().optional(), + company_phone: zod.string().optional(), + company_email: zod.string().email().optional(), + company_logo: zod.string().url().optional(), + notes: zod.string().optional(), +}) + +const InvoiceConfigPage = () => { + // TODO add implementation +} + +export const config = defineRouteConfig({ + label: "Default Invoice Config", +}) + +export default InvoiceConfigPage +``` + +A settings page file must export: + +1. A React component that renders the page. This is the file's default export. +2. A configuration object created with the `defineRouteConfig` function. It accepts an object with properties that define the page's configuration, such as its sidebar label. + +So far, the React component doesn't have any implementation. You also define a Zod schema that you'll use to validate the form data. + +Change the implementation of the `InvoiceConfigPage` component to the following: + +```tsx title="src/admin/routes/settings/invoice-config/page.tsx" highlights={InvoiceConfigPageHighlights} +const InvoiceConfigPage = () => { + const { data, isLoading, refetch } = useQuery<{ + invoice_config: InvoiceConfig + }>({ + queryFn: () => sdk.client.fetch("/admin/invoice-config"), + queryKey: ["invoice-config"], + }) + const { mutateAsync, isPending } = useMutation({ + mutationFn: (payload: zod.infer) => + sdk.client.fetch("/admin/invoice-config", { + method: "POST", + body: payload, + }), + onSuccess: () => { + refetch() + toast.success("Invoice config updated successfully") + }, + }) + + const getFormDefaultValues = useCallback(() => { + return { + company_name: data?.invoice_config.company_name || "", + company_address: data?.invoice_config.company_address || "", + company_phone: data?.invoice_config.company_phone || "", + company_email: data?.invoice_config.company_email || "", + company_logo: data?.invoice_config.company_logo || "", + notes: data?.invoice_config.notes || "", + } + }, [data]) + + const form = useForm>({ + defaultValues: getFormDefaultValues(), + }) + + const handleSubmit = form.handleSubmit((formData) => mutateAsync(formData)) + + const uploadLogo = async (event: React.ChangeEvent) => { + const file = event.target.files?.[0] + if (!file) { + return + } + + const { files } = await sdk.admin.upload.create({ + files: [file], + }) + + form.setValue("company_logo", files[0].url) + } + + useEffect(() => { + form.reset(getFormDefaultValues()) + }, [getFormDefaultValues]) + + + // TODO render form +} +``` + +In the component, you: + +1. Fetch the current invoice configuration using the `useQuery` hook from Tanstack Query and the JS SDK. +2. Define a mutation using the `useMutation` hook to update the invoice configuration when the form is submitted. +3. Define a function that returns the current invoice configuration data, which is useful to create the form. +4. Define a `form` instance with `useForm` from `react-hook-form`. You pass it the Zod validation schema as a type argument, and the default values using the `getFormDefaultValues` function. +5. Define a `handleSubmit` function that updates the invoice configuration using the mutation. +6. Define an `uploadLogo` function that uploads a logo using Medusa's [Upload API route](https://docs.medusajs.com/api/admin#uploads_postuploads). +7. Reset the form's default values when the `data` changes, ensuring the form reflects the latest invoice configuration. + +Finally, replace the `TODO` comment with the following `return` statement: + +```tsx title="src/admin/routes/settings/invoice-config/page.tsx" +return ( + +
+ Invoice Config +
+ +
+ { + return ( +
+
+ +
+ +
+ ) + }} + /> + { + return ( +
+
+ +
+