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:
146
src/lib/email.ts
146
src/lib/email.ts
@@ -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,
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user