diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx index 41b8371f1d5..7788a692460 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-partner-sheet.tsx @@ -1,23 +1,31 @@ import { invitePartnerAction } from "@/lib/actions/partners/invite-partner"; -import { mutatePrefix } from "@/lib/swr/mutate"; +import { saveInviteEmailDataAction } from "@/lib/actions/partners/save-invite-email-data"; import useProgram from "@/lib/swr/use-program"; import useWorkspace from "@/lib/swr/use-workspace"; +import { ProgramInviteEmailData, ProgramProps } from "@/lib/types"; import { invitePartnerSchema } from "@/lib/zod/schemas/partners"; import { GroupSelector } from "@/ui/partners/groups/group-selector"; import { X } from "@/ui/shared/icons"; import { BlurImage, Button, - Eye, - EyeSlash, InfoTooltip, + RichTextArea, + RichTextProvider, + RichTextToolbar, Sheet, - useLocalStorage, useMediaQuery, } from "@dub/ui"; -import { motion } from "motion/react"; +import { cn } from "@dub/utils"; import { useAction } from "next-safe-action/hooks"; -import { Dispatch, SetStateAction, useState } from "react"; +import { + Dispatch, + SetStateAction, + useEffect, + useMemo, + useRef, + useState, +} from "react"; import { useForm } from "react-hook-form"; import { toast } from "sonner"; import { z } from "zod"; @@ -28,34 +36,97 @@ interface InvitePartnerSheetProps { type InvitePartnerFormData = z.infer; +type EmailContent = { + subject: string; + title: string; + body: string; +}; + function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) { - const { program } = useProgram(); + const { program, mutate } = useProgram< + ProgramProps & { inviteEmailData: ProgramInviteEmailData } + >(undefined, { + keepPreviousData: true, // so the mutate doesn't cause a full page refresh + }); const { isMobile } = useMediaQuery(); const { id: workspaceId } = useWorkspace(); - const { register, handleSubmit, watch, setValue, clearErrors } = - useForm({ - defaultValues: { - groupId: program?.defaultGroupId || "", - }, - }); + // Default email content + const defaultEmailContent = useMemo(() => { + const programName = program?.name || "Dub"; + return { + subject: `${programName} invited you to join Dub Partners`, + title: "You've been invited", + body: `${programName} invited you to join their program on Dub Partners.\n\n${programName} uses [Dub Partners](https://dub.co/partners) to power their partner program and wants to work with great people like you!`, + }; + }, [program?.name]); + + // Load saved email content from program + const savedEmailContent = useMemo(() => { + if (program?.inviteEmailData) { + return { + subject: program.inviteEmailData.subject, + title: program.inviteEmailData.title, + body: program.inviteEmailData.body, + }; + } + return null; + }, [program?.inviteEmailData]); + + // State for email editing + const [isEditingEmail, setIsEditingEmail] = useState(false); + const [emailContent, setEmailContent] = useState( + savedEmailContent, + ); + const [draftEmailContent, setDraftEmailContent] = useState( + savedEmailContent || defaultEmailContent, + ); + + const { + register, + handleSubmit, + formState: { isSubmitting, isSubmitSuccessful }, + watch, + setValue, + } = useForm({ + defaultValues: { + groupId: program?.defaultGroupId || "", + }, + }); const email = watch("email"); const { executeAsync, isPending } = useAction(invitePartnerAction, { onSuccess: async () => { + await mutate(); toast.success("Invitation sent to partner!"); setIsOpen(false); - program && - mutatePrefix( - `/api/partners?workspaceId=${workspaceId}&programId=${program.id}`, - ); }, onError({ error }) { toast.error(error.serverError); }, }); + const { executeAsync: saveEmailDataAsync, isPending: isSavingEmailData } = + useAction(saveInviteEmailDataAction, { + onSuccess: async ({ input }) => { + toast.success("Email template saved!"); + + // Update local state with saved content + const updatedContent: EmailContent = { + subject: input.subject, + title: input.title, + body: input.body, + }; + setEmailContent(updatedContent); + setDraftEmailContent(updatedContent); + setIsEditingEmail(false); + }, + onError({ error }) { + toast.error(error.serverError); + }, + }); + const onSubmit = async (data: InvitePartnerFormData) => { if (!workspaceId || !program?.id) { return; @@ -67,6 +138,52 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) { }); }; + const handleStartEditing = () => { + setDraftEmailContent(emailContent || defaultEmailContent); + setIsEditingEmail(true); + }; + + const handleSaveEmail = async () => { + if (!workspaceId) { + return; + } + + const sanitizedSubject = draftEmailContent.subject.trim(); + const sanitizedTitle = draftEmailContent.title.trim(); + let sanitizedBody = draftEmailContent.body.trim(); + + // Enforce max length validation (matches schema) + if (sanitizedBody.length > 3000) { + sanitizedBody = sanitizedBody.substring(0, 3000); + toast.error("Email body was truncated to 3000 characters"); + } + + const updatedContent: EmailContent = { + subject: sanitizedSubject || defaultEmailContent.subject, + title: sanitizedTitle || defaultEmailContent.title, + body: sanitizedBody || defaultEmailContent.body, + }; + + // Ensure all values are non-empty (schema requirement) + const finalSubject = + updatedContent.subject.trim() || defaultEmailContent.subject; + const finalTitle = updatedContent.title.trim() || defaultEmailContent.title; + const finalBody = updatedContent.body.trim() || defaultEmailContent.body; + + // Save to server (state updates happen in onSuccess callback) + await saveEmailDataAsync({ + workspaceId, + subject: finalSubject, + title: finalTitle, + body: finalBody, + }); + }; + + const handleCancelEditing = () => { + setDraftEmailContent(emailContent || defaultEmailContent); + setIsEditingEmail(false); + }; + return (
@@ -175,7 +292,16 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) {
- + @@ -194,8 +320,10 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) { variant="primary" text="Send invite" className="w-fit" - loading={isPending} - disabled={isPending || !email} + loading={isPending || isSubmitting || isSubmitSuccessful} + disabled={ + isPending || !email || isEditingEmail || isSavingEmailData + } /> @@ -203,70 +331,227 @@ function InvitePartnerSheetContent({ setIsOpen }: InvitePartnerSheetProps) { ); } -function EmailPreview() { +function EmailPreview({ + isEditingEmail, + emailContent, + draftEmailContent, + setDraftEmailContent, + onStartEditing, + onSave, + onCancel, + isSavingEmailData, +}: { + isEditingEmail: boolean; + emailContent: EmailContent; + draftEmailContent: EmailContent; + setDraftEmailContent: (content: EmailContent) => void; + onStartEditing: () => void; + onSave: () => void; + onCancel: () => void; + isSavingEmailData: boolean; +}) { const { program } = useProgram(); + const { isMobile } = useMediaQuery(); + const richTextRef = useRef<{ setContent: (content: any) => void }>(null); - const [showPreview, setShowPreview] = useLocalStorage( - "show-partner-invite-email-preview", - true, - ); + const displayContent = isEditingEmail ? draftEmailContent : emailContent; + + // Update editor content when switching to edit mode + const prevIsEditingEmail = useRef(isEditingEmail); + useEffect(() => { + if (isEditingEmail && !prevIsEditingEmail.current && richTextRef.current) { + richTextRef.current.setContent(draftEmailContent.body); + } + prevIsEditingEmail.current = isEditingEmail; + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [isEditingEmail]); return ( -
-
+
+

Email preview

- +
- -
-
- -

- {program?.name || "Dub"} invited you to join Dub Partners -

-

- {program?.name || "Dub"} uses Dub Partners to power their - affiliate program and wants to partner with great people like - yourself! -

-
-
-

- From: - notifications@mail.dub.co -

-

- - Subject:{" "} - - You've been invited to Dub Partners -

+
+ {isEditingEmail ? ( +
+
+
+ +
+ + setDraftEmailContent({ + ...draftEmailContent, + subject: e.target.value, + }) + } + className="block w-full rounded-md border-neutral-300 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500" + placeholder="Email subject" + autoFocus={!isMobile} + /> +
+
+ +
+ +
+ + setDraftEmailContent({ + ...draftEmailContent, + title: e.target.value, + }) + } + className="block w-full rounded-md border-neutral-300 text-sm text-neutral-900 placeholder-neutral-400 focus:border-neutral-500 focus:outline-none focus:ring-neutral-500" + placeholder="Email title" + /> +
+
+ +
+ +
+ { + const markdown = (editor as any).getMarkdown() || null; + setDraftEmailContent({ + ...draftEmailContent, + body: markdown || "", + }); + }} + editorProps={{ + handleDOMEvents: { + keydown: (_, e) => { + if (e.key === "Enter" && (e.metaKey || e.ctrlKey)) { + e.preventDefault(); + e.stopPropagation(); + onSave(); + return false; + } + }, + }, + }} + > +
+
+ + +
+
+
+
+
+
-
- + ) : ( + <> +
+

+ From: + notifications@mail.dub.co +

+

+ + Subject:{" "} + + {displayContent.subject} +

+
+
+ +

+ {displayContent.title} +

+
+ + + +
+
+ + )} +
); } diff --git a/apps/web/lib/actions/partners/invite-partner.ts b/apps/web/lib/actions/partners/invite-partner.ts index 2818096078f..681457c0e0d 100644 --- a/apps/web/lib/actions/partners/invite-partner.ts +++ b/apps/web/lib/actions/partners/invite-partner.ts @@ -69,30 +69,51 @@ export const invitePartnerAction = authActionClient status: "invited", }); - waitUntil( - Promise.allSettled([ - (async () => { - await sendEmail({ - subject: `${program.name} invited you to join Dub Partners`, - variant: "notifications", - to: email, - replyTo: program.supportEmail || "noreply", - react: ProgramInvite({ - email, - name: enrolledPartner.name, - program: { - name: program.name, - slug: program.slug, - logo: program.logo, - }, - ...(await getPartnerInviteRewardsAndBounties({ - programId, - groupId: enrolledPartner.groupId || program.defaultGroupId, - })), + // Use saved invite email data from program if available + const inviteEmailData = program.inviteEmailData; + + const sendPartnerInvitePromise = (async () => { + try { + const rewardsAndBounties = await getPartnerInviteRewardsAndBounties({ + programId, + groupId: enrolledPartner.groupId || program.defaultGroupId, + }); + + await sendEmail({ + subject: + inviteEmailData?.subject || + `${program.name} invited you to join Dub Partners`, + variant: "notifications", + to: email, + replyTo: program.supportEmail || "noreply", + react: ProgramInvite({ + email, + name: enrolledPartner.name, + program: { + name: program.name, + slug: program.slug, + logo: program.logo, + }, + ...(inviteEmailData?.subject && { + subject: inviteEmailData.subject, }), - }); - })(), + ...(inviteEmailData?.title && { title: inviteEmailData.title }), + ...(inviteEmailData?.body && { body: inviteEmailData.body }), + ...rewardsAndBounties, + }), + }); + } catch (error) { + console.error("Failed to send partner invite email", { + error, + partnerId: enrolledPartner.partnerId || enrolledPartner.id, + programId, + }); + } + })(); + waitUntil( + Promise.allSettled([ + sendPartnerInvitePromise, recordAuditLog({ workspaceId: workspace.id, programId, diff --git a/apps/web/lib/actions/partners/save-invite-email-data.ts b/apps/web/lib/actions/partners/save-invite-email-data.ts new file mode 100644 index 00000000000..bd73ced06e1 --- /dev/null +++ b/apps/web/lib/actions/partners/save-invite-email-data.ts @@ -0,0 +1,45 @@ +"use server"; + +import { getDefaultProgramIdOrThrow } from "@/lib/api/programs/get-default-program-id-or-throw"; +import { sanitizeMarkdown } from "@/lib/partners/sanitize-markdown"; +import { prisma } from "@dub/prisma"; +import { z } from "zod"; +import { authActionClient } from "../safe-action"; + +const saveInviteEmailDataSchema = z.object({ + workspaceId: z.string(), + subject: z.string().trim().min(1), + title: z.string().trim().min(1), + body: z.string().trim().min(1).max(3000), +}); + +export const saveInviteEmailDataAction = authActionClient + .schema(saveInviteEmailDataSchema) + .action(async ({ parsedInput, ctx }) => { + const { workspace } = ctx; + const { subject, title, body } = parsedInput; + + const programId = getDefaultProgramIdOrThrow(workspace); + + // Sanitize emailBody before saving + const sanitizedBody = sanitizeMarkdown(body); + + if (!sanitizedBody) { + throw new Error( + "Email body contains invalid content. Please remove excessively long lines or unsupported characters.", + ); + } + + await prisma.program.update({ + where: { + id: programId, + }, + data: { + inviteEmailData: { + subject: subject.trim(), + title: title.trim(), + body: sanitizedBody, + }, + }, + }); + }); diff --git a/apps/web/lib/api/programs/get-program-or-throw.ts b/apps/web/lib/api/programs/get-program-or-throw.ts index 1b6283592a6..add3ea24766 100644 --- a/apps/web/lib/api/programs/get-program-or-throw.ts +++ b/apps/web/lib/api/programs/get-program-or-throw.ts @@ -1,3 +1,4 @@ +import { programInviteEmailDataSchema } from "@/lib/zod/schemas/program-invite-email"; import { ProgramSchema } from "@/lib/zod/schemas/programs"; import { prisma } from "@dub/prisma"; import { DubApiError } from "../errors"; @@ -22,5 +23,7 @@ export const getProgramOrThrow = async ({ }); } - return ProgramSchema.parse(program); + return ProgramSchema.extend({ + inviteEmailData: programInviteEmailDataSchema, + }).parse(program); }; diff --git a/apps/web/lib/partners/sanitize-markdown.ts b/apps/web/lib/partners/sanitize-markdown.ts new file mode 100644 index 00000000000..fb5ce46bede --- /dev/null +++ b/apps/web/lib/partners/sanitize-markdown.ts @@ -0,0 +1,51 @@ +"server-only"; + +/** + * Sanitizes and validates markdown content for safe use in email templates. + * + * This function: + * - Trims whitespace + * - Validates the content is valid text (not binary) + * - Checks for suspicious patterns that could cause DoS issues + * - Normalizes line endings + * + * @param markdown - The markdown string to sanitize + * @returns The sanitized markdown string, or null if invalid/binary content detected + */ +export function sanitizeMarkdown( + markdown: string | null | undefined, +): string | null { + if (!markdown || typeof markdown !== "string") { + return null; + } + + // Trim whitespace + let sanitized = markdown.trim(); + + // Return null if empty after trimming + if (!sanitized) { + return null; + } + + // Check for binary content - markdown should be valid UTF-8 text + // Reject if there are null bytes (indicates binary content) + if (sanitized.includes("\0")) { + return null; + } + + // Check for suspicious patterns that could cause DoS or rendering issues + // Reject content with excessively long lines to avoid malformed markdown + const maxLineLength = 1000; + const hasExcessivelyLongLine = sanitized + .split("\n") + .some((line) => line.length > maxLineLength); + + if (hasExcessivelyLongLine) { + return null; + } + + // Normalize line endings + sanitized = sanitized.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); + + return sanitized; +} diff --git a/apps/web/lib/types.ts b/apps/web/lib/types.ts index 9b323d73d85..0103e813cd7 100644 --- a/apps/web/lib/types.ts +++ b/apps/web/lib/types.ts @@ -97,6 +97,7 @@ import { programApplicationFormFieldWithValuesSchema, programApplicationFormSchema, } from "./zod/schemas/program-application-form"; +import { programInviteEmailDataSchema } from "./zod/schemas/program-invite-email"; import { programLanderSchema } from "./zod/schemas/program-lander"; import { programDataSchema } from "./zod/schemas/program-onboarding"; import { @@ -465,6 +466,10 @@ export type DiscountCodeProps = z.infer; export type ProgramProps = z.infer; +export type ProgramInviteEmailData = z.infer< + typeof programInviteEmailDataSchema +>; + export type ProgramLanderData = z.infer; export type ProgramApplicationFormData = z.infer< diff --git a/apps/web/lib/zod/schemas/partners.ts b/apps/web/lib/zod/schemas/partners.ts index d75a1aadc93..17343d1dfa4 100644 --- a/apps/web/lib/zod/schemas/partners.ts +++ b/apps/web/lib/zod/schemas/partners.ts @@ -710,6 +710,9 @@ export const invitePartnerSchema = z.object({ email: z.string().trim().email().min(1).max(100), username: z.string().max(100).optional(), groupId: z.string().nullish().default(null), + emailSubject: z.string().optional(), + emailTitle: z.string().optional(), + emailBody: z.string().max(3000).optional(), }); export const approvePartnerSchema = z.object({ diff --git a/apps/web/lib/zod/schemas/program-invite-email.ts b/apps/web/lib/zod/schemas/program-invite-email.ts new file mode 100644 index 00000000000..ba0e99fe1d2 --- /dev/null +++ b/apps/web/lib/zod/schemas/program-invite-email.ts @@ -0,0 +1,9 @@ +import { z } from "zod"; + +export const programInviteEmailDataSchema = z + .object({ + subject: z.string(), + title: z.string(), + body: z.string(), + }) + .nullish(); diff --git a/packages/email/package.json b/packages/email/package.json index 268740fc901..3080f26ac91 100644 --- a/packages/email/package.json +++ b/packages/email/package.json @@ -10,6 +10,7 @@ "dependencies": { "@dub/utils": "workspace:*", "@react-email/components": "^0.0.22", + "@react-email/markdown": "^0.0.17", "lucide-react": "^0.462.0", "nodemailer": "^6.9.3", "react-email": "^2.1.6", diff --git a/packages/email/src/templates/program-invite.tsx b/packages/email/src/templates/program-invite.tsx index 3d2ed16da35..405508e949e 100644 --- a/packages/email/src/templates/program-invite.tsx +++ b/packages/email/src/templates/program-invite.tsx @@ -14,8 +14,92 @@ import { Tailwind, Text, } from "@react-email/components"; +import { Markdown } from "@react-email/markdown"; +import type { CSSProperties } from "react"; import { Footer } from "../components/footer"; +const markdownCustomStyles: Record = { + p: { + color: "#525252", + fontSize: "14px", + lineHeight: "20px", + margin: "0 0 16px", + }, + link: { + fontWeight: "600", + color: "#000000", + textDecoration: "underline", + textDecorationStyle: "dotted", + textUnderlineOffset: "2px", + }, + ul: { + margin: "0 0 16px", + paddingLeft: "20px", + listStylePosition: "outside", + listStyleType: "disc", + color: "#525252", + fontSize: "14px", + lineHeight: "20px", + }, + ol: { + margin: "0 0 16px", + paddingLeft: "20px", + listStylePosition: "outside", + listStyleType: "decimal", + color: "#525252", + fontSize: "14px", + lineHeight: "20px", + }, + li: { + marginBottom: "8px", + }, + strong: { + fontWeight: "600", + color: "#1f2937", + }, + em: { + fontStyle: "italic", + }, + blockquote: { + margin: "16px 0", + padding: "0 0 0 16px", + borderLeft: "4px solid #e5e7eb", + backgroundColor: "#f9fafb", + color: "#525252", + fontSize: "14px", + lineHeight: "20px", + }, + codeInline: { + display: "inline-block", + padding: "2px 4px", + borderRadius: "4px", + backgroundColor: "#f4f4f5", + border: "1px solid #e4e4e7", + fontFamily: + "'SFMono-Regular', Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace", + fontSize: "12px", + lineHeight: "18px", + color: "#1f2937", + }, + code: { + margin: "16px 0", + overflowX: "auto", + borderRadius: "8px", + backgroundColor: "#111827", + border: "1px solid #1f2937", + padding: "12px", + fontSize: "12px", + lineHeight: "20px", + color: "#f9fafb", + }, + hr: { + margin: "24px 0", + borderColor: "#e5e7eb", + borderStyle: "solid", + borderWidth: "1px 0 0 0", + }, +}; + export default function ProgramInvite({ email = "panic@thedis.co", name = "John Doe", @@ -44,6 +128,9 @@ export default function ProgramInvite({ label: "Earn $100 after generating $1,000 in revenue", }, ], + subject, + title, + body, }: { email: string; name: string | null; @@ -54,11 +141,18 @@ export default function ProgramInvite({ }; rewards: { icon: string; label: string }[] | null; bounties: { icon: string; label: string }[] | null; + subject?: string; + title?: string; + body?: string; }) { + const emailTitle = title || "You've been invited"; + const emailSubject = + subject || `${program.name} invited you to join Dub Partners`; + return ( - Sign up for {program.name} + {emailSubject} @@ -71,30 +165,39 @@ export default function ProgramInvite({ - You've been invited + {emailTitle} - - {name && !name.includes("@") && <>Hi {name}, } - {program.name} invited you to join their program on Dub Partners. - + {body ? ( + + {body} + + ) : ( + <> + + {name && !name.includes("@") && <>Hi {name}, } + {program.name} invited you to join their program on Dub + Partners. + - - {program.name} uses{" "} - - Dub Partners - {" "} - to power their partner program and wants to work with great people - like you! - + + {program.name} uses{" "} + + Dub Partners + {" "} + to power their partner program and wants to work with great + people like you! + + + )}
Accept Invite diff --git a/packages/prisma/schema/program.prisma b/packages/prisma/schema/program.prisma index d2979df89f9..f9dca9486db 100644 --- a/packages/prisma/schema/program.prisma +++ b/packages/prisma/schema/program.prisma @@ -39,6 +39,7 @@ model Program { primaryRewardEvent EventType @default(sale) holdingPeriodDays Int @default(0) // number of days to wait before earnings are added to a payout minPayoutAmount Int @default(0) // Default minimum payout amount of $0 + inviteEmailData Json? @db.Json embedData Json? @db.Json resources Json? @db.Json termsUrl String? @db.Text diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3d06e1ea022..5394ab900c3 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -523,6 +523,9 @@ importers: '@react-email/components': specifier: ^0.0.22 version: 0.0.22(@types/react@18.2.48)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-email/markdown': + specifier: ^0.0.17 + version: 0.0.17(react@18.3.1) lucide-react: specifier: ^0.462.0 version: 0.462.0(react@18.3.1) @@ -540,7 +543,7 @@ importers: version: 2.1.6(@opentelemetry/api@1.9.0)(@swc/helpers@0.5.15)(eslint@8.48.0)(ts-node@10.9.2(@swc/core@1.3.101(@swc/helpers@0.5.15))(@types/node@22.18.3)(typescript@5.6.2)) resend: specifier: ^6.1.2 - version: 6.1.2(@react-email/render@0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) + version: 6.1.2(@react-email/render@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)) devDependencies: '@types/nodemailer': specifier: ~6.4.17 @@ -618,7 +621,7 @@ importers: dependencies: '@hubspot/cli': specifier: ^7.6.2 - version: 7.6.2(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.3.1)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)(typescript@5.6.2)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) + version: 7.6.2(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.6.2)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)(typescript@5.6.2)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) devDependencies: '@types/node': specifier: 18.11.9 @@ -4490,6 +4493,12 @@ packages: peerDependencies: react: ^18.2.0 + '@react-email/markdown@0.0.17': + resolution: {integrity: sha512-6op3AfsBC9BJKkhG+eoMFRFWlr0/f3FYbtQrK+VhGzJocEAY0WINIFN+W8xzXr//3IL0K/aKtnH3FtpIuescQQ==} + engines: {node: '>=22.0.0'} + peerDependencies: + react: ^18.0 || ^19.0 || ^19.0.0-rc + '@react-email/preview@0.0.10': resolution: {integrity: sha512-bRrv8teMMBlF7ttLp1zZUejkPUzrwMQXrigdagtEBOqsB8HxvJU2MR6Yyb3XOqBYldaIDOQJ1z61zyD2wRlKAw==} engines: {node: '>=18.0.0'} @@ -4503,6 +4512,13 @@ packages: react: ^18.2.0 react-dom: ^18.2.0 + '@react-email/render@2.0.0': + resolution: {integrity: sha512-rdjNj6iVzv8kRKDPFas+47nnoe6B40+nwukuXwY4FCwM7XBg6tmYr+chQryCuavUj2J65MMf6fztk1bxOUiSVA==} + engines: {node: '>=22.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.9': resolution: {integrity: sha512-ZDASHVvyKrWBS00o5pSH5khfMf46UtZhrHcSAfPSiC4nj7R8A0bf+3Wmbk8YmsaV+qWXUCUSHWwIAAlMRnJoAA==} engines: {node: '>=18.0.0'} @@ -9735,8 +9751,8 @@ packages: js-base64@3.7.7: resolution: {integrity: sha512-7rCnleh0z2CkXhH67J8K1Ytz0b2Y+yxTPL+/KOJoa20hfnVQ/3/T6W/KflYI4bRHRagNeXeU2bkNGI3v1oS/lw==} - js-beautify@1.14.11: - resolution: {integrity: sha512-rPogWqAfoYh1Ryqqh2agUpVfbxAhbjuN1SmU86dskQUKouRiggUTCO4+2ym9UPXllc2WAp0J+T5qxn7Um3lCdw==} + js-beautify@1.15.4: + resolution: {integrity: sha512-9/KXeZUKKJwqCXUdBxFJ3vPh467OCckSBmYDwSK/EtV090K+iMJ7zx2S3HLVDIWFQdqMIsZWbnaGiba18aWhaA==} engines: {node: '>=14'} hasBin: true @@ -10107,6 +10123,11 @@ packages: markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + marked@15.0.12: + resolution: {integrity: sha512-8dD6FusOQSrpv9Z1rdNMdlSgQOIP880DHqnohobOmYLElGEqAL/JvxvuxZO16r4HtjTlfPRDC1hbvxC9dPN2nA==} + engines: {node: '>= 18'} + hasBin: true + marked@16.4.1: resolution: {integrity: sha512-ntROs7RaN3EvWfy3EZi14H4YxmT6A5YvywfhO+0pm+cH/dnSQRmdAmoFIc3B9aiwTehyk7pESH4ofyBY+V5hZg==} engines: {node: '>= 20'} @@ -10708,8 +10729,8 @@ packages: engines: {node: '>=6'} hasBin: true - nopt@7.2.0: - resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} + nopt@7.2.1: + resolution: {integrity: sha512-taM24ViiimT/XntxbPyJQzCG+p4EKOpgD3mxFwW38mGjVUrfERQOeY4EDHjdnptttfHuHQXFx+lTP08Q+mLa/w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true @@ -11307,6 +11328,11 @@ packages: engines: {node: '>=14'} hasBin: true + prettier@3.6.2: + resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + engines: {node: '>=14'} + hasBin: true + pretty-format@26.6.2: resolution: {integrity: sha512-7AeGuCYNGmycyQbCqd/3PWH4eOoX/OiCa0uphp57NVTeAGdJGaAliecxwBDHYQCIvrW7aDBZCYeNTP/WX69mkg==} engines: {node: '>= 10'} @@ -15669,7 +15695,7 @@ snapshots: - encoding - supports-color - '@hubspot/cli@7.6.2(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.3.1)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)(typescript@5.6.2)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': + '@hubspot/cli@7.6.2(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.6.2)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)(typescript@5.6.2)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': dependencies: '@hubspot/local-dev-lib': 3.19.1 '@hubspot/project-parsing-lib': 0.8.6(@hubspot/local-dev-lib@3.19.1) @@ -15700,7 +15726,7 @@ snapshots: yargs: 17.7.2 yargs-parser: 21.1.1 optionalDependencies: - '@hubspot/cms-dev-server': 1.0.38(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.3.1)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0) + '@hubspot/cms-dev-server': 1.0.38(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.6.2)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0) '@modelcontextprotocol/sdk': 1.13.3 transitivePeerDependencies: - '@babel/core' @@ -15738,7 +15764,7 @@ snapshots: react-dom: 18.3.1(react@18.3.1) optional: true - '@hubspot/cms-dev-server@1.0.38(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.3.1)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)': + '@hubspot/cms-dev-server@1.0.38(@babel/core@7.28.4)(@types/node@18.11.9)(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(encoding@0.1.13)(prettier@3.6.2)(rollup@4.52.5)(styled-components@6.1.19(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(terser@5.27.0)': dependencies: '@babel/code-frame': 7.26.2 '@babel/parser': 7.26.2 @@ -15754,14 +15780,14 @@ snapshots: '@radix-ui/react-toggle': 1.1.10(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@radix-ui/react-tooltip': 1.1.6(@types/react-dom@19.1.9(@types/react@19.1.15))(@types/react@19.1.15)(react-dom@18.3.1(react@18.3.1))(react@18.3.1) '@sentry/node': 6.19.7 - '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-essentials': 8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-interactions': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-links': 8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.3.1)) - '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.3.1))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) - '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.3.1))(typescript@4.7.4) - '@storybook/react-vite': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@8.6.14(prettier@3.3.1))(typescript@4.7.4)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-essentials': 8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-interactions': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-links': 8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.6.2)) + '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.6.2))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) + '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.2))(typescript@4.7.4) + '@storybook/react-vite': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@8.6.14(prettier@3.6.2))(typescript@4.7.4)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) + '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.2)) '@vitejs/plugin-react': 4.7.0(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) ansi-to-html: 0.7.2 babel-plugin-macros: 3.1.0 @@ -15783,7 +15809,7 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) request: 2.88.2 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) styled-jsx: 5.1.2(@babel/core@7.28.4)(babel-plugin-macros@3.1.0)(react@18.3.1) tailwind-merge: 2.6.0 tailwindcss-animate: 1.0.7 @@ -18295,6 +18321,11 @@ snapshots: md-to-react-email: 5.0.2(react@18.3.1) react: 18.3.1 + '@react-email/markdown@0.0.17(react@18.3.1)': + dependencies: + marked: 15.0.12 + react: 18.3.1 + '@react-email/preview@0.0.10(react@18.3.1)': dependencies: react: 18.3.1 @@ -18302,11 +18333,19 @@ snapshots: '@react-email/render@0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': dependencies: html-to-text: 9.0.5 - js-beautify: 1.14.11 + js-beautify: 1.15.4 react: 18.3.1 react-dom: 18.3.1(react@18.3.1) react-promise-suspense: 0.3.4 + '@react-email/render@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)': + dependencies: + html-to-text: 9.0.5 + prettier: 3.6.2 + react: 18.3.1 + react-dom: 18.3.1(react@18.3.1) + optional: true + '@react-email/row@0.0.9(react@18.3.1)': dependencies: react: 18.3.1 @@ -19222,140 +19261,140 @@ snapshots: '@standard-schema/spec@1.0.0': {} - '@storybook/addon-actions@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-actions@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.3.1 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) uuid: 9.0.1 optional: true - '@storybook/addon-backgrounds@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-backgrounds@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optional: true - '@storybook/addon-controls@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-controls@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optional: true - '@storybook/addon-docs@8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-docs@8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.6.2))': dependencies: '@mdx-js/react': 3.1.1(@types/react@19.1.15)(react@19.1.1) - '@storybook/blocks': 8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.3.1)) - '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/react-dom-shim': 8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.3.1)) + '@storybook/blocks': 8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.6.2)) + '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/react-dom-shim': 8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.6.2)) react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' optional: true - '@storybook/addon-essentials@8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-essentials@8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.6.2))': dependencies: - '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-backgrounds': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-controls': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-docs': 8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-highlight': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-measure': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-outline': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-toolbars': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/addon-viewport': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - storybook: 8.6.14(prettier@3.3.1) + '@storybook/addon-actions': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-backgrounds': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-controls': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-docs': 8.6.14(@types/react@19.1.15)(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-highlight': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-measure': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-outline': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-toolbars': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/addon-viewport': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 transitivePeerDependencies: - '@types/react' optional: true - '@storybook/addon-highlight@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-highlight@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/addon-interactions@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-interactions@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.2)) polished: 4.3.1 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optional: true - '@storybook/addon-links@8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-links@8.6.14(react@18.3.1)(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 optional: true - '@storybook/addon-measure@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-measure@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) tiny-invariant: 1.3.3 optional: true - '@storybook/addon-outline@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-outline@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optional: true - '@storybook/addon-toolbars@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-toolbars@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/addon-viewport@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/addon-viewport@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: memoizerific: 1.11.3 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/blocks@8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/blocks@8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/icons': 1.4.0(react-dom@19.1.1(react@19.1.1))(react@19.1.1) - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 optionalDependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) optional: true - '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.3.1))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': + '@storybook/builder-vite@8.6.14(storybook@8.6.14(prettier@3.6.2))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': dependencies: - '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/csf-plugin': 8.6.14(storybook@8.6.14(prettier@3.6.2)) browser-assert: 1.2.1 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) ts-dedent: 2.2.0 vite: 5.4.8(@types/node@18.11.9)(terser@5.27.0) optional: true - '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/components@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/core@8.6.14(prettier@3.3.1)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/core@8.6.14(prettier@3.6.2)(storybook@8.6.14(prettier@3.6.2))': dependencies: - '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.6.2)) better-opn: 3.0.2 browser-assert: 1.2.1 esbuild: 0.23.1 @@ -19367,7 +19406,7 @@ snapshots: util: 0.12.5 ws: 8.18.3 optionalDependencies: - prettier: 3.3.1 + prettier: 3.6.2 transitivePeerDependencies: - bufferutil - storybook @@ -19375,9 +19414,9 @@ snapshots: - utf-8-validate optional: true - '@storybook/csf-plugin@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/csf-plugin@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) unplugin: 1.16.1 optional: true @@ -19390,91 +19429,91 @@ snapshots: react-dom: 19.1.1(react@19.1.1) optional: true - '@storybook/instrumenter@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/instrumenter@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 '@vitest/utils': 2.1.8 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/manager-api@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/manager-api@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/preview-api@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/preview-api@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/react-dom-shim@8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.2))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/react-dom-shim@8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.3.1))': + '@storybook/react-dom-shim@8.6.14(react-dom@19.1.1(react@19.1.1))(react@19.1.1)(storybook@8.6.14(prettier@3.6.2))': dependencies: react: 19.1.1 react-dom: 19.1.1(react@19.1.1) - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@8.6.14(prettier@3.3.1))(typescript@4.7.4)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': + '@storybook/react-vite@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.52.5)(storybook@8.6.14(prettier@3.6.2))(typescript@4.7.4)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0))': dependencies: '@joshwooding/vite-plugin-react-docgen-typescript': 0.5.0(typescript@4.7.4)(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) '@rollup/pluginutils': 5.3.0(rollup@4.52.5) - '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.3.1))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) - '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.3.1))(typescript@4.7.4) + '@storybook/builder-vite': 8.6.14(storybook@8.6.14(prettier@3.6.2))(vite@5.4.8(@types/node@18.11.9)(terser@5.27.0)) + '@storybook/react': 8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.2))(typescript@4.7.4) find-up: 5.0.0 magic-string: 0.30.17 react: 18.3.1 react-docgen: 7.1.1 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.10 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) tsconfig-paths: 4.2.0 vite: 5.4.8(@types/node@18.11.9)(terser@5.27.0) optionalDependencies: - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.2)) transitivePeerDependencies: - rollup - supports-color - typescript optional: true - '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.3.1))(typescript@4.7.4)': + '@storybook/react@8.6.14(@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.2))(typescript@4.7.4)': dependencies: - '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/components': 8.6.14(storybook@8.6.14(prettier@3.6.2)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/preview-api': 8.6.14(storybook@8.6.14(prettier@3.3.1)) - '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.3.1)) - '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/manager-api': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/preview-api': 8.6.14(storybook@8.6.14(prettier@3.6.2)) + '@storybook/react-dom-shim': 8.6.14(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.6.14(prettier@3.6.2)) + '@storybook/theming': 8.6.14(storybook@8.6.14(prettier@3.6.2)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optionalDependencies: - '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/test': 8.6.14(storybook@8.6.14(prettier@3.6.2)) typescript: 4.7.4 optional: true - '@storybook/test@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/test@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.3.1)) + '@storybook/instrumenter': 8.6.14(storybook@8.6.14(prettier@3.6.2)) '@testing-library/dom': 10.4.0 '@testing-library/jest-dom': 6.5.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) '@vitest/expect': 2.0.5 '@vitest/spy': 2.0.5 - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true - '@storybook/theming@8.6.14(storybook@8.6.14(prettier@3.3.1))': + '@storybook/theming@8.6.14(storybook@8.6.14(prettier@3.6.2))': dependencies: - storybook: 8.6.14(prettier@3.3.1) + storybook: 8.6.14(prettier@3.6.2) optional: true '@stripe/stripe-js@7.3.1': {} @@ -24008,7 +24047,7 @@ snapshots: dependencies: '@tootallnate/once': 1.1.2 agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.3 transitivePeerDependencies: - supports-color @@ -24479,7 +24518,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.4.0 + debug: 4.4.3 istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -24942,12 +24981,13 @@ snapshots: js-base64@3.7.7: {} - js-beautify@1.14.11: + js-beautify@1.15.4: dependencies: config-chain: 1.1.13 editorconfig: 1.0.4 glob: 10.4.5 - nopt: 7.2.0 + js-cookie: 3.0.5 + nopt: 7.2.1 js-cookie@3.0.5: {} @@ -25400,6 +25440,8 @@ snapshots: markdown-table@3.0.3: {} + marked@15.0.12: {} + marked@16.4.1: {} marked@7.0.4: {} @@ -25774,7 +25816,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.9 - debug: 4.4.0 + debug: 4.4.3 decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.1 @@ -26148,7 +26190,7 @@ snapshots: abbrev: 1.1.1 optional: true - nopt@7.2.0: + nopt@7.2.1: dependencies: abbrev: 2.0.0 @@ -26763,6 +26805,9 @@ snapshots: prettier@3.3.1: {} + prettier@3.6.2: + optional: true + pretty-format@26.6.2: dependencies: '@jest/types': 26.6.2 @@ -27507,9 +27552,9 @@ snapshots: requires-port@1.0.0: {} - resend@6.1.2(@react-email/render@0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): + resend@6.1.2(@react-email/render@2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1)): optionalDependencies: - '@react-email/render': 0.0.17(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@react-email/render': 2.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) resolve-alpn@1.2.1: {} @@ -28069,7 +28114,7 @@ snapshots: socks-proxy-agent@6.2.1: dependencies: agent-base: 6.0.2 - debug: 4.4.0 + debug: 4.4.3 socks: 2.8.4 transitivePeerDependencies: - supports-color @@ -28183,11 +28228,11 @@ snapshots: stoppable@1.1.0: {} - storybook@8.6.14(prettier@3.3.1): + storybook@8.6.14(prettier@3.6.2): dependencies: - '@storybook/core': 8.6.14(prettier@3.3.1)(storybook@8.6.14(prettier@3.3.1)) + '@storybook/core': 8.6.14(prettier@3.6.2)(storybook@8.6.14(prettier@3.6.2)) optionalDependencies: - prettier: 3.3.1 + prettier: 3.6.2 transitivePeerDependencies: - bufferutil - supports-color