From 22a26e1449b2e0d53b02026d03bbba1f2b016864 Mon Sep 17 00:00:00 2001 From: David Clark Date: Wed, 10 Jun 2026 23:30:49 -0400 Subject: [PATCH] add feature for AI generated network partner email customization --- .../program/partners/invite-email-preview.tsx | 146 ++++++++++-- .../partners/invite-network-partner-sheet.tsx | 69 +++++- .../(ee)/program/partners/invite-sheet-ui.tsx | 3 + .../generate-partner-network-invite-email.ts | 214 ++++++++++++++++++ apps/web/lib/api/links/usage-checks.ts | 4 +- ...t-program-network-invite-email-defaults.ts | 14 +- apps/web/lib/zod/schemas/partner-network.ts | 12 + .../workflows/move-group-workflow.test.ts | 16 +- 8 files changed, 440 insertions(+), 38 deletions(-) create mode 100644 apps/web/lib/actions/partners/generate-partner-network-invite-email.ts diff --git a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-email-preview.tsx b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-email-preview.tsx index d1c623a73b5..2055faf89c9 100644 --- a/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-email-preview.tsx +++ b/apps/web/app/app.dub.co/(dashboard)/[slug]/(ee)/program/partners/invite-email-preview.tsx @@ -4,11 +4,14 @@ import { RichTextArea, RichTextProvider, RichTextToolbar, + Sparkle3, + Tooltip, Trophy, useMediaQuery, } from "@dub/ui"; import { Lock } from "@dub/ui/icons"; import { cn } from "@dub/utils"; +import { RotateCcw } from "lucide-react"; import { ReactNode, useEffect, useRef, useState } from "react"; import { toast } from "sonner"; @@ -18,21 +21,36 @@ export type EmailContent = { body: string; }; +const INVITE_GENERATION_STEPS = ["Analyzing profile", "Constructing invite"]; + export function InviteEmailPreview({ emailContent, defaultEmailContent, fromAddress, onSave, + onGenerate, + onReset, onEditingChange, isSaving = false, + isGenerating = false, + showReset = false, + generateDisabledTooltip, + generationAvatar, }: { emailContent: EmailContent; defaultEmailContent: EmailContent; fromAddress: string; // Persists the sanitized content; returning false keeps the edit mode open onSave: (content: EmailContent) => Promise | boolean; + onGenerate?: () => Promise; + onReset?: () => void; onEditingChange?: (isEditing: boolean) => void; isSaving?: boolean; + isGenerating?: boolean; + showReset?: boolean; + // When set, disables the generate button and explains why + generateDisabledTooltip?: string; + generationAvatar?: ReactNode; }) { const { isMobile } = useMediaQuery(); const richTextRef = useRef<{ setContent: (content: any) => void }>(null); @@ -116,13 +134,42 @@ export function InviteEmailPreview({ /> ) : ( - + + )} +