meti-frontend/lib/email.ts
2025-08-15 23:03:15 +07:00

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()