feat(comms): logistics notification types, templates, and email settings
- Add 8 constants to NotificationTypes (FINALIST_CONFIRMED/DECLINED/EXPIRED/ WAITLIST_PROMOTED/REMINDER/WITHDRAWN, TRAVEL_CONFIRMED, VISA_STATUS_UPDATE) with matching icons and priorities in NotificationIcons/NotificationPriorities - Add 4 branded email templates: getFinalistReminderTemplate, getFinalistWithdrawnTemplate, getTravelConfirmedTemplate, getVisaStatusTemplate — registered in NOTIFICATION_EMAIL_TEMPLATES (admin-alert types use generic fallback) - Add 8 logistics seed rows to seed-notification-settings.ts; upserted to dev DB (idempotent) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -214,6 +214,64 @@ const NOTIFICATION_EMAIL_SETTINGS = [
|
|||||||
sendEmail: true,
|
sendEmail: true,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Logistics notifications
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_CONFIRMED',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Finalist Confirmed',
|
||||||
|
description: 'Admin alert when a team confirms their grand-finale attendance',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_DECLINED',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Finalist Declined',
|
||||||
|
description: 'Admin alert when a team declines or an admin declines their finalist slot',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_EXPIRED',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Finalist Confirmation Expired',
|
||||||
|
description: 'Admin alert when a pending confirmation passes its deadline without a response',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_WAITLIST_PROMOTED',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Waitlist Promoted',
|
||||||
|
description: 'Admin alert when a waitlisted team is promoted to a confirmed finalist slot',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_REMINDER',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Confirmation Reminder',
|
||||||
|
description: 'Reminder email to the team lead when the confirmation deadline is approaching',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'FINALIST_WITHDRAWN',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Finalist Slot Withdrawn',
|
||||||
|
description: 'Notification to the team when their confirmed grand-finale slot is withdrawn by an admin',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'TRAVEL_CONFIRMED',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Travel Confirmed',
|
||||||
|
description: 'Email to the attendee when their flight and travel details are confirmed',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
notificationType: 'VISA_STATUS_UPDATE',
|
||||||
|
category: 'logistics',
|
||||||
|
label: 'Visa Status Update',
|
||||||
|
description: 'Email to the attendee when their visa application status changes',
|
||||||
|
sendEmail: true,
|
||||||
|
},
|
||||||
|
|
||||||
// Admin notifications (in-app only by default)
|
// Admin notifications (in-app only by default)
|
||||||
{
|
{
|
||||||
notificationType: 'FILTERING_COMPLETE',
|
notificationType: 'FILTERING_COMPLETE',
|
||||||
|
|||||||
295
src/lib/email.ts
295
src/lib/email.ts
@@ -2188,6 +2188,265 @@ export function getAccountReminderTemplate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// =============================================================================
|
||||||
|
// Logistics email templates
|
||||||
|
// =============================================================================
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reminder email sent to a team lead when the confirmation deadline is approaching.
|
||||||
|
*/
|
||||||
|
function getFinalistReminderTemplate(
|
||||||
|
name: string,
|
||||||
|
projectTitle: string,
|
||||||
|
deadline: Date,
|
||||||
|
confirmUrl: string,
|
||||||
|
): EmailTemplate {
|
||||||
|
const greeting = name ? `Hi ${name},` : 'Hi there,'
|
||||||
|
const formattedDeadline = deadline.toLocaleString('en-GB', {
|
||||||
|
timeZone: 'Europe/Paris',
|
||||||
|
dateStyle: 'full',
|
||||||
|
timeStyle: 'short',
|
||||||
|
})
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
${sectionTitle('Reminder: Grand-Finale Attendance')}
|
||||||
|
${paragraph(greeting)}
|
||||||
|
${paragraph(`Your project <strong>${escapeHtml(projectTitle)}</strong> has been selected as a finalist for the Monaco Ocean Protection Challenge Grand Finale. Your confirmation is still pending.`)}
|
||||||
|
${infoBox(`Please confirm your attendance <strong>by ${escapeHtml(formattedDeadline)} (Paris time)</strong>. If no response is received by the deadline, your slot may be reallocated.`, 'warning')}
|
||||||
|
${ctaButton(confirmUrl, 'Confirm attendance')}
|
||||||
|
${paragraph('If you have any questions, please reach out to the MOPC team.')}
|
||||||
|
`
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
greeting,
|
||||||
|
'',
|
||||||
|
`Your project "${projectTitle}" has been selected as a finalist for the Monaco Ocean Protection Challenge Grand Finale. Your confirmation is still pending.`,
|
||||||
|
'',
|
||||||
|
`Please confirm your attendance by ${formattedDeadline} (Paris time). If no response is received by the deadline, your slot may be reallocated.`,
|
||||||
|
'',
|
||||||
|
`Confirm attendance: ${confirmUrl}`,
|
||||||
|
'',
|
||||||
|
'If you have any questions, please reach out to the MOPC team.',
|
||||||
|
'',
|
||||||
|
'The MOPC team',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: 'Reminder: confirm your grand-finale attendance',
|
||||||
|
html: getEmailWrapper(content),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Notification email sent to a team when their confirmed finalist slot is withdrawn by an admin.
|
||||||
|
*/
|
||||||
|
function getFinalistWithdrawnTemplate(
|
||||||
|
name: string,
|
||||||
|
projectTitle: string,
|
||||||
|
reason?: string,
|
||||||
|
): EmailTemplate {
|
||||||
|
const greeting = name ? `Hi ${name},` : 'Hi there,'
|
||||||
|
|
||||||
|
const reasonHtml = reason
|
||||||
|
? paragraph(`<strong>Reason:</strong> ${escapeHtml(reason)}`)
|
||||||
|
: ''
|
||||||
|
const reasonText = reason ? `Reason: ${reason}\n\n` : ''
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
${sectionTitle('Grand-Finale Slot Withdrawn')}
|
||||||
|
${paragraph(greeting)}
|
||||||
|
${infoBox(`We regret to inform you that your team's confirmed spot at the Grand Finale has been withdrawn for the project <strong>${escapeHtml(projectTitle)}</strong>.`, 'warning')}
|
||||||
|
${reasonHtml}
|
||||||
|
${paragraph('If you believe this is an error or have questions, please contact the MOPC team as soon as possible.')}
|
||||||
|
`
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
greeting,
|
||||||
|
'',
|
||||||
|
`We regret to inform you that your team's confirmed spot at the Grand Finale has been withdrawn for the project "${projectTitle}".`,
|
||||||
|
'',
|
||||||
|
reasonText + 'If you believe this is an error or have questions, please contact the MOPC team as soon as possible.',
|
||||||
|
'',
|
||||||
|
'The MOPC team',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: 'Your grand-finale slot has been withdrawn',
|
||||||
|
html: getEmailWrapper(content),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Travel confirmation email for a grand-finale attendee, including flight and hotel details.
|
||||||
|
*/
|
||||||
|
function getTravelConfirmedTemplate(
|
||||||
|
name: string,
|
||||||
|
projectTitle: string,
|
||||||
|
flight: {
|
||||||
|
arrivalAt?: Date | string | null
|
||||||
|
arrivalFlightNumber?: string | null
|
||||||
|
arrivalAirport?: string | null
|
||||||
|
departureAt?: Date | string | null
|
||||||
|
departureFlightNumber?: string | null
|
||||||
|
departureAirport?: string | null
|
||||||
|
},
|
||||||
|
hotel?: {
|
||||||
|
name: string
|
||||||
|
address?: string | null
|
||||||
|
link?: string | null
|
||||||
|
},
|
||||||
|
): EmailTemplate {
|
||||||
|
const greeting = name ? `Hi ${name},` : 'Hi there,'
|
||||||
|
|
||||||
|
const fmtDate = (d: Date | string | null | undefined) => {
|
||||||
|
if (!d) return null
|
||||||
|
const dt = d instanceof Date ? d : new Date(d)
|
||||||
|
return dt.toLocaleString('en-GB', { timeZone: 'Europe/Paris', dateStyle: 'full', timeStyle: 'short' })
|
||||||
|
}
|
||||||
|
|
||||||
|
const arrivalLines: string[] = []
|
||||||
|
const departureLines: string[] = []
|
||||||
|
|
||||||
|
if (flight.arrivalAt) arrivalLines.push(`Date: ${fmtDate(flight.arrivalAt)} (Paris time)`)
|
||||||
|
if (flight.arrivalFlightNumber) arrivalLines.push(`Flight: ${flight.arrivalFlightNumber}`)
|
||||||
|
if (flight.arrivalAirport) arrivalLines.push(`Airport: ${flight.arrivalAirport}`)
|
||||||
|
|
||||||
|
if (flight.departureAt) departureLines.push(`Date: ${fmtDate(flight.departureAt)} (Paris time)`)
|
||||||
|
if (flight.departureFlightNumber) departureLines.push(`Flight: ${flight.departureFlightNumber}`)
|
||||||
|
if (flight.departureAirport) departureLines.push(`Airport: ${flight.departureAirport}`)
|
||||||
|
|
||||||
|
const arrivalHtml =
|
||||||
|
arrivalLines.length > 0
|
||||||
|
? `<h3 style="margin:20px 0 8px;color:#0f172a;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:1px;">Arrival</h3>` +
|
||||||
|
`<ul style="margin:0 0 16px;padding-left:20px;color:${BRAND.textDark};font-size:14px;">` +
|
||||||
|
arrivalLines.map((l) => `<li style="margin:4px 0;">${escapeHtml(l)}</li>`).join('') +
|
||||||
|
`</ul>`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const departureHtml =
|
||||||
|
departureLines.length > 0
|
||||||
|
? `<h3 style="margin:20px 0 8px;color:#0f172a;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:1px;">Departure</h3>` +
|
||||||
|
`<ul style="margin:0 0 16px;padding-left:20px;color:${BRAND.textDark};font-size:14px;">` +
|
||||||
|
departureLines.map((l) => `<li style="margin:4px 0;">${escapeHtml(l)}</li>`).join('') +
|
||||||
|
`</ul>`
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const hotelHtml = hotel
|
||||||
|
? `<h3 style="margin:20px 0 8px;color:#0f172a;font-size:14px;font-weight:600;text-transform:uppercase;letter-spacing:1px;">Hotel</h3>` +
|
||||||
|
infoBox(
|
||||||
|
`<strong>${escapeHtml(hotel.name)}</strong>` +
|
||||||
|
(hotel.address ? `<br>${escapeHtml(hotel.address)}` : '') +
|
||||||
|
(hotel.link ? `<br><a href="${hotel.link}" style="color:${BRAND.darkBlue};">View hotel</a>` : ''),
|
||||||
|
'info',
|
||||||
|
)
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const hotelText = hotel
|
||||||
|
? ['\nHotel:', ` ${hotel.name}`, ...(hotel.address ? [` ${hotel.address}`] : []), ...(hotel.link ? [` ${hotel.link}`] : [])].join('\n')
|
||||||
|
: ''
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
${sectionTitle('Your travel is confirmed!')}
|
||||||
|
${paragraph(greeting)}
|
||||||
|
${paragraph(`Great news — your travel arrangements for the Grand Finale (<strong>${escapeHtml(projectTitle)}</strong>) have been confirmed. Here are your details:`)}
|
||||||
|
${arrivalHtml}
|
||||||
|
${departureHtml}
|
||||||
|
${hotelHtml}
|
||||||
|
${paragraph('If anything looks incorrect, please contact the MOPC team immediately.')}
|
||||||
|
`
|
||||||
|
|
||||||
|
const arrivalText = arrivalLines.length > 0 ? '\nArrival:\n' + arrivalLines.map((l) => ` ${l}`).join('\n') : ''
|
||||||
|
const departureText = departureLines.length > 0 ? '\nDeparture:\n' + departureLines.map((l) => ` ${l}`).join('\n') : ''
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
greeting,
|
||||||
|
'',
|
||||||
|
`Great news — your travel arrangements for the Grand Finale ("${projectTitle}") have been confirmed. Here are your details:`,
|
||||||
|
arrivalText,
|
||||||
|
departureText,
|
||||||
|
hotelText,
|
||||||
|
'',
|
||||||
|
'If anything looks incorrect, please contact the MOPC team immediately.',
|
||||||
|
'',
|
||||||
|
'The MOPC team',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: 'Your grand-finale travel is confirmed',
|
||||||
|
html: getEmailWrapper(content),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Visa status update email for a grand-finale attendee.
|
||||||
|
*/
|
||||||
|
function getVisaStatusTemplate(
|
||||||
|
name: string,
|
||||||
|
projectTitle: string,
|
||||||
|
status: 'INVITATION_SENT' | 'APPOINTMENT_BOOKED' | 'GRANTED' | 'DENIED',
|
||||||
|
note?: string,
|
||||||
|
): EmailTemplate {
|
||||||
|
const greeting = name ? `Hi ${name},` : 'Hi there,'
|
||||||
|
|
||||||
|
const statusCopy: Record<typeof status, { headline: string; body: string; variant: 'info' | 'success' | 'warning' }> = {
|
||||||
|
INVITATION_SENT: {
|
||||||
|
headline: 'Visa invitation letter sent',
|
||||||
|
body: 'Your official visa invitation letter for the Grand Finale has been sent. Please use it when applying for your visa at the relevant consulate or embassy.',
|
||||||
|
variant: 'info',
|
||||||
|
},
|
||||||
|
APPOINTMENT_BOOKED: {
|
||||||
|
headline: 'Visa appointment booked',
|
||||||
|
body: 'A visa appointment has been booked on your behalf. Please check your inbox or contact the MOPC team for the appointment details.',
|
||||||
|
variant: 'info',
|
||||||
|
},
|
||||||
|
GRANTED: {
|
||||||
|
headline: 'Visa granted — see you in Monaco!',
|
||||||
|
body: 'Congratulations — your visa has been granted. We look forward to welcoming you to the Monaco Ocean Protection Challenge Grand Finale.',
|
||||||
|
variant: 'success',
|
||||||
|
},
|
||||||
|
DENIED: {
|
||||||
|
headline: 'Visa application outcome',
|
||||||
|
body: 'Unfortunately, your visa application has not been successful at this time. Please contact the MOPC team as soon as possible so we can discuss next steps.',
|
||||||
|
variant: 'warning',
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const { headline, body, variant } = statusCopy[status]
|
||||||
|
const noteHtml = note ? paragraph(`<em>${escapeHtml(note)}</em>`) : ''
|
||||||
|
const noteText = note ? `\n${note}\n` : ''
|
||||||
|
|
||||||
|
const content = `
|
||||||
|
${sectionTitle('Visa update for the Grand Finale')}
|
||||||
|
${paragraph(greeting)}
|
||||||
|
${paragraph(`This is an update regarding your visa for the Grand Finale — project <strong>${escapeHtml(projectTitle)}</strong>.`)}
|
||||||
|
${infoBox(`<strong>${escapeHtml(headline)}</strong><br>${escapeHtml(body)}`, variant)}
|
||||||
|
${noteHtml}
|
||||||
|
${paragraph('If you have any questions, please don\'t hesitate to contact the MOPC team.')}
|
||||||
|
`
|
||||||
|
|
||||||
|
const text = [
|
||||||
|
greeting,
|
||||||
|
'',
|
||||||
|
`This is an update regarding your visa for the Grand Finale — project "${projectTitle}".`,
|
||||||
|
'',
|
||||||
|
`${headline}`,
|
||||||
|
body,
|
||||||
|
noteText,
|
||||||
|
'If you have any questions, please don\'t hesitate to contact the MOPC team.',
|
||||||
|
'',
|
||||||
|
'The MOPC team',
|
||||||
|
].join('\n')
|
||||||
|
|
||||||
|
return {
|
||||||
|
subject: 'Visa update for the grand finale',
|
||||||
|
html: getEmailWrapper(content),
|
||||||
|
text,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Template registry mapping notification types to template generators
|
* Template registry mapping notification types to template generators
|
||||||
*/
|
*/
|
||||||
@@ -2392,6 +2651,42 @@ export const NOTIFICATION_EMAIL_TEMPLATES: Record<string, TemplateGenerator> = {
|
|||||||
(ctx.metadata?.programName as string) || 'MOPC',
|
(ctx.metadata?.programName as string) || 'MOPC',
|
||||||
ctx.linkUrl
|
ctx.linkUrl
|
||||||
),
|
),
|
||||||
|
|
||||||
|
// Logistics templates (team/attendee-facing)
|
||||||
|
FINALIST_REMINDER: (ctx) =>
|
||||||
|
getFinalistReminderTemplate(
|
||||||
|
ctx.name || '',
|
||||||
|
(ctx.metadata?.projectTitle as string) || 'Your project',
|
||||||
|
new Date((ctx.metadata?.deadline as string) || Date.now()),
|
||||||
|
ctx.linkUrl || '',
|
||||||
|
),
|
||||||
|
FINALIST_WITHDRAWN: (ctx) =>
|
||||||
|
getFinalistWithdrawnTemplate(
|
||||||
|
ctx.name || '',
|
||||||
|
(ctx.metadata?.projectTitle as string) || 'Your project',
|
||||||
|
ctx.metadata?.reason as string | undefined,
|
||||||
|
),
|
||||||
|
TRAVEL_CONFIRMED: (ctx) =>
|
||||||
|
getTravelConfirmedTemplate(
|
||||||
|
ctx.name || '',
|
||||||
|
(ctx.metadata?.projectTitle as string) || 'Your project',
|
||||||
|
{
|
||||||
|
arrivalAt: ctx.metadata?.arrivalAt as string | undefined,
|
||||||
|
arrivalFlightNumber: ctx.metadata?.arrivalFlightNumber as string | undefined,
|
||||||
|
arrivalAirport: ctx.metadata?.arrivalAirport as string | undefined,
|
||||||
|
departureAt: ctx.metadata?.departureAt as string | undefined,
|
||||||
|
departureFlightNumber: ctx.metadata?.departureFlightNumber as string | undefined,
|
||||||
|
departureAirport: ctx.metadata?.departureAirport as string | undefined,
|
||||||
|
},
|
||||||
|
ctx.metadata?.hotel as { name: string; address?: string; link?: string } | undefined,
|
||||||
|
),
|
||||||
|
VISA_STATUS_UPDATE: (ctx) =>
|
||||||
|
getVisaStatusTemplate(
|
||||||
|
ctx.name || '',
|
||||||
|
(ctx.metadata?.projectTitle as string) || 'Your project',
|
||||||
|
(ctx.metadata?.status as 'INVITATION_SENT' | 'APPOINTMENT_BOOKED' | 'GRANTED' | 'DENIED') || 'INVITATION_SENT',
|
||||||
|
ctx.metadata?.note as string | undefined,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -95,6 +95,16 @@ export const NotificationTypes = {
|
|||||||
FINALISTS_ANNOUNCED: 'FINALISTS_ANNOUNCED',
|
FINALISTS_ANNOUNCED: 'FINALISTS_ANNOUNCED',
|
||||||
WINNERS_ANNOUNCED: 'WINNERS_ANNOUNCED',
|
WINNERS_ANNOUNCED: 'WINNERS_ANNOUNCED',
|
||||||
REPORT_AVAILABLE: 'REPORT_AVAILABLE',
|
REPORT_AVAILABLE: 'REPORT_AVAILABLE',
|
||||||
|
|
||||||
|
// Logistics
|
||||||
|
FINALIST_CONFIRMED: 'FINALIST_CONFIRMED',
|
||||||
|
FINALIST_DECLINED: 'FINALIST_DECLINED',
|
||||||
|
FINALIST_EXPIRED: 'FINALIST_EXPIRED',
|
||||||
|
FINALIST_WAITLIST_PROMOTED: 'FINALIST_WAITLIST_PROMOTED',
|
||||||
|
FINALIST_REMINDER: 'FINALIST_REMINDER',
|
||||||
|
FINALIST_WITHDRAWN: 'FINALIST_WITHDRAWN',
|
||||||
|
TRAVEL_CONFIRMED: 'TRAVEL_CONFIRMED',
|
||||||
|
VISA_STATUS_UPDATE: 'VISA_STATUS_UPDATE',
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]
|
export type NotificationType = (typeof NotificationTypes)[keyof typeof NotificationTypes]
|
||||||
@@ -129,6 +139,14 @@ export const NotificationIcons: Record<string, string> = {
|
|||||||
[NotificationTypes.AWARD_RESULTS]: 'Trophy',
|
[NotificationTypes.AWARD_RESULTS]: 'Trophy',
|
||||||
[NotificationTypes.AI_RANKING_COMPLETE]: 'BarChart3',
|
[NotificationTypes.AI_RANKING_COMPLETE]: 'BarChart3',
|
||||||
[NotificationTypes.AI_RANKING_FAILED]: 'AlertTriangle',
|
[NotificationTypes.AI_RANKING_FAILED]: 'AlertTriangle',
|
||||||
|
[NotificationTypes.FINALIST_CONFIRMED]: 'CheckCircle',
|
||||||
|
[NotificationTypes.FINALIST_DECLINED]: 'XCircle',
|
||||||
|
[NotificationTypes.FINALIST_EXPIRED]: 'AlertTriangle',
|
||||||
|
[NotificationTypes.FINALIST_WAITLIST_PROMOTED]: 'TrendingUp',
|
||||||
|
[NotificationTypes.FINALIST_REMINDER]: 'Clock',
|
||||||
|
[NotificationTypes.FINALIST_WITHDRAWN]: 'UserMinus',
|
||||||
|
[NotificationTypes.TRAVEL_CONFIRMED]: 'Plane',
|
||||||
|
[NotificationTypes.VISA_STATUS_UPDATE]: 'FileText',
|
||||||
}
|
}
|
||||||
|
|
||||||
// Priority by notification type
|
// Priority by notification type
|
||||||
@@ -155,6 +173,14 @@ export const NotificationPriorities: Record<string, NotificationPriority> = {
|
|||||||
[NotificationTypes.AWARD_VOTING_OPEN]: 'high',
|
[NotificationTypes.AWARD_VOTING_OPEN]: 'high',
|
||||||
[NotificationTypes.AI_RANKING_COMPLETE]: 'normal',
|
[NotificationTypes.AI_RANKING_COMPLETE]: 'normal',
|
||||||
[NotificationTypes.AI_RANKING_FAILED]: 'high',
|
[NotificationTypes.AI_RANKING_FAILED]: 'high',
|
||||||
|
[NotificationTypes.FINALIST_EXPIRED]: 'urgent',
|
||||||
|
[NotificationTypes.FINALIST_REMINDER]: 'high',
|
||||||
|
[NotificationTypes.FINALIST_WITHDRAWN]: 'high',
|
||||||
|
[NotificationTypes.FINALIST_CONFIRMED]: 'normal',
|
||||||
|
[NotificationTypes.FINALIST_DECLINED]: 'high',
|
||||||
|
[NotificationTypes.FINALIST_WAITLIST_PROMOTED]: 'high',
|
||||||
|
[NotificationTypes.TRAVEL_CONFIRMED]: 'high',
|
||||||
|
[NotificationTypes.VISA_STATUS_UPDATE]: 'high',
|
||||||
}
|
}
|
||||||
|
|
||||||
interface CreateNotificationParams {
|
interface CreateNotificationParams {
|
||||||
|
|||||||
Reference in New Issue
Block a user