feat(email): port remaining 7 templates to react-email
Phase 2 (single commit) — applies the portal-auth.tsx pattern to every hand-strung transactional email template. JSX components rendered via @react-email/components' render() replace inline-style string templates + hand-rolled escapeHtml(). Ported (.ts → .tsx, public function signatures become async): crm-invite.tsx — admin/super-admin CRM invite admin-email-change.tsx — sign-in email changed notification inquiry-client-confirmation.tsx — public berth inquiry receipt inquiry-sales-notification.tsx — internal sales alert for inquiries residential-inquiry.tsx — pair: client confirmation + sales alert notification-digest.tsx — daily/hourly unread-notification digest document-signing.tsx — triplet: invitation + completed + reminder Each template now defines its body as a typed React component, drops escapeHtml() entirely (react-email auto-escapes string interpolation in JSX text + attributes), and passes the rendered HTML to the existing renderShell() for shell wrapping. The shell + branding flow is unchanged. Caller migration (all sync → async): src/app/api/public/residential-inquiries/route.ts src/lib/queue/workers/email.ts src/lib/services/notification-digest.service.ts src/lib/services/users.service.ts src/lib/services/document-signing-emails.service.ts src/lib/services/crm-invite.service.ts All call sites already lived inside async functions; only the await was needed. No public API shape changes other than return type (now Promise). The pattern now applies uniformly across all 8 email templates (portal- auth.tsx + the 7 in this commit). Email template directory is fully react-email-based. 1298/1298 vitest green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -69,7 +69,7 @@ export async function createCrmInvite(args: {
|
||||
});
|
||||
|
||||
const link = `${env.APP_URL}/set-password?token=${raw}`;
|
||||
const result = crmInviteEmail({
|
||||
const result = await crmInviteEmail({
|
||||
link,
|
||||
ttlHours: INVITE_TTL_HOURS,
|
||||
recipientName: args.name,
|
||||
@@ -232,7 +232,7 @@ export async function resendCrmInvite(
|
||||
|
||||
const link = `${env.APP_URL}/set-password?token=${raw}`;
|
||||
const branding = await getBrandingShell(meta.portId);
|
||||
const result = crmInviteEmail(
|
||||
const result = await crmInviteEmail(
|
||||
{
|
||||
link,
|
||||
ttlHours: INVITE_TTL_HOURS,
|
||||
|
||||
@@ -151,7 +151,7 @@ export async function sendSigningInvitation(args: SigningInvitationArgs): Promis
|
||||
args.signerRole,
|
||||
);
|
||||
|
||||
const { subject, html, text } = signingInvitationEmail(
|
||||
const { subject, html, text } = await signingInvitationEmail(
|
||||
{
|
||||
recipientName: args.recipient.name,
|
||||
documentLabel: args.documentLabel,
|
||||
@@ -194,7 +194,7 @@ export async function sendSigningReminder(args: SigningReminderArgs): Promise<vo
|
||||
args.signerRole,
|
||||
);
|
||||
|
||||
const { subject, html, text } = signingReminderEmail(
|
||||
const { subject, html, text } = await signingReminderEmail(
|
||||
{
|
||||
recipientName: args.recipient.name,
|
||||
documentLabel: args.documentLabel,
|
||||
@@ -242,7 +242,7 @@ export async function sendSigningCompleted(args: SigningCompletedArgs): Promise<
|
||||
await Promise.all(
|
||||
args.recipients.map((recipient) =>
|
||||
sendLimit(async () => {
|
||||
const { subject, html, text } = signingCompletedEmail(
|
||||
const { subject, html, text } = await signingCompletedEmail(
|
||||
{
|
||||
recipientName: recipient.name,
|
||||
documentLabel: args.documentLabel,
|
||||
|
||||
@@ -138,7 +138,7 @@ export async function runNotificationDigest(now: Date = new Date()): Promise<Dig
|
||||
|
||||
const visible = rows.slice(0, MAX_ITEMS_PER_USER);
|
||||
const inboxLink = `${env.APP_URL}/notifications`;
|
||||
const result = notificationDigestEmail(
|
||||
const result = await notificationDigestEmail(
|
||||
{
|
||||
portName: port.name,
|
||||
recipientName: u.name ?? '',
|
||||
|
||||
@@ -385,7 +385,7 @@ async function notifyAdminEmailChange(args: {
|
||||
getBrandingShell(args.portId).catch(() => null),
|
||||
]);
|
||||
|
||||
const { subject, html, text } = adminEmailChangeEmail(
|
||||
const { subject, html, text } = await adminEmailChangeEmail(
|
||||
{
|
||||
recipientName: args.displayName,
|
||||
newEmail: args.newEmail,
|
||||
|
||||
Reference in New Issue
Block a user