Award shortlist UX improvements + configurable invite link expiry
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m30s
All checks were successful
Build and Push Docker Image / build (push) Successful in 8m30s
Award shortlist: - Expandable reasoning text (click to toggle, hover hint) - Bulk select/deselect all checkbox in header - Top N projects highlighted with amber background - New bulkToggleShortlisted backend mutation Invite link expiry: - New "Invitation Link Expiry (hours)" field in Security Settings - Reads from systemSettings `invite_link_expiry_hours` (default 72h / 3 days) - Email template dynamically shows "X hours" or "X days" based on setting - All 3 invite paths (bulk create, single invite, bulk resend) use setting Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -289,21 +289,29 @@ Together for a healthier ocean.
|
||||
/**
|
||||
* Generate generic invitation email template (not round-specific)
|
||||
*/
|
||||
function formatExpiryLabel(hours: number): string {
|
||||
if (hours < 24) return `${hours} hour${hours !== 1 ? 's' : ''}`
|
||||
const days = Math.round(hours / 24)
|
||||
return `${days} day${days !== 1 ? 's' : ''}`
|
||||
}
|
||||
|
||||
function getGenericInvitationTemplate(
|
||||
name: string,
|
||||
url: string,
|
||||
role: string
|
||||
role: string,
|
||||
expiryHours: number = 72
|
||||
): EmailTemplate {
|
||||
const roleLabel = role === 'JURY_MEMBER' ? 'jury member' : role.toLowerCase().replace('_', ' ')
|
||||
const article = /^[aeiou]/i.test(roleLabel) ? 'an' : 'a'
|
||||
const greeting = name ? `Hello ${name},` : 'Hello,'
|
||||
const expiryLabel = formatExpiryLabel(expiryHours)
|
||||
|
||||
const content = `
|
||||
${sectionTitle(greeting)}
|
||||
${paragraph(`You've been invited to join the Monaco Ocean Protection Challenge platform as ${article} <strong>${roleLabel}</strong>.`)}
|
||||
${paragraph('Click the button below to set up your account and get started.')}
|
||||
${ctaButton(url, 'Accept Invitation')}
|
||||
${infoBox('This link will expire in 7 days.', 'info')}
|
||||
${infoBox(`This link will expire in ${expiryLabel}.`, 'info')}
|
||||
`
|
||||
|
||||
return {
|
||||
@@ -318,7 +326,7 @@ Click the link below to set up your account and get started:
|
||||
|
||||
${url}
|
||||
|
||||
This link will expire in 7 days.
|
||||
This link will expire in ${expiryLabel}.
|
||||
|
||||
---
|
||||
Monaco Ocean Protection Challenge
|
||||
@@ -1581,9 +1589,10 @@ export async function sendInvitationEmail(
|
||||
email: string,
|
||||
name: string | null,
|
||||
url: string,
|
||||
role: string
|
||||
role: string,
|
||||
expiryHours?: number
|
||||
): Promise<void> {
|
||||
const template = getGenericInvitationTemplate(name || '', url, role)
|
||||
const template = getGenericInvitationTemplate(name || '', url, role, expiryHours)
|
||||
const { transporter, from } = await getTransporter()
|
||||
|
||||
await transporter.sendMail({
|
||||
|
||||
Reference in New Issue
Block a user