442 lines
18 KiB
TypeScript
442 lines
18 KiB
TypeScript
import { Resend } from "resend"
|
|
|
|
const resend = process.env.RESEND_API_KEY ? new Resend(process.env.RESEND_API_KEY) : null
|
|
|
|
interface EmailUser {
|
|
name: string
|
|
email: string
|
|
username: string
|
|
memberId: string
|
|
department: string
|
|
}
|
|
|
|
interface WelcomeEmailData extends EmailUser {
|
|
password: string
|
|
loginUrl: string
|
|
}
|
|
|
|
interface StatusChangeEmailData extends EmailUser {
|
|
status: "verified" | "rejected" | "pending"
|
|
loginUrl: string
|
|
}
|
|
|
|
interface VotingReminderEmailData extends EmailUser {
|
|
eventTitle: string
|
|
eventEndDate: string
|
|
votingUrl: string
|
|
}
|
|
|
|
export class EmailService {
|
|
private isDevelopment = process.env.NODE_ENV === "development"
|
|
private fromEmail = process.env.RESEND_FROM_EMAIL || "onboarding@resend.dev"
|
|
private baseUrl = process.env.NEXT_PUBLIC_BASE_URL || "http://localhost:3000"
|
|
|
|
async sendWelcomeEmail(data: WelcomeEmailData): Promise<boolean> {
|
|
try {
|
|
// Only attempt to send email if RESEND_API_KEY is configured
|
|
if (!process.env.RESEND_API_KEY || !resend) {
|
|
console.log("RESEND_API_KEY not configured, skipping email send")
|
|
return true // Return success to not block user creation
|
|
}
|
|
|
|
const { data: result, error } = await resend.emails.send({
|
|
from: this.fromEmail,
|
|
to: [data.email],
|
|
subject: "Selamat Datang di Platform E-Voting METI",
|
|
html: this.getWelcomeEmailTemplate(data),
|
|
})
|
|
|
|
if (error) {
|
|
console.error("Error sending welcome email:", error)
|
|
// Don't block user creation if email fails
|
|
return true
|
|
}
|
|
|
|
console.log("Welcome email sent successfully:", result?.id)
|
|
return true
|
|
} catch (error) {
|
|
console.error("Failed to send welcome email:", error)
|
|
// Return true to not block user creation in development
|
|
return true
|
|
}
|
|
}
|
|
|
|
async sendStatusChangeEmail(data: StatusChangeEmailData): Promise<boolean> {
|
|
try {
|
|
// Only attempt to send email if RESEND_API_KEY is configured
|
|
if (!process.env.RESEND_API_KEY || !resend) {
|
|
console.log("RESEND_API_KEY not configured, skipping email send")
|
|
return true // Return success to not block user creation
|
|
}
|
|
|
|
const subject = this.getStatusEmailSubject(data.status)
|
|
|
|
const { data: result, error } = await resend.emails.send({
|
|
from: this.fromEmail,
|
|
to: [data.email],
|
|
subject,
|
|
html: this.getStatusChangeEmailTemplate(data),
|
|
})
|
|
|
|
if (error) {
|
|
console.error("Error sending status change email:", error)
|
|
// Don't block user creation if email fails
|
|
return true
|
|
}
|
|
|
|
console.log("Status change email sent successfully:", result?.id)
|
|
return true
|
|
} catch (error) {
|
|
console.error("Failed to send status change email:", error)
|
|
// Return true to not block user creation in development
|
|
return true
|
|
}
|
|
}
|
|
|
|
async sendVotingReminderEmail(data: VotingReminderEmailData): Promise<boolean> {
|
|
try {
|
|
// Only attempt to send email if RESEND_API_KEY is configured
|
|
if (!process.env.RESEND_API_KEY || !resend) {
|
|
console.log("RESEND_API_KEY not configured, skipping email send")
|
|
return true // Return success to not block user creation
|
|
}
|
|
|
|
const { data: result, error } = await resend.emails.send({
|
|
from: this.fromEmail,
|
|
to: [data.email],
|
|
subject: `Reminder: Voting ${data.eventTitle} - METI`,
|
|
html: this.getVotingReminderEmailTemplate(data),
|
|
})
|
|
|
|
if (error) {
|
|
console.error("Error sending voting reminder email:", error)
|
|
// Don't block user creation if email fails
|
|
return true
|
|
}
|
|
|
|
console.log("Voting reminder email sent successfully:", result?.id)
|
|
return true
|
|
} catch (error) {
|
|
console.error("Failed to send voting reminder email:", error)
|
|
// Return true to not block user creation in development
|
|
return true
|
|
}
|
|
}
|
|
|
|
private getStatusEmailSubject(status: string): string {
|
|
switch (status) {
|
|
case "verified":
|
|
return "Akun Anda Telah Diverifikasi - METI E-Voting"
|
|
case "rejected":
|
|
return "Status Verifikasi Akun - METI E-Voting"
|
|
case "pending":
|
|
return "Status Akun Ditangguhkan - METI E-Voting"
|
|
default:
|
|
return "Update Status Akun - METI E-Voting"
|
|
}
|
|
}
|
|
|
|
private getWelcomeEmailTemplate(data: WelcomeEmailData): string {
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Selamat Datang di METI E-Voting</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: linear-gradient(135deg, #1e40af 0%, #3b82f6 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
|
|
.logo { font-size: 24px; font-weight: bold; margin-bottom: 10px; }
|
|
.content { background: #f8fafc; padding: 30px; border-radius: 0 0 10px 10px; }
|
|
.credentials-box { background: white; border: 2px solid #e2e8f0; border-radius: 8px; padding: 20px; margin: 20px 0; }
|
|
.credential-item { display: flex; justify-content: space-between; margin: 10px 0; padding: 10px; background: #f1f5f9; border-radius: 5px; }
|
|
.credential-label { font-weight: bold; color: #475569; }
|
|
.credential-value { font-family: monospace; background: #1e293b; color: #f1f5f9; padding: 4px 8px; border-radius: 4px; }
|
|
.button { display: inline-block; background: #1e40af; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0; font-weight: bold; }
|
|
.button:hover { background: #1d4ed8; }
|
|
.warning { background: #fef3c7; border: 1px solid #f59e0b; padding: 15px; border-radius: 6px; margin: 20px 0; }
|
|
.footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 14px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="logo">🗳️ METI E-Voting Platform</div>
|
|
<p>Sistem Pemilihan Elektronik METI (New & Renewable Energy)</p>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<h2>Selamat Datang, ${data.name}!</h2>
|
|
|
|
<p>Akun Anda telah berhasil dibuat di Platform E-Voting METI. Berikut adalah informasi akun Anda:</p>
|
|
|
|
<div class="credentials-box">
|
|
<h3>📋 Informasi Akun</h3>
|
|
<div class="credential-item">
|
|
<span class="credential-label">Nama Lengkap:</span>
|
|
<span>${data.name}</span>
|
|
</div>
|
|
<div class="credential-item">
|
|
<span class="credential-label">ID Anggota:</span>
|
|
<span class="credential-value">${data.memberId}</span>
|
|
</div>
|
|
<div class="credential-item">
|
|
<span class="credential-label">Departemen:</span>
|
|
<span>${data.department}</span>
|
|
</div>
|
|
<div class="credential-item">
|
|
<span class="credential-label">Email:</span>
|
|
<span>${data.email}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="credentials-box">
|
|
<h3>🔐 Kredensial Login</h3>
|
|
<div class="credential-item">
|
|
<span class="credential-label">Username:</span>
|
|
<span class="credential-value">${data.username}</span>
|
|
</div>
|
|
<div class="credential-item">
|
|
<span class="credential-label">Password:</span>
|
|
<span class="credential-value">${data.password}</span>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="warning">
|
|
<strong>⚠️ Penting:</strong>
|
|
<ul>
|
|
<li>Akun Anda saat ini berstatus <strong>PENDING</strong> dan menunggu verifikasi admin</li>
|
|
<li>Anda belum dapat login hingga akun diverifikasi</li>
|
|
<li>Simpan kredensial login ini dengan aman</li>
|
|
<li>Jangan bagikan informasi login kepada orang lain</li>
|
|
</ul>
|
|
</div>
|
|
|
|
<div style="text-align: center;">
|
|
<a href="${data.loginUrl}" class="button">🚀 Login ke Platform</a>
|
|
</div>
|
|
|
|
<h3>📝 Langkah Selanjutnya:</h3>
|
|
<ol>
|
|
<li>Tunggu email konfirmasi verifikasi dari admin</li>
|
|
<li>Setelah diverifikasi, login menggunakan kredensial di atas</li>
|
|
<li>Ikuti event voting yang tersedia</li>
|
|
<li>Berikan suara Anda untuk kandidat pilihan</li>
|
|
</ol>
|
|
|
|
<p>Jika Anda memiliki pertanyaan, silakan hubungi administrator METI.</p>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p>Email ini dikirim secara otomatis oleh sistem METI E-Voting Platform</p>
|
|
<p>© 2024 METI (New & Renewable Energy). All rights reserved.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`
|
|
}
|
|
|
|
private getStatusChangeEmailTemplate(data: StatusChangeEmailData): string {
|
|
const statusInfo = this.getStatusInfo(data.status)
|
|
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Update Status Akun - METI E-Voting</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: ${statusInfo.headerColor}; color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
|
|
.logo { font-size: 24px; font-weight: bold; margin-bottom: 10px; }
|
|
.content { background: #f8fafc; padding: 30px; border-radius: 0 0 10px 10px; }
|
|
.status-box { background: white; border: 2px solid ${statusInfo.borderColor}; border-radius: 8px; padding: 20px; margin: 20px 0; text-align: center; }
|
|
.status-icon { font-size: 48px; margin-bottom: 15px; }
|
|
.status-title { font-size: 24px; font-weight: bold; color: ${statusInfo.textColor}; margin-bottom: 10px; }
|
|
.button { display: inline-block; background: ${statusInfo.buttonColor}; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0; font-weight: bold; }
|
|
.info-box { background: ${statusInfo.bgColor}; border: 1px solid ${statusInfo.borderColor}; padding: 15px; border-radius: 6px; margin: 20px 0; }
|
|
.footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 14px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="logo">🗳️ METI E-Voting Platform</div>
|
|
<p>Update Status Keanggotaan</p>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<h2>Halo, ${data.name}!</h2>
|
|
|
|
<div class="status-box">
|
|
<div class="status-icon">${statusInfo.icon}</div>
|
|
<div class="status-title">${statusInfo.title}</div>
|
|
<p>${statusInfo.description}</p>
|
|
</div>
|
|
|
|
<div class="info-box">
|
|
<h3>📋 Informasi Akun</h3>
|
|
<p><strong>Nama:</strong> ${data.name}</p>
|
|
<p><strong>ID Anggota:</strong> ${data.memberId}</p>
|
|
<p><strong>Departemen:</strong> ${data.department}</p>
|
|
<p><strong>Status Saat Ini:</strong> <strong style="color: ${statusInfo.textColor}">${statusInfo.statusText}</strong></p>
|
|
</div>
|
|
|
|
${statusInfo.actionText}
|
|
|
|
${
|
|
data.status === "verified"
|
|
? `
|
|
<div style="text-align: center;">
|
|
<a href="${data.loginUrl}" class="button">🚀 Login & Mulai Voting</a>
|
|
</div>
|
|
`
|
|
: ""
|
|
}
|
|
|
|
<p>Jika Anda memiliki pertanyaan tentang status akun ini, silakan hubungi administrator METI.</p>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p>Email ini dikirim secara otomatis oleh sistem METI E-Voting Platform</p>
|
|
<p>© 2024 METI (New & Renewable Energy). All rights reserved.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`
|
|
}
|
|
|
|
private getVotingReminderEmailTemplate(data: VotingReminderEmailData): string {
|
|
return `
|
|
<!DOCTYPE html>
|
|
<html lang="id">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Reminder Voting - METI E-Voting</title>
|
|
<style>
|
|
body { font-family: Arial, sans-serif; line-height: 1.6; color: #333; max-width: 600px; margin: 0 auto; padding: 20px; }
|
|
.header { background: linear-gradient(135deg, #dc2626 0%, #ef4444 100%); color: white; padding: 30px; text-align: center; border-radius: 10px 10px 0 0; }
|
|
.logo { font-size: 24px; font-weight: bold; margin-bottom: 10px; }
|
|
.content { background: #f8fafc; padding: 30px; border-radius: 0 0 10px 10px; }
|
|
.event-box { background: white; border: 2px solid #fbbf24; border-radius: 8px; padding: 20px; margin: 20px 0; }
|
|
.button { display: inline-block; background: #dc2626; color: white; padding: 12px 24px; text-decoration: none; border-radius: 6px; margin: 20px 0; font-weight: bold; }
|
|
.urgent { background: #fef2f2; border: 1px solid #fca5a5; padding: 15px; border-radius: 6px; margin: 20px 0; }
|
|
.footer { text-align: center; margin-top: 30px; padding-top: 20px; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 14px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="header">
|
|
<div class="logo">⏰ METI E-Voting Platform</div>
|
|
<p>Reminder Voting</p>
|
|
</div>
|
|
|
|
<div class="content">
|
|
<h2>Halo, ${data.name}!</h2>
|
|
|
|
<div class="urgent">
|
|
<h3>🚨 Jangan Lupa Voting!</h3>
|
|
<p>Ini adalah pengingat bahwa Anda belum memberikan suara untuk event voting yang sedang berlangsung.</p>
|
|
</div>
|
|
|
|
<div class="event-box">
|
|
<h3>📊 Informasi Event</h3>
|
|
<p><strong>Event:</strong> ${data.eventTitle}</p>
|
|
<p><strong>Batas Waktu:</strong> ${new Date(data.eventEndDate).toLocaleDateString("id-ID", {
|
|
weekday: "long",
|
|
year: "numeric",
|
|
month: "long",
|
|
day: "numeric",
|
|
hour: "2-digit",
|
|
minute: "2-digit",
|
|
})}</p>
|
|
<p><strong>Status:</strong> Belum Voting</p>
|
|
</div>
|
|
|
|
<div style="text-align: center;">
|
|
<a href="${data.votingUrl}" class="button">🗳️ Voting Sekarang</a>
|
|
</div>
|
|
|
|
<h3>💡 Mengapa Suara Anda Penting?</h3>
|
|
<ul>
|
|
<li>Setiap suara menentukan masa depan METI</li>
|
|
<li>Partisipasi Anda sangat berharga</li>
|
|
<li>Proses demokratis membutuhkan keterlibatan semua anggota</li>
|
|
</ul>
|
|
|
|
<p><strong>Catatan:</strong> Pastikan Anda voting sebelum batas waktu berakhir. Setelah event ditutup, Anda tidak dapat lagi memberikan suara.</p>
|
|
</div>
|
|
|
|
<div class="footer">
|
|
<p>Email ini dikirim secara otomatis oleh sistem METI E-Voting Platform</p>
|
|
<p>© 2024 METI (New & Renewable Energy). All rights reserved.</p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
`
|
|
}
|
|
|
|
private getStatusInfo(status: string) {
|
|
switch (status) {
|
|
case "verified":
|
|
return {
|
|
icon: "✅",
|
|
title: "Akun Terverifikasi!",
|
|
description: "Selamat! Akun Anda telah diverifikasi dan dapat digunakan untuk voting.",
|
|
statusText: "TERVERIFIKASI",
|
|
headerColor: "linear-gradient(135deg, #059669 0%, #10b981 100%)",
|
|
borderColor: "#10b981",
|
|
textColor: "#059669",
|
|
buttonColor: "#059669",
|
|
bgColor: "#ecfdf5",
|
|
actionText:
|
|
"<p><strong>✨ Apa yang bisa Anda lakukan sekarang:</strong></p><ul><li>Login ke platform e-voting</li><li>Ikuti event voting yang tersedia</li><li>Berikan suara untuk kandidat pilihan Anda</li><li>Lihat hasil voting real-time</li></ul>",
|
|
}
|
|
case "rejected":
|
|
return {
|
|
icon: "❌",
|
|
title: "Verifikasi Ditolak",
|
|
description:
|
|
"Maaf, verifikasi akun Anda ditolak. Silakan hubungi administrator untuk informasi lebih lanjut.",
|
|
statusText: "DITOLAK",
|
|
headerColor: "linear-gradient(135deg, #dc2626 0%, #ef4444 100%)",
|
|
borderColor: "#ef4444",
|
|
textColor: "#dc2626",
|
|
buttonColor: "#dc2626",
|
|
bgColor: "#fef2f2",
|
|
actionText:
|
|
"<p><strong>📞 Langkah selanjutnya:</strong></p><ul><li>Hubungi administrator METI</li><li>Tanyakan alasan penolakan</li><li>Perbaiki dokumen atau informasi yang diperlukan</li><li>Ajukan ulang jika memungkinkan</li></ul>",
|
|
}
|
|
case "pending":
|
|
return {
|
|
icon: "⏳",
|
|
title: "Status Ditangguhkan",
|
|
description: "Akun Anda sementara ditangguhkan dan sedang dalam review ulang.",
|
|
statusText: "DITANGGUHKAN",
|
|
headerColor: "linear-gradient(135deg, #d97706 0%, #f59e0b 100%)",
|
|
borderColor: "#f59e0b",
|
|
textColor: "#d97706",
|
|
buttonColor: "#d97706",
|
|
bgColor: "#fffbeb",
|
|
actionText:
|
|
"<p><strong>⏰ Yang perlu Anda ketahui:</strong></p><ul><li>Akun Anda sedang dalam review</li><li>Anda tidak dapat login sementara waktu</li><li>Tunggu email konfirmasi lebih lanjut</li><li>Hubungi admin jika ada pertanyaan</li></ul>",
|
|
}
|
|
default:
|
|
return {
|
|
icon: "❓",
|
|
title: "Status Tidak Dikenal",
|
|
description: "Status akun Anda tidak dapat diidentifikasi.",
|
|
statusText: "TIDAK DIKENAL",
|
|
headerColor: "linear-gradient(135deg, #6b7280 0%, #9ca3af 100%)",
|
|
borderColor: "#9ca3af",
|
|
textColor: "#6b7280",
|
|
buttonColor: "#6b7280",
|
|
bgColor: "#f9fafb",
|
|
actionText: "<p>Silakan hubungi administrator untuk klarifikasi status akun Anda.</p>",
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
export const emailService = new EmailService()
|