diff --git a/.cursor/rules/read-rules-and-skills-first.mdc b/.cursor/rules/read-rules-and-skills-first.mdc
new file mode 100644
index 0000000..43e6142
--- /dev/null
+++ b/.cursor/rules/read-rules-and-skills-first.mdc
@@ -0,0 +1,24 @@
+---
+description: Forces the agent to read all applicable rules and skills before writing any code
+alwaysApply: true
+---
+
+# Read Rules & Skills Before Coding
+
+**Before writing or editing any code**, you MUST:
+
+1. **Read all glob-matched rules** in `.cursor/rules/` that apply to the file types you are about to create or edit (e.g. `typescript-javascript-standards.mdc` for `.ts`/`.tsx` files).
+2. **Read every skill referenced** by those rules (e.g. if a rule says "Use the /react-component skill", read that skill file immediately).
+3. **Only then** begin writing code, ensuring every standard is applied on the first pass.
+
+```
+❌ BAD — write code, then retroactively fix standards
+1. Create component
+2. User points out missing JSDoc / tests / arrow functions
+3. Spend extra tokens fixing
+
+✅ GOOD — read rules first, write correct code once
+1. Read typescript-javascript-standards.mdc
+2. Read react-component skill, vitest-unit-testing skill
+3. Create component following all standards (arrow fn, JSDoc, DI, tests)
+```
diff --git a/.cursor/rules/typescript-javascript-standards.mdc b/.cursor/rules/typescript-javascript-standards.mdc
index c8d4d62..76bdec1 100644
--- a/.cursor/rules/typescript-javascript-standards.mdc
+++ b/.cursor/rules/typescript-javascript-standards.mdc
@@ -92,6 +92,10 @@ test("getActiveUsers returns only active users", async () => {
Use the /react-component skill when creating a React component.
+## Email template
+
+Use the /email-template skill when creating an email template.
+
## React Server Action/Function
Follow the /route-action-gen-workflow rule when creating a server action/function.
diff --git a/.cursor/skills/email-template/SKILL.md b/.cursor/skills/email-template/SKILL.md
new file mode 100644
index 0000000..0ed8b26
--- /dev/null
+++ b/.cursor/skills/email-template/SKILL.md
@@ -0,0 +1,105 @@
+---
+name: email-template
+description: Create email templates using React Email in the @workspace/email-templates package. Use when the user asks to create, add, build, or scaffold an email template, transactional email, or mentions React Email, email-templates, or @react-email/components.
+---
+
+# Email Template Creation
+
+Create email templates in `packages/email-templates/src/` using `@react-email/components`.
+
+## File Conventions
+
+- Place templates in a **domain subfolder**: `src/user/`, `src/order/`, `src/billing/`, etc.
+- File names in **kebab-case**: `password-forgot.tsx`, `order-confirmation.tsx`.
+- No `index.tsx` files inside subfolders -- each template is a standalone file.
+
+## Required Exports
+
+Every template file must export exactly these four things:
+
+1. **Props interface** -- named `{ComponentName}Props`.
+2. **Named const component** -- arrow function typed as `React.JSX.Element`.
+3. **PreviewProps** -- static property on the component with sample data, using `satisfies`.
+4. **Default export** -- the component.
+
+```tsx
+import {
+ Body,
+ Container,
+ Head,
+ Heading,
+ Html,
+ Preview,
+ Text,
+} from "@react-email/components";
+
+export interface InviteEmailProps {
+ inviterName: string;
+ inviteUrl: string;
+}
+
+/**
+ * Email sent when a user is invited to join a workspace.
+ *
+ * @param props.inviterName - Name of the person who sent the invite.
+ * @param props.inviteUrl - URL the recipient clicks to accept.
+ * @returns The invite email React Email component.
+ */
+export const InviteEmail = ({
+ inviterName,
+ inviteUrl,
+}: InviteEmailProps): React.JSX.Element => {
+ return (
+
+
+
+ You're invited!
+
+ {inviterName} invited you to join the workspace.
+
+
+
+
+ );
+};
+
+InviteEmail.PreviewProps = {
+ inviterName: "Jane",
+ inviteUrl: "https://example.com/invite?token=abc",
+} satisfies InviteEmailProps;
+
+export default InviteEmail;
+```
+
+## Styling
+
+- Use plain `React.CSSProperties` objects defined as `const` at the bottom of the file.
+- Common styles: `main` (body background + font), `container` (white card), `heading`, `paragraph`, `button`, `buttonContainer`, `hr`, `footer`.
+- Match the existing style objects from other templates for visual consistency.
+
+## After Creating a Template
+
+Run the codegen script to regenerate the template registry:
+
+```bash
+pnpm --filter @workspace/email-templates generate
+```
+
+This updates `src/index.ts` with the new template's entry in `TemplateMap` and `templates`. The generated file must not be edited by hand.
+
+## Checklist
+
+Before finalizing a template:
+
+- [ ] File is in a domain subfolder under `src/` (e.g., `src/user/`)
+- [ ] File name is kebab-case
+- [ ] Exports an `interface` ending in `Props`
+- [ ] Exports a named `const` arrow-function component with JSDoc
+- [ ] Component return type is `React.JSX.Element`
+- [ ] `PreviewProps` set with `satisfies {PropsType}`
+- [ ] Default export of the component
+- [ ] Styles are `React.CSSProperties` objects at the bottom
+- [ ] Co-located `.test.tsx` with 100% coverage (renders HTML containing key props)
+- [ ] Ran `pnpm --filter @workspace/email-templates generate`
diff --git a/apps/web/app/email-demo/actions/route.post.config.ts b/apps/web/app/email-demo/actions/route.post.config.ts
new file mode 100644
index 0000000..fbe365d
--- /dev/null
+++ b/apps/web/app/email-demo/actions/route.post.config.ts
@@ -0,0 +1,54 @@
+/**
+ * To test this end point during development, run the following command:
+ *
+ * 1. Run "docker-compose up" from the root of the monorepo to start the email server.
+ * 2. Set the environment variable "NODE_ENV" to "development".
+ * 3. Run the development server by running "pnpm dev --filter=web" from the root of the monorepo.
+ * 4. Send a request to the end point using the following command:
+ * curl -X POST http://localhost:3000/api/email-demo -H "Content-Type: application/json" -d '{"email": "test@example.com", "name": "Test User"}'
+ */
+
+import { z } from "zod";
+import {
+ createRequestValidator,
+ HandlerFunc,
+ successResponse,
+} from "route-action-gen/lib";
+import { getEmailSender } from "@/lib/email/smtp";
+
+const bodyValidator = z.object({
+ email: z.string().email(),
+ name: z.string().min(1),
+});
+
+export const requestValidator = createRequestValidator({
+ body: bodyValidator,
+});
+
+export const responseValidator = z.object({
+ success: z.boolean(),
+});
+
+export const handler: HandlerFunc<
+ typeof requestValidator,
+ typeof responseValidator,
+ undefined
+> = async (data) => {
+ const { body } = data;
+
+ const emailSender = getEmailSender();
+
+ await emailSender.send({
+ to: body.email,
+ subject: "Hello, World!",
+ template: "user/signup",
+ data: {
+ name: body.name,
+ verifyUrl: "https://example.com/verify?token=123",
+ },
+ });
+
+ return successResponse({
+ success: true,
+ });
+};
diff --git a/apps/web/app/email-demo/actions/route.ts b/apps/web/app/email-demo/actions/route.ts
new file mode 100644
index 0000000..a661b1e
--- /dev/null
+++ b/apps/web/app/email-demo/actions/route.ts
@@ -0,0 +1 @@
+export * from "./.generated/route";
diff --git a/apps/web/lib/email/smtp.ts b/apps/web/lib/email/smtp.ts
new file mode 100644
index 0000000..fb5bb1a
--- /dev/null
+++ b/apps/web/lib/email/smtp.ts
@@ -0,0 +1,34 @@
+import {
+ createEmailSender,
+ createResendTransport,
+ createSmtpTransport,
+ EmailSender,
+} from "@workspace/email-send";
+import { env } from "@workspace/env";
+
+let emailSender: EmailSender | null = null;
+
+export const getEmailSender = ({
+ from = "HyperJump ",
+}: {
+ from?: string;
+} = {}): EmailSender => {
+ if (!emailSender) {
+ if (env.NODE_ENV === "production" && !env.RESEND_API_KEY) {
+ throw new Error("RESEND_API_KEY is not set");
+ }
+ emailSender = createEmailSender({
+ from,
+ transport:
+ env.NODE_ENV === "production"
+ ? createResendTransport({
+ apiKey: env.RESEND_API_KEY!,
+ })
+ : createSmtpTransport({
+ host: "localhost",
+ port: 2500,
+ }),
+ });
+ }
+ return emailSender;
+};
diff --git a/apps/web/next-env.d.ts b/apps/web/next-env.d.ts
index 0c7fad7..2d5420e 100644
--- a/apps/web/next-env.d.ts
+++ b/apps/web/next-env.d.ts
@@ -1,7 +1,7 @@
///
///
///
-import "./.next/dev/types/routes.d.ts";
+import "./.next/types/routes.d.ts";
// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
diff --git a/apps/web/package.json b/apps/web/package.json
index 137c053..02b6760 100644
--- a/apps/web/package.json
+++ b/apps/web/package.json
@@ -16,6 +16,7 @@
"test:coverage": "vitest --run --coverage"
},
"dependencies": {
+ "@workspace/email-send": "workspace:*",
"@workspace/env": "workspace:*",
"@workspace/ui": "workspace:*",
"@prisma/adapter-pg": "^7.2.0",
diff --git a/docker-compose.yml b/docker-compose.yml
index bd112f4..9ca764e 100644
--- a/docker-compose.yml
+++ b/docker-compose.yml
@@ -12,3 +12,10 @@ services:
volumes:
- ./data:/var/lib/postgresql/data
restart: unless-stopped
+ inbucket:
+ image: inbucket/inbucket:latest
+ ports:
+ - 9000:9000 # web interface. Open http://localhost:9000 to view the inbox.
+ - 2500:2500 # SMTP server. Use this to send emails.
+ - 1100:1100 # POP3 server
+ restart: unless-stopped
diff --git a/docs/docs/guide/email.mdx b/docs/docs/guide/email.mdx
index 90c951f..a7c4871 100644
--- a/docs/docs/guide/email.mdx
+++ b/docs/docs/guide/email.mdx
@@ -1,3 +1,344 @@
---
title: E-mail
---
+
+This project uses two shared packages for email: `@workspace/email-templates` for designing email templates with [React Email](https://react.email), and `@workspace/email-send` for rendering and delivering them via any transport (SMTP, Resend, etc.).
+
+The setup is fully type-safe. TypeScript will prevent you from using a template that does not exist or passing the wrong data to a template.
+
+## Email Templates
+
+Email templates live in the `packages/email-templates/src/` directory. Each template is a React component that uses components from `@react-email/components` to define the email's HTML structure.
+
+### Creating a template
+
+Create a new `.tsx` file inside `src/`, organized by domain folder. For example, to create an order confirmation email:
+
+```tsx title="packages/email-templates/src/order/confirmation.tsx"
+import {
+ Body,
+ Container,
+ Head,
+ Heading,
+ Html,
+ Preview,
+ Text,
+} from "@react-email/components";
+
+export interface OrderConfirmationEmailProps {
+ customerName: string;
+ orderId: string;
+}
+
+export const OrderConfirmationEmail = ({
+ customerName,
+ orderId,
+}: OrderConfirmationEmailProps) => {
+ return (
+
+
+ Your order #{orderId} is confirmed
+
+
+ Thank you, {customerName}!
+ Your order #{orderId} has been confirmed.
+
+
+
+ );
+};
+
+OrderConfirmationEmail.PreviewProps = {
+ customerName: "Jane",
+ orderId: "12345",
+} satisfies OrderConfirmationEmailProps;
+
+export default OrderConfirmationEmail;
+```
+
+Every template file must follow these conventions:
+
+1. Export an **interface** ending in `Props` (e.g., `OrderConfirmationEmailProps`).
+2. Export a **named const** for the component (e.g., `OrderConfirmationEmail`).
+3. Set `PreviewProps` on the component for the React Email dev preview.
+4. Add a **default export** of the component.
+
+
+ When using Cursor, you can ask the agent to create a new email template. The
+ agent will use the skill included in this project to create the email template
+ using React Email and even generate the tests. For example, you can ask the
+ agent to create a new email template for a user signup by saying "Create a new
+ email template for a user signup".
+
+
+### Generating the template registry
+
+After creating or removing a template, run the code generation script:
+
+```bash
+pnpm --filter @workspace/email-templates generate
+```
+
+This scans `src/` for all `.tsx` template files, extracts the component and props names, and writes a `src/index.ts` file that exports a `TemplateMap` type and a `templates` runtime object. You never need to edit `src/index.ts` by hand.
+
+
+ The `generate` script is wired into Turbo's `generate` task, which runs
+ automatically before `build`, `test`, and `dev`. You only need to run it
+ manually when you want to verify the output immediately.
+
+
+The generated `TemplateMap` type is what makes the sending API type-safe. It maps template string keys (derived from file paths) to their props types:
+
+```ts
+type TemplateMap = {
+ "order/confirmation": OrderConfirmationEmailProps;
+ "user/password-forgot": PasswordForgotEmailProps;
+ "user/signup": SignupEmailProps;
+};
+```
+
+### Previewing templates
+
+Start the React Email dev server to preview templates in the browser:
+
+```bash
+pnpm --filter @workspace/email-templates dev
+```
+
+The `PreviewProps` you set on each component will be used as sample data in the preview.
+
+## Sending Emails
+
+The `@workspace/email-send` package provides a type-safe API for rendering templates and delivering them. It is transport-agnostic -- you choose how emails are delivered by plugging in a transport.
+
+### Setup
+
+Add `@workspace/email-send` as a dependency in your app's `package.json`:
+
+```json title="apps/web/package.json"
+{
+ "dependencies": {
+ "@workspace/email-send": "workspace:*"
+ }
+}
+```
+
+### Creating a sender
+
+Use `createEmailSender` to create a configured sender. You provide a default `from` address and a transport:
+
+```ts tab="Resend"
+import {
+ createEmailSender,
+ createResendTransport,
+} from "@workspace/email-send";
+import { env } from "@workspace/env";
+
+export const emailSender = createEmailSender({
+ from: "App ",
+ transport: createResendTransport({ apiKey: env.RESEND_API_KEY }),
+});
+```
+
+```ts tab="SMTP"
+import { createEmailSender, createSmtpTransport } from "@workspace/email-send";
+import { env } from "@workspace/env";
+
+export const emailSender = createEmailSender({
+ from: "App ",
+ transport: createSmtpTransport({
+ host: env.SMTP_HOST,
+ port: env.SMTP_PORT,
+ secure: true,
+ auth: {
+ user: env.SMTP_USER,
+ pass: env.SMTP_PASS,
+ },
+ }),
+});
+```
+
+### Sending an email
+
+Call `emailSender.send()` with the template name and its data. TypeScript enforces that:
+
+- The `template` value must be one of the registered template keys.
+- The `data` object must match the template's props type exactly.
+
+```ts
+await emailSender.send({
+ to: "user@example.com",
+ subject: "Welcome!",
+ template: "user/signup",
+ data: { name: "Alice", verifyUrl: "https://example.com/verify?token=abc" },
+});
+```
+
+If you pass a template name that does not exist, or data that does not match the template's props, you get a compile-time error:
+
+```ts
+// Type error: '"user/nonexistent"' is not assignable
+await emailSender.send({
+ to: "user@example.com",
+ subject: "Hello",
+ template: "user/nonexistent",
+ data: {},
+});
+
+// Type error: Property 'verifyUrl' is missing
+await emailSender.send({
+ to: "user@example.com",
+ subject: "Welcome",
+ template: "user/signup",
+ data: { name: "Alice" },
+});
+```
+
+### Overriding the sender address
+
+You can override the `from` address on a per-email basis:
+
+```ts
+await emailSender.send({
+ to: "user@example.com",
+ subject: "Reset your password",
+ template: "user/password-forgot",
+ data: { name: "Alice", resetUrl: "https://example.com/reset?token=xyz" },
+ from: "Support ",
+});
+```
+
+### Sending to multiple recipients
+
+Pass an array to the `to` field:
+
+```ts
+await emailSender.send({
+ to: ["alice@example.com", "bob@example.com"],
+ subject: "Team update",
+ template: "user/signup",
+ data: { name: "Team", verifyUrl: "https://example.com/verify" },
+});
+```
+
+## Architecture
+
+The email system is split into two packages to separate concerns:
+
+- **`@workspace/email-templates`** owns the templates and the auto-generated type registry. It has no knowledge of how emails are delivered.
+- **`@workspace/email-send`** owns the rendering and delivery logic. It depends on `email-templates` for the registry but is agnostic about which transport is used.
+
+When you call `emailSender.send()`, the following happens:
+
+1. The template component is looked up from the registry by name.
+2. The component is rendered to an HTML string using React Email's `render()`.
+3. The HTML is passed to the configured transport, which delivers it.
+
+### Adding a new transport
+
+To support a new email provider, create a function that returns an `EmailTransport`:
+
+```ts
+import type { EmailTransport } from "@workspace/email-send";
+
+export const createMailgunTransport = (config: {
+ apiKey: string;
+ domain: string;
+}): EmailTransport => ({
+ async send(message) {
+ // Call the Mailgun API with message.from, message.to, message.subject, message.html
+ return { success: true, messageId: "..." };
+ },
+});
+```
+
+The `EmailTransport` interface has a single method:
+
+```ts
+interface EmailTransport {
+ send(message: EmailMessage): Promise;
+}
+```
+
+Where `EmailMessage` is `{ from, to, subject, html }` and `EmailResult` is `{ success, messageId? }`.
+
+## Testing
+
+When testing code that sends emails, inject a fake transport instead of mocking the email sender:
+
+```ts
+import { createEmailSender } from "@workspace/email-send";
+import type { EmailTransport, EmailMessage } from "@workspace/email-send";
+
+const createFakeTransport = (): EmailTransport & {
+ lastMessage?: EmailMessage;
+} => {
+ const transport: EmailTransport & { lastMessage?: EmailMessage } = {
+ async send(message) {
+ transport.lastMessage = message;
+ return { success: true, messageId: "test-id" };
+ },
+ };
+ return transport;
+};
+
+it("sends a welcome email on signup", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const emailSender = createEmailSender({
+ from: "test@example.com",
+ transport,
+ render: async () => "rendered",
+ });
+
+ // Act
+ await signupUser({ email: "user@example.com", emailSender });
+
+ // Assert
+ expect(transport.lastMessage?.to).toBe("user@example.com");
+ expect(transport.lastMessage?.subject).toContain("Welcome");
+});
+```
+
+The `createEmailSender` config also accepts a `render` override, so you can skip the actual template rendering in unit tests and focus on verifying the send behavior.
+
+## Development
+
+To test the email system during development, run the following command:
+
+```bash
+# From the root of the monorepo
+docker-compose up
+```
+
+This will start the email server and make the web UI available at `http://localhost:9000`, where you can view the inbox and the sent emails.
+
+In your app, you can create an email sender using the `createEmailSender` function with the transport set to the `createSmtpTransport` function and configure the SMTP server to use the email server.
+
+```ts
+const emailSender = createEmailSender({
+ from: "App ",
+ transport: createSmtpTransport({
+ host: "localhost",
+ port: 2500,
+ }),
+});
+```
+
+See the [apps/web/email-demo/actions/route.post.config.ts](https://github.com/hyperjump-tech/hyperjump-web-framework/blob/main/apps/web/app/email-demo/actions/route.post.config.ts) file for an example.
+
+## Ground Rules
+
+| | Action |
+| --- | ----------------------------------------------------------------------------------------------- |
+| ✅ | Use `@react-email/components` for building email templates. |
+| ✅ | Export an interface ending in `Props` and a named const from every template file. |
+| ✅ | Run `pnpm generate` (or let Turbo run it) after adding or removing templates. |
+| ✅ | Use `createEmailSender` with a transport -- never call a provider's API directly from app code. |
+| ✅ | Inject a fake transport in tests instead of mocking internals. |
+| ❌ | Do not edit `packages/email-templates/src/index.ts` by hand. It is auto-generated. |
+| ❌ | Do not hardcode API keys. Use `@workspace/env` for credentials like `RESEND_API_KEY`. |
+
+```
+
+```
diff --git a/packages/email-send/package.json b/packages/email-send/package.json
new file mode 100644
index 0000000..a67f490
--- /dev/null
+++ b/packages/email-send/package.json
@@ -0,0 +1,32 @@
+{
+ "name": "@workspace/email-send",
+ "version": "0.0.1",
+ "type": "module",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "exports": {
+ ".": "./src/index.ts"
+ },
+ "scripts": {
+ "test": "vitest --run",
+ "test:coverage": "vitest --run --coverage"
+ },
+ "private": true,
+ "dependencies": {
+ "@workspace/email-templates": "workspace:*",
+ "@react-email/components": "^1.0.7",
+ "react": "^19.2.4",
+ "nodemailer": "^7.0.3",
+ "resend": "^4.5.1"
+ },
+ "devDependencies": {
+ "@types/nodemailer": "^6.4.17",
+ "@types/react": "catalog:",
+ "@types/node": "catalog:",
+ "vitest": "catalog:",
+ "@vitest/coverage-v8": "catalog:",
+ "eslint": "catalog:",
+ "@workspace/typescript-config": "workspace:*",
+ "typescript": "catalog:"
+ }
+}
diff --git a/packages/email-send/src/create-email-sender.test.ts b/packages/email-send/src/create-email-sender.test.ts
new file mode 100644
index 0000000..f70966b
--- /dev/null
+++ b/packages/email-send/src/create-email-sender.test.ts
@@ -0,0 +1,168 @@
+import { afterEach, describe, expect, it, vi } from "vitest";
+import { createEmailSender } from "./create-email-sender";
+import type { EmailMessage, EmailResult, EmailTransport } from "./types";
+
+const createFakeTransport = (
+ result: EmailResult = { success: true, messageId: "fake-id" },
+): EmailTransport & { lastMessage: EmailMessage | undefined } => {
+ const transport: EmailTransport & { lastMessage: EmailMessage | undefined } =
+ {
+ lastMessage: undefined,
+ async send(message: EmailMessage): Promise {
+ transport.lastMessage = message;
+ return result;
+ },
+ };
+ return transport;
+};
+
+const fakeRender = async (): Promise => "rendered";
+
+describe("createEmailSender", () => {
+ afterEach(() => {
+ vi.restoreAllMocks();
+ });
+
+ it("renders the template and sends via transport", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const sender = createEmailSender({
+ from: "noreply@example.com",
+ transport,
+ render: fakeRender,
+ });
+
+ // Act
+ const result = await sender.send({
+ to: "user@example.com",
+ subject: "Welcome!",
+ template: "user/signup",
+ data: { name: "Alice", verifyUrl: "https://example.com/verify" },
+ });
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: "fake-id" });
+ expect(transport.lastMessage).toEqual({
+ from: "noreply@example.com",
+ to: "user@example.com",
+ subject: "Welcome!",
+ html: "rendered",
+ });
+ });
+
+ it("uses per-send from address when provided", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const sender = createEmailSender({
+ from: "default@example.com",
+ transport,
+ render: fakeRender,
+ });
+
+ // Act
+ await sender.send({
+ to: "user@example.com",
+ subject: "Reset",
+ template: "user/password-forgot",
+ data: { name: "Bob", resetUrl: "https://example.com/reset" },
+ from: "override@example.com",
+ });
+
+ // Assert
+ expect(transport.lastMessage!.from).toBe("override@example.com");
+ });
+
+ it("falls back to config from when per-send from is omitted", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const sender = createEmailSender({
+ from: "default@example.com",
+ transport,
+ render: fakeRender,
+ });
+
+ // Act
+ await sender.send({
+ to: "user@example.com",
+ subject: "Hello",
+ template: "user/signup",
+ data: { name: "Carol", verifyUrl: "https://example.com" },
+ });
+
+ // Assert
+ expect(transport.lastMessage!.from).toBe("default@example.com");
+ });
+
+ it("propagates transport errors", async () => {
+ // Setup
+ const transport: EmailTransport = {
+ async send(): Promise {
+ throw new Error("Transport failure");
+ },
+ };
+ const sender = createEmailSender({
+ from: "noreply@example.com",
+ transport,
+ render: fakeRender,
+ });
+
+ // Act & Assert
+ await expect(
+ sender.send({
+ to: "user@example.com",
+ subject: "Fail",
+ template: "user/signup",
+ data: { name: "Dave", verifyUrl: "https://example.com" },
+ }),
+ ).rejects.toThrow("Transport failure");
+ });
+
+ it("passes template and data to the render function", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const render = vi.fn().mockResolvedValue("mock");
+ const sender = createEmailSender({
+ from: "noreply@example.com",
+ transport,
+ render,
+ });
+
+ // Act
+ await sender.send({
+ to: "user@example.com",
+ subject: "Test",
+ template: "user/signup",
+ data: { name: "Eve", verifyUrl: "https://example.com/verify" },
+ });
+
+ // Assert
+ expect(render).toHaveBeenCalledWith("user/signup", {
+ name: "Eve",
+ verifyUrl: "https://example.com/verify",
+ });
+ });
+
+ it("supports array of recipients", async () => {
+ // Setup
+ const transport = createFakeTransport();
+ const sender = createEmailSender({
+ from: "noreply@example.com",
+ transport,
+ render: fakeRender,
+ });
+
+ // Act
+ await sender.send({
+ to: ["a@example.com", "b@example.com"],
+ subject: "Batch",
+ template: "user/signup",
+ data: { name: "Team", verifyUrl: "https://example.com" },
+ });
+
+ // Assert
+ expect(transport.lastMessage!.to).toEqual([
+ "a@example.com",
+ "b@example.com",
+ ]);
+ });
+});
diff --git a/packages/email-send/src/create-email-sender.ts b/packages/email-send/src/create-email-sender.ts
new file mode 100644
index 0000000..b5d813a
--- /dev/null
+++ b/packages/email-send/src/create-email-sender.ts
@@ -0,0 +1,35 @@
+import type { TemplateMap } from "@workspace/email-templates";
+import { renderTemplate } from "./render-template";
+import type {
+ EmailResult,
+ EmailSender,
+ EmailSenderConfig,
+ SendEmailOptions,
+} from "./types";
+
+/**
+ * Creates a configured email sender that renders templates and delivers them
+ * via the provided transport.
+ *
+ * @param config - Sender configuration including default `from`, transport, and
+ * an optional `render` override (defaults to the built-in `renderTemplate`).
+ * @returns An {@link EmailSender} with a type-safe `send` method.
+ */
+export const createEmailSender = (config: EmailSenderConfig): EmailSender => {
+ const renderFn = config.render ?? renderTemplate;
+
+ return {
+ async send(
+ options: SendEmailOptions,
+ ): Promise {
+ const html = await renderFn(options.template, options.data);
+
+ return config.transport.send({
+ from: options.from ?? config.from,
+ to: options.to,
+ subject: options.subject,
+ html,
+ });
+ },
+ };
+};
diff --git a/packages/email-send/src/index.ts b/packages/email-send/src/index.ts
new file mode 100644
index 0000000..bbf8d61
--- /dev/null
+++ b/packages/email-send/src/index.ts
@@ -0,0 +1,12 @@
+export { createEmailSender } from "./create-email-sender";
+export { renderTemplate } from "./render-template";
+export { createSmtpTransport, type SmtpConfig } from "./transports/smtp";
+export { createResendTransport, type ResendConfig } from "./transports/resend";
+export type {
+ EmailMessage,
+ EmailResult,
+ EmailSender,
+ EmailSenderConfig,
+ EmailTransport,
+ SendEmailOptions,
+} from "./types";
diff --git a/packages/email-send/src/render-template.test.ts b/packages/email-send/src/render-template.test.ts
new file mode 100644
index 0000000..0fa3242
--- /dev/null
+++ b/packages/email-send/src/render-template.test.ts
@@ -0,0 +1,42 @@
+import { describe, expect, it } from "vitest";
+import { renderTemplate } from "./render-template";
+
+describe("renderTemplate", () => {
+ it("renders user/signup template with the given props", async () => {
+ // Setup
+ const data = { name: "Alice", verifyUrl: "https://example.com/verify" };
+
+ // Act
+ const html = await renderTemplate("user/signup", data);
+
+ // Assert
+ expect(html).toContain("Alice");
+ expect(html).toContain("https://example.com/verify");
+ expect(html).toContain("Verify Email");
+ });
+
+ it("renders user/password-forgot template with the given props", async () => {
+ // Setup
+ const data = { name: "Bob", resetUrl: "https://example.com/reset" };
+
+ // Act
+ const html = await renderTemplate("user/password-forgot", data);
+
+ // Assert
+ expect(html).toContain("Bob");
+ expect(html).toContain("https://example.com/reset");
+ expect(html).toContain("Reset Password");
+ });
+
+ it("returns a complete HTML document", async () => {
+ // Act
+ const html = await renderTemplate("user/signup", {
+ name: "Test",
+ verifyUrl: "https://example.com",
+ });
+
+ // Assert
+ expect(html).toContain("");
+ });
+});
diff --git a/packages/email-send/src/render-template.ts b/packages/email-send/src/render-template.ts
new file mode 100644
index 0000000..dfccacd
--- /dev/null
+++ b/packages/email-send/src/render-template.ts
@@ -0,0 +1,21 @@
+import { render } from "@react-email/components";
+import { templates, type TemplateMap } from "@workspace/email-templates";
+import { createElement } from "react";
+
+/**
+ * Renders an email template to an HTML string.
+ *
+ * Looks up the React component from the template registry by name, passes
+ * the provided data as props, and returns the rendered HTML.
+ *
+ * @param template - The template identifier (e.g. `"user/signup"`).
+ * @param data - Props matching the chosen template's expected shape.
+ * @returns The rendered HTML string.
+ */
+export const renderTemplate = async (
+ template: T,
+ data: TemplateMap[T],
+): Promise => {
+ const Component = templates[template];
+ return render(createElement(Component, data));
+};
diff --git a/packages/email-send/src/transports/index.ts b/packages/email-send/src/transports/index.ts
new file mode 100644
index 0000000..cba9c76
--- /dev/null
+++ b/packages/email-send/src/transports/index.ts
@@ -0,0 +1,2 @@
+export { createSmtpTransport, type SmtpConfig } from "./smtp";
+export { createResendTransport, type ResendConfig } from "./resend";
diff --git a/packages/email-send/src/transports/resend.test.ts b/packages/email-send/src/transports/resend.test.ts
new file mode 100644
index 0000000..6829248
--- /dev/null
+++ b/packages/email-send/src/transports/resend.test.ts
@@ -0,0 +1,121 @@
+import { describe, expect, it } from "vitest";
+import type { EmailMessage } from "../types";
+import {
+ createResendTransport,
+ type ResendClient,
+ sendViaResend,
+} from "./resend";
+
+const createFakeMessage = (
+ overrides?: Partial,
+): EmailMessage => ({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Test Subject",
+ html: "Hello
",
+ ...overrides,
+});
+
+const createFakeClient = (
+ response: {
+ data: { id: string } | null;
+ error: { message: string } | null;
+ } = {
+ data: { id: "resend-msg-id" },
+ error: null,
+ },
+): ResendClient & { lastPayload: Record | undefined } => {
+ const client: ResendClient & {
+ lastPayload: Record | undefined;
+ } = {
+ lastPayload: undefined,
+ emails: {
+ send: async (payload) => {
+ client.lastPayload = payload;
+ return response;
+ },
+ },
+ };
+ return client;
+};
+
+describe("sendViaResend", () => {
+ it("sends the message and returns messageId on success", async () => {
+ // Setup
+ const message = createFakeMessage();
+ const client = createFakeClient({ data: { id: "abc-123" }, error: null });
+
+ // Act
+ const result = await sendViaResend(message, client);
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: "abc-123" });
+ });
+
+ it("passes from, to, subject, and html to the client", async () => {
+ // Setup
+ const message = createFakeMessage({
+ from: "a@b.com",
+ to: ["x@y.com", "z@w.com"],
+ subject: "Multi",
+ html: "Bold",
+ });
+ const client = createFakeClient();
+
+ // Act
+ await sendViaResend(message, client);
+
+ // Assert
+ expect(client.lastPayload).toEqual({
+ from: "a@b.com",
+ to: ["x@y.com", "z@w.com"],
+ subject: "Multi",
+ html: "Bold",
+ });
+ });
+
+ it("throws when the Resend API returns an error", async () => {
+ // Setup
+ const message = createFakeMessage();
+ const client = createFakeClient({
+ data: null,
+ error: { message: "Invalid API key" },
+ });
+
+ // Act & Assert
+ await expect(sendViaResend(message, client)).rejects.toThrow(
+ "Invalid API key",
+ );
+ });
+
+ it("returns undefined messageId when data is null but no error", async () => {
+ // Setup
+ const message = createFakeMessage();
+ const client = createFakeClient({ data: null, error: null });
+
+ // Act
+ const result = await sendViaResend(message, client);
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: undefined });
+ });
+});
+
+describe("createResendTransport", () => {
+ it("returns an EmailTransport that delegates to the injected client", async () => {
+ // Setup
+ const client = createFakeClient({
+ data: { id: "injected-id" },
+ error: null,
+ });
+ const transport = createResendTransport({ apiKey: "re_unused" }, client);
+ const message = createFakeMessage();
+
+ // Act
+ const result = await transport.send(message);
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: "injected-id" });
+ expect(client.lastPayload).toBeDefined();
+ });
+});
diff --git a/packages/email-send/src/transports/resend.ts b/packages/email-send/src/transports/resend.ts
new file mode 100644
index 0000000..b0beef8
--- /dev/null
+++ b/packages/email-send/src/transports/resend.ts
@@ -0,0 +1,64 @@
+import { Resend } from "resend";
+import type { EmailMessage, EmailResult, EmailTransport } from "../types";
+
+/** Configuration for the Resend email transport. */
+export interface ResendConfig {
+ /** Resend API key (starts with `re_`). */
+ apiKey: string;
+}
+
+/** Minimal interface of the Resend client needed for sending emails. */
+export interface ResendClient {
+ emails: {
+ send(payload: {
+ from: string;
+ to: string | string[];
+ subject: string;
+ html: string;
+ }): Promise<{
+ data: { id: string } | null;
+ error: { message: string } | null;
+ }>;
+ };
+}
+
+/**
+ * Sends an email message via the Resend API.
+ *
+ * @param message - The rendered email message.
+ * @param client - A Resend client instance (or compatible fake).
+ * @returns The send result with `success` and optional `messageId`.
+ * @throws When the Resend API returns an error.
+ */
+export const sendViaResend = async (
+ message: EmailMessage,
+ client: ResendClient,
+): Promise => {
+ const { data, error } = await client.emails.send({
+ from: message.from,
+ to: message.to,
+ subject: message.subject,
+ html: message.html,
+ });
+
+ if (error) {
+ throw new Error(error.message);
+ }
+
+ return { success: true, messageId: data?.id };
+};
+
+/**
+ * Creates a Resend-based email transport.
+ *
+ * @param config - Resend API key configuration.
+ * @param client - Resend client instance. Defaults to a real Resend client
+ * created from `config.apiKey`. Override in tests with a fake.
+ * @returns An {@link EmailTransport} that sends via Resend.
+ */
+export const createResendTransport = (
+ config: ResendConfig,
+ client: ResendClient = new Resend(config.apiKey),
+): EmailTransport => ({
+ send: (message) => sendViaResend(message, client),
+});
diff --git a/packages/email-send/src/transports/smtp.test.ts b/packages/email-send/src/transports/smtp.test.ts
new file mode 100644
index 0000000..1d18e4a
--- /dev/null
+++ b/packages/email-send/src/transports/smtp.test.ts
@@ -0,0 +1,112 @@
+import type Mail from "nodemailer/lib/mailer";
+import { describe, expect, it } from "vitest";
+import type { EmailMessage } from "../types";
+import { createSmtpTransport, sendViaSMTP } from "./smtp";
+
+const createFakeMessage = (
+ overrides?: Partial,
+): EmailMessage => ({
+ from: "sender@example.com",
+ to: "recipient@example.com",
+ subject: "Test Subject",
+ html: "Hello
",
+ ...overrides,
+});
+
+const createFakeTransporter = (
+ messageId: string = "smtp-msg-id",
+): Pick & { lastCall: Mail.Options | undefined } => {
+ const fake: Pick & { lastCall: Mail.Options | undefined } =
+ {
+ lastCall: undefined,
+ sendMail: async (options: Mail.Options) => {
+ fake.lastCall = options;
+ return { messageId } as never;
+ },
+ };
+ return fake;
+};
+
+describe("sendViaSMTP", () => {
+ it("sends the message via the transporter and returns the messageId", async () => {
+ // Setup
+ const message = createFakeMessage();
+ const transporter = createFakeTransporter("test-123");
+
+ // Act
+ const result = await sendViaSMTP(message, transporter);
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: "test-123" });
+ });
+
+ it("passes from, to, subject, and html to sendMail", async () => {
+ // Setup
+ const message = createFakeMessage({
+ from: "a@b.com",
+ to: "c@d.com",
+ subject: "Hi",
+ html: "Bold",
+ });
+ const transporter = createFakeTransporter();
+
+ // Act
+ await sendViaSMTP(message, transporter);
+
+ // Assert
+ expect(transporter.lastCall).toEqual({
+ from: "a@b.com",
+ to: "c@d.com",
+ subject: "Hi",
+ html: "Bold",
+ });
+ });
+
+ it("joins array recipients into a comma-separated string", async () => {
+ // Setup
+ const message = createFakeMessage({
+ to: ["a@example.com", "b@example.com"],
+ });
+ const transporter = createFakeTransporter();
+
+ // Act
+ await sendViaSMTP(message, transporter);
+
+ // Assert
+ expect(transporter.lastCall!.to).toBe("a@example.com, b@example.com");
+ });
+
+ it("propagates transporter errors", async () => {
+ // Setup
+ const message = createFakeMessage();
+ const transporter: Pick = {
+ sendMail: async () => {
+ throw new Error("SMTP connection refused");
+ },
+ };
+
+ // Act & Assert
+ await expect(sendViaSMTP(message, transporter)).rejects.toThrow(
+ "SMTP connection refused",
+ );
+ });
+});
+
+describe("createSmtpTransport", () => {
+ it("returns an EmailTransport that delegates to the injected transporter", async () => {
+ // Setup
+ const transporter = createFakeTransporter("injected-id");
+ const transport = createSmtpTransport(
+ { host: "unused", port: 587 },
+ transporter,
+ );
+ const message = createFakeMessage();
+
+ // Act
+ const result = await transport.send(message);
+
+ // Assert
+ expect(result).toEqual({ success: true, messageId: "injected-id" });
+ expect(transporter.lastCall).toBeDefined();
+ });
+});
diff --git a/packages/email-send/src/transports/smtp.ts b/packages/email-send/src/transports/smtp.ts
new file mode 100644
index 0000000..2baa413
--- /dev/null
+++ b/packages/email-send/src/transports/smtp.ts
@@ -0,0 +1,55 @@
+import nodemailer from "nodemailer";
+import type Mail from "nodemailer/lib/mailer";
+import type SMTPTransport from "nodemailer/lib/smtp-transport";
+import type { EmailMessage, EmailResult, EmailTransport } from "../types";
+
+/** SMTP connection configuration. */
+export interface SmtpConfig {
+ /** SMTP server hostname (e.g. `"smtp.example.com"`). */
+ host: string;
+ /** SMTP server port (e.g. `587` for STARTTLS, `465` for SSL). */
+ port: number;
+ /** Use implicit TLS (`true` for port 465, `false` for STARTTLS). */
+ secure?: boolean;
+ /** Optional SMTP authentication credentials. */
+ auth?: {
+ user: string;
+ pass: string;
+ };
+}
+
+/**
+ * Sends an email message via a nodemailer transporter.
+ *
+ * @param message - The rendered email message.
+ * @param transporter - The nodemailer transporter instance.
+ * @returns The send result with `success` and optional `messageId`.
+ */
+export const sendViaSMTP = async (
+ message: EmailMessage,
+ transporter: Pick,
+): Promise => {
+ const info = (await transporter.sendMail({
+ from: message.from,
+ to: Array.isArray(message.to) ? message.to.join(", ") : message.to,
+ subject: message.subject,
+ html: message.html,
+ })) as SMTPTransport.SentMessageInfo;
+
+ return { success: true, messageId: info.messageId };
+};
+
+/**
+ * Creates an SMTP-based email transport using nodemailer.
+ *
+ * @param config - SMTP server connection settings.
+ * @param transporter - Nodemailer transporter instance. Defaults to one
+ * created from `config`. Override in tests with a fake.
+ * @returns An {@link EmailTransport} that sends via SMTP.
+ */
+export const createSmtpTransport = (
+ config: SmtpConfig,
+ transporter: Pick = nodemailer.createTransport(config),
+): EmailTransport => ({
+ send: (message) => sendViaSMTP(message, transporter),
+});
diff --git a/packages/email-send/src/types.ts b/packages/email-send/src/types.ts
new file mode 100644
index 0000000..18cf020
--- /dev/null
+++ b/packages/email-send/src/types.ts
@@ -0,0 +1,70 @@
+import type { TemplateMap } from "@workspace/email-templates";
+
+/** A rendered email message ready to be sent by a transport. */
+export interface EmailMessage {
+ /** Sender address (e.g. `"App "`). */
+ from: string;
+ /** One or more recipient addresses. */
+ to: string | string[];
+ /** Email subject line. */
+ subject: string;
+ /** Rendered HTML body. */
+ html: string;
+}
+
+/** The result returned after a transport sends an email. */
+export interface EmailResult {
+ /** Whether the send operation succeeded. */
+ success: boolean;
+ /** Provider-specific message identifier, if available. */
+ messageId?: string;
+}
+
+/** Delivery mechanism that sends a rendered email message. */
+export interface EmailTransport {
+ /** Sends the given email message and returns the result. */
+ send(message: EmailMessage): Promise;
+}
+
+/** Configuration for creating an {@link EmailSender}. */
+export interface EmailSenderConfig {
+ /** Default sender address used when `from` is not specified per-send. */
+ from: string;
+ /** The transport used to deliver emails. */
+ transport: EmailTransport;
+ /**
+ * Renders a template to an HTML string. Defaults to the built-in
+ * `renderTemplate` implementation. Override in tests with a fake.
+ */
+ render?: (
+ template: T,
+ data: TemplateMap[T],
+ ) => Promise;
+}
+
+/** Options for a single {@link EmailSender.send} call. */
+export interface SendEmailOptions {
+ /** One or more recipient addresses. */
+ to: string | string[];
+ /** Email subject line. */
+ subject: string;
+ /** Template identifier (e.g. `"user/signup"`). */
+ template: T;
+ /** Template-specific data -- must match the template's props type. */
+ data: TemplateMap[T];
+ /** Override the sender address for this email only. */
+ from?: string;
+}
+
+/** A configured email sender that renders templates and delivers them. */
+export interface EmailSender {
+ /**
+ * Renders the given template with its data and sends the resulting email.
+ *
+ * @param options - Template, recipients, subject, and template data.
+ * @returns The result of the send operation.
+ */
+ send(
+ options: SendEmailOptions,
+ ): Promise;
+}
diff --git a/packages/email-send/tsconfig.json b/packages/email-send/tsconfig.json
new file mode 100644
index 0000000..fa8f58e
--- /dev/null
+++ b/packages/email-send/tsconfig.json
@@ -0,0 +1,8 @@
+{
+ "extends": "@workspace/typescript-config/node16.json",
+ "compilerOptions": {
+ "jsx": "react-jsx"
+ },
+ "include": ["src"],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/email-send/vitest.config.ts b/packages/email-send/vitest.config.ts
new file mode 100644
index 0000000..a8572e0
--- /dev/null
+++ b/packages/email-send/vitest.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "html"],
+ reportsDirectory: "./coverage",
+ },
+ environment: "node",
+ exclude: ["node_modules", "dist"],
+ },
+});
diff --git a/packages/email-templates/README.md b/packages/email-templates/README.md
new file mode 100644
index 0000000..563c186
--- /dev/null
+++ b/packages/email-templates/README.md
@@ -0,0 +1,19 @@
+# `email-templates`
+
+This package contains the email templates for the project using [React Email](https://react.email/).
+
+## Usage
+
+To run the development server and preview the emails, run the following command:
+
+```bash
+pnpm run dev
+```
+
+To export the emails as HTML files, run the following command:
+
+```bash
+pnpm run export
+```
+
+To add more email templates, create a new file in the `src` directory.
diff --git a/packages/email-templates/package.json b/packages/email-templates/package.json
new file mode 100644
index 0000000..4cebcb0
--- /dev/null
+++ b/packages/email-templates/package.json
@@ -0,0 +1,35 @@
+{
+ "name": "@workspace/email-templates",
+ "version": "0.0.1",
+ "main": "./src/index.ts",
+ "types": "./src/index.ts",
+ "scripts": {
+ "dev": "pnpm run generate && email dev --dir src",
+ "export": "pnpm run generate && email export --dir src",
+ "generate": "tsx scripts/generate-index.ts",
+ "test": "vitest --run",
+ "test:coverage": "vitest --run --coverage"
+ },
+ "private": true,
+ "dependencies": {
+ "@react-email/components": "^1.0.7",
+ "react": "^19.2.4",
+ "react-dom": "^19.2.4",
+ "zod": "catalog:"
+ },
+ "devDependencies": {
+ "@react-email/preview-server": "^5.0.1",
+ "@types/react": "^19.2.14",
+ "@types/react-dom": "^19.2.3",
+ "react-email": "^5.2.8",
+ "vitest": "catalog:",
+ "@vitest/coverage-v8": "catalog:",
+ "@types/node": "^20.19.9",
+ "dotenv-cli": "^11.0.0",
+ "eslint": "^9.32.0",
+ "postcss": "^8.4.35",
+ "@workspace/typescript-config": "workspace:*",
+ "tsx": "^4",
+ "typescript": "catalog:"
+ }
+}
diff --git a/packages/email-templates/scripts/generate-index.test.ts b/packages/email-templates/scripts/generate-index.test.ts
new file mode 100644
index 0000000..4f70ce4
--- /dev/null
+++ b/packages/email-templates/scripts/generate-index.test.ts
@@ -0,0 +1,335 @@
+import fs from "node:fs";
+import os from "node:os";
+import path from "node:path";
+import { afterEach, beforeEach, describe, expect, it } from "vitest";
+import {
+ buildEntries,
+ extractExports,
+ findTemplateFiles,
+ generate,
+ generateIndexContent,
+} from "./generate-index";
+
+const TEMPLATE_CONTENT = `import { Html } from "@react-email/components";
+
+export interface WelcomeEmailProps {
+ name: string;
+}
+
+export const WelcomeEmail = ({ name }: WelcomeEmailProps) => {
+ return Hello {name};
+};
+
+export default WelcomeEmail;
+`;
+
+const SECOND_TEMPLATE_CONTENT = `import { Html } from "@react-email/components";
+
+export interface InviteEmailProps {
+ inviteUrl: string;
+}
+
+export const InviteEmail = ({ inviteUrl }: InviteEmailProps) => {
+ return {inviteUrl};
+};
+
+export default InviteEmail;
+`;
+
+describe("findTemplateFiles", () => {
+ let tmpDir: string;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "email-templates-test-"));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ it("finds .tsx files recursively", () => {
+ // Setup
+ const sub = path.join(tmpDir, "user");
+ fs.mkdirSync(sub);
+ fs.writeFileSync(path.join(sub, "signup.tsx"), TEMPLATE_CONTENT);
+
+ // Act
+ const files = findTemplateFiles(tmpDir);
+
+ // Assert
+ expect(files).toEqual([path.join(sub, "signup.tsx")]);
+ });
+
+ it("excludes .test.tsx files", () => {
+ // Setup
+ fs.writeFileSync(path.join(tmpDir, "signup.tsx"), TEMPLATE_CONTENT);
+ fs.writeFileSync(path.join(tmpDir, "signup.test.tsx"), "test file");
+
+ // Act
+ const files = findTemplateFiles(tmpDir);
+
+ // Assert
+ expect(files).toEqual([path.join(tmpDir, "signup.tsx")]);
+ });
+
+ it("returns sorted results across nested directories", () => {
+ // Setup
+ const authDir = path.join(tmpDir, "auth");
+ const userDir = path.join(tmpDir, "user");
+ fs.mkdirSync(authDir);
+ fs.mkdirSync(userDir);
+ fs.writeFileSync(path.join(userDir, "signup.tsx"), TEMPLATE_CONTENT);
+ fs.writeFileSync(path.join(authDir, "invite.tsx"), TEMPLATE_CONTENT);
+
+ // Act
+ const files = findTemplateFiles(tmpDir);
+
+ // Assert
+ expect(files).toEqual([
+ path.join(authDir, "invite.tsx"),
+ path.join(userDir, "signup.tsx"),
+ ]);
+ });
+
+ it("returns an empty array when no .tsx files exist", () => {
+ // Act
+ const files = findTemplateFiles(tmpDir);
+
+ // Assert
+ expect(files).toEqual([]);
+ });
+});
+
+describe("extractExports", () => {
+ it("extracts component name and props type from valid template content", () => {
+ // Act
+ const result = extractExports(TEMPLATE_CONTENT);
+
+ // Assert
+ expect(result).toEqual({
+ componentName: "WelcomeEmail",
+ propsTypeName: "WelcomeEmailProps",
+ });
+ });
+
+ it("returns null when no props interface is found", () => {
+ // Setup
+ const content = `export const Foo = () => ;`;
+
+ // Act
+ const result = extractExports(content);
+
+ // Assert
+ expect(result).toBeNull();
+ });
+
+ it("returns null when no exported const is found", () => {
+ // Setup
+ const content = `export interface FooProps { name: string; }`;
+
+ // Act
+ const result = extractExports(content);
+
+ // Assert
+ expect(result).toBeNull();
+ });
+});
+
+describe("buildEntries", () => {
+ let tmpDir: string;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "email-templates-test-"));
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ it("builds entries with correct keys, names, and import paths", () => {
+ // Setup
+ const sub = path.join(tmpDir, "user");
+ fs.mkdirSync(sub);
+ const filePath = path.join(sub, "welcome.tsx");
+ fs.writeFileSync(filePath, TEMPLATE_CONTENT);
+
+ // Act
+ const entries = buildEntries([filePath], tmpDir);
+
+ // Assert
+ expect(entries).toEqual([
+ {
+ key: "user/welcome",
+ componentName: "WelcomeEmail",
+ propsTypeName: "WelcomeEmailProps",
+ importPath: "./user/welcome",
+ },
+ ]);
+ });
+
+ it("skips files that fail extraction", () => {
+ // Setup
+ const filePath = path.join(tmpDir, "broken.tsx");
+ fs.writeFileSync(filePath, "const x = 1;");
+
+ // Act
+ const entries = buildEntries([filePath], tmpDir);
+
+ // Assert
+ expect(entries).toEqual([]);
+ });
+
+ it("sorts entries alphabetically by key", () => {
+ // Setup
+ const userDir = path.join(tmpDir, "user");
+ const authDir = path.join(tmpDir, "auth");
+ fs.mkdirSync(userDir);
+ fs.mkdirSync(authDir);
+ const userFile = path.join(userDir, "welcome.tsx");
+ const authFile = path.join(authDir, "invite.tsx");
+ fs.writeFileSync(userFile, TEMPLATE_CONTENT);
+ fs.writeFileSync(authFile, SECOND_TEMPLATE_CONTENT);
+
+ // Act
+ const entries = buildEntries([userFile, authFile], tmpDir);
+
+ // Assert
+ expect(entries[0]!.key).toBe("auth/invite");
+ expect(entries[1]!.key).toBe("user/welcome");
+ });
+});
+
+describe("generateIndexContent", () => {
+ it("generates correct output for a single entry", () => {
+ // Setup
+ const entries = [
+ {
+ key: "user/signup",
+ componentName: "SignupEmail",
+ propsTypeName: "SignupEmailProps",
+ importPath: "./user/signup",
+ },
+ ];
+
+ // Act
+ const content = generateIndexContent(entries);
+
+ // Assert
+ expect(content).toContain(
+ "// @generated by scripts/generate-index.ts -- DO NOT EDIT",
+ );
+ expect(content).toContain(
+ 'import { SignupEmail, type SignupEmailProps } from "./user/signup";',
+ );
+ expect(content).toContain("export { SignupEmail, type SignupEmailProps };");
+ expect(content).toContain('"user/signup": SignupEmailProps;');
+ expect(content).toContain('"user/signup": SignupEmail,');
+ });
+
+ it("generates sorted entries for multiple templates", () => {
+ // Setup
+ const entries = [
+ {
+ key: "auth/invite",
+ componentName: "InviteEmail",
+ propsTypeName: "InviteEmailProps",
+ importPath: "./auth/invite",
+ },
+ {
+ key: "user/signup",
+ componentName: "SignupEmail",
+ propsTypeName: "SignupEmailProps",
+ importPath: "./user/signup",
+ },
+ ];
+
+ // Act
+ const content = generateIndexContent(entries);
+
+ // Assert
+ const lines = content.split("\n");
+ const importLines = lines.filter((l) => l.startsWith("import"));
+ expect(importLines[0]).toContain("InviteEmail");
+ expect(importLines[1]).toContain("SignupEmail");
+ });
+
+ it("generates empty TemplateMap for zero entries", () => {
+ // Act
+ const content = generateIndexContent([]);
+
+ // Assert
+ expect(content).toContain("export type TemplateMap = {");
+ expect(content).toContain("};");
+ expect(content).not.toContain("import");
+ });
+});
+
+describe("generate", () => {
+ let tmpDir: string;
+ let srcDir: string;
+ let outputPath: string;
+
+ beforeEach(() => {
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "email-templates-gen-"));
+ srcDir = path.join(tmpDir, "src");
+ fs.mkdirSync(srcDir);
+ outputPath = path.join(srcDir, "index.ts");
+ });
+
+ afterEach(() => {
+ fs.rmSync(tmpDir, { recursive: true, force: true });
+ });
+
+ it("writes a generated index.ts from scanned templates", () => {
+ // Setup
+ const userDir = path.join(srcDir, "user");
+ fs.mkdirSync(userDir);
+ fs.writeFileSync(path.join(userDir, "welcome.tsx"), TEMPLATE_CONTENT);
+
+ // Act
+ generate(srcDir, outputPath);
+
+ // Assert
+ const content = fs.readFileSync(outputPath, "utf-8");
+ expect(content).toContain("// @generated");
+ expect(content).toContain("WelcomeEmail");
+ expect(content).toContain("WelcomeEmailProps");
+ expect(content).toContain('"user/welcome"');
+ });
+
+ it("produces valid output with multiple templates across directories", () => {
+ // Setup
+ const userDir = path.join(srcDir, "user");
+ const authDir = path.join(srcDir, "auth");
+ fs.mkdirSync(userDir);
+ fs.mkdirSync(authDir);
+ fs.writeFileSync(path.join(userDir, "welcome.tsx"), TEMPLATE_CONTENT);
+ fs.writeFileSync(path.join(authDir, "invite.tsx"), SECOND_TEMPLATE_CONTENT);
+
+ // Act
+ generate(srcDir, outputPath);
+
+ // Assert
+ const content = fs.readFileSync(outputPath, "utf-8");
+ expect(content).toContain('"auth/invite"');
+ expect(content).toContain('"user/welcome"');
+ expect(content).toContain("InviteEmail");
+ expect(content).toContain("WelcomeEmail");
+ });
+
+ it("ignores test files in the output", () => {
+ // Setup
+ const userDir = path.join(srcDir, "user");
+ fs.mkdirSync(userDir);
+ fs.writeFileSync(path.join(userDir, "welcome.tsx"), TEMPLATE_CONTENT);
+ fs.writeFileSync(path.join(userDir, "welcome.test.tsx"), "test file");
+
+ // Act
+ generate(srcDir, outputPath);
+
+ // Assert
+ const content = fs.readFileSync(outputPath, "utf-8");
+ expect(content).not.toContain("test file");
+ expect(content).toContain("WelcomeEmail");
+ });
+});
diff --git a/packages/email-templates/scripts/generate-index.ts b/packages/email-templates/scripts/generate-index.ts
new file mode 100644
index 0000000..bb9b769
--- /dev/null
+++ b/packages/email-templates/scripts/generate-index.ts
@@ -0,0 +1,165 @@
+import fs from "node:fs";
+import path from "node:path";
+
+interface TemplateEntry {
+ key: string;
+ componentName: string;
+ propsTypeName: string;
+ importPath: string;
+}
+
+/**
+ * Recursively finds all `.tsx` template files under the given directory,
+ * excluding test files (`*.test.tsx`).
+ *
+ * @param dir - The directory to scan.
+ * @param baseDir - The root directory used to compute relative paths.
+ * @returns Sorted array of absolute file paths.
+ */
+export const findTemplateFiles = (
+ dir: string,
+ baseDir: string = dir,
+): string[] => {
+ const entries = fs.readdirSync(dir, { withFileTypes: true });
+ const files: string[] = [];
+
+ for (const entry of entries) {
+ const fullPath = path.join(dir, entry.name);
+ if (entry.isDirectory()) {
+ files.push(...findTemplateFiles(fullPath, baseDir));
+ } else if (
+ entry.name.endsWith(".tsx") &&
+ !entry.name.endsWith(".test.tsx")
+ ) {
+ files.push(fullPath);
+ }
+ }
+
+ return files.sort();
+};
+
+/**
+ * Extracts the exported component name and props type name from a template
+ * file's content using regex. Expects the conventions:
+ * - `export interface FooProps { ... }`
+ * - `export const Foo = ...`
+ *
+ * @param content - The file content to parse.
+ * @returns The component and props type names, or `null` if not found.
+ */
+export const extractExports = (
+ content: string,
+): { componentName: string; propsTypeName: string } | null => {
+ const propsMatch = content.match(/export interface (\w+Props)/);
+ const componentMatch = content.match(/export const (\w+)\s*=/);
+
+ if (!propsMatch?.[1] || !componentMatch?.[1]) {
+ return null;
+ }
+
+ return {
+ componentName: componentMatch[1],
+ propsTypeName: propsMatch[1],
+ };
+};
+
+/**
+ * Builds a sorted array of template entries from the discovered files.
+ *
+ * @param files - Absolute paths to template files.
+ * @param srcDir - The `src/` directory root, used to derive template keys and import paths.
+ * @returns Array of template entries with key, component name, props type, and import path.
+ */
+export const buildEntries = (
+ files: string[],
+ srcDir: string,
+): TemplateEntry[] => {
+ const entries: TemplateEntry[] = [];
+
+ for (const file of files) {
+ const content = fs.readFileSync(file, "utf-8");
+ const exported = extractExports(content);
+ if (!exported) continue;
+
+ const relative = path.relative(srcDir, file);
+ const key = relative.replace(/\.tsx$/, "");
+ const importPath = "./" + key;
+
+ entries.push({
+ key,
+ componentName: exported.componentName,
+ propsTypeName: exported.propsTypeName,
+ importPath,
+ });
+ }
+
+ return entries.sort((a, b) => a.key.localeCompare(b.key));
+};
+
+/**
+ * Generates the content of `src/index.ts` from the given template entries.
+ *
+ * @param entries - The sorted template entries to include.
+ * @returns The full file content string for `src/index.ts`.
+ */
+export const generateIndexContent = (entries: TemplateEntry[]): string => {
+ const imports = entries
+ .map(
+ (e) =>
+ `import { ${e.componentName}, type ${e.propsTypeName} } from "${e.importPath}";`,
+ )
+ .join("\n");
+
+ const reExports = entries
+ .map((e) => `export { ${e.componentName}, type ${e.propsTypeName} };`)
+ .join("\n");
+
+ const templateMapEntries = entries
+ .map((e) => ` "${e.key}": ${e.propsTypeName};`)
+ .join("\n");
+
+ const templatesObjectEntries = entries
+ .map((e) => ` "${e.key}": ${e.componentName},`)
+ .join("\n");
+
+ return `// @generated by scripts/generate-index.ts -- DO NOT EDIT
+
+${imports}
+
+${reExports}
+
+export type TemplateMap = {
+${templateMapEntries}
+};
+
+export const templates: {
+ [K in keyof TemplateMap]: React.ComponentType;
+} = {
+${templatesObjectEntries}
+};
+`;
+};
+
+/**
+ * Main entry point: scans the src/ directory for template files and writes
+ * the generated index.ts.
+ *
+ * @param srcDir - Path to the src/ directory. Defaults to the package's src/.
+ * @param outputPath - Path to write the generated file. Defaults to src/index.ts.
+ */
+export const generate = (
+ srcDir: string = path.resolve(__dirname, "..", "src"),
+ outputPath: string = path.resolve(__dirname, "..", "src", "index.ts"),
+): void => {
+ const files = findTemplateFiles(srcDir);
+ const entries = buildEntries(files, srcDir);
+ const content = generateIndexContent(entries);
+ fs.writeFileSync(outputPath, content, "utf-8");
+};
+
+const isDirectRun =
+ process.argv[1] && path.resolve(process.argv[1]) === path.resolve(__filename);
+
+if (isDirectRun) {
+ generate();
+}
diff --git a/packages/email-templates/src/index.ts b/packages/email-templates/src/index.ts
new file mode 100644
index 0000000..152823e
--- /dev/null
+++ b/packages/email-templates/src/index.ts
@@ -0,0 +1,19 @@
+// @generated by scripts/generate-index.ts -- DO NOT EDIT
+
+import { PasswordForgotEmail, type PasswordForgotEmailProps } from "./user/password-forgot";
+import { SignupEmail, type SignupEmailProps } from "./user/signup";
+
+export { PasswordForgotEmail, type PasswordForgotEmailProps };
+export { SignupEmail, type SignupEmailProps };
+
+export type TemplateMap = {
+ "user/password-forgot": PasswordForgotEmailProps;
+ "user/signup": SignupEmailProps;
+};
+
+export const templates: {
+ [K in keyof TemplateMap]: React.ComponentType;
+} = {
+ "user/password-forgot": PasswordForgotEmail,
+ "user/signup": SignupEmail,
+};
diff --git a/packages/email-templates/src/user/password-forgot.test.tsx b/packages/email-templates/src/user/password-forgot.test.tsx
new file mode 100644
index 0000000..ec8d834
--- /dev/null
+++ b/packages/email-templates/src/user/password-forgot.test.tsx
@@ -0,0 +1,73 @@
+import { render } from "@react-email/components";
+import { describe, expect, it } from "vitest";
+import { PasswordForgotEmail } from "./password-forgot";
+
+describe("PasswordForgotEmail", () => {
+ it("renders the user's name in the greeting", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Hi");
+ expect(html).toContain("Alice");
+ });
+
+ it("includes the reset URL as a link", async () => {
+ // Setup
+ const resetUrl = "https://example.com/reset?token=abc";
+
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain(resetUrl);
+ });
+
+ it("includes an email preview text", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Reset your password");
+ });
+
+ it("renders a 'Reset Password' call-to-action button", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Reset Password");
+ });
+
+ it("mentions the link expiration time", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("expire in 1 hour");
+ });
+
+ it("includes fallback text with the reset URL for non-HTML clients", async () => {
+ // Setup
+ const resetUrl = "https://example.com/reset?token=xyz";
+
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ const resetUrlOccurrences = html.split(resetUrl).length - 1;
+ expect(resetUrlOccurrences).toBeGreaterThanOrEqual(2);
+ });
+});
diff --git a/packages/email-templates/src/user/password-forgot.tsx b/packages/email-templates/src/user/password-forgot.tsx
new file mode 100644
index 0000000..29bcab3
--- /dev/null
+++ b/packages/email-templates/src/user/password-forgot.tsx
@@ -0,0 +1,124 @@
+import {
+ Body,
+ Button,
+ Container,
+ Head,
+ Heading,
+ Hr,
+ Html,
+ Preview,
+ Section,
+ Text,
+} from "@react-email/components";
+
+export interface PasswordForgotEmailProps {
+ name: string;
+ resetUrl: string;
+}
+
+/**
+ * Email sent when a user requests a password reset.
+ * Contains a time-limited link to set a new password.
+ *
+ * @param props.name - The user's display name.
+ * @param props.resetUrl - The URL the user clicks to reset their password.
+ * @returns The password-forgot email React Email component.
+ */
+export const PasswordForgotEmail = ({
+ name,
+ resetUrl,
+}: PasswordForgotEmailProps): React.JSX.Element => {
+ return (
+
+
+ Reset your password
+
+
+ Reset your password
+ Hi {name},
+
+ We received a request to reset your password. Click the button below
+ to choose a new one.
+
+
+
+ This link will expire in 1 hour. If you didn't request a
+ password reset, you can safely ignore this email — your password
+ will remain unchanged.
+
+
+
+ If the button doesn't work, copy and paste this URL into your
+ browser: {resetUrl}
+
+
+
+
+ );
+};
+
+PasswordForgotEmail.PreviewProps = {
+ name: "Jane",
+ resetUrl: "https://example.com/reset-password?token=xyz789",
+} satisfies PasswordForgotEmailProps;
+
+export default PasswordForgotEmail;
+
+const main: React.CSSProperties = {
+ backgroundColor: "#f6f9fc",
+ fontFamily:
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
+};
+
+const container: React.CSSProperties = {
+ backgroundColor: "#ffffff",
+ margin: "40px auto",
+ padding: "40px",
+ borderRadius: "8px",
+ maxWidth: "480px",
+};
+
+const heading: React.CSSProperties = {
+ fontSize: "24px",
+ fontWeight: "bold",
+ color: "#1a1a1a",
+ marginBottom: "24px",
+};
+
+const paragraph: React.CSSProperties = {
+ fontSize: "16px",
+ lineHeight: "26px",
+ color: "#4a4a4a",
+};
+
+const buttonContainer: React.CSSProperties = {
+ textAlign: "center" as const,
+ margin: "32px 0",
+};
+
+const button: React.CSSProperties = {
+ backgroundColor: "#0f172a",
+ borderRadius: "6px",
+ color: "#ffffff",
+ fontSize: "16px",
+ fontWeight: "bold",
+ textDecoration: "none",
+ textAlign: "center" as const,
+ padding: "12px 24px",
+};
+
+const hr: React.CSSProperties = {
+ borderColor: "#e6e6e6",
+ margin: "32px 0",
+};
+
+const footer: React.CSSProperties = {
+ fontSize: "12px",
+ lineHeight: "20px",
+ color: "#8c8c8c",
+ wordBreak: "break-all",
+};
diff --git a/packages/email-templates/src/user/signup.test.tsx b/packages/email-templates/src/user/signup.test.tsx
new file mode 100644
index 0000000..8489d1a
--- /dev/null
+++ b/packages/email-templates/src/user/signup.test.tsx
@@ -0,0 +1,59 @@
+import { render } from "@react-email/components";
+import { describe, expect, it } from "vitest";
+import { SignupEmail } from "./signup";
+
+describe("SignupEmail", () => {
+ it("renders the user's name in the heading", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Welcome,");
+ expect(html).toContain("Alice");
+ });
+
+ it("includes the verify URL as a link", async () => {
+ // Setup
+ const verifyUrl = "https://example.com/verify?token=abc";
+
+ // Act
+ const html = await render();
+
+ // Assert
+ expect(html).toContain(verifyUrl);
+ });
+
+ it("includes an email preview text", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Welcome — verify your email to get started");
+ });
+
+ it("renders a 'Verify Email' call-to-action button", async () => {
+ // Act
+ const html = await render(
+ ,
+ );
+
+ // Assert
+ expect(html).toContain("Verify Email");
+ });
+
+ it("includes fallback text with the verify URL for non-HTML clients", async () => {
+ // Setup
+ const verifyUrl = "https://example.com/verify?token=xyz";
+
+ // Act
+ const html = await render();
+
+ // Assert
+ const verifyUrlOccurrences = html.split(verifyUrl).length - 1;
+ expect(verifyUrlOccurrences).toBeGreaterThanOrEqual(2);
+ });
+});
diff --git a/packages/email-templates/src/user/signup.tsx b/packages/email-templates/src/user/signup.tsx
new file mode 100644
index 0000000..3c71207
--- /dev/null
+++ b/packages/email-templates/src/user/signup.tsx
@@ -0,0 +1,122 @@
+import {
+ Body,
+ Button,
+ Container,
+ Head,
+ Heading,
+ Hr,
+ Html,
+ Preview,
+ Section,
+ Text,
+} from "@react-email/components";
+
+export interface SignupEmailProps {
+ name: string;
+ verifyUrl: string;
+}
+
+/**
+ * Email sent to new users after signing up.
+ * Contains a verification link to confirm their email address.
+ *
+ * @param props.name - The user's display name.
+ * @param props.verifyUrl - The URL the user clicks to verify their email.
+ * @returns The signup email React Email component.
+ */
+export const SignupEmail = ({
+ name,
+ verifyUrl,
+}: SignupEmailProps): React.JSX.Element => {
+ return (
+
+
+ Welcome — verify your email to get started
+
+
+ Welcome, {name}!
+
+ Thanks for signing up. Please verify your email address by clicking
+ the button below.
+
+
+
+ If you didn't create an account, you can safely ignore this
+ email.
+
+
+
+ If the button doesn't work, copy and paste this URL into your
+ browser: {verifyUrl}
+
+
+
+
+ );
+};
+
+SignupEmail.PreviewProps = {
+ name: "Jane",
+ verifyUrl: "https://example.com/verify?token=abc123",
+} satisfies SignupEmailProps;
+
+export default SignupEmail;
+
+const main: React.CSSProperties = {
+ backgroundColor: "#f6f9fc",
+ fontFamily:
+ '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif',
+};
+
+const container: React.CSSProperties = {
+ backgroundColor: "#ffffff",
+ margin: "40px auto",
+ padding: "40px",
+ borderRadius: "8px",
+ maxWidth: "480px",
+};
+
+const heading: React.CSSProperties = {
+ fontSize: "24px",
+ fontWeight: "bold",
+ color: "#1a1a1a",
+ marginBottom: "24px",
+};
+
+const paragraph: React.CSSProperties = {
+ fontSize: "16px",
+ lineHeight: "26px",
+ color: "#4a4a4a",
+};
+
+const buttonContainer: React.CSSProperties = {
+ textAlign: "center" as const,
+ margin: "32px 0",
+};
+
+const button: React.CSSProperties = {
+ backgroundColor: "#0f172a",
+ borderRadius: "6px",
+ color: "#ffffff",
+ fontSize: "16px",
+ fontWeight: "bold",
+ textDecoration: "none",
+ textAlign: "center" as const,
+ padding: "12px 24px",
+};
+
+const hr: React.CSSProperties = {
+ borderColor: "#e6e6e6",
+ margin: "32px 0",
+};
+
+const footer: React.CSSProperties = {
+ fontSize: "12px",
+ lineHeight: "20px",
+ color: "#8c8c8c",
+ wordBreak: "break-all",
+};
diff --git a/packages/email-templates/tsconfig.json b/packages/email-templates/tsconfig.json
new file mode 100644
index 0000000..ec76e43
--- /dev/null
+++ b/packages/email-templates/tsconfig.json
@@ -0,0 +1,5 @@
+{
+ "extends": "@workspace/typescript-config/react-library.json",
+ "include": ["."],
+ "exclude": ["node_modules"]
+}
diff --git a/packages/email-templates/vitest.config.ts b/packages/email-templates/vitest.config.ts
new file mode 100644
index 0000000..a8572e0
--- /dev/null
+++ b/packages/email-templates/vitest.config.ts
@@ -0,0 +1,13 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: {
+ coverage: {
+ provider: "v8",
+ reporter: ["text", "json", "html"],
+ reportsDirectory: "./coverage",
+ },
+ environment: "node",
+ exclude: ["node_modules", "dist"],
+ },
+});
diff --git a/packages/env/env.example b/packages/env/env.example
index 4c8cbbb..e02b043 100644
--- a/packages/env/env.example
+++ b/packages/env/env.example
@@ -18,3 +18,9 @@ NEXT_PUBLIC_DEFAULT_LANGUAGE=de #required
# the default timeout of the operation
NEXT_PUBLIC_DEFAULT_TIMEOUT_MS=10000 #number
+
+# The current Node.js environment (e.g. development, production, test)
+NODE_ENV=development #required
+
+# API key for Resend email service. Get it from https://resend.com/api-keys
+RESEND_API_KEY=
diff --git a/packages/env/src/index.ts b/packages/env/src/index.ts
index 46c3f0e..a3d4aee 100644
--- a/packages/env/src/index.ts
+++ b/packages/env/src/index.ts
@@ -8,6 +8,8 @@ export const env = createEnv({
DB_POOLING_URL: z.string().min(1),
DB_URL_NON_POOLING: z.string().min(1),
DB_CERT_BASE_64: z.string().optional(),
+ NODE_ENV: z.string().min(1),
+ RESEND_API_KEY: z.string().optional(),
},
client: {
NEXT_PUBLIC_DEFAULT_LANGUAGE: z.string().min(1),
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4eccd04..9e8df5b 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -89,6 +89,9 @@ importers:
'@workspace/database':
specifier: workspace:*
version: link:../../packages/database
+ '@workspace/email-send':
+ specifier: workspace:*
+ version: link:../../packages/email-send
'@workspace/env':
specifier: workspace:*
version: link:../../packages/env
@@ -173,7 +176,7 @@ importers:
version: 7.4.0
'@prisma/client':
specifier: ^7.2.0
- version: 7.4.0(prisma@7.4.0(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.2))(typescript@5.9.2)
+ version: 7.4.0(prisma@7.4.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.2))(typescript@5.9.2)
'@workspace/env':
specifier: workspace:*
version: link:../env
@@ -198,7 +201,7 @@ importers:
version: link:../typescript-config
prisma:
specifier: ^7.2.0
- version: 7.4.0(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.2)
+ version: 7.4.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.2)
rimraf:
specifier: ^6.0.1
version: 6.1.2
@@ -209,6 +212,104 @@ importers:
specifier: 'catalog:'
version: 5.9.2
+ packages/email-send:
+ dependencies:
+ '@react-email/components':
+ specifier: ^1.0.7
+ version: 1.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@workspace/email-templates':
+ specifier: workspace:*
+ version: link:../email-templates
+ nodemailer:
+ specifier: ^7.0.3
+ version: 7.0.13
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ resend:
+ specifier: ^4.5.1
+ version: 4.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ devDependencies:
+ '@types/node':
+ specifier: 'catalog:'
+ version: 20.19.9
+ '@types/nodemailer':
+ specifier: ^6.4.17
+ version: 6.4.22
+ '@types/react':
+ specifier: 'catalog:'
+ version: 19.1.9
+ '@vitest/coverage-v8':
+ specifier: 'catalog:'
+ version: 4.0.18(vitest@4.0.18(@types/node@20.19.9)(jiti@2.5.1)(jsdom@28.0.0)(lightningcss@1.30.1)(tsx@4.21.0))
+ '@workspace/typescript-config':
+ specifier: workspace:*
+ version: link:../typescript-config
+ eslint:
+ specifier: 'catalog:'
+ version: 9.32.0(jiti@2.5.1)
+ typescript:
+ specifier: 'catalog:'
+ version: 5.9.2
+ vitest:
+ specifier: 'catalog:'
+ version: 4.0.18(@types/node@20.19.9)(jiti@2.5.1)(jsdom@28.0.0)(lightningcss@1.30.1)(tsx@4.21.0)
+
+ packages/email-templates:
+ dependencies:
+ '@react-email/components':
+ specifier: ^1.0.7
+ version: 1.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ react:
+ specifier: ^19.2.4
+ version: 19.2.4
+ react-dom:
+ specifier: ^19.2.4
+ version: 19.2.4(react@19.2.4)
+ zod:
+ specifier: 'catalog:'
+ version: 3.25.76
+ devDependencies:
+ '@react-email/preview-server':
+ specifier: ^5.0.1
+ version: 5.2.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@types/node':
+ specifier: ^20.19.9
+ version: 20.19.9
+ '@types/react':
+ specifier: ^19.2.14
+ version: 19.2.14
+ '@types/react-dom':
+ specifier: ^19.2.3
+ version: 19.2.3(@types/react@19.2.14)
+ '@vitest/coverage-v8':
+ specifier: 'catalog:'
+ version: 4.0.18(vitest@4.0.18(@types/node@20.19.9)(jiti@2.5.1)(jsdom@28.0.0)(lightningcss@1.30.1)(tsx@4.21.0))
+ '@workspace/typescript-config':
+ specifier: workspace:*
+ version: link:../typescript-config
+ dotenv-cli:
+ specifier: ^11.0.0
+ version: 11.0.0
+ eslint:
+ specifier: ^9.32.0
+ version: 9.39.2(jiti@2.5.1)
+ postcss:
+ specifier: ^8.4.35
+ version: 8.5.6
+ react-email:
+ specifier: ^5.2.8
+ version: 5.2.8
+ tsx:
+ specifier: ^4
+ version: 4.21.0
+ typescript:
+ specifier: 'catalog:'
+ version: 5.9.2
+ vitest:
+ specifier: 'catalog:'
+ version: 4.0.18(@types/node@20.19.9)(jiti@2.5.1)(jsdom@28.0.0)(lightningcss@1.30.1)(tsx@4.21.0)
+
packages/env:
dependencies:
'@t3-oss/env-nextjs':
@@ -326,7 +427,7 @@ importers:
version: link:../typescript-config
next:
specifier: ^15.5.9
- version: 15.5.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0)
+ version: 15.5.9(react-dom@19.2.4(react@19.2.0))(react@19.2.0)
vitest:
specifier: 'catalog:'
version: 4.0.18(@types/node@20.19.9)(jiti@2.5.1)(jsdom@28.0.0)(lightningcss@1.30.1)(tsx@4.21.0)
@@ -431,6 +532,18 @@ packages:
'@asamuzakjp/nwsapi@2.3.9':
resolution: {integrity: sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==}
+ '@babel/code-frame@7.29.0':
+ resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/generator@7.29.1':
+ resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/helper-globals@7.28.0':
+ resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==}
+ engines: {node: '>=6.9.0'}
+
'@babel/helper-string-parser@7.27.1':
resolution: {integrity: sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==}
engines: {node: '>=6.9.0'}
@@ -448,6 +561,14 @@ packages:
resolution: {integrity: sha512-FVFaVs2/dZgD3Y9ZD+AKNKjyGKzwu0C54laAXWUXgLcVXcCX6YZ6GhK2cp7FogSN2OA0Fu+QT8dP3FUdo9ShSQ==}
engines: {node: '>=6.9.0'}
+ '@babel/template@7.28.6':
+ resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==}
+ engines: {node: '>=6.9.0'}
+
+ '@babel/traverse@7.29.0':
+ resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==}
+ engines: {node: '>=6.9.0'}
+
'@babel/types@7.29.0':
resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==}
engines: {node: '>=6.9.0'}
@@ -520,148 +641,448 @@ packages:
'@emnapi/runtime@1.7.1':
resolution: {integrity: sha512-PVtJr5CmLwYAU9PZDMITZoR5iAOShYREoR45EyyLrbntV50mdePTgUn4AmOw90Ifcj+x2kRjdzr1HP3RrNiHGA==}
+ '@esbuild/aix-ppc64@0.25.10':
+ resolution: {integrity: sha512-0NFWnA+7l41irNuaSVlLfgNT12caWJVLzp5eAVhZ0z1qpxbockccEt3s+149rE64VUI3Ml2zt8Nv5JVc4QXTsw==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
+ '@esbuild/aix-ppc64@0.25.12':
+ resolution: {integrity: sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [aix]
+
'@esbuild/aix-ppc64@0.27.3':
resolution: {integrity: sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [aix]
+ '@esbuild/android-arm64@0.25.10':
+ resolution: {integrity: sha512-LSQa7eDahypv/VO6WKohZGPSJDq5OVOo3UoFR1E4t4Gj1W7zEQMUhI+lo81H+DtB+kP+tDgBp+M4oNCwp6kffg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
+ '@esbuild/android-arm64@0.25.12':
+ resolution: {integrity: sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [android]
+
'@esbuild/android-arm64@0.27.3':
resolution: {integrity: sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [android]
+ '@esbuild/android-arm@0.25.10':
+ resolution: {integrity: sha512-dQAxF1dW1C3zpeCDc5KqIYuZ1tgAdRXNoZP7vkBIRtKZPYe2xVr/d3SkirklCHudW1B45tGiUlz2pUWDfbDD4w==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
+ '@esbuild/android-arm@0.25.12':
+ resolution: {integrity: sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [android]
+
'@esbuild/android-arm@0.27.3':
resolution: {integrity: sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==}
engines: {node: '>=18'}
cpu: [arm]
os: [android]
+ '@esbuild/android-x64@0.25.10':
+ resolution: {integrity: sha512-MiC9CWdPrfhibcXwr39p9ha1x0lZJ9KaVfvzA0Wxwz9ETX4v5CHfF09bx935nHlhi+MxhA63dKRRQLiVgSUtEg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
+ '@esbuild/android-x64@0.25.12':
+ resolution: {integrity: sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [android]
+
'@esbuild/android-x64@0.27.3':
resolution: {integrity: sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [android]
+ '@esbuild/darwin-arm64@0.25.10':
+ resolution: {integrity: sha512-JC74bdXcQEpW9KkV326WpZZjLguSZ3DfS8wrrvPMHgQOIEIG/sPXEN/V8IssoJhbefLRcRqw6RQH2NnpdprtMA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
+ '@esbuild/darwin-arm64@0.25.12':
+ resolution: {integrity: sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [darwin]
+
'@esbuild/darwin-arm64@0.27.3':
resolution: {integrity: sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [darwin]
+ '@esbuild/darwin-x64@0.25.10':
+ resolution: {integrity: sha512-tguWg1olF6DGqzws97pKZ8G2L7Ig1vjDmGTwcTuYHbuU6TTjJe5FXbgs5C1BBzHbJ2bo1m3WkQDbWO2PvamRcg==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
+ '@esbuild/darwin-x64@0.25.12':
+ resolution: {integrity: sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [darwin]
+
'@esbuild/darwin-x64@0.27.3':
resolution: {integrity: sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==}
engines: {node: '>=18'}
cpu: [x64]
os: [darwin]
+ '@esbuild/freebsd-arm64@0.25.10':
+ resolution: {integrity: sha512-3ZioSQSg1HT2N05YxeJWYR+Libe3bREVSdWhEEgExWaDtyFbbXWb49QgPvFH8u03vUPX10JhJPcz7s9t9+boWg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ resolution: {integrity: sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [freebsd]
+
'@esbuild/freebsd-arm64@0.27.3':
resolution: {integrity: sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==}
engines: {node: '>=18'}
cpu: [arm64]
os: [freebsd]
+ '@esbuild/freebsd-x64@0.25.10':
+ resolution: {integrity: sha512-LLgJfHJk014Aa4anGDbh8bmI5Lk+QidDmGzuC2D+vP7mv/GeSN+H39zOf7pN5N8p059FcOfs2bVlrRr4SK9WxA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
+ '@esbuild/freebsd-x64@0.25.12':
+ resolution: {integrity: sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [freebsd]
+
'@esbuild/freebsd-x64@0.27.3':
resolution: {integrity: sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==}
engines: {node: '>=18'}
cpu: [x64]
os: [freebsd]
+ '@esbuild/linux-arm64@0.25.10':
+ resolution: {integrity: sha512-5luJWN6YKBsawd5f9i4+c+geYiVEw20FVW5x0v1kEMWNq8UctFjDiMATBxLvmmHA4bf7F6hTRaJgtghFr9iziQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
+ '@esbuild/linux-arm64@0.25.12':
+ resolution: {integrity: sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [linux]
+
'@esbuild/linux-arm64@0.27.3':
resolution: {integrity: sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==}
engines: {node: '>=18'}
cpu: [arm64]
os: [linux]
+ '@esbuild/linux-arm@0.25.10':
+ resolution: {integrity: sha512-oR31GtBTFYCqEBALI9r6WxoU/ZofZl962pouZRTEYECvNF/dtXKku8YXcJkhgK/beU+zedXfIzHijSRapJY3vg==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
+ '@esbuild/linux-arm@0.25.12':
+ resolution: {integrity: sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==}
+ engines: {node: '>=18'}
+ cpu: [arm]
+ os: [linux]
+
'@esbuild/linux-arm@0.27.3':
resolution: {integrity: sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==}
engines: {node: '>=18'}
cpu: [arm]
os: [linux]
+ '@esbuild/linux-ia32@0.25.10':
+ resolution: {integrity: sha512-NrSCx2Kim3EnnWgS4Txn0QGt0Xipoumb6z6sUtl5bOEZIVKhzfyp/Lyw4C1DIYvzeW/5mWYPBFJU3a/8Yr75DQ==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
+ '@esbuild/linux-ia32@0.25.12':
+ resolution: {integrity: sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [linux]
+
'@esbuild/linux-ia32@0.27.3':
resolution: {integrity: sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==}
engines: {node: '>=18'}
cpu: [ia32]
os: [linux]
+ '@esbuild/linux-loong64@0.25.10':
+ resolution: {integrity: sha512-xoSphrd4AZda8+rUDDfD9J6FUMjrkTz8itpTITM4/xgerAZZcFW7Dv+sun7333IfKxGG8gAq+3NbfEMJfiY+Eg==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
+ '@esbuild/linux-loong64@0.25.12':
+ resolution: {integrity: sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==}
+ engines: {node: '>=18'}
+ cpu: [loong64]
+ os: [linux]
+
'@esbuild/linux-loong64@0.27.3':
resolution: {integrity: sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==}
engines: {node: '>=18'}
cpu: [loong64]
os: [linux]
+ '@esbuild/linux-mips64el@0.25.10':
+ resolution: {integrity: sha512-ab6eiuCwoMmYDyTnyptoKkVS3k8fy/1Uvq7Dj5czXI6DF2GqD2ToInBI0SHOp5/X1BdZ26RKc5+qjQNGRBelRA==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
+ '@esbuild/linux-mips64el@0.25.12':
+ resolution: {integrity: sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==}
+ engines: {node: '>=18'}
+ cpu: [mips64el]
+ os: [linux]
+
'@esbuild/linux-mips64el@0.27.3':
resolution: {integrity: sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==}
engines: {node: '>=18'}
cpu: [mips64el]
os: [linux]
+ '@esbuild/linux-ppc64@0.25.10':
+ resolution: {integrity: sha512-NLinzzOgZQsGpsTkEbdJTCanwA5/wozN9dSgEl12haXJBzMTpssebuXR42bthOF3z7zXFWH1AmvWunUCkBE4EA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
+ '@esbuild/linux-ppc64@0.25.12':
+ resolution: {integrity: sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==}
+ engines: {node: '>=18'}
+ cpu: [ppc64]
+ os: [linux]
+
'@esbuild/linux-ppc64@0.27.3':
resolution: {integrity: sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==}
engines: {node: '>=18'}
cpu: [ppc64]
os: [linux]
+ '@esbuild/linux-riscv64@0.25.10':
+ resolution: {integrity: sha512-FE557XdZDrtX8NMIeA8LBJX3dC2M8VGXwfrQWU7LB5SLOajfJIxmSdyL/gU1m64Zs9CBKvm4UAuBp5aJ8OgnrA==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
+ '@esbuild/linux-riscv64@0.25.12':
+ resolution: {integrity: sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==}
+ engines: {node: '>=18'}
+ cpu: [riscv64]
+ os: [linux]
+
'@esbuild/linux-riscv64@0.27.3':
resolution: {integrity: sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==}
engines: {node: '>=18'}
cpu: [riscv64]
os: [linux]
+ '@esbuild/linux-s390x@0.25.10':
+ resolution: {integrity: sha512-3BBSbgzuB9ajLoVZk0mGu+EHlBwkusRmeNYdqmznmMc9zGASFjSsxgkNsqmXugpPk00gJ0JNKh/97nxmjctdew==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
+ '@esbuild/linux-s390x@0.25.12':
+ resolution: {integrity: sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==}
+ engines: {node: '>=18'}
+ cpu: [s390x]
+ os: [linux]
+
'@esbuild/linux-s390x@0.27.3':
resolution: {integrity: sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==}
engines: {node: '>=18'}
cpu: [s390x]
os: [linux]
+ '@esbuild/linux-x64@0.25.10':
+ resolution: {integrity: sha512-QSX81KhFoZGwenVyPoberggdW1nrQZSvfVDAIUXr3WqLRZGZqWk/P4T8p2SP+de2Sr5HPcvjhcJzEiulKgnxtA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
+ '@esbuild/linux-x64@0.25.12':
+ resolution: {integrity: sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [linux]
+
'@esbuild/linux-x64@0.27.3':
resolution: {integrity: sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==}
engines: {node: '>=18'}
cpu: [x64]
os: [linux]
+ '@esbuild/netbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-AKQM3gfYfSW8XRk8DdMCzaLUFB15dTrZfnX8WXQoOUpUBQ+NaAFCP1kPS/ykbbGYz7rxn0WS48/81l9hFl3u4A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [netbsd]
+
'@esbuild/netbsd-arm64@0.27.3':
resolution: {integrity: sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==}
engines: {node: '>=18'}
cpu: [arm64]
os: [netbsd]
+ '@esbuild/netbsd-x64@0.25.10':
+ resolution: {integrity: sha512-7RTytDPGU6fek/hWuN9qQpeGPBZFfB4zZgcz2VK2Z5VpdUxEI8JKYsg3JfO0n/Z1E/6l05n0unDCNc4HnhQGig==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
+ '@esbuild/netbsd-x64@0.25.12':
+ resolution: {integrity: sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [netbsd]
+
'@esbuild/netbsd-x64@0.27.3':
resolution: {integrity: sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==}
engines: {node: '>=18'}
cpu: [x64]
os: [netbsd]
+ '@esbuild/openbsd-arm64@0.25.10':
+ resolution: {integrity: sha512-5Se0VM9Wtq797YFn+dLimf2Zx6McttsH2olUBsDml+lm0GOCRVebRWUvDtkY4BWYv/3NgzS8b/UM3jQNh5hYyw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ resolution: {integrity: sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openbsd]
+
'@esbuild/openbsd-arm64@0.27.3':
resolution: {integrity: sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openbsd]
+ '@esbuild/openbsd-x64@0.25.10':
+ resolution: {integrity: sha512-XkA4frq1TLj4bEMB+2HnI0+4RnjbuGZfet2gs/LNs5Hc7D89ZQBHQ0gL2ND6Lzu1+QVkjp3x1gIcPKzRNP8bXw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
+ '@esbuild/openbsd-x64@0.25.12':
+ resolution: {integrity: sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [openbsd]
+
'@esbuild/openbsd-x64@0.27.3':
resolution: {integrity: sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==}
engines: {node: '>=18'}
cpu: [x64]
os: [openbsd]
+ '@esbuild/openharmony-arm64@0.25.10':
+ resolution: {integrity: sha512-AVTSBhTX8Y/Fz6OmIVBip9tJzZEUcY8WLh7I59+upa5/GPhh2/aM6bvOMQySspnCCHvFi79kMtdJS1w0DXAeag==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ resolution: {integrity: sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [openharmony]
+
'@esbuild/openharmony-arm64@0.27.3':
resolution: {integrity: sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==}
engines: {node: '>=18'}
cpu: [arm64]
os: [openharmony]
+ '@esbuild/sunos-x64@0.25.10':
+ resolution: {integrity: sha512-fswk3XT0Uf2pGJmOpDB7yknqhVkJQkAQOcW/ccVOtfx05LkbWOaRAtn5SaqXypeKQra1QaEa841PgrSL9ubSPQ==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
+ '@esbuild/sunos-x64@0.25.12':
+ resolution: {integrity: sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [sunos]
+
'@esbuild/sunos-x64@0.27.3':
resolution: {integrity: sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==}
engines: {node: '>=18'}
cpu: [x64]
os: [sunos]
- '@esbuild/win32-arm64@0.27.3':
- resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
+ '@esbuild/win32-arm64@0.25.10':
+ resolution: {integrity: sha512-ah+9b59KDTSfpaCg6VdJoOQvKjI33nTaQr4UluQwW7aEwZQsbMCfTmfEO4VyewOxx4RaDT/xCy9ra2GPWmO7Kw==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.25.12':
+ resolution: {integrity: sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-arm64@0.27.3':
+ resolution: {integrity: sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==}
+ engines: {node: '>=18'}
+ cpu: [arm64]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.10':
+ resolution: {integrity: sha512-QHPDbKkrGO8/cz9LKVnJU22HOi4pxZnZhhA2HYHez5Pz4JeffhDjf85E57Oyco163GnzNCVkZK0b/n4Y0UHcSw==}
+ engines: {node: '>=18'}
+ cpu: [ia32]
+ os: [win32]
+
+ '@esbuild/win32-ia32@0.25.12':
+ resolution: {integrity: sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==}
engines: {node: '>=18'}
- cpu: [arm64]
+ cpu: [ia32]
os: [win32]
'@esbuild/win32-ia32@0.27.3':
@@ -670,6 +1091,18 @@ packages:
cpu: [ia32]
os: [win32]
+ '@esbuild/win32-x64@0.25.10':
+ resolution: {integrity: sha512-9KpxSVFCu0iK1owoez6aC/s/EdUQLDN3adTxGCqxMVhrPDj6bt5dbrHDXUuq+Bs2vATFBBrQS5vdQ/Ed2P+nbw==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
+ '@esbuild/win32-x64@0.25.12':
+ resolution: {integrity: sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==}
+ engines: {node: '>=18'}
+ cpu: [x64]
+ os: [win32]
+
'@esbuild/win32-x64@0.27.3':
resolution: {integrity: sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==}
engines: {node: '>=18'}
@@ -928,6 +1361,10 @@ packages:
resolution: {integrity: sha512-ZT55BDLV0yv0RBm2czMiZ+SqCGO7AvmOM3G/w2xhVPH+te0aKgFjmBvGlL1dH+ql2tgGO3MVrbb3jCKyvpgnxA==}
engines: {node: 20 || >=22}
+ '@isaacs/cliui@9.0.0':
+ resolution: {integrity: sha512-AokJm4tuBHillT+FpMtxQ60n8ObyXBatq7jD2/JA9dxbDDokKQm8KMht5ibGzLVU9IJDIKK4TPKgMHEYMn3lMg==}
+ engines: {node: '>=18'}
+
'@isaacs/fs-minipass@4.0.1':
resolution: {integrity: sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==}
engines: {node: '>=18.0.0'}
@@ -964,6 +1401,9 @@ packages:
'@next/env@16.0.7':
resolution: {integrity: sha512-gpaNgUh5nftFKRkRQGnVi5dpcYSKGcZZkQffZ172OrG/XkrnS7UBTQ648YY+8ME92cC4IojpI2LqTC8sTDhAaw==}
+ '@next/env@16.1.6':
+ resolution: {integrity: sha512-N1ySLuZjnAtN3kFnwhAwPvZah8RJxKasD7x1f8shFqhncnWZn4JMfg37diLNuoHsLAlrDfM3g4mawVdtAG8XLQ==}
+
'@next/eslint-plugin-next@15.4.5':
resolution: {integrity: sha512-YhbrlbEt0m4jJnXHMY/cCUDBAWgd5SaTa5mJjzOt82QwflAFfW/h3+COp2TfVSzhmscIZ5sg2WXt3MLziqCSCw==}
@@ -979,6 +1419,12 @@ packages:
cpu: [arm64]
os: [darwin]
+ '@next/swc-darwin-arm64@16.1.6':
+ resolution: {integrity: sha512-wTzYulosJr/6nFnqGW7FrG3jfUUlEf8UjGA0/pyypJl42ExdVgC6xJgcXQ+V8QFn6niSG2Pb8+MIG1mZr2vczw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [darwin]
+
'@next/swc-darwin-x64@15.5.7':
resolution: {integrity: sha512-UP6CaDBcqaCBuiq/gfCEJw7sPEoX1aIjZHnBWN9v9qYHQdMKvCKcAVs4OX1vIjeE+tC5EIuwDTVIoXpUes29lg==}
engines: {node: '>= 10'}
@@ -991,6 +1437,12 @@ packages:
cpu: [x64]
os: [darwin]
+ '@next/swc-darwin-x64@16.1.6':
+ resolution: {integrity: sha512-BLFPYPDO+MNJsiDWbeVzqvYd4NyuRrEYVB5k2N3JfWncuHAy2IVwMAOlVQDFjj+krkWzhY2apvmekMkfQR0CUQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [darwin]
+
'@next/swc-linux-arm64-gnu@15.5.7':
resolution: {integrity: sha512-NCslw3GrNIw7OgmRBxHtdWFQYhexoUCq+0oS2ccjyYLtcn1SzGzeM54jpTFonIMUjNbHmpKpziXnpxhSWLcmBA==}
engines: {node: '>= 10'}
@@ -1003,6 +1455,12 @@ packages:
cpu: [arm64]
os: [linux]
+ '@next/swc-linux-arm64-gnu@16.1.6':
+ resolution: {integrity: sha512-OJYkCd5pj/QloBvoEcJ2XiMnlJkRv9idWA/j0ugSuA34gMT6f5b7vOiCQHVRpvStoZUknhl6/UxOXL4OwtdaBw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
'@next/swc-linux-arm64-musl@15.5.7':
resolution: {integrity: sha512-nfymt+SE5cvtTrG9u1wdoxBr9bVB7mtKTcj0ltRn6gkP/2Nu1zM5ei8rwP9qKQP0Y//umK+TtkKgNtfboBxRrw==}
engines: {node: '>= 10'}
@@ -1015,6 +1473,12 @@ packages:
cpu: [arm64]
os: [linux]
+ '@next/swc-linux-arm64-musl@16.1.6':
+ resolution: {integrity: sha512-S4J2v+8tT3NIO9u2q+S0G5KdvNDjXfAv06OhfOzNDaBn5rw84DGXWndOEB7d5/x852A20sW1M56vhC/tRVbccQ==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [linux]
+
'@next/swc-linux-x64-gnu@15.5.7':
resolution: {integrity: sha512-hvXcZvCaaEbCZcVzcY7E1uXN9xWZfFvkNHwbe/n4OkRhFWrs1J1QV+4U1BN06tXLdaS4DazEGXwgqnu/VMcmqw==}
engines: {node: '>= 10'}
@@ -1027,6 +1491,12 @@ packages:
cpu: [x64]
os: [linux]
+ '@next/swc-linux-x64-gnu@16.1.6':
+ resolution: {integrity: sha512-2eEBDkFlMMNQnkTyPBhQOAyn2qMxyG2eE7GPH2WIDGEpEILcBPI/jdSv4t6xupSP+ot/jkfrCShLAa7+ZUPcJQ==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
'@next/swc-linux-x64-musl@15.5.7':
resolution: {integrity: sha512-4IUO539b8FmF0odY6/SqANJdgwn1xs1GkPO5doZugwZ3ETF6JUdckk7RGmsfSf7ws8Qb2YB5It33mvNL/0acqA==}
engines: {node: '>= 10'}
@@ -1039,6 +1509,12 @@ packages:
cpu: [x64]
os: [linux]
+ '@next/swc-linux-x64-musl@16.1.6':
+ resolution: {integrity: sha512-oicJwRlyOoZXVlxmIMaTq7f8pN9QNbdes0q2FXfRsPhfCi8n8JmOZJm5oo1pwDaFbnnD421rVU409M3evFbIqg==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [linux]
+
'@next/swc-win32-arm64-msvc@15.5.7':
resolution: {integrity: sha512-CpJVTkYI3ZajQkC5vajM7/ApKJUOlm6uP4BknM3XKvJ7VXAvCqSjSLmM0LKdYzn6nBJVSjdclx8nYJSa3xlTgQ==}
engines: {node: '>= 10'}
@@ -1051,6 +1527,12 @@ packages:
cpu: [arm64]
os: [win32]
+ '@next/swc-win32-arm64-msvc@16.1.6':
+ resolution: {integrity: sha512-gQmm8izDTPgs+DCWH22kcDmuUp7NyiJgEl18bcr8irXA5N2m2O+JQIr6f3ct42GOs9c0h8QF3L5SzIxcYAAXXw==}
+ engines: {node: '>= 10'}
+ cpu: [arm64]
+ os: [win32]
+
'@next/swc-win32-x64-msvc@15.5.7':
resolution: {integrity: sha512-gMzgBX164I6DN+9/PGA+9dQiwmTkE4TloBNx8Kv9UiGARsr9Nba7IpcBRA1iTV9vwlYnrE3Uy6I7Aj6qLjQuqw==}
engines: {node: '>= 10'}
@@ -1063,6 +1545,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@next/swc-win32-x64-msvc@16.1.6':
+ resolution: {integrity: sha512-NRfO39AIrzBnixKbjuo2YiYhB6o9d8v/ymU9m/Xk8cyVk+k7XylniXkHwjs4s70wedVffc6bQNbufk5v0xEm0A==}
+ engines: {node: '>= 10'}
+ cpu: [x64]
+ os: [win32]
+
'@nodelib/fs.scandir@2.1.5':
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@@ -1282,6 +1770,175 @@ packages:
'@types/react':
optional: true
+ '@react-email/body@0.2.1':
+ resolution: {integrity: sha512-ljDiQiJDu/Fq//vSIIP0z5Nuvt4+DX1RqGasstChDGJB/14ogd4VdNS9aacoede/ZjGy3o3Qb+cxyS+XgM6SwQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/button@0.2.1':
+ resolution: {integrity: sha512-qXyj7RZLE7POy9BMKSoqQ00tOXThjOZSUnI2Yu9i29IHngPlmrNayIWBoVKtElES7OWwypUcpiajwi1mUWx6/A==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/code-block@0.2.1':
+ resolution: {integrity: sha512-M3B7JpVH4ytgn83/ujRR1k1DQHvTeABiDM61OvAbjLRPhC/5KLHU5KkzIbbuGIrjWwxAbL1kSQzU8MhLEtSxyw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/code-inline@0.0.6':
+ resolution: {integrity: sha512-jfhebvv3dVsp3OdPgKXnk8+e2pBiDVZejDOBFzBa/IblrAJ9cQDkN6rBD5IyEg8hTOxwbw3iaI/yZFmDmIguIA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/column@0.0.14':
+ resolution: {integrity: sha512-f+W+Bk2AjNO77zynE33rHuQhyqVICx4RYtGX9NKsGUg0wWjdGP0qAuIkhx9Rnmk4/hFMo1fUrtYNqca9fwJdHg==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/components@1.0.7':
+ resolution: {integrity: sha512-mY+v4C1SMaGOKuKp0QWDQLGK+3fvH06ZE10EVavv+T6tQneDHq9cpQ9NdCrvuO1nWZnWrA/0tRpvyqyF0uo93w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/container@0.0.16':
+ resolution: {integrity: sha512-QWBB56RkkU0AJ9h+qy33gfT5iuZknPC7Un/IjZv9B0QmMIK+WWacc0cH6y2SV5Cv/b99hU94fjEMOOO4enpkbQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/font@0.0.10':
+ resolution: {integrity: sha512-0urVSgCmQIfx5r7Xc586miBnQUVnGp3OTYUm8m5pwtQRdTRO5XrTtEfNJ3JhYhSOruV0nD8fd+dXtKXobum6tA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/head@0.0.13':
+ resolution: {integrity: sha512-AJg6le/08Gz4tm+6MtKXqtNNyKHzmooOCdmtqmWxD7FxoAdU1eVcizhtQ0gcnVaY6ethEyE/hnEzQxt1zu5Kog==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/heading@0.0.16':
+ resolution: {integrity: sha512-jmsKnQm1ykpBzw4hCYHwBkt5pW2jScXffPeEH5ZRF5tZeF5b1pvlFTO9han7C0pCkZYo1kEvWiRtx69yfCIwuw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/hr@0.0.12':
+ resolution: {integrity: sha512-TwmOmBDibavUQpXBxpmZYi2Iks/yeZOzFYh+di9EltMSnEabH8dMZXrl+pxNXzCgZ2XE8HY7VmUL65Lenfu5PA==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/html@0.0.12':
+ resolution: {integrity: sha512-KTShZesan+UsreU7PDUV90afrZwU5TLwYlALuCSU0OT+/U8lULNNbAUekg+tGwCnOfIKYtpDPKkAMRdYlqUznw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/img@0.0.12':
+ resolution: {integrity: sha512-sRCpEARNVTf3FQhZOC+JTvu5r6ubiYWkT0ucYXg8ctkyi4G8QG+jgYPiNUqVeTLA2STOfmPM/nrk1nb84y6CPQ==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/link@0.0.13':
+ resolution: {integrity: sha512-lkWc/NjOcefRZMkQoSDDbuKBEBDES9aXnFEOuPH845wD3TxPwh+QTf0fStuzjoRLUZWpHnio4z7qGGRYusn/sw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/markdown@0.0.18':
+ resolution: {integrity: sha512-gSuYK5fsMbGk87jDebqQ6fa2fKcWlkf2Dkva8kMONqLgGCq8/0d+ZQYMEJsdidIeBo3kmsnHZPrwdFB4HgjUXg==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/preview-server@5.2.8':
+ resolution: {integrity: sha512-drQ0C7vi7P0uE7Ox1Cyiujsx0oqp2RbIscOdSBR5qvzw3EKjlGbW2pWjQ000cjxTq3Si7lqlRKhOIF8MzOnqHw==}
+
+ '@react-email/preview@0.0.14':
+ resolution: {integrity: sha512-aYK8q0IPkBXyMsbpMXgxazwHxYJxTrXrV95GFuu2HbEiIToMwSyUgb8HDFYwPqqfV03/jbwqlsXmFxsOd+VNaw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/render@1.1.2':
+ resolution: {integrity: sha512-RnRehYN3v9gVlNMehHPHhyp2RQo7+pSkHDtXPvg3s0GbzM9SQMW4Qrf8GRNvtpLC4gsI+Wt0VatNRUFqjvevbw==}
+ engines: {node: '>=18.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/render@2.0.4':
+ resolution: {integrity: sha512-kht2oTFQ1SwrLpd882ahTvUtNa9s53CERHstiTbzhm6aR2Hbykp/mQ4tpPvsBGkKAEvKRlDEoooh60Uk6nHK1g==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+ react-dom: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/row@0.0.13':
+ resolution: {integrity: sha512-bYnOac40vIKCId7IkwuLAAsa3fKfSfqCvv6epJKmPE0JBuu5qI4FHFCl9o9dVpIIS08s/ub+Y/txoMt0dYziGw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/section@0.0.17':
+ resolution: {integrity: sha512-qNl65ye3W0Rd5udhdORzTV9ezjb+GFqQQSae03NDzXtmJq6sqVXNWNiVolAjvJNypim+zGXmv6J9TcV5aNtE/w==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
+ '@react-email/tailwind@2.0.4':
+ resolution: {integrity: sha512-cDp8Ss6LJKI8zBLKE+tsXFurn6I2nnQNg1qqjfZuNPNoToN1Uyx3egW0bwSVk1JjrNWx/Xnme7ZxvNLRrU9K0Q==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ '@react-email/body': 0.2.1
+ '@react-email/button': 0.2.1
+ '@react-email/code-block': 0.2.1
+ '@react-email/code-inline': 0.0.6
+ '@react-email/container': 0.0.16
+ '@react-email/heading': 0.0.16
+ '@react-email/hr': 0.0.12
+ '@react-email/img': 0.0.12
+ '@react-email/link': 0.0.13
+ '@react-email/preview': 0.0.14
+ '@react-email/text': 0.1.6
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+ peerDependenciesMeta:
+ '@react-email/body':
+ optional: true
+ '@react-email/button':
+ optional: true
+ '@react-email/code-block':
+ optional: true
+ '@react-email/code-inline':
+ optional: true
+ '@react-email/container':
+ optional: true
+ '@react-email/heading':
+ optional: true
+ '@react-email/hr':
+ optional: true
+ '@react-email/img':
+ optional: true
+ '@react-email/link':
+ optional: true
+ '@react-email/preview':
+ optional: true
+
+ '@react-email/text@0.1.6':
+ resolution: {integrity: sha512-TYqkioRS45wTR5il3dYk/SbUjjEdhSwh9BtRNB99qNH1pXAwA45H7rAuxehiu8iJQJH0IyIr+6n62gBz9ezmsw==}
+ engines: {node: '>=20.0.0'}
+ peerDependencies:
+ react: ^18.0 || ^19.0 || ^19.0.0-rc
+
'@rollup/rollup-android-arm-eabi@4.54.0':
resolution: {integrity: sha512-OywsdRHrFvCdvsewAInDKCNyR3laPA2mc9bRYJ6LBp5IyvF3fvXbbNR0bSzHlZVFtn6E0xw2oZlyjg4rKCVcng==}
cpu: [arm]
@@ -1392,6 +2049,12 @@ packages:
cpu: [x64]
os: [win32]
+ '@selderee/plugin-htmlparser2@0.11.0':
+ resolution: {integrity: sha512-P33hHGdldxGabLFjPPpaTxVolMrzrcegejx+0GxjrIb9Zv48D8yAIA/QTDR2dFl7Uz7urX8aX6+5bCZslr+gWQ==}
+
+ '@socket.io/component-emitter@3.1.2':
+ resolution: {integrity: sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==}
+
'@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
@@ -1534,6 +2197,9 @@ packages:
'@types/chai@5.2.3':
resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==}
+ '@types/cors@2.8.19':
+ resolution: {integrity: sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==}
+
'@types/deep-eql@4.0.2':
resolution: {integrity: sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==}
@@ -1562,6 +2228,9 @@ packages:
'@types/node@20.19.9':
resolution: {integrity: sha512-cuVNgarYWZqxRJDQHEB58GEONhOK79QVR/qYx4S7kcUObQvUwvFnYxJuuHUKm2aieN9X3yZB4LZsuYNU1Qphsw==}
+ '@types/nodemailer@6.4.22':
+ resolution: {integrity: sha512-HV16KRsW7UyZBITE07B62k8PRAKFqRSFXn1T7vslurVjN761tMDBhk5Lbt17ehyTzK6XcyJnAgUpevrvkcVOzw==}
+
'@types/pg@8.16.0':
resolution: {integrity: sha512-RmhMd/wD+CF8Dfo+cVIy3RR5cl8CyfXQ0tGgW6XBL8L4LM/UTEbNXYRbLwU6w+CgrKBNbrQWt4FUtTfaU5jSYQ==}
@@ -1578,6 +2247,9 @@ packages:
'@types/react@19.1.9':
resolution: {integrity: sha512-WmdoynAX8Stew/36uTSVMcLJJ1KRh6L3IZRx1PZ7qJtBqT3dYTgyDTx8H1qoRghErydW7xw9mSJ3wS//tCRpFA==}
+ '@types/react@19.2.14':
+ resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==}
+
'@types/react@19.2.7':
resolution: {integrity: sha512-MWtvHrGZLFttgeEj28VXHxpmwYbor/ATPYbBfSFZEIRK0ecCFLl2Qo55z52Hss+UV9CRN7trSeq1zbgx7YDWWg==}
@@ -1684,6 +2356,10 @@ packages:
'@vitest/utils@4.0.18':
resolution: {integrity: sha512-msMRKLMVLWygpK3u2Hybgi4MNjcYJvwTb0Ru09+fOyCXIgT5raYP041DRRdiJiI3k/2U6SEbAETB3YtBrUkCFA==}
+ accepts@1.3.8:
+ resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==}
+ engines: {node: '>= 0.6'}
+
acorn-jsx@5.3.2:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies:
@@ -1706,9 +2382,20 @@ packages:
resolution: {integrity: sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==}
engines: {node: '>=8'}
+ ajv-formats@3.0.1:
+ resolution: {integrity: sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==}
+ peerDependencies:
+ ajv: ^8.0.0
+ peerDependenciesMeta:
+ ajv:
+ optional: true
+
ajv@6.12.6:
resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==}
+ ajv@8.18.0:
+ resolution: {integrity: sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==}
+
ansi-escapes@4.3.2:
resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==}
engines: {node: '>=8'}
@@ -1717,6 +2404,10 @@ packages:
resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==}
engines: {node: '>=8'}
+ ansi-regex@6.2.2:
+ resolution: {integrity: sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==}
+ engines: {node: '>=12'}
+
ansi-styles@3.2.1:
resolution: {integrity: sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==}
engines: {node: '>=4'}
@@ -1786,6 +2477,9 @@ packages:
resolution: {integrity: sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==}
engines: {node: '>= 0.4'}
+ atomically@2.1.1:
+ resolution: {integrity: sha512-P4w9o2dqARji6P7MHprklbfiArZAWvo07yW7qs3pdljb3BWr12FIB7W+p0zJiuiVsUpRO0iZn1kFFcpPegg0tQ==}
+
available-typed-arrays@1.0.7:
resolution: {integrity: sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==}
engines: {node: '>= 0.4'}
@@ -1800,6 +2494,14 @@ packages:
base64-js@1.5.1:
resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==}
+ base64id@2.0.0:
+ resolution: {integrity: sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==}
+ engines: {node: ^4.5.0 || >= 5.9}
+
+ baseline-browser-mapping@2.9.19:
+ resolution: {integrity: sha512-ipDqC8FrAl/76p2SSWKSI+H9tFwm7vYqXQrItCuiVPt26Km0jS+NzSsBWAaBusvSbQcfJG+JitdMm+wZAgTYqg==}
+ hasBin: true
+
basic-ftp@5.0.5:
resolution: {integrity: sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==}
engines: {node: '>=10.0.0'}
@@ -1919,6 +2621,10 @@ packages:
resolution: {integrity: sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==}
engines: {node: '>=8'}
+ cli-cursor@5.0.0:
+ resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==}
+ engines: {node: '>=18'}
+
cli-spinners@2.9.2:
resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==}
engines: {node: '>=6'}
@@ -1959,9 +2665,17 @@ packages:
resolution: {integrity: sha512-yPVavfyCcRhmorC7rWlkHn15b4wDVgVmBA7kV4QVBsF7kv/9TKJAbAXVTxvTnwP8HHKjRCJDClKbciiYS7p0DQ==}
engines: {node: '>=16'}
+ commander@13.1.0:
+ resolution: {integrity: sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==}
+ engines: {node: '>=18'}
+
concat-map@0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==}
+ conf@15.1.0:
+ resolution: {integrity: sha512-Uy5YN9KEu0WWDaZAVJ5FAmZoaJt9rdK6kH+utItPyGsCqCgaTKkrmZx3zoE0/3q6S3bcp3Ihkk+ZqPxWxFK5og==}
+ engines: {node: '>=20'}
+
confbox@0.2.4:
resolution: {integrity: sha512-ysOGlgTFbN2/Y6Cg3Iye8YKulHw+R2fNXHrgSmXISQdMnomY6eNDprVdW9R5xBguEqI954+S6709UyiO7B+6OQ==}
@@ -1972,9 +2686,17 @@ packages:
constant-case@2.0.0:
resolution: {integrity: sha512-eS0N9WwmjTqrOmR3o83F5vW8Z+9R1HnVz3xmzT2PMFug9ly+Au/fxRWlEBSb6LcZwspSsEn9Xs1uw9YgzAg1EQ==}
+ cookie@0.7.2:
+ resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==}
+ engines: {node: '>= 0.6'}
+
core-js-pure@3.45.0:
resolution: {integrity: sha512-OtwjqcDpY2X/eIIg1ol/n0y/X8A9foliaNt1dSK0gV3J2/zw+89FcNG3mPK+N8YWts4ZFUPxnrAzsxs/lf8yDA==}
+ cors@2.8.6:
+ resolution: {integrity: sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==}
+ engines: {node: '>= 0.10'}
+
create-require@1.1.1:
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
@@ -1993,9 +2715,6 @@ packages:
resolution: {integrity: sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==}
engines: {node: '>=20'}
- csstype@3.1.3:
- resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==}
-
csstype@3.2.3:
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
@@ -2023,6 +2742,14 @@ packages:
resolution: {integrity: sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==}
engines: {node: '>= 0.4'}
+ debounce-fn@6.0.0:
+ resolution: {integrity: sha512-rBMW+F2TXryBwB54Q0d8drNEI+TfoS9JpNTAoVpukbWEhjXQq4rySFYLaqXMFXwdv61Zb2OHtj5bviSoimqxRQ==}
+ engines: {node: '>=18'}
+
+ debounce@2.2.0:
+ resolution: {integrity: sha512-Xks6RUDLZFdz8LIdR6q0MTH44k7FikOmnh5xkSjMig6ch45afc8sjTjRQf3P6ax8dMgcQrYO/AR2RGWURrruqw==}
+ engines: {node: '>=18'}
+
debug@4.4.1:
resolution: {integrity: sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==}
engines: {node: '>=6.0'}
@@ -2046,6 +2773,10 @@ packages:
resolution: {integrity: sha512-HOJkrhaYsweh+W+e74Yn7YStZOilkoPb6fycpwNLKzSPtruFs48nYis0zy5yJz1+ktUhHxoRDJ27RQAWLIJVJw==}
engines: {node: '>=16.0.0'}
+ deepmerge@4.3.1:
+ resolution: {integrity: sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==}
+ engines: {node: '>=0.10.0'}
+
defaults@1.0.4:
resolution: {integrity: sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==}
@@ -2098,9 +2829,30 @@ packages:
dom-accessibility-api@0.6.3:
resolution: {integrity: sha512-7ZgogeTnjuHbo+ct10G9Ffp0mif17idi0IyWNVA/wcwcm7NPOD/WEHVP3n7n3MhXqxoIYm8d6MuZohYWIZ4T3w==}
+ dom-serializer@2.0.0:
+ resolution: {integrity: sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==}
+
+ domelementtype@2.3.0:
+ resolution: {integrity: sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==}
+
+ domhandler@5.0.3:
+ resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==}
+ engines: {node: '>= 4'}
+
+ domutils@3.2.2:
+ resolution: {integrity: sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==}
+
dot-case@2.1.1:
resolution: {integrity: sha512-HnM6ZlFqcajLsyudHq7LeeLDr2rFAVYtDv/hV5qchQEidSck8j9OPUsXY9KwJv/lHMtYlX4DjRQqwFYa+0r8Ug==}
+ dot-prop@10.1.0:
+ resolution: {integrity: sha512-MVUtAugQMOff5RnBy2d9N31iG0lNwg1qAoAOn7pOK5wf94WIaE3My2p3uwTQuvS2AcqchkcR3bHByjaM0mmi7Q==}
+ engines: {node: '>=20'}
+
+ dotenv-cli@11.0.0:
+ resolution: {integrity: sha512-r5pA8idbk7GFWuHEU7trSTflWcdBpQEK+Aw17UrSHjS6CReuhrrPcyC3zcQBPQvhArRHnBo/h6eLH1fkCvNlww==}
+ hasBin: true
+
dotenv-cli@7.4.4:
resolution: {integrity: sha512-XkBYCG0tPIes+YZr4SpfFv76SQrV/LeCE8CI7JSEMi3VR9MvTihCGTOtbIexD6i2mXF+6px7trb1imVCXSNMDw==}
hasBin: true
@@ -2109,6 +2861,10 @@ packages:
resolution: {integrity: sha512-GopVGCpVS1UKH75VKHGuQFqS1Gusej0z4FyQkPdwjil2gNIv+LNsqBlboOzpJFZKVT95GkCyWJbBSdFEFUWI2A==}
engines: {node: '>=12'}
+ dotenv-expand@12.0.3:
+ resolution: {integrity: sha512-uc47g4b+4k/M/SeaW1y4OApx+mtLWl92l5LMPP0GNXctZqELk+YGgOPIIC5elYmUH4OuoK3JLhuRUYegeySiFA==}
+ engines: {node: '>=12'}
+
dotenv@16.0.3:
resolution: {integrity: sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==}
engines: {node: '>=12'}
@@ -2117,6 +2873,10 @@ packages:
resolution: {integrity: sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==}
engines: {node: '>=12'}
+ dotenv@17.3.1:
+ resolution: {integrity: sha512-IO8C/dzEb6O3F9/twg6ZLXz164a2fhTnEWb95H23Dm4OuN+92NmEAlTrupP9VW6Jm3sO26tQlqyvyi4CsnY9GA==}
+ engines: {node: '>=12'}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -2124,6 +2884,9 @@ packages:
effect@3.18.4:
resolution: {integrity: sha512-b1LXQJLe9D11wfnOKAk3PKxuqYshQ0Heez+y5pnkd3jLj1yx9QhM72zZ9uUrOQyNvrs2GZZd/3maL0ZV18YuDA==}
+ emoji-regex@10.6.0:
+ resolution: {integrity: sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -2131,14 +2894,30 @@ packages:
resolution: {integrity: sha512-i6UzDscO/XfAcNYD75CfICkmfLedpyPDdozrLMmQc5ORaQcdMoc21OnlEylMIqI7U8eniKrPMxxtj8k0vhmJhA==}
engines: {node: '>=14'}
+ engine.io-parser@5.2.3:
+ resolution: {integrity: sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==}
+ engines: {node: '>=10.0.0'}
+
+ engine.io@6.6.5:
+ resolution: {integrity: sha512-2RZdgEbXmp5+dVbRm0P7HQUImZpICccJy7rN7Tv+SFa55pH+lxnuw6/K1ZxxBfHoYpSkHLAO92oa8O4SwFXA2A==}
+ engines: {node: '>=10.2.0'}
+
enhanced-resolve@5.18.2:
resolution: {integrity: sha512-6Jw4sE1maoRJo3q8MsSIn2onJFbLTOjY9hlx4DZXmOKvLRd1Ok2kXmAGXaafL2+ijsJZ1ClYbl/pmqr9+k4iUQ==}
engines: {node: '>=10.13.0'}
+ entities@4.5.0:
+ resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
+ engines: {node: '>=0.12'}
+
entities@6.0.1:
resolution: {integrity: sha512-aN97NXWF6AWBTahfVOIrB/NShkzi5H7F9r1s9mD3cDj4Ko5f2qhhVoYMibXF7GlLveb/D2ioWay8lxI97Ven3g==}
engines: {node: '>=0.12'}
+ env-paths@3.0.0:
+ resolution: {integrity: sha512-dtJUTepzMW3Lm/NPxRf3wP4642UWhjL2sQxc+ym2YMj1m/H2zDNQOlezafzkHwn6sMstjHTwG6iQQsctDW/b1A==}
+ engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0}
+
es-abstract@1.24.0:
resolution: {integrity: sha512-WSzPgsdLtTcQwm4CROfS5ju2Wa1QQcVeT37jFjYzdFz1r9ahadC8B8/a4qxJxM+09F18iumCdRmlr96ZYkQvEg==}
engines: {node: '>= 0.4'}
@@ -2174,6 +2953,16 @@ packages:
resolution: {integrity: sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==}
engines: {node: '>= 0.4'}
+ esbuild@0.25.10:
+ resolution: {integrity: sha512-9RiGKvCwaqxO2owP61uQ4BgNborAQskMR6QusfWzQqv7AZOg5oGehdY2pRJMTKuwxd1IDBP4rSbI5lHzU7SMsQ==}
+ engines: {node: '>=18'}
+ hasBin: true
+
+ esbuild@0.25.12:
+ resolution: {integrity: sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==}
+ engines: {node: '>=18'}
+ hasBin: true
+
esbuild@0.27.3:
resolution: {integrity: sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==}
engines: {node: '>=18'}
@@ -2299,6 +3088,9 @@ packages:
resolution: {integrity: sha512-h5+1OzzfCC3Ef7VbtKdcv7zsstUQwUDlYpUTvjeUsJAssPgLn7QzbboPtL5ro04Mq0rPOsMzl7q5hIbRs2wD1A==}
engines: {node: '>=8.0.0'}
+ fast-deep-equal@2.0.1:
+ resolution: {integrity: sha512-bCK/2Z4zLidyB4ReuIsvALH6w31YfAQDmXMqMx6FyfHqvBxtjC0eRumeSu4Bs3XtXwpyIywtSTrVT99BxY1f9w==}
+
fast-deep-equal@3.1.3:
resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==}
@@ -2316,6 +3108,9 @@ packages:
fast-levenshtein@2.0.6:
resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==}
+ fast-uri@3.1.0:
+ resolution: {integrity: sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==}
+
fastq@1.19.1:
resolution: {integrity: sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==}
@@ -2400,6 +3195,10 @@ packages:
generate-function@2.3.1:
resolution: {integrity: sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==}
+ get-east-asian-width@1.4.0:
+ resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==}
+ engines: {node: '>=18'}
+
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -2438,6 +3237,12 @@ packages:
resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==}
engines: {node: '>=10.13.0'}
+ glob@11.1.0:
+ resolution: {integrity: sha512-vuNwKSaKiqm7g0THUBu2x7ckSs3XJLXE+2ssL7/MfTGPLLcrJQ/4Uq1CjPTtO5cCIiRxqvN6Twy1qOwhL0Xjcw==}
+ engines: {node: 20 || >=22}
+ deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me
+ hasBin: true
+
glob@13.0.0:
resolution: {integrity: sha512-tvZgpqk6fz4BaNZ66ZsRaZnbHvP/jG3uKJvAZOwEVUL4RTA5nJeeLYfyN9/VA8NX/V3IBG+hkeuGpKjvELkVhA==}
engines: {node: 20 || >=22}
@@ -2535,6 +3340,13 @@ packages:
html-escaper@2.0.2:
resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==}
+ html-to-text@9.0.5:
+ resolution: {integrity: sha512-qY60FjREgVZL03vJU6IfMV4GDjGBIoOyvuFdpBDIX9yTlDw0TjxVBQp+P8NvpdIXNJvfWBTNul7fsAQJq2FNpg==}
+ engines: {node: '>=14'}
+
+ htmlparser2@8.0.2:
+ resolution: {integrity: sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==}
+
http-proxy-agent@7.0.2:
resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==}
engines: {node: '>= 14'}
@@ -2667,6 +3479,10 @@ packages:
resolution: {integrity: sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==}
engines: {node: '>=8'}
+ is-interactive@2.0.0:
+ resolution: {integrity: sha512-qP1vozQRI+BMOPcjFzrjXuQvdak2pHNUMZoeG2eRbiSqyvbEf/wQtEOTOX1guk6E3t36RkaqiSt8A/6YElNxLQ==}
+ engines: {node: '>=12'}
+
is-lower-case@1.1.3:
resolution: {integrity: sha512-+5A1e/WJpLLXZEDlgz4G//WYSHyQBD32qa4Jd3Lw06qQlv3fJHnp3YIHjTQSGzHMgzmVKz2ZP3rBxTHkPw/lxA==}
@@ -2732,6 +3548,14 @@ packages:
resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==}
engines: {node: '>=10'}
+ is-unicode-supported@1.3.0:
+ resolution: {integrity: sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ==}
+ engines: {node: '>=12'}
+
+ is-unicode-supported@2.1.0:
+ resolution: {integrity: sha512-mE00Gnza5EEB3Ds0HfMyllZzbBrmLOX3vfWoj9A9PEnTfratQ/BcaJOuMhnkhjXvb2+FkY3VuHqtAGpTPmglFQ==}
+ engines: {node: '>=18'}
+
is-upper-case@1.1.2:
resolution: {integrity: sha512-GQYSJMgfeAmVwh9ixyk888l7OIhNAGKtY6QA+IrWlu9MDTCaXmeozOZ2S9Knj7bQwBO/H6J2kb+pbyTUiMNbsw==}
@@ -2773,6 +3597,14 @@ packages:
resolution: {integrity: sha512-H0dkQoCa3b2VEeKQBOxFph+JAbcrQdE7KC0UkqwpLmv2EC4P41QXP+rqo9wYodACiG5/WM5s9oDApTU8utwj9g==}
engines: {node: '>= 0.4'}
+ jackspeak@4.2.3:
+ resolution: {integrity: sha512-ykkVRwrYvFm1nb2AJfKKYPr0emF6IiXDYUaFx4Zn9ZuIH7MrzEZ3sD5RlqGXNRpHtvUHJyOnCEFxOlNDtGo7wg==}
+ engines: {node: 20 || >=22}
+
+ jiti@2.4.2:
+ resolution: {integrity: sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==}
+ hasBin: true
+
jiti@2.5.1:
resolution: {integrity: sha512-twQoecYPiVA5K/h6SxtORw/Bs3ar+mLUtoPSc7iMXzQzK8d7eJ/R09wmTwAjiamETn1cXYPGfNnu7DMoHgu12w==}
hasBin: true
@@ -2799,15 +3631,31 @@ packages:
canvas:
optional: true
+ jsesc@3.1.0:
+ resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==}
+ engines: {node: '>=6'}
+ hasBin: true
+
json-buffer@3.0.1:
resolution: {integrity: sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==}
json-schema-traverse@0.4.1:
resolution: {integrity: sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==}
+ json-schema-traverse@1.0.0:
+ resolution: {integrity: sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==}
+
+ json-schema-typed@8.0.2:
+ resolution: {integrity: sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==}
+
json-stable-stringify-without-jsonify@1.0.1:
resolution: {integrity: sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==}
+ json5@2.2.3:
+ resolution: {integrity: sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==}
+ engines: {node: '>=6'}
+ hasBin: true
+
jsonfile@6.1.0:
resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
@@ -2818,6 +3666,13 @@ packages:
keyv@4.5.4:
resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==}
+ kleur@3.0.3:
+ resolution: {integrity: sha512-eTIzlVOSUR+JxdDFepEYcBMtZ9Qqdef+rnzWdRZuMbOywu5tO2w2N7rqjoANZ5k9vywhL6Br1VRjUIgTQx4E8w==}
+ engines: {node: '>=6'}
+
+ leac@0.6.0:
+ resolution: {integrity: sha512-y+SqErxb8h7nE/fiEX07jsbuhrpO9lL8eca7/Y1nuWV2moNlXhyd59iDGcRf6moVyDMbmTNzL40SUyrFU/yDpg==}
+
levn@0.4.1:
resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
engines: {node: '>= 0.8.0'}
@@ -2912,6 +3767,14 @@ packages:
resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==}
engines: {node: '>=10'}
+ log-symbols@6.0.0:
+ resolution: {integrity: sha512-i24m8rpwhmPIS4zscNzK6MSEhk0DUWa/8iYQWxhffV8jkI4Phvs3F+quL5xvS0gdQR0FyTCMMH33Y78dDTzzIw==}
+ engines: {node: '>=18'}
+
+ log-symbols@7.0.1:
+ resolution: {integrity: sha512-ja1E3yCr9i/0hmBVaM0bfwDjnGy8I/s6PP4DFp+yP+a+mrHO4Rm7DtmnqROTUkHIkqffC84YY7AeqX6oFk0WFg==}
+ engines: {node: '>=18'}
+
long@5.3.2:
resolution: {integrity: sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==}
@@ -2962,6 +3825,11 @@ packages:
make-error@1.3.6:
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
+ marked@15.0.12:
+ resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==}
+ engines: {node: '>= 18'}
+ hasBin: true
+
math-intrinsics@1.1.0:
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
engines: {node: '>= 0.4'}
@@ -2980,10 +3848,30 @@ packages:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
+ mime-db@1.52.0:
+ resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
+ engines: {node: '>= 0.6'}
+
+ mime-db@1.54.0:
+ resolution: {integrity: sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@2.1.35:
+ resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
+ engines: {node: '>= 0.6'}
+
+ mime-types@3.0.2:
+ resolution: {integrity: sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==}
+ engines: {node: '>=18'}
+
mimic-fn@2.1.0:
resolution: {integrity: sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==}
engines: {node: '>=6'}
+ mimic-function@5.0.1:
+ resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==}
+ engines: {node: '>=18'}
+
min-indent@1.0.1:
resolution: {integrity: sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==}
engines: {node: '>=4'}
@@ -3058,6 +3946,10 @@ packages:
natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
+ negotiator@0.6.3:
+ resolution: {integrity: sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==}
+ engines: {node: '>= 0.6'}
+
neo-async@2.6.2:
resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==}
@@ -3114,6 +4006,27 @@ packages:
sass:
optional: true
+ next@16.1.6:
+ resolution: {integrity: sha512-hkyRkcu5x/41KoqnROkfTm2pZVbKxvbZRuNvKXLRXxs3VfyO0WhY50TQS40EuKO9SW3rBj/sF3WbVwDACeMZyw==}
+ engines: {node: '>=20.9.0'}
+ hasBin: true
+ peerDependencies:
+ '@opentelemetry/api': ^1.1.0
+ '@playwright/test': ^1.51.1
+ babel-plugin-react-compiler: '*'
+ react: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ react-dom: ^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0
+ sass: ^1.3.0
+ peerDependenciesMeta:
+ '@opentelemetry/api':
+ optional: true
+ '@playwright/test':
+ optional: true
+ babel-plugin-react-compiler:
+ optional: true
+ sass:
+ optional: true
+
no-case@2.3.2:
resolution: {integrity: sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==}
@@ -3133,6 +4046,10 @@ packages:
resolution: {integrity: sha512-Cov028YhBZ5aB7MdMWJEmwyBig43aGL5WT4vdoB28Oitau1zZAcHUn8Sgfk9HM33TqhtLJ9PlM/O0Mv+QpV/4Q==}
engines: {node: '>=8.9.4'}
+ nodemailer@7.0.13:
+ resolution: {integrity: sha512-PNDFSJdP+KFgdsG3ZzMXCgquO7I6McjY2vlqILjtJd0hy8wEvtugS9xKRF2NWlPNGxvLCXlTNIae4serI7dinw==}
+ engines: {node: '>=6.0.0'}
+
normalize-path@3.0.0:
resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==}
engines: {node: '>=0.10.0'}
@@ -3141,6 +4058,11 @@ packages:
resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==}
engines: {node: '>=8'}
+ nypm@0.6.2:
+ resolution: {integrity: sha512-7eM+hpOtrKrBDCh7Ypu2lJ9Z7PNZBdi/8AT3AX8xoCj43BBVHD0hPSTEvMtkMpfs8FCqBGhxB+uToIQimA111g==}
+ engines: {node: ^14.16.0 || >=16.10.0}
+ hasBin: true
+
nypm@0.6.5:
resolution: {integrity: sha512-K6AJy1GMVyfyMXRVB88700BJqNUkByijGJM8kEHpLdcAt+vSQAVfkWWHYzuRXHSY6xA2sNc5RjTj0p9rE2izVQ==}
engines: {node: '>=18'}
@@ -3187,6 +4109,10 @@ packages:
resolution: {integrity: sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==}
engines: {node: '>=6'}
+ onetime@7.0.0:
+ resolution: {integrity: sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==}
+ engines: {node: '>=18'}
+
optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
engines: {node: '>= 0.8.0'}
@@ -3199,6 +4125,10 @@ packages:
resolution: {integrity: sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==}
engines: {node: '>=10'}
+ ora@8.2.0:
+ resolution: {integrity: sha512-weP+BZ8MVNnlCm8c0Qdc1WSWq4Qn7I+9CJGm7Qali6g44e/PUzbjNqJX5NJ9ljlNMosfJvg1fKEGILklK9cwnw==}
+ engines: {node: '>=18'}
+
os-tmpdir@1.0.2:
resolution: {integrity: sha512-D2FR03Vir7FIu45XBY20mTb+/ZSWB00sjU9jdQXt83gDrI4Ztz5Fs7/yy74g2N5SVQY4xY1qDr4rNddwYRVX0g==}
engines: {node: '>=0.10.0'}
@@ -3240,6 +4170,9 @@ packages:
parse5@8.0.0:
resolution: {integrity: sha512-9m4m5GSgXjL4AjumKzq1Fgfp3Z8rsvjRNbnkVwfu2ImRqE5D0LnY2QfDen18FSY9C573YU5XxSapdHZTZ2WolA==}
+ parseley@0.12.1:
+ resolution: {integrity: sha512-e6qHKe3a9HWr0oMRVDTRhKce+bRO8VGQR3NyVwcjwrbhMmFCX9KszEV35+rn4AdilFAq9VPxP/Fe1wC9Qjd2lw==}
+
pascal-case@2.0.1:
resolution: {integrity: sha512-qjS4s8rBOJa2Xm0jmxXiyh1+OFf6ekCWOvUaRgAQSktzlTbMotS0nmG9gyYAybCWBcuP4fsBeRCKNwGBnMe2OQ==}
@@ -3272,6 +4205,9 @@ packages:
pathe@2.0.3:
resolution: {integrity: sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==}
+ peberminta@0.9.0:
+ resolution: {integrity: sha512-XIxfHpEuSJbITd1H3EeQwpcZbTLHc+VVr8ANI9t5sit565tsI4/xK3KWTUFE2e6QiangUkh3B0jihzmGnNrRsQ==}
+
perfect-debounce@1.0.0:
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
@@ -3384,6 +4320,14 @@ packages:
typescript:
optional: true
+ prismjs@1.30.0:
+ resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
+ engines: {node: '>=6'}
+
+ prompts@2.4.2:
+ resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==}
+ engines: {node: '>= 6'}
+
prop-types@15.8.1:
resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==}
@@ -3424,9 +4368,22 @@ packages:
peerDependencies:
react: ^19.2.0
+ react-dom@19.2.4:
+ resolution: {integrity: sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==}
+ peerDependencies:
+ react: ^19.2.4
+
+ react-email@5.2.8:
+ resolution: {integrity: sha512-noPcnpl78vsyBnhiKCzxK9Mdsv7ncAYI80osS5kbMgaKH2IgPtPab5BzLJX6INXuiNk5ju+9YRnCjPoPTOHZjA==}
+ engines: {node: '>=20.0.0'}
+ hasBin: true
+
react-is@16.13.1:
resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==}
+ react-promise-suspense@0.3.4:
+ resolution: {integrity: sha512-I42jl7L3Ze6kZaq+7zXWSunBa3b1on5yfvUW6Eo/3fFOj6dZ5Bqmcd264nJbTK/gn1HjjILAjSwnZbV4RpSaNQ==}
+
react@19.1.1:
resolution: {integrity: sha512-w8nqGImo45dmMIfljjMwOGtbmC/mk4CMYhWIicdSflH91J9TyCyczcPFXJzrZ/ZXcgGRFeP6BU0BEJTw6tZdfQ==}
engines: {node: '>=0.10.0'}
@@ -3435,6 +4392,10 @@ packages:
resolution: {integrity: sha512-tmbWg6W31tQLeB5cdIBOicJDJRR2KzXsV7uSK9iNfLWQ5bIZfxuPEHp7M8wiHyHnn0DD1i7w3Zmin0FtkrwoCQ==}
engines: {node: '>=0.10.0'}
+ react@19.2.4:
+ resolution: {integrity: sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==}
+ engines: {node: '>=0.10.0'}
+
readable-stream@3.6.2:
resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==}
engines: {node: '>= 6'}
@@ -3476,6 +4437,10 @@ packages:
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
engines: {node: '>=0.10.0'}
+ resend@4.8.0:
+ resolution: {integrity: sha512-R8eBOFQDO6dzRTDmaMEdpqrkmgSjPpVXt4nGfWsZdYOet0kqra0xgbvTES6HmCriZEXbmGk3e0DiGIaLFTFSHA==}
+ engines: {node: '>=18'}
+
resolve-from@4.0.0:
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
engines: {node: '>=4'}
@@ -3496,6 +4461,10 @@ packages:
resolution: {integrity: sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==}
engines: {node: '>=8'}
+ restore-cursor@5.1.0:
+ resolution: {integrity: sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==}
+ engines: {node: '>=18'}
+
retry@0.12.0:
resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==}
engines: {node: '>= 4'}
@@ -3561,6 +4530,9 @@ packages:
scheduler@0.27.0:
resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==}
+ selderee@0.11.0:
+ resolution: {integrity: sha512-5TF+l7p4+OsnP8BCCvSyZiSPc4x4//p5uPwK8TCnVPJYRmU2aYKMpOXvw8zM5a5JvuuCGN1jmsMwuU2W02ukfA==}
+
semver@6.3.1:
resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==}
hasBin: true
@@ -3636,6 +4608,9 @@ packages:
resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==}
engines: {node: '>=14'}
+ sisteransi@1.0.5:
+ resolution: {integrity: sha512-bLGGlR1QxBcynn2d5YmDX4MGjlZvy2MRBDRNHLJ8VI6l6+9FUiyTFNJ0IveOSP0bcXgVDPRcfGqA0pjaqUpfVg==}
+
slash@3.0.0:
resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==}
engines: {node: '>=8'}
@@ -3647,6 +4622,17 @@ packages:
snake-case@2.1.0:
resolution: {integrity: sha512-FMR5YoPFwOLuh4rRz92dywJjyKYZNLpMn1R5ujVpIYkbA9p01fq8RMg0FkO4M+Yobt4MjHeLTJVm5xFFBHSV2Q==}
+ socket.io-adapter@2.5.6:
+ resolution: {integrity: sha512-DkkO/dz7MGln0dHn5bmN3pPy+JmywNICWrJqVWiVOyvXjWQFIv9c2h24JrQLLFJ2aQVQf/Cvl1vblnd4r2apLQ==}
+
+ socket.io-parser@4.2.5:
+ resolution: {integrity: sha512-bPMmpy/5WWKHea5Y/jYAP6k74A+hvmRCQaJuJB6I/ML5JZq/KfNieUVo/3Mh7SAqn7TyFdIo6wqYHInG1MU1bQ==}
+ engines: {node: '>=10.0.0'}
+
+ socket.io@4.8.3:
+ resolution: {integrity: sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==}
+ engines: {node: '>=10.2.0'}
+
socks-proxy-agent@8.0.5:
resolution: {integrity: sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==}
engines: {node: '>= 14'}
@@ -3691,6 +4677,10 @@ packages:
std-env@3.10.0:
resolution: {integrity: sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==}
+ stdin-discarder@0.2.2:
+ resolution: {integrity: sha512-UhDfHmA92YAlNnCfhmq0VeNL5bDbiZGg7sZ2IvPsXubGkiNa9EC+tUTsjBRsYUAz87btI6/1wf4XoVvQ3uRnmQ==}
+ engines: {node: '>=18'}
+
stop-iteration-iterator@1.1.0:
resolution: {integrity: sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==}
engines: {node: '>= 0.4'}
@@ -3699,6 +4689,10 @@ packages:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
+ string-width@7.2.0:
+ resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==}
+ engines: {node: '>=18'}
+
string.prototype.matchall@4.0.12:
resolution: {integrity: sha512-6CC9uyBL+/48dYizRf7H7VAYCMCNTBeM78x/VTUe9bFEaxBepPJDa1Ow99LqI/1yF7kuy7Q3cQsYMrcjGUcskA==}
engines: {node: '>= 0.4'}
@@ -3725,6 +4719,14 @@ packages:
resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==}
engines: {node: '>=8'}
+ strip-ansi@7.1.2:
+ resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==}
+ engines: {node: '>=12'}
+
+ strip-bom@3.0.0:
+ resolution: {integrity: sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==}
+ engines: {node: '>=4'}
+
strip-final-newline@2.0.0:
resolution: {integrity: sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==}
engines: {node: '>=6'}
@@ -3741,6 +4743,12 @@ packages:
resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==}
engines: {node: '>=8'}
+ stubborn-fs@2.0.0:
+ resolution: {integrity: sha512-Y0AvSwDw8y+nlSNFXMm2g6L51rBGdAQT20J3YSOqxC53Lo3bjWRtr2BKcfYoAf352WYpsZSTURrA0tqhfgudPA==}
+
+ stubborn-utils@1.0.2:
+ resolution: {integrity: sha512-zOh9jPYI+xrNOyisSelgym4tolKTJCQd5GBhK0+0xJvcYDcwlOoxF/rnFKQ2KRZknXSG9jWAp66fwP6AxN9STg==}
+
styled-jsx@5.1.6:
resolution: {integrity: sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==}
engines: {node: '>= 12.0.0'}
@@ -3772,12 +4780,19 @@ packages:
symbol-tree@3.2.4:
resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==}
+ tagged-tag@1.0.0:
+ resolution: {integrity: sha512-yEFYrVhod+hdNyx7g5Bnkkb0G6si8HJurOoOEgC8B/O0uXLHlaey/65KRv6cuWBNhBgHKAROVpc7QyYqE5gFng==}
+ engines: {node: '>=20'}
+
tailwind-merge@3.3.1:
resolution: {integrity: sha512-gBXpgUm/3rp1lMZZrM/w7D8GKqshif0zAymAhbCyIt8KMe+0v9DQ7cdYLR4FHH/cKpdTXb+A/tKKU3eolfsI+g==}
tailwindcss@4.1.11:
resolution: {integrity: sha512-2E9TBm6MDD/xKYe+dvJZAmg3yxIEDNRc0jwlNyDg/4Fil2QcSLjFKGVff0lAf1jjeaArlG/M75Ey/EYr/OJtBA==}
+ tailwindcss@4.1.18:
+ resolution: {integrity: sha512-4+Z+0yiYyEtUVCScyfHCxOYP06L5Ne+JiHhY2IjR2KWMIWhJOYZKLSGZaP5HkZ8+bY0cxfzwDE5uOmzFXyIwxw==}
+
tapable@2.2.2:
resolution: {integrity: sha512-Re10+NauLTMCudc7T5WLFLAwDhQ0JWdrMK+9B2M8zR5hRExKmsRDCBA7/aV/pNJFltmBFO5BAMlQFi/vq3nKOg==}
engines: {node: '>=6'}
@@ -3870,6 +4885,10 @@ packages:
typescript:
optional: true
+ tsconfig-paths@4.2.0:
+ resolution: {integrity: sha512-NoZ4roiN7LnbKn9QqE1amc9DJfzvZXxF4xDavcOWt1BPkdx+m+0gJuPM+S0vCe7zTJMYUP0R8pO2XMr+Y8oLIg==}
+ engines: {node: '>=6'}
+
tslib@1.14.1:
resolution: {integrity: sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==}
@@ -3926,6 +4945,10 @@ packages:
resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==}
engines: {node: '>=10'}
+ type-fest@5.4.4:
+ resolution: {integrity: sha512-JnTrzGu+zPV3aXIUhnyWJj4z/wigMsdYajGLIYakqyOW1nPllzXEJee0QQbHj+CTIQtXGlAjuK0UY+2xTyjVAw==}
+ engines: {node: '>=20'}
+
typed-array-buffer@1.0.3:
resolution: {integrity: sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==}
engines: {node: '>= 0.4'}
@@ -3959,6 +4982,10 @@ packages:
engines: {node: '>=0.8.0'}
hasBin: true
+ uint8array-extras@1.5.0:
+ resolution: {integrity: sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==}
+ engines: {node: '>=18'}
+
unbox-primitive@1.1.0:
resolution: {integrity: sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==}
engines: {node: '>= 0.4'}
@@ -4004,6 +5031,10 @@ packages:
resolution: {integrity: sha512-OljLrQ9SQdOUqTaQxqL5dEfZWrXExyyWsozYlAWFawPVNuD83igl7uJD2RTkNMbniIYgt8l81eCJGIdQF7avLQ==}
engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0}
+ vary@1.1.2:
+ resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==}
+ engines: {node: '>= 0.8'}
+
vite-tsconfig-paths@6.1.1:
resolution: {integrity: sha512-2cihq7zliibCCZ8P9cKJrQBkfgdvcFkOOc3Y02o3GWUDLgqjWsZudaoiuOwO/gzTzy17cS5F7ZPo4bsnS4DGkg==}
peerDependencies:
@@ -4106,6 +5137,9 @@ packages:
resolution: {integrity: sha512-9CcxtEKsf53UFwkSUZjG+9vydAsFO4lFHBpJUtjBcoJOCJpKnSJNwCw813zrYJHpCJ7sgfbtOe0V5Ku7Pa1XMQ==}
engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0}
+ when-exit@2.1.5:
+ resolution: {integrity: sha512-VGkKJ564kzt6Ms1dbgPP/yuIoQCrsFAnRbptpC5wOEsDaNsbCB2bnfnaA8i/vRs5tjUSEOtIuvl9/MyVsvQZCg==}
+
which-boxed-primitive@1.1.1:
resolution: {integrity: sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==}
engines: {node: '>= 0.4'}
@@ -4146,6 +5180,18 @@ packages:
wrappy@1.0.2:
resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==}
+ ws@8.18.3:
+ resolution: {integrity: sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==}
+ engines: {node: '>=10.0.0'}
+ peerDependencies:
+ bufferutil: ^4.0.1
+ utf-8-validate: '>=5.0.2'
+ peerDependenciesMeta:
+ bufferutil:
+ optional: true
+ utf-8-validate:
+ optional: true
+
xml-name-validator@5.0.0:
resolution: {integrity: sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==}
engines: {node: '>=18'}
@@ -4172,6 +5218,10 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
+ yoctocolors@2.1.2:
+ resolution: {integrity: sha512-CzhO+pFNo8ajLM2d2IW/R93ipy99LWjtwblvC1RsoSUMZgyLbYFr221TnSNT7GjGdYui6P459mw9JH/g/zW2ug==}
+ engines: {node: '>=18'}
+
zeptomatch@2.1.0:
resolution: {integrity: sha512-KiGErG2J0G82LSpniV0CtIzjlJ10E04j02VOudJsPyPwNZgGnRKQy7I1R7GMyg/QswnE4l7ohSGrQbQbjXPPDA==}
@@ -4209,6 +5259,22 @@ snapshots:
'@asamuzakjp/nwsapi@2.3.9': {}
+ '@babel/code-frame@7.29.0':
+ dependencies:
+ '@babel/helper-validator-identifier': 7.28.5
+ js-tokens: 4.0.0
+ picocolors: 1.1.1
+
+ '@babel/generator@7.29.1':
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+ '@jridgewell/gen-mapping': 0.3.12
+ '@jridgewell/trace-mapping': 0.3.31
+ jsesc: 3.1.0
+
+ '@babel/helper-globals@7.28.0': {}
+
'@babel/helper-string-parser@7.27.1': {}
'@babel/helper-validator-identifier@7.28.5': {}
@@ -4221,6 +5287,24 @@ snapshots:
dependencies:
core-js-pure: 3.45.0
+ '@babel/template@7.28.6':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/parser': 7.29.0
+ '@babel/types': 7.29.0
+
+ '@babel/traverse@7.29.0':
+ dependencies:
+ '@babel/code-frame': 7.29.0
+ '@babel/generator': 7.29.1
+ '@babel/helper-globals': 7.28.0
+ '@babel/parser': 7.29.0
+ '@babel/template': 7.28.6
+ '@babel/types': 7.29.0
+ debug: 4.4.1
+ transitivePeerDependencies:
+ - supports-color
+
'@babel/types@7.29.0':
dependencies:
'@babel/helper-string-parser': 7.27.1
@@ -4284,81 +5368,237 @@ snapshots:
tslib: 2.8.1
optional: true
+ '@esbuild/aix-ppc64@0.25.10':
+ optional: true
+
+ '@esbuild/aix-ppc64@0.25.12':
+ optional: true
+
'@esbuild/aix-ppc64@0.27.3':
optional: true
+ '@esbuild/android-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/android-arm64@0.25.12':
+ optional: true
+
'@esbuild/android-arm64@0.27.3':
optional: true
+ '@esbuild/android-arm@0.25.10':
+ optional: true
+
+ '@esbuild/android-arm@0.25.12':
+ optional: true
+
'@esbuild/android-arm@0.27.3':
optional: true
+ '@esbuild/android-x64@0.25.10':
+ optional: true
+
+ '@esbuild/android-x64@0.25.12':
+ optional: true
+
'@esbuild/android-x64@0.27.3':
optional: true
+ '@esbuild/darwin-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/darwin-arm64@0.25.12':
+ optional: true
+
'@esbuild/darwin-arm64@0.27.3':
optional: true
+ '@esbuild/darwin-x64@0.25.10':
+ optional: true
+
+ '@esbuild/darwin-x64@0.25.12':
+ optional: true
+
'@esbuild/darwin-x64@0.27.3':
optional: true
+ '@esbuild/freebsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/freebsd-arm64@0.25.12':
+ optional: true
+
'@esbuild/freebsd-arm64@0.27.3':
optional: true
+ '@esbuild/freebsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/freebsd-x64@0.25.12':
+ optional: true
+
'@esbuild/freebsd-x64@0.27.3':
optional: true
+ '@esbuild/linux-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-arm64@0.25.12':
+ optional: true
+
'@esbuild/linux-arm64@0.27.3':
optional: true
+ '@esbuild/linux-arm@0.25.10':
+ optional: true
+
+ '@esbuild/linux-arm@0.25.12':
+ optional: true
+
'@esbuild/linux-arm@0.27.3':
optional: true
+ '@esbuild/linux-ia32@0.25.10':
+ optional: true
+
+ '@esbuild/linux-ia32@0.25.12':
+ optional: true
+
'@esbuild/linux-ia32@0.27.3':
optional: true
+ '@esbuild/linux-loong64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-loong64@0.25.12':
+ optional: true
+
'@esbuild/linux-loong64@0.27.3':
optional: true
+ '@esbuild/linux-mips64el@0.25.10':
+ optional: true
+
+ '@esbuild/linux-mips64el@0.25.12':
+ optional: true
+
'@esbuild/linux-mips64el@0.27.3':
optional: true
+ '@esbuild/linux-ppc64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-ppc64@0.25.12':
+ optional: true
+
'@esbuild/linux-ppc64@0.27.3':
optional: true
+ '@esbuild/linux-riscv64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-riscv64@0.25.12':
+ optional: true
+
'@esbuild/linux-riscv64@0.27.3':
optional: true
+ '@esbuild/linux-s390x@0.25.10':
+ optional: true
+
+ '@esbuild/linux-s390x@0.25.12':
+ optional: true
+
'@esbuild/linux-s390x@0.27.3':
optional: true
+ '@esbuild/linux-x64@0.25.10':
+ optional: true
+
+ '@esbuild/linux-x64@0.25.12':
+ optional: true
+
'@esbuild/linux-x64@0.27.3':
optional: true
+ '@esbuild/netbsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/netbsd-arm64@0.25.12':
+ optional: true
+
'@esbuild/netbsd-arm64@0.27.3':
optional: true
+ '@esbuild/netbsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/netbsd-x64@0.25.12':
+ optional: true
+
'@esbuild/netbsd-x64@0.27.3':
optional: true
+ '@esbuild/openbsd-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/openbsd-arm64@0.25.12':
+ optional: true
+
'@esbuild/openbsd-arm64@0.27.3':
optional: true
+ '@esbuild/openbsd-x64@0.25.10':
+ optional: true
+
+ '@esbuild/openbsd-x64@0.25.12':
+ optional: true
+
'@esbuild/openbsd-x64@0.27.3':
optional: true
+ '@esbuild/openharmony-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/openharmony-arm64@0.25.12':
+ optional: true
+
'@esbuild/openharmony-arm64@0.27.3':
optional: true
+ '@esbuild/sunos-x64@0.25.10':
+ optional: true
+
+ '@esbuild/sunos-x64@0.25.12':
+ optional: true
+
'@esbuild/sunos-x64@0.27.3':
optional: true
+ '@esbuild/win32-arm64@0.25.10':
+ optional: true
+
+ '@esbuild/win32-arm64@0.25.12':
+ optional: true
+
'@esbuild/win32-arm64@0.27.3':
optional: true
+ '@esbuild/win32-ia32@0.25.10':
+ optional: true
+
+ '@esbuild/win32-ia32@0.25.12':
+ optional: true
+
'@esbuild/win32-ia32@0.27.3':
optional: true
+ '@esbuild/win32-x64@0.25.10':
+ optional: true
+
+ '@esbuild/win32-x64@0.25.12':
+ optional: true
+
'@esbuild/win32-x64@0.27.3':
optional: true
@@ -4560,6 +5800,8 @@ snapshots:
dependencies:
'@isaacs/balanced-match': 4.0.1
+ '@isaacs/cliui@9.0.0': {}
+
'@isaacs/fs-minipass@4.0.1':
dependencies:
minipass: 7.1.2
@@ -4599,6 +5841,8 @@ snapshots:
'@next/env@16.0.7': {}
+ '@next/env@16.1.6': {}
+
'@next/eslint-plugin-next@15.4.5':
dependencies:
fast-glob: 3.3.1
@@ -4609,48 +5853,72 @@ snapshots:
'@next/swc-darwin-arm64@16.0.7':
optional: true
+ '@next/swc-darwin-arm64@16.1.6':
+ optional: true
+
'@next/swc-darwin-x64@15.5.7':
optional: true
'@next/swc-darwin-x64@16.0.7':
optional: true
+ '@next/swc-darwin-x64@16.1.6':
+ optional: true
+
'@next/swc-linux-arm64-gnu@15.5.7':
optional: true
'@next/swc-linux-arm64-gnu@16.0.7':
optional: true
+ '@next/swc-linux-arm64-gnu@16.1.6':
+ optional: true
+
'@next/swc-linux-arm64-musl@15.5.7':
optional: true
'@next/swc-linux-arm64-musl@16.0.7':
optional: true
+ '@next/swc-linux-arm64-musl@16.1.6':
+ optional: true
+
'@next/swc-linux-x64-gnu@15.5.7':
optional: true
'@next/swc-linux-x64-gnu@16.0.7':
optional: true
+ '@next/swc-linux-x64-gnu@16.1.6':
+ optional: true
+
'@next/swc-linux-x64-musl@15.5.7':
optional: true
'@next/swc-linux-x64-musl@16.0.7':
optional: true
+ '@next/swc-linux-x64-musl@16.1.6':
+ optional: true
+
'@next/swc-win32-arm64-msvc@15.5.7':
optional: true
'@next/swc-win32-arm64-msvc@16.0.7':
optional: true
+ '@next/swc-win32-arm64-msvc@16.1.6':
+ optional: true
+
'@next/swc-win32-x64-msvc@15.5.7':
optional: true
'@next/swc-win32-x64-msvc@16.0.7':
optional: true
+ '@next/swc-win32-x64-msvc@16.1.6':
+ optional: true
+
'@nodelib/fs.scandir@2.1.5':
dependencies:
'@nodelib/fs.stat': 2.0.5
@@ -4673,6 +5941,13 @@ snapshots:
'@prisma/client-runtime-utils@7.4.0': {}
+ '@prisma/client@7.4.0(prisma@7.4.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.2))(typescript@5.9.2)':
+ dependencies:
+ '@prisma/client-runtime-utils': 7.4.0
+ optionalDependencies:
+ prisma: 7.4.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.2)
+ typescript: 5.9.2
+
'@prisma/client@7.4.0(prisma@7.4.0(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.2))(typescript@5.9.2)':
dependencies:
'@prisma/client-runtime-utils': 7.4.0
@@ -4744,11 +6019,18 @@ snapshots:
'@prisma/query-plan-executor@7.2.0': {}
+ '@prisma/studio-core@0.13.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@types/react': 19.2.14
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+
'@prisma/studio-core@0.13.1(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)':
dependencies:
'@types/react': 19.2.7
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
+ optional: true
'@radix-ui/primitive@1.1.3': {}
@@ -4848,32 +6130,178 @@ snapshots:
'@types/react': 19.1.9
'@types/react-dom': 19.1.7(@types/react@19.1.9)
- '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)':
+ '@radix-ui/react-use-callback-ref@1.1.1(@types/react@19.1.9)(react@19.1.1)':
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ '@types/react': 19.1.9
+
+ '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)':
+ dependencies:
+ '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1)
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ '@types/react': 19.1.9
+
+ '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)':
+ dependencies:
+ '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
+ react: 19.1.1
+ optionalDependencies:
+ '@types/react': 19.1.9
+
+ '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)':
+ dependencies:
+ react: 19.1.1
+ optionalDependencies:
+ '@types/react': 19.1.9
+
+ '@react-email/body@0.2.1(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/button@0.2.1(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/code-block@0.2.1(react@19.2.4)':
+ dependencies:
+ prismjs: 1.30.0
+ react: 19.2.4
+
+ '@react-email/code-inline@0.0.6(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/column@0.0.14(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/components@1.0.7(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@react-email/body': 0.2.1(react@19.2.4)
+ '@react-email/button': 0.2.1(react@19.2.4)
+ '@react-email/code-block': 0.2.1(react@19.2.4)
+ '@react-email/code-inline': 0.0.6(react@19.2.4)
+ '@react-email/column': 0.0.14(react@19.2.4)
+ '@react-email/container': 0.0.16(react@19.2.4)
+ '@react-email/font': 0.0.10(react@19.2.4)
+ '@react-email/head': 0.0.13(react@19.2.4)
+ '@react-email/heading': 0.0.16(react@19.2.4)
+ '@react-email/hr': 0.0.12(react@19.2.4)
+ '@react-email/html': 0.0.12(react@19.2.4)
+ '@react-email/img': 0.0.12(react@19.2.4)
+ '@react-email/link': 0.0.13(react@19.2.4)
+ '@react-email/markdown': 0.0.18(react@19.2.4)
+ '@react-email/preview': 0.0.14(react@19.2.4)
+ '@react-email/render': 2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ '@react-email/row': 0.0.13(react@19.2.4)
+ '@react-email/section': 0.0.17(react@19.2.4)
+ '@react-email/tailwind': 2.0.4(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)
+ '@react-email/text': 0.1.6(react@19.2.4)
+ react: 19.2.4
+ transitivePeerDependencies:
+ - react-dom
+
+ '@react-email/container@0.0.16(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/font@0.0.10(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/head@0.0.13(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/heading@0.0.16(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/hr@0.0.12(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/html@0.0.12(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/img@0.0.12(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/link@0.0.13(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/markdown@0.0.18(react@19.2.4)':
+ dependencies:
+ marked: 15.0.12
+ react: 19.2.4
+
+ '@react-email/preview-server@5.2.8(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ esbuild: 0.25.10
+ next: 16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ transitivePeerDependencies:
+ - '@babel/core'
+ - '@opentelemetry/api'
+ - '@playwright/test'
+ - babel-plugin-macros
+ - babel-plugin-react-compiler
+ - react
+ - react-dom
+ - sass
+
+ '@react-email/preview@0.0.14(react@19.2.4)':
dependencies:
- react: 19.1.1
- optionalDependencies:
- '@types/react': 19.1.9
+ react: 19.2.4
- '@radix-ui/react-use-controllable-state@1.2.2(@types/react@19.1.9)(react@19.1.1)':
+ '@react-email/render@1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@radix-ui/react-use-effect-event': 0.0.2(@types/react@19.1.9)(react@19.1.1)
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
- react: 19.1.1
- optionalDependencies:
- '@types/react': 19.1.9
+ html-to-text: 9.0.5
+ prettier: 3.6.2
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ react-promise-suspense: 0.3.4
- '@radix-ui/react-use-effect-event@0.0.2(@types/react@19.1.9)(react@19.1.1)':
+ '@react-email/render@2.0.4(react-dom@19.2.4(react@19.2.4))(react@19.2.4)':
dependencies:
- '@radix-ui/react-use-layout-effect': 1.1.1(@types/react@19.1.9)(react@19.1.1)
- react: 19.1.1
- optionalDependencies:
- '@types/react': 19.1.9
+ html-to-text: 9.0.5
+ prettier: 3.6.2
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
- '@radix-ui/react-use-layout-effect@1.1.1(@types/react@19.1.9)(react@19.1.1)':
+ '@react-email/row@0.0.13(react@19.2.4)':
dependencies:
- react: 19.1.1
+ react: 19.2.4
+
+ '@react-email/section@0.0.17(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
+
+ '@react-email/tailwind@2.0.4(@react-email/body@0.2.1(react@19.2.4))(@react-email/button@0.2.1(react@19.2.4))(@react-email/code-block@0.2.1(react@19.2.4))(@react-email/code-inline@0.0.6(react@19.2.4))(@react-email/container@0.0.16(react@19.2.4))(@react-email/heading@0.0.16(react@19.2.4))(@react-email/hr@0.0.12(react@19.2.4))(@react-email/img@0.0.12(react@19.2.4))(@react-email/link@0.0.13(react@19.2.4))(@react-email/preview@0.0.14(react@19.2.4))(@react-email/text@0.1.6(react@19.2.4))(react@19.2.4)':
+ dependencies:
+ '@react-email/text': 0.1.6(react@19.2.4)
+ react: 19.2.4
+ tailwindcss: 4.1.18
optionalDependencies:
- '@types/react': 19.1.9
+ '@react-email/body': 0.2.1(react@19.2.4)
+ '@react-email/button': 0.2.1(react@19.2.4)
+ '@react-email/code-block': 0.2.1(react@19.2.4)
+ '@react-email/code-inline': 0.0.6(react@19.2.4)
+ '@react-email/container': 0.0.16(react@19.2.4)
+ '@react-email/heading': 0.0.16(react@19.2.4)
+ '@react-email/hr': 0.0.12(react@19.2.4)
+ '@react-email/img': 0.0.12(react@19.2.4)
+ '@react-email/link': 0.0.13(react@19.2.4)
+ '@react-email/preview': 0.0.14(react@19.2.4)
+
+ '@react-email/text@0.1.6(react@19.2.4)':
+ dependencies:
+ react: 19.2.4
'@rollup/rollup-android-arm-eabi@4.54.0':
optional: true
@@ -4941,6 +6369,13 @@ snapshots:
'@rollup/rollup-win32-x64-msvc@4.54.0':
optional: true
+ '@selderee/plugin-htmlparser2@0.11.0':
+ dependencies:
+ domhandler: 5.0.3
+ selderee: 0.11.0
+
+ '@socket.io/component-emitter@3.1.2': {}
+
'@standard-schema/spec@1.1.0': {}
'@swc/helpers@0.5.15':
@@ -5090,6 +6525,10 @@ snapshots:
'@types/deep-eql': 4.0.2
assertion-error: 2.0.1
+ '@types/cors@2.8.19':
+ dependencies:
+ '@types/node': 20.19.9
+
'@types/deep-eql@4.0.2': {}
'@types/estree@1.0.8': {}
@@ -5115,12 +6554,16 @@ snapshots:
'@types/minimatch@6.0.0':
dependencies:
- minimatch: 9.0.5
+ minimatch: 10.1.1
'@types/node@20.19.9':
dependencies:
undici-types: 6.21.0
+ '@types/nodemailer@6.4.22':
+ dependencies:
+ '@types/node': 20.19.9
+
'@types/pg@8.16.0':
dependencies:
'@types/node': 20.19.9
@@ -5131,13 +6574,21 @@ snapshots:
dependencies:
'@types/react': 19.1.9
+ '@types/react-dom@19.2.3(@types/react@19.2.14)':
+ dependencies:
+ '@types/react': 19.2.14
+
'@types/react-dom@19.2.3(@types/react@19.2.7)':
dependencies:
'@types/react': 19.2.7
'@types/react@19.1.9':
dependencies:
- csstype: 3.1.3
+ csstype: 3.2.3
+
+ '@types/react@19.2.14':
+ dependencies:
+ csstype: 3.2.3
'@types/react@19.2.7':
dependencies:
@@ -5295,6 +6746,11 @@ snapshots:
'@vitest/pretty-format': 4.0.18
tinyrainbow: 3.0.3
+ accepts@1.3.8:
+ dependencies:
+ mime-types: 2.1.35
+ negotiator: 0.6.3
+
acorn-jsx@5.3.2(acorn@8.15.0):
dependencies:
acorn: 8.15.0
@@ -5312,6 +6768,10 @@ snapshots:
clean-stack: 2.2.0
indent-string: 4.0.0
+ ajv-formats@3.0.1(ajv@8.18.0):
+ optionalDependencies:
+ ajv: 8.18.0
+
ajv@6.12.6:
dependencies:
fast-deep-equal: 3.1.3
@@ -5319,12 +6779,21 @@ snapshots:
json-schema-traverse: 0.4.1
uri-js: 4.4.1
+ ajv@8.18.0:
+ dependencies:
+ fast-deep-equal: 3.1.3
+ fast-uri: 3.1.0
+ json-schema-traverse: 1.0.0
+ require-from-string: 2.0.2
+
ansi-escapes@4.3.2:
dependencies:
type-fest: 0.21.3
ansi-regex@5.0.1: {}
+ ansi-regex@6.2.2: {}
+
ansi-styles@3.2.1:
dependencies:
color-convert: 1.9.3
@@ -5417,6 +6886,11 @@ snapshots:
async-function@1.0.0: {}
+ atomically@2.1.1:
+ dependencies:
+ stubborn-fs: 2.0.0
+ when-exit: 2.1.5
+
available-typed-arrays@1.0.7:
dependencies:
possible-typed-array-names: 1.1.0
@@ -5427,6 +6901,10 @@ snapshots:
base64-js@1.5.1: {}
+ base64id@2.0.0: {}
+
+ baseline-browser-mapping@2.9.19: {}
+
basic-ftp@5.0.5: {}
bidi-js@1.0.3:
@@ -5588,6 +7066,10 @@ snapshots:
dependencies:
restore-cursor: 3.1.0
+ cli-cursor@5.0.0:
+ dependencies:
+ restore-cursor: 5.1.0
+
cli-spinners@2.9.2: {}
cli-width@3.0.0: {}
@@ -5614,8 +7096,22 @@ snapshots:
commander@11.1.0: {}
+ commander@13.1.0: {}
+
concat-map@0.0.1: {}
+ conf@15.1.0:
+ dependencies:
+ ajv: 8.18.0
+ ajv-formats: 3.0.1(ajv@8.18.0)
+ atomically: 2.1.1
+ debounce-fn: 6.0.0
+ dot-prop: 10.1.0
+ env-paths: 3.0.0
+ json-schema-typed: 8.0.2
+ semver: 7.7.3
+ uint8array-extras: 1.5.0
+
confbox@0.2.4: {}
consola@3.4.2: {}
@@ -5625,8 +7121,15 @@ snapshots:
snake-case: 2.1.0
upper-case: 1.1.3
+ cookie@0.7.2: {}
+
core-js-pure@3.45.0: {}
+ cors@2.8.6:
+ dependencies:
+ object-assign: 4.1.1
+ vary: 1.1.2
+
create-require@1.1.1: {}
cross-spawn@7.0.6:
@@ -5649,8 +7152,6 @@ snapshots:
css-tree: 3.1.0
lru-cache: 11.2.4
- csstype@3.1.3: {}
-
csstype@3.2.3: {}
data-uri-to-buffer@4.0.1: {}
@@ -5682,6 +7183,12 @@ snapshots:
es-errors: 1.3.0
is-data-view: 1.0.2
+ debounce-fn@6.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
+ debounce@2.2.0: {}
+
debug@4.4.1:
dependencies:
ms: 2.1.3
@@ -5694,6 +7201,8 @@ snapshots:
deepmerge-ts@7.1.5: {}
+ deepmerge@4.3.1: {}
+
defaults@1.0.4:
dependencies:
clone: 1.0.4
@@ -5750,10 +7259,39 @@ snapshots:
dom-accessibility-api@0.6.3: {}
+ dom-serializer@2.0.0:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ entities: 4.5.0
+
+ domelementtype@2.3.0: {}
+
+ domhandler@5.0.3:
+ dependencies:
+ domelementtype: 2.3.0
+
+ domutils@3.2.2:
+ dependencies:
+ dom-serializer: 2.0.0
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+
dot-case@2.1.1:
dependencies:
no-case: 2.3.2
+ dot-prop@10.1.0:
+ dependencies:
+ type-fest: 5.4.4
+
+ dotenv-cli@11.0.0:
+ dependencies:
+ cross-spawn: 7.0.6
+ dotenv: 17.3.1
+ dotenv-expand: 12.0.3
+ minimist: 1.2.8
+
dotenv-cli@7.4.4:
dependencies:
cross-spawn: 7.0.6
@@ -5763,10 +7301,16 @@ snapshots:
dotenv-expand@10.0.0: {}
+ dotenv-expand@12.0.3:
+ dependencies:
+ dotenv: 16.6.1
+
dotenv@16.0.3: {}
dotenv@16.6.1: {}
+ dotenv@17.3.1: {}
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -5778,17 +7322,41 @@ snapshots:
'@standard-schema/spec': 1.1.0
fast-check: 3.23.2
+ emoji-regex@10.6.0: {}
+
emoji-regex@8.0.0: {}
empathic@2.0.0: {}
+ engine.io-parser@5.2.3: {}
+
+ engine.io@6.6.5:
+ dependencies:
+ '@types/cors': 2.8.19
+ '@types/node': 20.19.9
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cookie: 0.7.2
+ cors: 2.8.6
+ debug: 4.4.1
+ engine.io-parser: 5.2.3
+ ws: 8.18.3
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
enhanced-resolve@5.18.2:
dependencies:
graceful-fs: 4.2.11
tapable: 2.2.2
+ entities@4.5.0: {}
+
entities@6.0.1: {}
+ env-paths@3.0.0: {}
+
es-abstract@1.24.0:
dependencies:
array-buffer-byte-length: 1.0.2
@@ -5892,6 +7460,64 @@ snapshots:
is-date-object: 1.1.0
is-symbol: 1.1.1
+ esbuild@0.25.10:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.10
+ '@esbuild/android-arm': 0.25.10
+ '@esbuild/android-arm64': 0.25.10
+ '@esbuild/android-x64': 0.25.10
+ '@esbuild/darwin-arm64': 0.25.10
+ '@esbuild/darwin-x64': 0.25.10
+ '@esbuild/freebsd-arm64': 0.25.10
+ '@esbuild/freebsd-x64': 0.25.10
+ '@esbuild/linux-arm': 0.25.10
+ '@esbuild/linux-arm64': 0.25.10
+ '@esbuild/linux-ia32': 0.25.10
+ '@esbuild/linux-loong64': 0.25.10
+ '@esbuild/linux-mips64el': 0.25.10
+ '@esbuild/linux-ppc64': 0.25.10
+ '@esbuild/linux-riscv64': 0.25.10
+ '@esbuild/linux-s390x': 0.25.10
+ '@esbuild/linux-x64': 0.25.10
+ '@esbuild/netbsd-arm64': 0.25.10
+ '@esbuild/netbsd-x64': 0.25.10
+ '@esbuild/openbsd-arm64': 0.25.10
+ '@esbuild/openbsd-x64': 0.25.10
+ '@esbuild/openharmony-arm64': 0.25.10
+ '@esbuild/sunos-x64': 0.25.10
+ '@esbuild/win32-arm64': 0.25.10
+ '@esbuild/win32-ia32': 0.25.10
+ '@esbuild/win32-x64': 0.25.10
+
+ esbuild@0.25.12:
+ optionalDependencies:
+ '@esbuild/aix-ppc64': 0.25.12
+ '@esbuild/android-arm': 0.25.12
+ '@esbuild/android-arm64': 0.25.12
+ '@esbuild/android-x64': 0.25.12
+ '@esbuild/darwin-arm64': 0.25.12
+ '@esbuild/darwin-x64': 0.25.12
+ '@esbuild/freebsd-arm64': 0.25.12
+ '@esbuild/freebsd-x64': 0.25.12
+ '@esbuild/linux-arm': 0.25.12
+ '@esbuild/linux-arm64': 0.25.12
+ '@esbuild/linux-ia32': 0.25.12
+ '@esbuild/linux-loong64': 0.25.12
+ '@esbuild/linux-mips64el': 0.25.12
+ '@esbuild/linux-ppc64': 0.25.12
+ '@esbuild/linux-riscv64': 0.25.12
+ '@esbuild/linux-s390x': 0.25.12
+ '@esbuild/linux-x64': 0.25.12
+ '@esbuild/netbsd-arm64': 0.25.12
+ '@esbuild/netbsd-x64': 0.25.12
+ '@esbuild/openbsd-arm64': 0.25.12
+ '@esbuild/openbsd-x64': 0.25.12
+ '@esbuild/openharmony-arm64': 0.25.12
+ '@esbuild/sunos-x64': 0.25.12
+ '@esbuild/win32-arm64': 0.25.12
+ '@esbuild/win32-ia32': 0.25.12
+ '@esbuild/win32-x64': 0.25.12
+
esbuild@0.27.3:
optionalDependencies:
'@esbuild/aix-ppc64': 0.27.3
@@ -6113,6 +7739,8 @@ snapshots:
dependencies:
pure-rand: 6.1.0
+ fast-deep-equal@2.0.1: {}
+
fast-deep-equal@3.1.3: {}
fast-glob@3.3.1:
@@ -6135,6 +7763,8 @@ snapshots:
fast-levenshtein@2.0.6: {}
+ fast-uri@3.1.0: {}
+
fastq@1.19.1:
dependencies:
reusify: 1.1.0
@@ -6223,6 +7853,8 @@ snapshots:
dependencies:
is-property: 1.0.2
+ get-east-asian-width@1.4.0: {}
+
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -6280,6 +7912,15 @@ snapshots:
dependencies:
is-glob: 4.0.3
+ glob@11.1.0:
+ dependencies:
+ foreground-child: 3.3.1
+ jackspeak: 4.2.3
+ minimatch: 10.1.1
+ minipass: 7.1.2
+ package-json-from-dist: 1.0.1
+ path-scurry: 2.0.1
+
glob@13.0.0:
dependencies:
minimatch: 10.1.1
@@ -6380,6 +8021,21 @@ snapshots:
html-escaper@2.0.2: {}
+ html-to-text@9.0.5:
+ dependencies:
+ '@selderee/plugin-htmlparser2': 0.11.0
+ deepmerge: 4.3.1
+ dom-serializer: 2.0.0
+ htmlparser2: 8.0.2
+ selderee: 0.11.0
+
+ htmlparser2@8.0.2:
+ dependencies:
+ domelementtype: 2.3.0
+ domhandler: 5.0.3
+ domutils: 3.2.2
+ entities: 4.5.0
+
http-proxy-agent@7.0.2:
dependencies:
agent-base: 7.1.4
@@ -6540,6 +8196,8 @@ snapshots:
is-interactive@1.0.0: {}
+ is-interactive@2.0.0: {}
+
is-lower-case@1.1.3:
dependencies:
lower-case: 1.1.4
@@ -6595,6 +8253,10 @@ snapshots:
is-unicode-supported@0.1.0: {}
+ is-unicode-supported@1.3.0: {}
+
+ is-unicode-supported@2.1.0: {}
+
is-upper-case@1.1.2:
dependencies:
upper-case: 1.1.3
@@ -6638,6 +8300,12 @@ snapshots:
has-symbols: 1.1.0
set-function-name: 2.0.2
+ jackspeak@4.2.3:
+ dependencies:
+ '@isaacs/cliui': 9.0.0
+
+ jiti@2.4.2: {}
+
jiti@2.5.1: {}
js-tokens@10.0.0: {}
@@ -6676,12 +8344,20 @@ snapshots:
- '@noble/hashes'
- supports-color
+ jsesc@3.1.0: {}
+
json-buffer@3.0.1: {}
json-schema-traverse@0.4.1: {}
+ json-schema-traverse@1.0.0: {}
+
+ json-schema-typed@8.0.2: {}
+
json-stable-stringify-without-jsonify@1.0.1: {}
+ json5@2.2.3: {}
+
jsonfile@6.1.0:
dependencies:
universalify: 2.0.1
@@ -6699,6 +8375,10 @@ snapshots:
dependencies:
json-buffer: 3.0.1
+ kleur@3.0.3: {}
+
+ leac@0.6.0: {}
+
levn@0.4.1:
dependencies:
prelude-ls: 1.2.1
@@ -6770,6 +8450,16 @@ snapshots:
chalk: 4.1.2
is-unicode-supported: 0.1.0
+ log-symbols@6.0.0:
+ dependencies:
+ chalk: 5.6.2
+ is-unicode-supported: 1.3.0
+
+ log-symbols@7.0.1:
+ dependencies:
+ is-unicode-supported: 2.1.0
+ yoctocolors: 2.1.2
+
long@5.3.2: {}
loose-envify@1.4.0:
@@ -6818,6 +8508,8 @@ snapshots:
make-error@1.3.6: {}
+ marked@15.0.12: {}
+
math-intrinsics@1.1.0: {}
mdn-data@2.12.2: {}
@@ -6831,8 +8523,22 @@ snapshots:
braces: 3.0.3
picomatch: 2.3.1
+ mime-db@1.52.0: {}
+
+ mime-db@1.54.0: {}
+
+ mime-types@2.1.35:
+ dependencies:
+ mime-db: 1.52.0
+
+ mime-types@3.0.2:
+ dependencies:
+ mime-db: 1.54.0
+
mimic-fn@2.1.0: {}
+ mimic-function@5.0.1: {}
+
min-indent@1.0.1: {}
minimatch@10.1.1:
@@ -6898,6 +8604,8 @@ snapshots:
natural-compare@1.4.0: {}
+ negotiator@0.6.3: {}
+
neo-async@2.6.2: {}
netmask@2.0.2: {}
@@ -6912,14 +8620,14 @@ snapshots:
react: 19.2.0
react-dom: 19.2.0(react@19.2.0)
- next@15.5.9(react-dom@19.2.0(react@19.2.0))(react@19.2.0):
+ next@15.5.9(react-dom@19.2.4(react@19.2.0))(react@19.2.0):
dependencies:
'@next/env': 15.5.9
'@swc/helpers': 0.5.15
caniuse-lite: 1.0.30001731
postcss: 8.4.31
react: 19.2.0
- react-dom: 19.2.0(react@19.2.0)
+ react-dom: 19.2.4(react@19.2.0)
styled-jsx: 5.1.6(react@19.2.0)
optionalDependencies:
'@next/swc-darwin-arm64': 15.5.7
@@ -6958,6 +8666,30 @@ snapshots:
- '@babel/core'
- babel-plugin-macros
+ next@16.1.6(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ '@next/env': 16.1.6
+ '@swc/helpers': 0.5.15
+ baseline-browser-mapping: 2.9.19
+ caniuse-lite: 1.0.30001731
+ postcss: 8.4.31
+ react: 19.2.4
+ react-dom: 19.2.4(react@19.2.4)
+ styled-jsx: 5.1.6(react@19.2.4)
+ optionalDependencies:
+ '@next/swc-darwin-arm64': 16.1.6
+ '@next/swc-darwin-x64': 16.1.6
+ '@next/swc-linux-arm64-gnu': 16.1.6
+ '@next/swc-linux-arm64-musl': 16.1.6
+ '@next/swc-linux-x64-gnu': 16.1.6
+ '@next/swc-linux-x64-musl': 16.1.6
+ '@next/swc-win32-arm64-msvc': 16.1.6
+ '@next/swc-win32-x64-msvc': 16.1.6
+ sharp: 0.34.5
+ transitivePeerDependencies:
+ - '@babel/core'
+ - babel-plugin-macros
+
no-case@2.3.2:
dependencies:
lower-case: 1.1.4
@@ -6986,12 +8718,22 @@ snapshots:
mkdirp: 0.5.6
resolve: 1.22.10
+ nodemailer@7.0.13: {}
+
normalize-path@3.0.0: {}
npm-run-path@4.0.1:
dependencies:
path-key: 3.1.1
+ nypm@0.6.2:
+ dependencies:
+ citty: 0.1.6
+ consola: 3.4.2
+ pathe: 2.0.3
+ pkg-types: 2.3.0
+ tinyexec: 1.0.2
+
nypm@0.6.5:
dependencies:
citty: 0.2.1
@@ -7046,6 +8788,10 @@ snapshots:
dependencies:
mimic-fn: 2.1.0
+ onetime@7.0.0:
+ dependencies:
+ mimic-function: 5.0.1
+
optionator@0.9.4:
dependencies:
deep-is: 0.1.4
@@ -7078,6 +8824,18 @@ snapshots:
strip-ansi: 6.0.1
wcwidth: 1.0.1
+ ora@8.2.0:
+ dependencies:
+ chalk: 5.6.2
+ cli-cursor: 5.0.0
+ cli-spinners: 2.9.2
+ is-interactive: 2.0.0
+ is-unicode-supported: 2.1.0
+ log-symbols: 6.0.0
+ stdin-discarder: 0.2.2
+ string-width: 7.2.0
+ strip-ansi: 7.1.2
+
os-tmpdir@1.0.2: {}
own-keys@1.0.1:
@@ -7130,6 +8888,11 @@ snapshots:
dependencies:
entities: 6.0.1
+ parseley@0.12.1:
+ dependencies:
+ leac: 0.6.0
+ peberminta: 0.9.0
+
pascal-case@2.0.1:
dependencies:
camel-case: 3.0.0
@@ -7156,6 +8919,8 @@ snapshots:
pathe@2.0.3: {}
+ peberminta@0.9.0: {}
+
perfect-debounce@1.0.0: {}
pg-cloudflare@1.3.0:
@@ -7239,6 +9004,22 @@ snapshots:
prettier@3.6.2: {}
+ prisma@7.4.0(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.2):
+ dependencies:
+ '@prisma/config': 7.4.0
+ '@prisma/dev': 0.20.0(typescript@5.9.2)
+ '@prisma/engines': 7.4.0
+ '@prisma/studio-core': 0.13.1(@types/react@19.2.14)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ mysql2: 3.15.3
+ postgres: 3.4.7
+ optionalDependencies:
+ typescript: 5.9.2
+ transitivePeerDependencies:
+ - '@types/react'
+ - magicast
+ - react
+ - react-dom
+
prisma@7.4.0(@types/react@19.2.7)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(typescript@5.9.2):
dependencies:
'@prisma/config': 7.4.0
@@ -7254,6 +9035,14 @@ snapshots:
- magicast
- react
- react-dom
+ optional: true
+
+ prismjs@1.30.0: {}
+
+ prompts@2.4.2:
+ dependencies:
+ kleur: 3.0.3
+ sisteransi: 1.0.5
prop-types@15.8.1:
dependencies:
@@ -7310,12 +9099,52 @@ snapshots:
react: 19.2.0
scheduler: 0.27.0
+ react-dom@19.2.4(react@19.2.0):
+ dependencies:
+ react: 19.2.0
+ scheduler: 0.27.0
+
+ react-dom@19.2.4(react@19.2.4):
+ dependencies:
+ react: 19.2.4
+ scheduler: 0.27.0
+
+ react-email@5.2.8:
+ dependencies:
+ '@babel/parser': 7.29.0
+ '@babel/traverse': 7.29.0
+ chokidar: 4.0.3
+ commander: 13.1.0
+ conf: 15.1.0
+ debounce: 2.2.0
+ esbuild: 0.25.12
+ glob: 11.1.0
+ jiti: 2.4.2
+ log-symbols: 7.0.1
+ mime-types: 3.0.2
+ normalize-path: 3.0.0
+ nypm: 0.6.2
+ ora: 8.2.0
+ prompts: 2.4.2
+ socket.io: 4.8.3
+ tsconfig-paths: 4.2.0
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
react-is@16.13.1: {}
+ react-promise-suspense@0.3.4:
+ dependencies:
+ fast-deep-equal: 2.0.1
+
react@19.1.1: {}
react@19.2.0: {}
+ react@19.2.4: {}
+
readable-stream@3.6.2:
dependencies:
inherits: 2.0.4
@@ -7368,6 +9197,13 @@ snapshots:
require-from-string@2.0.2: {}
+ resend@4.8.0(react-dom@19.2.4(react@19.2.4))(react@19.2.4):
+ dependencies:
+ '@react-email/render': 1.1.2(react-dom@19.2.4(react@19.2.4))(react@19.2.4)
+ transitivePeerDependencies:
+ - react
+ - react-dom
+
resolve-from@4.0.0: {}
resolve-pkg-maps@1.0.0: {}
@@ -7389,6 +9225,11 @@ snapshots:
onetime: 5.1.2
signal-exit: 3.0.7
+ restore-cursor@5.1.0:
+ dependencies:
+ onetime: 7.0.0
+ signal-exit: 4.1.0
+
retry@0.12.0: {}
reusify@1.1.0: {}
@@ -7475,6 +9316,10 @@ snapshots:
scheduler@0.27.0: {}
+ selderee@0.11.0:
+ dependencies:
+ parseley: 0.12.1
+
semver@6.3.1: {}
semver@7.6.2: {}
@@ -7584,6 +9429,8 @@ snapshots:
signal-exit@4.1.0: {}
+ sisteransi@1.0.5: {}
+
slash@3.0.0: {}
smart-buffer@4.2.0: {}
@@ -7592,6 +9439,36 @@ snapshots:
dependencies:
no-case: 2.3.2
+ socket.io-adapter@2.5.6:
+ dependencies:
+ debug: 4.4.1
+ ws: 8.18.3
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
+ socket.io-parser@4.2.5:
+ dependencies:
+ '@socket.io/component-emitter': 3.1.2
+ debug: 4.4.1
+ transitivePeerDependencies:
+ - supports-color
+
+ socket.io@4.8.3:
+ dependencies:
+ accepts: 1.3.8
+ base64id: 2.0.0
+ cors: 2.8.6
+ debug: 4.4.1
+ engine.io: 6.6.5
+ socket.io-adapter: 2.5.6
+ socket.io-parser: 4.2.5
+ transitivePeerDependencies:
+ - bufferutil
+ - supports-color
+ - utf-8-validate
+
socks-proxy-agent@8.0.5:
dependencies:
agent-base: 7.1.4
@@ -7638,6 +9515,8 @@ snapshots:
std-env@3.10.0: {}
+ stdin-discarder@0.2.2: {}
+
stop-iteration-iterator@1.1.0:
dependencies:
es-errors: 1.3.0
@@ -7649,6 +9528,12 @@ snapshots:
is-fullwidth-code-point: 3.0.0
strip-ansi: 6.0.1
+ string-width@7.2.0:
+ dependencies:
+ emoji-regex: 10.6.0
+ get-east-asian-width: 1.4.0
+ strip-ansi: 7.1.2
+
string.prototype.matchall@4.0.12:
dependencies:
call-bind: 1.0.8
@@ -7701,6 +9586,12 @@ snapshots:
dependencies:
ansi-regex: 5.0.1
+ strip-ansi@7.1.2:
+ dependencies:
+ ansi-regex: 6.2.2
+
+ strip-bom@3.0.0: {}
+
strip-final-newline@2.0.0: {}
strip-indent@3.0.0:
@@ -7711,11 +9602,22 @@ snapshots:
strip-json-comments@3.1.1: {}
+ stubborn-fs@2.0.0:
+ dependencies:
+ stubborn-utils: 1.0.2
+
+ stubborn-utils@1.0.2: {}
+
styled-jsx@5.1.6(react@19.2.0):
dependencies:
client-only: 0.0.1
react: 19.2.0
+ styled-jsx@5.1.6(react@19.2.4):
+ dependencies:
+ client-only: 0.0.1
+ react: 19.2.4
+
supports-color@5.5.0:
dependencies:
has-flag: 3.0.0
@@ -7733,10 +9635,14 @@ snapshots:
symbol-tree@3.2.4: {}
+ tagged-tag@1.0.0: {}
+
tailwind-merge@3.3.1: {}
tailwindcss@4.1.11: {}
+ tailwindcss@4.1.18: {}
+
tapable@2.2.2: {}
tar@6.2.1:
@@ -7830,6 +9736,12 @@ snapshots:
optionalDependencies:
typescript: 5.9.2
+ tsconfig-paths@4.2.0:
+ dependencies:
+ json5: 2.2.3
+ minimist: 1.2.8
+ strip-bom: 3.0.0
+
tslib@1.14.1: {}
tslib@2.8.1: {}
@@ -7876,6 +9788,10 @@ snapshots:
type-fest@0.21.3: {}
+ type-fest@5.4.4:
+ dependencies:
+ tagged-tag: 1.0.0
+
typed-array-buffer@1.0.3:
dependencies:
call-bound: 1.0.4
@@ -7925,6 +9841,8 @@ snapshots:
uglify-js@3.19.3:
optional: true
+ uint8array-extras@1.5.0: {}
+
unbox-primitive@1.1.0:
dependencies:
call-bound: 1.0.4
@@ -7963,6 +9881,8 @@ snapshots:
validate-npm-package-name@5.0.1: {}
+ vary@1.1.2: {}
+
vite-tsconfig-paths@6.1.1(typescript@5.9.2)(vite@7.3.1(@types/node@20.19.9)(jiti@2.5.1)(lightningcss@1.30.1)(tsx@4.21.0)):
dependencies:
debug: 4.4.1
@@ -8048,6 +9968,8 @@ snapshots:
transitivePeerDependencies:
- '@noble/hashes'
+ when-exit@2.1.5: {}
+
which-boxed-primitive@1.1.1:
dependencies:
is-bigint: 1.1.0
@@ -8110,6 +10032,8 @@ snapshots:
wrappy@1.0.2: {}
+ ws@8.18.3: {}
+
xml-name-validator@5.0.0: {}
xmlchars@2.2.0: {}
@@ -8124,6 +10048,8 @@ snapshots:
yocto-queue@0.1.0: {}
+ yoctocolors@2.1.2: {}
+
zeptomatch@2.1.0:
dependencies:
grammex: 3.1.12
diff --git a/turbo.json b/turbo.json
index eeb356e..b87b6b2 100644
--- a/turbo.json
+++ b/turbo.json
@@ -3,7 +3,7 @@
"ui": "tui",
"tasks": {
"generate": {
- "dependsOn": ["^build"],
+ "dependsOn": ["^build", "db:generate"],
"cache": false
},
"build": {