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:
96
src/lib/email/templates/inquiry-client-confirmation.tsx
Normal file
96
src/lib/email/templates/inquiry-client-confirmation.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import { Link, Text, render } from '@react-email/components';
|
||||
import * as React from 'react';
|
||||
|
||||
import { brandingPrimaryColor, renderShell, type BrandingShell } from '@/lib/email/shell';
|
||||
|
||||
export interface InquiryClientConfirmationData {
|
||||
firstName: string;
|
||||
mooringNumber: string | null;
|
||||
contactEmail: string;
|
||||
portName?: string;
|
||||
}
|
||||
|
||||
interface RenderOpts {
|
||||
branding?: BrandingShell | null;
|
||||
}
|
||||
|
||||
function ClientConfirmationBody({
|
||||
firstName,
|
||||
berthText,
|
||||
contactEmail,
|
||||
portName,
|
||||
accent,
|
||||
}: {
|
||||
firstName: string;
|
||||
berthText: string;
|
||||
contactEmail: string;
|
||||
portName: string;
|
||||
accent: string;
|
||||
}) {
|
||||
return (
|
||||
<>
|
||||
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>Dear {firstName},</Text>
|
||||
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>
|
||||
Thank you for expressing interest in {berthText}. Our team has registered your interest, and
|
||||
we will reach out to you very shortly by your preferred method of contact with more
|
||||
information.
|
||||
</Text>
|
||||
<Text style={{ marginBottom: '10px', fontSize: '16px' }}>
|
||||
If you have any questions, please feel free to reach out to us at{' '}
|
||||
<Link
|
||||
href={`mailto:${contactEmail}`}
|
||||
style={{ color: accent, textDecoration: 'underline' }}
|
||||
>
|
||||
{contactEmail}
|
||||
</Link>
|
||||
.
|
||||
</Text>
|
||||
<Text style={{ fontSize: '16px' }}>
|
||||
Best regards,
|
||||
<br />
|
||||
The {portName} Sales Team
|
||||
</Text>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
export async function inquiryClientConfirmation(
|
||||
data: InquiryClientConfirmationData,
|
||||
overrides?: RenderOpts,
|
||||
) {
|
||||
const { firstName, mooringNumber, contactEmail } = data;
|
||||
const portName = data.portName ?? 'Port Nimara';
|
||||
const berthText = mooringNumber ? `Berth ${mooringNumber}` : `a ${portName} Berth`;
|
||||
const subject = mooringNumber
|
||||
? `Thank You for Your Interest in Berth ${mooringNumber}`
|
||||
: `Thank You for Your Interest in a ${portName} Berth`;
|
||||
const accent = brandingPrimaryColor(overrides?.branding);
|
||||
|
||||
const body = await render(
|
||||
<ClientConfirmationBody
|
||||
firstName={firstName}
|
||||
berthText={berthText}
|
||||
contactEmail={contactEmail}
|
||||
portName={portName}
|
||||
accent={accent}
|
||||
/>,
|
||||
{ pretty: false },
|
||||
);
|
||||
|
||||
const text = [
|
||||
`Dear ${firstName},`,
|
||||
'',
|
||||
`Thank you for expressing interest in ${berthText}. Our team has registered your interest, and we will reach out to you very shortly by your preferred method of contact with more information.`,
|
||||
'',
|
||||
`If you have any questions, please feel free to reach out to us at ${contactEmail}.`,
|
||||
'',
|
||||
'Best regards,',
|
||||
`The ${portName} Sales Team`,
|
||||
].join('\n');
|
||||
|
||||
return {
|
||||
subject,
|
||||
html: renderShell({ title: subject, body, branding: overrides?.branding }),
|
||||
text,
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user