import { render } from '@react-email/components'; import { Button, Hr, Link, Text } from '@react-email/components'; import * as React from 'react'; import { brandingPrimaryColor, renderShell, safeUrl, type BrandingShell } from '@/lib/email/shell'; interface ActivationData { portName: string; link: string; ttlHours: number; recipientName?: string; } interface ResetData { portName: string; link: string; ttlMinutes: number; recipientName?: string; } interface RenderOpts { subject?: string | null; branding?: BrandingShell | null; } // ─── React-email body components ────────────────────────────────────────────── // react-email's `render()` auto-escapes string interpolation, so we don't // need our hand-rolled escapeHtml() on these bodies. Inline styles use // camelCase per CSSProperties — react-email serialises them to // email-client-safe inline `style="..."` attributes on output. function ActivationBody({ portName, link, ttlHours, recipientName, accent, }: ActivationData & { accent: string }) { const greeting = recipientName ? `Dear ${recipientName},` : 'Welcome,'; return ( <> Welcome to {portName} {greeting} You've been invited to access the {portName} client portal. Click the button below to set your password and activate your account. The link expires in {ttlHours} hours.

If the button doesn't work, paste this link into your browser:
{link}
Thank you,
{portName} CRM
); } function ResetBody({ portName, link, ttlMinutes, recipientName, accent, }: ResetData & { accent: string }) { const greeting = recipientName ? `Dear ${recipientName},` : 'Hello,'; return ( <> Password reset {greeting} We received a request to reset the password on your {portName} client portal account. Click the button below to choose a new one. The link expires in {ttlMinutes} minutes.

If you didn't request this, you can safely ignore this email — your password will remain unchanged. Thank you,
{portName} CRM
); } // ─── Public surface ─────────────────────────────────────────────────────────── export async function activationEmail( data: ActivationData, overrides?: RenderOpts, ): Promise<{ subject: string; html: string; text: string }> { const subject = overrides?.subject ? overrides.subject .replace(/\{\{portName\}\}/g, data.portName) .replace(/\{\{recipientName\}\}/g, data.recipientName ?? '') .replace(/\{\{ttlHours\}\}/g, String(data.ttlHours)) : `Activate your ${data.portName} client portal account`; const accent = brandingPrimaryColor(overrides?.branding); const body = await render(, { pretty: false, }); const text = [ `Welcome to ${data.portName}`, '', `You've been invited to access the ${data.portName} client portal.`, `Activate your account by visiting: ${data.link}`, '', `The link expires in ${data.ttlHours} hours.`, '', `Thank you,`, `${data.portName} CRM`, ].join('\n'); return { subject, html: renderShell({ title: subject, body, branding: overrides?.branding }), text, }; } export async function resetEmail( data: ResetData, overrides?: RenderOpts, ): Promise<{ subject: string; html: string; text: string }> { const subject = overrides?.subject ? overrides.subject .replace(/\{\{portName\}\}/g, data.portName) .replace(/\{\{recipientName\}\}/g, data.recipientName ?? '') .replace(/\{\{ttlMinutes\}\}/g, String(data.ttlMinutes)) : `Reset your ${data.portName} client portal password`; const accent = brandingPrimaryColor(overrides?.branding); const body = await render(, { pretty: false, }); const text = [ `Password reset for ${data.portName}`, '', `Reset your password by visiting: ${data.link}`, `The link expires in ${data.ttlMinutes} minutes.`, '', `If you didn't request this, you can safely ignore this email.`, '', `Thank you,`, `${data.portName} CRM`, ].join('\n'); return { subject, html: renderShell({ title: subject, body, branding: overrides?.branding }), text, }; }