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

@@ -271,6 +271,20 @@ const NOTIFICATION_EMAIL_SETTINGS = [
description: 'Email to the attendee when their visa application status changes', description: 'Email to the attendee when their visa application status changes',
sendEmail: true, sendEmail: true,
}, },
{
notificationType: 'GRAND_FINAL_DOCS_REMINDER',
category: 'logistics',
label: 'Final Documents Reminder',
description: 'Reminder to finalist teams to upload their Grand Final documents before the deadline',
sendEmail: true,
},
{
notificationType: 'GRAND_FINAL_DOCS_SUBMITTED',
category: 'logistics',
label: 'Final Documents Submitted',
description: 'Notifies the team mentor when a finalist uploads a Grand Final document',
sendEmail: false,
},
// Admin notifications (in-app only by default) // Admin notifications (in-app only by default)
{ {

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. * 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()), new Date((ctx.metadata?.deadline as string) || Date.now()),
ctx.linkUrl || '', 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) => FINALIST_WITHDRAWN: (ctx) =>
getFinalistWithdrawnTemplate( getFinalistWithdrawnTemplate(
ctx.name || '', ctx.name || '',

View File

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