feat(notifications): renderNotificationEmail + previewEmailTemplate + logistics sample data
- Export `renderNotificationEmail` from email.ts (pure template resolver, no send) - Refactor `sendStyledNotificationEmail` to delegate to `renderNotificationEmail` - Hoist sampleData to module-level `NOTIFICATION_SAMPLE_DATA` in notification router - Add 8 logistics sample entries (FINALIST_*/TRAVEL_CONFIRMED/VISA_STATUS_UPDATE) - Add `notification.previewEmailTemplate` adminProcedure query (returns subject/html/hasStyledTemplate) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -336,7 +336,7 @@ function statCard(label: string, value: string | number): string {
|
||||
// Email Templates
|
||||
// =============================================================================
|
||||
|
||||
interface EmailTemplate {
|
||||
export interface EmailTemplate {
|
||||
subject: string
|
||||
html: string
|
||||
text: string
|
||||
@@ -2689,6 +2689,23 @@ export const NOTIFICATION_EMAIL_TEMPLATES: Record<string, TemplateGenerator> = {
|
||||
),
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the email template for a notification type without sending.
|
||||
* Exported for use by preview endpoints.
|
||||
*/
|
||||
export function renderNotificationEmail(
|
||||
name: string,
|
||||
type: string,
|
||||
context: NotificationEmailContext,
|
||||
subjectOverride?: string,
|
||||
): EmailTemplate {
|
||||
const generator = NOTIFICATION_EMAIL_TEMPLATES[type]
|
||||
const template = generator
|
||||
? generator({ ...context, linkUrl: ensureAbsoluteUrl(context.linkUrl), name })
|
||||
: getNotificationEmailTemplate(name, subjectOverride || context.title, context.message, ensureAbsoluteUrl(context.linkUrl))
|
||||
return subjectOverride ? { ...template, subject: subjectOverride } : template
|
||||
}
|
||||
|
||||
/**
|
||||
* Send styled notification email using the appropriate template
|
||||
*/
|
||||
@@ -2699,32 +2716,7 @@ export async function sendStyledNotificationEmail(
|
||||
context: NotificationEmailContext,
|
||||
subjectOverride?: string
|
||||
): Promise<void> {
|
||||
// Safety net: always ensure linkUrl is absolute before passing to templates
|
||||
const safeContext = {
|
||||
...context,
|
||||
linkUrl: ensureAbsoluteUrl(context.linkUrl),
|
||||
}
|
||||
const templateGenerator = NOTIFICATION_EMAIL_TEMPLATES[type]
|
||||
|
||||
let template: EmailTemplate
|
||||
|
||||
if (templateGenerator) {
|
||||
// Use styled template
|
||||
template = templateGenerator({ ...safeContext, name })
|
||||
// Apply subject override if provided
|
||||
if (subjectOverride) {
|
||||
template.subject = subjectOverride
|
||||
}
|
||||
} else {
|
||||
// Fall back to generic template
|
||||
template = getNotificationEmailTemplate(
|
||||
name,
|
||||
subjectOverride || safeContext.title,
|
||||
safeContext.message,
|
||||
safeContext.linkUrl
|
||||
)
|
||||
}
|
||||
|
||||
const template = renderNotificationEmail(name, type, context, subjectOverride)
|
||||
await sendEmail({ to: email, subject: template.subject, text: template.text, html: template.html })
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user