The 🐾 emoji rendered inconsistently across mail clients/OS fonts and looked off-brand. Replaces it with the exact lucide-react "paw-print" mark (same path data as the landing page's filled paw icon) inlined as raw SVG in the email header. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
123 lines
5.1 KiB
TypeScript
123 lines
5.1 KiB
TypeScript
import "server-only";
|
|
|
|
/**
|
|
* Branded HTML shell for transactional emails, mirroring the landing page's
|
|
* orange gradient hero + paw-print wordmark (src/app/page.tsx) so PawFeed
|
|
* mail doesn't look like unstyled plaintext dumped into an HTML wrapper
|
|
* (Gitea report: "html plain" emails with a bare "Hallo," greeting).
|
|
*
|
|
* Table-based layout with inline styles throughout — email clients (Outlook
|
|
* especially) ignore most <style> block CSS and flexbox/grid entirely, so
|
|
* this deliberately avoids Tailwind classes and modern layout properties.
|
|
*/
|
|
|
|
interface BrandedMailInput {
|
|
previewText: string;
|
|
greetingName?: string | null;
|
|
bodyHtml: string;
|
|
ctaLabel?: string;
|
|
ctaUrl?: string;
|
|
}
|
|
|
|
const ORANGE_500 = "#f97316";
|
|
const ORANGE_600 = "#ea580c";
|
|
const ORANGE_50 = "#fff7ed";
|
|
const TEXT_MUTED = "#78716c";
|
|
const TEXT_BODY = "#292524";
|
|
|
|
/**
|
|
* Same mark as the landing page's filled paw icon (lucide-react "paw-print",
|
|
* fill="currentColor" strokeWidth={0} — see src/app/page.tsx navbar/hero).
|
|
* Inlined as raw SVG rather than an emoji so the header matches the brand
|
|
* mark exactly instead of relying on the recipient's OS emoji font.
|
|
*/
|
|
const PAW_MARK_SVG = `<svg width="28" height="28" viewBox="0 0 24 24" fill="#ffffff" xmlns="http://www.w3.org/2000/svg" role="img" aria-label="PawFeed">
|
|
<circle cx="11" cy="4" r="2" />
|
|
<circle cx="18" cy="8" r="2" />
|
|
<circle cx="20" cy="16" r="2" />
|
|
<path d="M9 10a5 5 0 0 1 5 5v3.5a3.5 3.5 0 0 1-6.84 1.045Q6.52 17.48 4.46 16.84A3.5 3.5 0 0 1 5.5 10Z" />
|
|
</svg>`;
|
|
|
|
export function buildGreeting(firstName?: string | null): string {
|
|
return firstName ? `Hallo ${firstName},` : "Hallo,";
|
|
}
|
|
|
|
export function renderBrandedMailHtml({
|
|
previewText,
|
|
greetingName,
|
|
bodyHtml,
|
|
ctaLabel,
|
|
ctaUrl,
|
|
}: BrandedMailInput): string {
|
|
const greeting = buildGreeting(greetingName);
|
|
const cta =
|
|
ctaLabel && ctaUrl
|
|
? `
|
|
<tr>
|
|
<td style="padding: 8px 40px 32px;">
|
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0">
|
|
<tr>
|
|
<td style="border-radius: 10px; background-color: ${ORANGE_500};">
|
|
<a href="${ctaUrl}" style="display: inline-block; padding: 13px 28px; font-family: 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 15px; font-weight: 600; color: #ffffff; text-decoration: none;">
|
|
${ctaLabel}
|
|
</a>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>`
|
|
: "";
|
|
|
|
return `<!doctype html>
|
|
<html lang="de">
|
|
<head>
|
|
<meta charset="utf-8" />
|
|
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
<title>PawFeed</title>
|
|
</head>
|
|
<body style="margin: 0; padding: 0; background-color: #f5f5f4;">
|
|
<div style="display: none; max-height: 0; overflow: hidden; opacity: 0;">${previewText}</div>
|
|
<table role="presentation" width="100%" cellpadding="0" cellspacing="0" border="0" style="background-color: #f5f5f4; padding: 32px 16px;">
|
|
<tr>
|
|
<td align="center">
|
|
<table role="presentation" width="600" cellpadding="0" cellspacing="0" border="0" style="max-width: 600px; width: 100%; background-color: #ffffff; border-radius: 20px; overflow: hidden;">
|
|
<tr>
|
|
<td style="background-color: ${ORANGE_500}; padding: 36px 40px;" align="center">
|
|
<table role="presentation" cellpadding="0" cellspacing="0" border="0" align="center" style="margin: 0 auto;">
|
|
<tr>
|
|
<td style="vertical-align: middle; padding-right: 10px; line-height: 0;">
|
|
${PAW_MARK_SVG}
|
|
</td>
|
|
<td style="vertical-align: middle;">
|
|
<span style="font-family: 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 26px; font-weight: 800; color: #ffffff; letter-spacing: -0.02em;">PawFeed</span>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
<div style="font-family: 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 14px; font-weight: 500; color: ${ORANGE_50}; margin-top: 6px;">
|
|
Where pets are the stars.
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 36px 40px 8px; font-family: 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 16px; line-height: 1.6; color: ${TEXT_BODY};">
|
|
<p style="margin: 0 0 16px;">${greeting}</p>
|
|
${bodyHtml}
|
|
</td>
|
|
</tr>
|
|
${cta}
|
|
<tr>
|
|
<td style="padding: 24px 40px 32px; border-top: 1px solid #f0efed;">
|
|
<p style="margin: 0; font-family: 'Segoe UI', Helvetica, Arial, sans-serif; font-size: 13px; line-height: 1.6; color: ${TEXT_MUTED};">
|
|
Dein PawFeed-Team<br />
|
|
Diese E-Mail wurde automatisch von <a href="https://pawfeed.org" style="color: ${ORANGE_600}; text-decoration: none;">pawfeed.org</a> versendet.
|
|
</p>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</td>
|
|
</tr>
|
|
</table>
|
|
</body>
|
|
</html>`;
|
|
}
|