Add background filtering jobs, improved date picker, AI reasoning display

- Implement background job system for AI filtering to avoid HTTP timeouts
- Add FilteringJob model to track progress of long-running filtering operations
- Add real-time progress polling for filtering operations on round details page
- Create custom DateTimePicker component with calendar popup (no year picker hassle)
- Fix round date persistence bug (refetchOnWindowFocus was resetting form state)
- Integrate filtering controls into round details page for filtering rounds
- Display AI reasoning for flagged/filtered projects in results table
- Add onboarding system scaffolding (schema, routes, basic UI)
- Allow setting round dates in the past for manual overrides

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-02-03 19:48:41 +01:00
parent 8be740a4fb
commit e2782b2b19
24 changed files with 3692 additions and 443 deletions

View File

@@ -470,6 +470,98 @@ Together for a healthier ocean.
}
}
/**
* Generate application confirmation email template
*/
function getApplicationConfirmationTemplate(
name: string,
projectName: string,
programName: string,
customMessage?: string
): EmailTemplate {
const greeting = name ? `Hello ${name},` : 'Hello,'
const customMessageHtml = customMessage
? `<div style="color: ${BRAND.textDark}; font-size: 15px; line-height: 1.7; margin: 20px 0; padding: 20px; background-color: ${BRAND.lightGray}; border-radius: 8px;">${customMessage.replace(/\n/g, '<br>')}</div>`
: ''
const content = `
${sectionTitle(greeting)}
${paragraph(`Thank you for submitting your application to <strong style="color: ${BRAND.darkBlue};">${programName}</strong>!`)}
${infoBox(`Your project "<strong>${projectName}</strong>" has been successfully received.`, 'success')}
${customMessageHtml}
${paragraph('Our team will review your submission and get back to you soon. In the meantime, if you have any questions, please don\'t hesitate to reach out.')}
<p style="color: ${BRAND.textMuted}; margin: 24px 0 0 0; font-size: 13px; text-align: center;">
You will receive email updates about your application status.
</p>
`
return {
subject: `Application Received - ${projectName}`,
html: getEmailWrapper(content),
text: `
${greeting}
Thank you for submitting your application to ${programName}!
Your project "${projectName}" has been successfully received.
${customMessage || ''}
Our team will review your submission and get back to you soon. In the meantime, if you have any questions, please don't hesitate to reach out.
---
Monaco Ocean Protection Challenge
Together for a healthier ocean.
`,
}
}
/**
* Generate team member invite email template
*/
function getTeamMemberInviteTemplate(
name: string,
projectName: string,
teamLeadName: string,
inviteUrl: string
): EmailTemplate {
const greeting = name ? `Hello ${name},` : 'Hello,'
const content = `
${sectionTitle(greeting)}
${paragraph(`<strong>${teamLeadName}</strong> has invited you to join their team for the project "<strong style="color: ${BRAND.darkBlue};">${projectName}</strong>" on the Monaco Ocean Protection Challenge platform.`)}
${paragraph('Click the button below to accept the invitation and set up your account.')}
${ctaButton(inviteUrl, 'Accept Invitation')}
${infoBox('This invitation link will expire in 30 days.', 'info')}
<p style="color: ${BRAND.textMuted}; margin: 24px 0 0 0; font-size: 13px; text-align: center;">
If you weren't expecting this invitation, you can safely ignore this email.
</p>
`
return {
subject: `You've been invited to join "${projectName}"`,
html: getEmailWrapper(content),
text: `
${greeting}
${teamLeadName} has invited you to join their team for the project "${projectName}" on the Monaco Ocean Protection Challenge platform.
Click the link below to accept the invitation and set up your account:
${inviteUrl}
This invitation link will expire in 30 days.
If you weren't expecting this invitation, you can safely ignore this email.
---
Monaco Ocean Protection Challenge
Together for a healthier ocean.
`,
}
}
// =============================================================================
// Email Sending Functions
// =============================================================================
@@ -634,3 +726,57 @@ export async function verifyEmailConnection(): Promise<boolean> {
return false
}
}
/**
* Send application confirmation email to applicant
*/
export async function sendApplicationConfirmationEmail(
email: string,
applicantName: string,
projectName: string,
programName: string,
customMessage?: string
): Promise<void> {
const template = getApplicationConfirmationTemplate(
applicantName,
projectName,
programName,
customMessage
)
const { transporter, from } = await getTransporter()
await transporter.sendMail({
from,
to: email,
subject: template.subject,
text: template.text,
html: template.html,
})
}
/**
* Send team member invite email
*/
export async function sendTeamMemberInviteEmail(
email: string,
memberName: string,
projectName: string,
teamLeadName: string,
inviteUrl: string
): Promise<void> {
const template = getTeamMemberInviteTemplate(
memberName,
projectName,
teamLeadName,
inviteUrl
)
const { transporter, from } = await getTransporter()
await transporter.sendMail({
from,
to: email,
subject: template.subject,
text: template.text,
html: template.html,
})
}