Closes Wave 1.4 (CRITICAL). Three templates still inlined URLs
directly into `href` without the existing safeUrl() helper:
- inquiry-client-confirmation: `mailto:${contactEmail}` href —
user-supplied email straight to an HTML attribute.
- inquiry-sales-notification: `${crmUrl}` from inquiry form input.
- residential-inquiry: same `mailto:${contactEmail}` pattern.
Each call now passes through `safeUrl()` from `@/lib/email/shell`,
which (a) scheme-allow-lists to http(s)/mailto/tel/root-relative and
(b) HTML-attribute-escapes the result. A stray `"` in any URL would
have escaped the attribute; a `javascript:` scheme would have
triggered XSS in webmail clients that run scripts.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
97 lines
2.8 KiB
TypeScript
97 lines
2.8 KiB
TypeScript
import { Link, Text, render } from '@react-email/components';
|
|
import * as React from 'react';
|
|
|
|
import { brandingPrimaryColor, renderShell, safeUrl, 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={safeUrl(`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,
|
|
};
|
|
}
|