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:
@@ -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)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -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 || '',
|
||||||
|
|||||||
@@ -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]
|
||||||
|
|||||||
Reference in New Issue
Block a user