feat: extend notification system with batch sender, bulk dialog, and logging
Add NotificationLog schema extensions (nullable userId, email, roundId, projectId, batchId fields), batch notification sender service, and bulk notification dialog UI. Include utility scripts for debugging and seeding. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
120
scripts/send-invite-direct.ts
Normal file
120
scripts/send-invite-direct.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
// Import just the template helper without hitting DB
|
||||
// We'll construct the email manually since the DB connection fails
|
||||
|
||||
const BRAND = {
|
||||
red: '#de0f1e',
|
||||
darkBlue: '#053d57',
|
||||
white: '#fefefe',
|
||||
teal: '#557f8c',
|
||||
};
|
||||
|
||||
const token = '6f974b1da9fae95f74bbcd2419df589730979ac945aeaa5413021c00311b5165';
|
||||
const url = 'http://localhost:3000/accept-invite?token=' + token;
|
||||
|
||||
// Replicate the styled email template from email.ts
|
||||
function getStyledHtml(name: string, inviteUrl: string) {
|
||||
return `<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>You're invited to join the MOPC Portal</title>
|
||||
</head>
|
||||
<body style="margin: 0; padding: 0; background-color: #f8fafc; font-family: 'Montserrat', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;">
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="background-color: #f8fafc;">
|
||||
<tr>
|
||||
<td align="center" style="padding: 40px 20px;">
|
||||
<table role="presentation" width="600" cellspacing="0" cellpadding="0" border="0" style="max-width: 600px; width: 100%;">
|
||||
<!-- Header -->
|
||||
<tr>
|
||||
<td style="background: linear-gradient(135deg, ${BRAND.darkBlue} 0%, ${BRAND.teal} 100%); border-radius: 16px 16px 0 0; padding: 32px 40px; text-align: center;">
|
||||
<h1 style="color: ${BRAND.white}; font-size: 22px; font-weight: 700; margin: 0; letter-spacing: -0.02em;">
|
||||
Monaco Ocean Protection Challenge
|
||||
</h1>
|
||||
<p style="color: rgba(255,255,255,0.8); font-size: 13px; font-weight: 300; margin: 8px 0 0 0; letter-spacing: 0.05em; text-transform: uppercase;">
|
||||
Together for a healthier ocean
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Body -->
|
||||
<tr>
|
||||
<td style="background-color: #ffffff; padding: 40px; border-radius: 0 0 16px 16px; box-shadow: 0 4px 6px -1px rgba(0,0,0,0.1);">
|
||||
<h2 style="color: ${BRAND.darkBlue}; font-size: 20px; font-weight: 600; margin: 0 0 24px 0;">
|
||||
Hello ${name},
|
||||
</h2>
|
||||
<p style="color: #475569; font-size: 15px; line-height: 1.7; margin: 0 0 16px 0; font-weight: 400;">
|
||||
You've been invited to join the Monaco Ocean Protection Challenge platform as an <strong>applicant</strong>.
|
||||
</p>
|
||||
<p style="color: #475569; font-size: 15px; line-height: 1.7; margin: 0 0 24px 0; font-weight: 400;">
|
||||
Click the button below to set up your account and get started.
|
||||
</p>
|
||||
<!-- CTA Button -->
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin: 28px 0;">
|
||||
<tr>
|
||||
<td align="center">
|
||||
<a href="${inviteUrl}" style="display: inline-block; background: linear-gradient(135deg, ${BRAND.red} 0%, #c40d19 100%); color: #ffffff; text-decoration: none; padding: 14px 36px; border-radius: 10px; font-size: 15px; font-weight: 600; letter-spacing: 0.02em; box-shadow: 0 4px 14px rgba(222, 15, 30, 0.3);">
|
||||
Accept Invitation
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
<!-- Info Box -->
|
||||
<table role="presentation" width="100%" cellspacing="0" cellpadding="0" border="0" style="margin: 20px 0;">
|
||||
<tr>
|
||||
<td style="background-color: #eff6ff; border-left: 4px solid ${BRAND.darkBlue}; border-radius: 0 8px 8px 0; padding: 16px 20px;">
|
||||
<p style="color: #1e40af; margin: 0; font-size: 13px; line-height: 1.6;">
|
||||
This link will expire in 3 days.
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
<!-- Footer -->
|
||||
<tr>
|
||||
<td style="padding: 24px 40px; text-align: center;">
|
||||
<p style="color: #94a3b8; font-size: 12px; line-height: 1.6; margin: 0;">
|
||||
Monaco Ocean Protection Challenge<br>
|
||||
<span style="color: #cbd5e1;">Together for a healthier ocean.</span>
|
||||
</p>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</td>
|
||||
</tr>
|
||||
</table>
|
||||
</body>
|
||||
</html>`;
|
||||
}
|
||||
|
||||
async function main() {
|
||||
console.log('Creating transporter...');
|
||||
const transporter = nodemailer.createTransport({
|
||||
host: 'mail.monaco-opc.com',
|
||||
port: 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: 'noreply@monaco-opc.com',
|
||||
pass: '9EythPDcz1Fya4M88iigkB1wojNf8QEVPuRRnD9dJMBpT3pk2',
|
||||
},
|
||||
});
|
||||
|
||||
console.log('Sending styled invitation email...');
|
||||
const info = await transporter.sendMail({
|
||||
from: 'MOPC Portal <noreply@monaco-opc.com>',
|
||||
to: 'matt.ciaccio@gmail.com',
|
||||
subject: "You're invited to join the MOPC Portal",
|
||||
text: `Hello Matt Ciaccio,\n\nYou've been invited to join the Monaco Ocean Protection Challenge platform as an applicant.\n\nClick the link below to set up your account:\n\n${url}\n\nThis link will expire in 3 days.\n\n---\nMonaco Ocean Protection Challenge\nTogether for a healthier ocean.`,
|
||||
html: getStyledHtml('Matt Ciaccio', url),
|
||||
});
|
||||
|
||||
console.log('SUCCESS! Message ID:', info.messageId);
|
||||
process.exit(0);
|
||||
}
|
||||
|
||||
main().catch(err => {
|
||||
console.error('FAILED:', err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user