feat(final-docs): GRAND_FINAL_DOCS_REMINDER/SUBMITTED notification types + email template

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Matt
2026-06-09 15:23:44 +02:00
parent 9e058e6ad7
commit f3d3a21156
3 changed files with 60 additions and 0 deletions

View File

@@ -2238,6 +2238,42 @@ function getFinalistReminderTemplate(
}
}
/**
* Reminder email sent to a finalist team to upload their Grand Final documents.
*/
function getGrandFinalDocsReminderTemplate(
name: string,
projectTitle: string,
deadline: Date | null,
uploadUrl: string,
missing: string[],
): EmailTemplate {
const greeting = name ? `Hi ${name},` : 'Hi there,'
const formattedDeadline = deadline
? deadline.toLocaleString('en-GB', { timeZone: 'Europe/Paris', dateStyle: 'full', timeStyle: 'short' })
: null
const missingLine = missing.length
? `Still needed: <strong>${escapeHtml(missing.join(', '))}</strong>.`
: 'Please make sure all required documents are uploaded.'
const content = `
${sectionTitle('Grand Final — Final Documents')}
${paragraph(greeting)}
${paragraph(`Please upload the final documents for <strong>${escapeHtml(projectTitle)}</strong> ahead of the Monaco Ocean Protection Challenge Grand Finale.`)}
${paragraph(missingLine)}
${formattedDeadline ? infoBox(`Deadline: <strong>${escapeHtml(formattedDeadline)} (Paris time)</strong>.`, 'warning') : ''}
${ctaButton(uploadUrl, 'Upload documents')}
${paragraph('If you have any questions, please reach out to the MOPC team.')}
`
const text = [
greeting, '',
`Please upload the final documents for "${projectTitle}" ahead of the Grand Finale.`,
missing.length ? `Still needed: ${missing.join(', ')}.` : 'Please make sure all required documents are uploaded.',
formattedDeadline ? `Deadline: ${formattedDeadline} (Paris time).` : '',
`Upload documents: ${uploadUrl}`, '', 'The MOPC team',
].join('\n')
return { subject: 'Action needed: upload your Grand Final documents', html: getEmailWrapper(content), text }
}
/**
* Notification email sent to a team when their confirmed finalist slot is withdrawn by an admin.
*/
@@ -2685,6 +2721,14 @@ export const NOTIFICATION_EMAIL_TEMPLATES: Record<string, TemplateGenerator> = {
new Date((ctx.metadata?.deadline as string) || Date.now()),
ctx.linkUrl || '',
),
GRAND_FINAL_DOCS_REMINDER: (ctx) =>
getGrandFinalDocsReminderTemplate(
ctx.name || '',
(ctx.metadata?.projectTitle as string) || 'Your project',
ctx.metadata?.deadline ? new Date(ctx.metadata.deadline as string) : null,
ctx.linkUrl || '',
(ctx.metadata?.missing as string[]) || [],
),
FINALIST_WITHDRAWN: (ctx) =>
getFinalistWithdrawnTemplate(
ctx.name || '',

View File

@@ -105,6 +105,8 @@ export const NotificationTypes = {
FINALIST_WITHDRAWN: 'FINALIST_WITHDRAWN',
TRAVEL_CONFIRMED: 'TRAVEL_CONFIRMED',
VISA_STATUS_UPDATE: 'VISA_STATUS_UPDATE',
GRAND_FINAL_DOCS_REMINDER: 'GRAND_FINAL_DOCS_REMINDER',
GRAND_FINAL_DOCS_SUBMITTED: 'GRAND_FINAL_DOCS_SUBMITTED',
} as const
export type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]