751 lines
34 KiB
TypeScript
751 lines
34 KiB
TypeScript
import { useEffect, useState } from 'react';
|
|
import { supabase } from '@/integrations/supabase/client';
|
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from '@/components/ui/card';
|
|
import { Button } from '@/components/ui/button';
|
|
import { Input } from '@/components/ui/input';
|
|
import { Label } from '@/components/ui/label';
|
|
import { Switch } from '@/components/ui/switch';
|
|
import { Alert, AlertDescription } from '@/components/ui/alert';
|
|
import { RichTextEditor } from '@/components/RichTextEditor';
|
|
import { toast } from '@/hooks/use-toast';
|
|
import { Mail, ChevronDown, ChevronUp, Webhook } from 'lucide-react';
|
|
import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible';
|
|
import { EmailTemplatePreview } from '@/components/admin/EmailTemplatePreview';
|
|
|
|
interface NotificationTemplate {
|
|
id: string;
|
|
key: string;
|
|
name: string;
|
|
is_active: boolean;
|
|
email_subject: string;
|
|
email_body_html: string;
|
|
webhook_url: string;
|
|
last_payload_example: Record<string, unknown> | null;
|
|
}
|
|
|
|
const RELEVANT_SHORTCODES = {
|
|
'payment_success': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{link_akses}', '{thank_you_page}'],
|
|
'access_granted': ['{nama}', '{email}', '{produk}', '{link_akses}', '{username_akses}', '{password_akses}', '{kadaluarsa_akses}'],
|
|
'order_created': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{produk}', '{payment_link}', '{thank_you_page}'],
|
|
'payment_reminder': ['{nama}', '{email}', '{order_id}', '{tanggal_pesanan}', '{total}', '{metode_pembayaran}', '{batas_pembayaran}', '{jumlah_pembayaran}', '{bank_tujuan}', '{nomor_rekening}', '{payment_link}', '{thank_you_page}'],
|
|
'consulting_scheduled': ['{nama}', '{email}', '{tanggal_konsultasi}', '{jam_konsultasi}', '{durasi_konsultasi}', '{link_meet}', '{jenis_konsultasi}', '{topik_konsultasi}'],
|
|
'event_reminder': ['{nama}', '{email}', '{judul_event}', '{tanggal_event}', '{jam_event}', '{link_event}', '{lokasi_event}', '{kapasitas_event}'],
|
|
'bootcamp_progress': ['{nama}', '{email}', '{judul_bootcamp}', '{progres_bootcamp}', '{modul_selesai}', '{modul_selanjutnya}', '{link_progress}'],
|
|
};
|
|
|
|
const DEFAULT_TEMPLATES: { key: string; name: string; defaultSubject: string; defaultBody: string }[] = [
|
|
{
|
|
key: 'payment_success',
|
|
name: 'Pembayaran Berhasil',
|
|
defaultSubject: 'Pembayaran Berhasil - Order #{order_id}',
|
|
defaultBody: `
|
|
<h2>Pembayaran Berhasil! 🎉</h2>
|
|
<p>Halo <strong>{nama}</strong>, terima kasih atas pembayaran Anda. Kami senang menginformasikan bahwa pembayaran Anda telah berhasil dikonfirmasi.</p>
|
|
|
|
<h3>Detail Pembayaran</h3>
|
|
<table style="width: 100%; border: 2px solid #000; margin-bottom: 25px; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Parameter</th>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Value</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Order ID</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{order_id}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Tanggal Pesanan</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">{tanggal_pesanan}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Total Pembayaran</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{total}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Metode Pembayaran</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">{metode_pembayaran}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Produk yang Dibeli</h3>
|
|
<p><strong>{produk}</strong></p>
|
|
|
|
<p style="margin-top: 30px;">
|
|
<a href="#" style="display:inline-block;background-color:#000;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #000;box-shadow:4px 4px 0px 0px #000000;margin:10px 0;transition:all 0.1s;text-align:center">
|
|
Lihat Detail Pesanan
|
|
</a>
|
|
</p>
|
|
|
|
<blockquote style="margin: 0 0 20px 0; padding: 15px 20px; border-left: 6px solid #00A651; background-color: #E6F4EA; font-style: italic; font-weight: 500; color: #005A2B;">
|
|
<strong>Info:</strong> Anda akan menerima email terpisah untuk mengakses produk Anda.
|
|
</blockquote>
|
|
`
|
|
},
|
|
{
|
|
key: 'access_granted',
|
|
name: 'Akses Produk Diberikan',
|
|
defaultSubject: 'Akses Anda Sudah Aktif - {produk}',
|
|
defaultBody: `
|
|
<h2>Selamat! Akses Aktif 🚀</h2>
|
|
<p>Halo <strong>{nama}</strong>, selamat! Akses Anda ke <strong>{produk}</strong> sudah aktif dan siap digunakan.</p>
|
|
|
|
<p>Anda sekarang dapat mengakses semua materi dan fitur yang tersedia dalam produk ini.</p>
|
|
|
|
<div style="background-color: #F4F4F5; border: 2px dashed #000; padding: 20px; text-align: center; margin: 20px 0; letter-spacing: 5px; font-family: 'Courier New', Courier, monospace; font-size: 32px; font-weight: 700; color: #000;">
|
|
ACCESS GRANTED
|
|
</div>
|
|
|
|
<p style="margin-top: 30px; text-align: center;">
|
|
<a href="{link_akses}" style="display:inline-block;background-color:#000;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #000;box-shadow:4px 4px 0px 0px #000000;margin:10px 0;transition:all 0.1s;width:100%;box-sizing:border-box">
|
|
Akses Sekarang
|
|
</a>
|
|
</p>
|
|
|
|
<h3>Penting:</h3>
|
|
<ul>
|
|
<li>Simpan link akses ini dengan aman</li>
|
|
<li>Jangan bagikan kredensial Anda kepada orang lain</li>
|
|
<li>Jika mengalami kendala, hubungi support kami</li>
|
|
</ul>
|
|
`
|
|
},
|
|
{
|
|
key: 'order_created',
|
|
name: 'Pesanan Dibuat',
|
|
defaultSubject: 'Pesanan #{order_id} Sedang Diproses',
|
|
defaultBody: `
|
|
<h2>Pesanan Diterima ✅</h2>
|
|
<p>Halo <strong>{nama}</strong>, terima kasih telah melakukan pesanan. Kami telah menerima pesanan Anda dengan detail sebagai berikut:</p>
|
|
|
|
<table style="width: 100%; border: 2px solid #000; margin-bottom: 25px; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Informasi Pesanan</th>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Detail</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Nomor Pesanan</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{order_id}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Total Pembayaran</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{total}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Status</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Menunggu Pembayaran</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<h3>Langkah Selanjutnya:</h3>
|
|
<ol>
|
|
<li>Selesaikan pembayaran sebelum batas waktu</li>
|
|
<li>Setelah pembayaran dikonfirmasi, Anda akan menerima email akses produk</li>
|
|
<li>Simpan bukti pembayaran untuk arsip Anda</li>
|
|
</ol>
|
|
|
|
<blockquote style="margin: 0 0 20px 0; padding: 15px 20px; border-left: 6px solid #E11D48; background-color: #FFE4E6; font-style: italic; font-weight: 500; color: #881337;">
|
|
<strong>Penting:</strong> Segera lakukan pembayaran agar pesanan tidak kedaluwarsa.
|
|
</blockquote>
|
|
`
|
|
},
|
|
{
|
|
key: 'payment_reminder',
|
|
name: 'Pengingat Pembayaran',
|
|
defaultSubject: 'Reminder: Segera Selesaikan Pembayaran #{order_id}',
|
|
defaultBody: `
|
|
<h2>Reminder Pembayaran ⏰</h2>
|
|
<p>Halo <strong>{nama}</strong>, ini adalah pengingat bahwa pesanan Anda masih menunggu pembayaran.</p>
|
|
|
|
<table style="width: 100%; border: 2px solid #000; margin-bottom: 25px; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Detail Pesanan</th>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Informasi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Order ID</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{order_id}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Total Pembayaran</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{total}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Tanggal Pesanan</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">{tanggal_pesanan}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p style="margin-top: 30px; text-align: center;">
|
|
<a href="#" style="display:inline-block;background-color:#E11D48;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #E11D48;box-shadow:4px 4px 0px 0px #E11D48;margin:10px 0;transition:all 0.1s;width:100%;box-sizing:border-box">
|
|
Bayar Sekarang
|
|
</a>
|
|
</p>
|
|
|
|
<blockquote style="margin: 0 0 20px 0; padding: 15px 20px; border-left: 6px solid #E11D48; background-color: #FFE4E6; font-style: italic; font-weight: 500; color: #881337;">
|
|
<strong>Peringatan:</strong> Jika pembayaran tidak diselesaikan dalam batas waktu, pesanan akan otomatis dibatalkan.
|
|
</blockquote>
|
|
`
|
|
},
|
|
{
|
|
key: 'consulting_scheduled',
|
|
name: 'Konsultasi Terjadwal',
|
|
defaultSubject: 'Konsultasi Terjadwal - {tanggal_konsultasi}',
|
|
defaultBody: `
|
|
<h2>Sesi Konsultasi Dikonfirmasi 📅</h2>
|
|
<p>Halo <strong>{nama}</strong>, sesi konsultasi Anda telah berhasil dijadwalkan. Berikut adalah detailnya:</p>
|
|
|
|
<table style="width: 100%; border: 2px solid #000; margin-bottom: 25px; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Detail Sesi</th>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Informasi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Tanggal</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{tanggal_konsultasi}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Waktu</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{jam_konsultasi}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Link Meeting</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">
|
|
<a href="{link_meet}" style="color: #000; text-decoration: underline; font-weight: 700;">{link_meet}</a>
|
|
</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p style="margin-top: 30px; text-align: center;">
|
|
<a href="{link_meet}" style="display:inline-block;background-color:#0066cc;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #0066cc;box-shadow:4px 4px 0px 0px #0066cc;margin:10px 0;transition:all 0.1s">
|
|
Bergabung ke Meeting
|
|
</a>
|
|
</p>
|
|
|
|
<h3>Persiapan Sebelum Sesi:</h3>
|
|
<ul>
|
|
<li>Uji koneksi internet Anda</li>
|
|
<li>Siapkan materi atau pertanyaan yang akan dibahas</li>
|
|
<li>Login 10 menit sebelum jadwal</li>
|
|
<li>Gunakan laptop dengan kamera dan mikrofon</li>
|
|
</ul>
|
|
|
|
<blockquote style="margin: 0 0 20px 0; padding: 15px 20px; border-left: 6px solid #1976D2; background-color: #E3F2FD; font-style: italic; font-weight: 500; color: #0D47A1;">
|
|
<strong>Tip:</strong> Gunakan Google Chrome untuk pengalaman meeting terbaik.
|
|
</blockquote>
|
|
`
|
|
},
|
|
{
|
|
key: 'event_reminder',
|
|
name: 'Reminder Webinar/Bootcamp',
|
|
defaultSubject: 'Reminder: {judul_event} - {tanggal_event}',
|
|
defaultBody: `
|
|
<h2>Jangan Sampai Ketinggalan! 🔥</h2>
|
|
<p>Halo <strong>{nama}</strong>, jangan lupa bahwa <strong>{judul_event}</strong> akan segera dimulai!</p>
|
|
|
|
<div style="background-color: #F4F4F5; border: 2px dashed #000; padding: 20px; text-align: center; margin: 20px 0;">
|
|
<h3 style="margin-top: 0; color: #E11D48;">EVENT STARTING SOON!</h3>
|
|
<div style="font-size: 24px; font-weight: 700; letter-spacing: 2px; margin: 10px 0;">
|
|
{judul_event}
|
|
</div>
|
|
</div>
|
|
|
|
<table style="width: 100%; border: 2px solid #000; margin-bottom: 25px; border-collapse: collapse;">
|
|
<thead>
|
|
<tr>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Event Detail</th>
|
|
<th style="background-color: #000; color: #FFF; padding: 12px; text-align: left; font-size: 14px; text-transform: uppercase; font-weight: 700; border: 1px solid #000;">Informasi</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Judul Event</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;"><strong>{judul_event}</strong></td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Tanggal</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">{tanggal_event}</td>
|
|
</tr>
|
|
<tr>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">Waktu</td>
|
|
<td style="padding: 12px; border: 1px solid #000; font-size: 15px; vertical-align: top;">{jam_event}</td>
|
|
</tr>
|
|
</tbody>
|
|
</table>
|
|
|
|
<p style="margin-top: 30px; text-align: center;">
|
|
<a href="{link_event}" style="display:inline-block;background-color:#00A651;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #00A651;box-shadow:4px 4px 0px 0px #00A651;margin:10px 0;transition:all 0.1s;width:100%;box-sizing:border-box">
|
|
Bergabung Sekarang
|
|
</a>
|
|
</p>
|
|
|
|
<h3>Persiapan Event:</h3>
|
|
<ul>
|
|
<li>Stabilkan koneksi internet Anda</li>
|
|
<li>Siapkan notebook untuk mencatat</li>
|
|
<li>Login 15 menit sebelum event dimulai</li>
|
|
<li>Siapkan pertanyaan untuk sesi Q&A</li>
|
|
</ul>
|
|
`
|
|
},
|
|
{
|
|
key: 'bootcamp_progress',
|
|
name: 'Progress Bootcamp',
|
|
defaultSubject: 'Update Progress Bootcamp - {nama}',
|
|
defaultBody: `
|
|
<h2>Progress Update 📈</h2>
|
|
<p>Halo <strong>{nama}</strong>, ini adalah update terbaru tentang progress bootcamp Anda.</p>
|
|
|
|
<div style="background-color: #F4F4F5; border: 2px dashed #000; padding: 20px; text-align: center; margin: 20px 0;">
|
|
<div style="font-size: 18px; font-weight: 700; margin-bottom: 10px;">PROGRESS ANDA</div>
|
|
<div style="font-size: 48px; font-weight: 900; color: #00A651;">75%</div>
|
|
<div style="font-size: 14px; color: #666;">Completed Modules: 15/20</div>
|
|
</div>
|
|
|
|
<h3>Module Selesai:</h3>
|
|
<ul>
|
|
<li>✅ Fundamentals & Basics</li>
|
|
<li>✅ Advanced Concepts</li>
|
|
<li>✅ Practical Applications</li>
|
|
<li>✅ Project Workshop</li>
|
|
</ul>
|
|
|
|
<h3>Module Berikutnya:</h3>
|
|
<ul>
|
|
<li>🔄 Final Assessment</li>
|
|
<li>📋 Portfolio Development</li>
|
|
</ul>
|
|
|
|
<p style="margin-top: 30px; text-align: center;">
|
|
<a href="#" style="display:inline-block;background-color:#000;color:#FFF !important;padding:14px 28px;font-weight:700;text-transform:uppercase;text-decoration:none !important;font-size:16px;border:2px solid #000;box-shadow:4px 4px 0px 0px #000000;margin:10px 0;transition:all 0.1s">
|
|
Lanjut Belajar
|
|
</a>
|
|
</p>
|
|
|
|
<blockquote style="margin: 0 0 20px 0; padding: 15px 20px; border-left: 6px solid #00A651; background-color: #E6F4EA; font-style: italic; font-weight: 500; color: #005A2B;">
|
|
<strong>Motivasi:</strong> Anda sudah 75% selesai! Terus semangat, kesuksesan Anda sudah di depan mata!
|
|
</blockquote>
|
|
`
|
|
},
|
|
];
|
|
|
|
export function NotifikasiTab() {
|
|
const [templates, setTemplates] = useState<NotificationTemplate[]>([]);
|
|
const [loading, setLoading] = useState(true);
|
|
const [expandedTemplates, setExpandedTemplates] = useState<Set<string>>(new Set());
|
|
const [testingTemplate, setTestingTemplate] = useState<string | null>(null);
|
|
const [previewTemplate, setPreviewTemplate] = useState<NotificationTemplate | null>(null);
|
|
const [isPreviewOpen, setIsPreviewOpen] = useState(false);
|
|
|
|
useEffect(() => {
|
|
fetchData();
|
|
}, []);
|
|
|
|
const fetchData = async () => {
|
|
try {
|
|
console.log('Fetching templates...');
|
|
// Fetch templates
|
|
const { data: templatesData, error: fetchError } = await supabase.from('notification_templates').select('*').order('key');
|
|
|
|
if (fetchError) {
|
|
console.error('Error fetching templates:', fetchError);
|
|
toast({
|
|
title: 'Error',
|
|
description: 'Gagal mengambil template: ' + fetchError.message,
|
|
variant: 'destructive'
|
|
});
|
|
setLoading(false);
|
|
return;
|
|
}
|
|
|
|
console.log('Templates data:', templatesData);
|
|
console.log('Template count:', templatesData?.length);
|
|
|
|
if (templatesData && templatesData.length > 0) {
|
|
console.log('Setting templates from database:', templatesData.length);
|
|
// Check if any templates have empty content
|
|
const emptyTemplates = templatesData.filter(t => !t.email_subject || !t.email_body_html);
|
|
if (emptyTemplates.length > 0) {
|
|
console.log('Found templates with empty content:', emptyTemplates.map(t => ({ key: t.key, name: t.name })));
|
|
console.log('Reseeding templates with empty content...');
|
|
await forceSeedTemplates();
|
|
} else {
|
|
setTemplates(templatesData);
|
|
}
|
|
} else {
|
|
console.log('No templates found, seeding default templates...');
|
|
// Seed default templates if none exist
|
|
await seedTemplates();
|
|
}
|
|
} catch (error) {
|
|
console.error('Unexpected error in fetchData:', error);
|
|
toast({
|
|
title: 'Error',
|
|
description: 'Terjadi kesalahan tak terduga saat mengambil data',
|
|
variant: 'destructive'
|
|
});
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
const forceSeedTemplates = async () => {
|
|
try {
|
|
console.log('Force reseed: Deleting existing templates...');
|
|
// Delete existing templates
|
|
const { error: deleteError } = await supabase.from('notification_templates').delete().neq('id', '');
|
|
if (deleteError) {
|
|
console.error('Error deleting templates:', deleteError);
|
|
}
|
|
|
|
console.log('Force reseed: Inserting default templates...');
|
|
await seedTemplates();
|
|
} catch (error) {
|
|
console.error('Error in forceSeedTemplates:', error);
|
|
}
|
|
};
|
|
|
|
const seedTemplates = async () => {
|
|
try {
|
|
console.log('Seeding default templates...');
|
|
const toUpsert = DEFAULT_TEMPLATES.map(t => ({
|
|
key: t.key,
|
|
name: t.name,
|
|
is_active: false,
|
|
email_subject: t.defaultSubject,
|
|
email_body_html: t.defaultBody,
|
|
webhook_url: '',
|
|
}));
|
|
|
|
console.log('Upserting templates:', toUpsert.length);
|
|
const { data, error } = await supabase
|
|
.from('notification_templates')
|
|
.upsert(toUpsert, {
|
|
onConflict: 'key',
|
|
ignoreDuplicates: false
|
|
})
|
|
.select();
|
|
|
|
if (error) {
|
|
console.error('Error seeding templates:', error);
|
|
toast({
|
|
title: 'Error',
|
|
description: 'Gagal membuat template default: ' + error.message,
|
|
variant: 'destructive'
|
|
});
|
|
return;
|
|
}
|
|
|
|
console.log('Templates seeded/updated successfully:', data);
|
|
if (data) {
|
|
setTemplates(data);
|
|
toast({
|
|
title: 'Berhasil',
|
|
description: `Berhasil membuat ${data.length} template default`
|
|
});
|
|
}
|
|
} catch (error) {
|
|
console.error('Unexpected error in seedTemplates:', error);
|
|
toast({
|
|
title: 'Error',
|
|
description: 'Terjadi kesalahan saat membuat template default',
|
|
variant: 'destructive'
|
|
});
|
|
}
|
|
};
|
|
|
|
|
|
const updateTemplate = async (template: NotificationTemplate) => {
|
|
const { id, key, name, ...updates } = template;
|
|
const { error } = await supabase.from('notification_templates').update(updates).eq('id', id);
|
|
if (error) toast({ title: 'Error', description: error.message, variant: 'destructive' });
|
|
else toast({ title: 'Berhasil', description: `Template "${name}" disimpan` });
|
|
};
|
|
|
|
const sendTestEmail = async (template: NotificationTemplate & { test_email?: string }) => {
|
|
if (!template.test_email) {
|
|
toast({ title: 'Error', description: 'Masukkan email tujuan', variant: 'destructive' });
|
|
return;
|
|
}
|
|
|
|
setTestingTemplate(template.id);
|
|
try {
|
|
// Fetch email settings from notification_settings
|
|
const { data: emailData } = await supabase
|
|
.from('notification_settings')
|
|
.select('*')
|
|
.single();
|
|
|
|
if (!emailData || !emailData.api_token || !emailData.from_email) {
|
|
throw new Error('Konfigurasi email provider belum lengkap');
|
|
}
|
|
|
|
// Import EmailTemplateRenderer and ShortcodeProcessor
|
|
const { EmailTemplateRenderer, ShortcodeProcessor } = await import('@/lib/email-templates/master-template');
|
|
|
|
// Process shortcodes and render with master template
|
|
const processedSubject = ShortcodeProcessor.process(template.email_subject || '');
|
|
const processedContent = ShortcodeProcessor.process(template.email_body_html || '');
|
|
const fullHtml = EmailTemplateRenderer.render({
|
|
subject: processedSubject,
|
|
content: processedContent,
|
|
brandName: 'ACCESS HUB'
|
|
});
|
|
|
|
// Send test email using send-email-v2
|
|
const { data, error } = await supabase.functions.invoke('send-email-v2', {
|
|
body: {
|
|
recipient: template.test_email,
|
|
api_token: emailData.api_token,
|
|
from_name: emailData.from_name,
|
|
from_email: emailData.from_email,
|
|
subject: processedSubject,
|
|
content: fullHtml,
|
|
},
|
|
});
|
|
|
|
if (error) throw error;
|
|
|
|
if (data?.success) {
|
|
toast({ title: 'Berhasil', description: `Email test "${template.name}" dikirim ke ${template.test_email}` });
|
|
} else {
|
|
throw new Error(data?.message || 'Failed to send test email');
|
|
}
|
|
} catch (error: any) {
|
|
console.error('Test template email error:', error);
|
|
toast({
|
|
title: 'Error',
|
|
description: error.message || 'Gagal mengirim email test template',
|
|
variant: 'destructive'
|
|
});
|
|
} finally {
|
|
setTestingTemplate(null);
|
|
}
|
|
};
|
|
|
|
const toggleExpand = (id: string) => {
|
|
setExpandedTemplates(prev => {
|
|
const next = new Set(prev);
|
|
if (next.has(id)) next.delete(id);
|
|
else next.add(id);
|
|
return next;
|
|
});
|
|
};
|
|
|
|
if (loading) return <div className="animate-pulse h-64 bg-muted rounded-md" />;
|
|
|
|
return (
|
|
<div className="space-y-6">
|
|
{/* Notification Templates Info */}
|
|
<Card className="border-2 border-border">
|
|
<CardHeader>
|
|
<CardTitle className="flex items-center gap-2">
|
|
<Mail className="w-5 h-5" />
|
|
Konfigurasi Email
|
|
</CardTitle>
|
|
<CardDescription>
|
|
Pengaturan provider email (Mailketing API) ada di tab <strong>Integrasi</strong>
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent>
|
|
<Alert>
|
|
<Mail className="w-4 h-4" />
|
|
<AlertDescription>
|
|
Konfigurasikan provider email di tab <strong>Integrasi → Provider Email</strong> untuk mengirim notifikasi.
|
|
Gunakan Mailketing API untuk pengiriman email yang andal.
|
|
</AlertDescription>
|
|
</Alert>
|
|
<div className="mt-3 pt-3 border-t">
|
|
<Button
|
|
variant="outline"
|
|
size="sm"
|
|
onClick={forceSeedTemplates}
|
|
className="text-xs"
|
|
>
|
|
🔄 Reset Template Default
|
|
</Button>
|
|
<span className="text-xs text-muted-foreground ml-2">
|
|
Gunakan jika template kosong atau bermasalah
|
|
</span>
|
|
</div>
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Notification Templates */}
|
|
<Card className="border-2 border-border">
|
|
<CardHeader>
|
|
<CardTitle>Template Notifikasi</CardTitle>
|
|
<CardDescription>
|
|
Atur template email untuk berbagai jenis notifikasi
|
|
</CardDescription>
|
|
</CardHeader>
|
|
<CardContent className="space-y-4">
|
|
<div className="text-sm text-muted-foreground p-3 bg-muted rounded-md space-y-2">
|
|
<p className="font-medium">Shortcode yang tersedia:</p>
|
|
<p className="text-xs">Setiap template memiliki shortcode yang relevan dengan jenis notifikasinya. Lihat detail template untuk shortcode yang tersedia.</p>
|
|
<p className="text-xs mt-3 p-2 bg-background rounded border">
|
|
<strong>Penting:</strong> Email dikirim melalui Mailketing API yang dikonfigurasi di tab Integrasi.
|
|
Pastikan API token valid dan domain pengirim sudah terdaftar di Mailketing.
|
|
Toggle "Aktifkan" hanya mengontrol pengiriman email. Jika <code>webhook_url</code> diisi,
|
|
sistem tetap akan mengirim payload ke URL tersebut meskipun email dinonaktifkan.
|
|
</p>
|
|
</div>
|
|
|
|
{templates.map((template) => (
|
|
<Collapsible
|
|
key={template.id}
|
|
open={expandedTemplates.has(template.id)}
|
|
onOpenChange={() => toggleExpand(template.id)}
|
|
>
|
|
<div className="border-2 border-border rounded-lg">
|
|
<CollapsibleTrigger asChild>
|
|
<div className="flex items-center justify-between p-4 cursor-pointer hover:bg-muted/50">
|
|
<div className="flex items-center gap-4">
|
|
<Switch
|
|
checked={template.is_active}
|
|
onCheckedChange={(checked) => {
|
|
const updated = { ...template, is_active: checked };
|
|
setTemplates(templates.map(t => t.id === template.id ? updated : t));
|
|
updateTemplate(updated);
|
|
}}
|
|
onClick={(e) => e.stopPropagation()}
|
|
/>
|
|
<div>
|
|
<p className="font-medium">{template.name}</p>
|
|
<p className="text-sm text-muted-foreground">{template.key}</p>
|
|
</div>
|
|
</div>
|
|
{expandedTemplates.has(template.id) ? (
|
|
<ChevronUp className="w-5 h-5" />
|
|
) : (
|
|
<ChevronDown className="w-5 h-5" />
|
|
)}
|
|
</div>
|
|
</CollapsibleTrigger>
|
|
|
|
<CollapsibleContent>
|
|
<div className="p-4 pt-0 space-y-6 border-t">
|
|
<div className="space-y-4">
|
|
<div className="space-y-2">
|
|
<Label>Subjek Email</Label>
|
|
<Input
|
|
value={template.email_subject}
|
|
onChange={(e) => {
|
|
setTemplates(templates.map(t =>
|
|
t.id === template.id ? { ...t, email_subject: e.target.value } : t
|
|
));
|
|
}}
|
|
placeholder="Subjek email..."
|
|
className="border-2"
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label>Isi Email (HTML)</Label>
|
|
<RichTextEditor
|
|
content={template.email_body_html}
|
|
onChange={(html) => {
|
|
setTemplates(templates.map(t =>
|
|
t.id === template.id ? { ...t, email_body_html: html } : t
|
|
));
|
|
}}
|
|
placeholder="Tulis isi email..."
|
|
/>
|
|
</div>
|
|
|
|
<div className="space-y-2">
|
|
<Label className="flex items-center gap-2">
|
|
<Webhook className="w-4 h-4" />
|
|
Webhook URL (opsional, untuk n8n/Zapier)
|
|
</Label>
|
|
<Input
|
|
value={template.webhook_url}
|
|
onChange={(e) => {
|
|
setTemplates(templates.map(t =>
|
|
t.id === template.id ? { ...t, webhook_url: e.target.value } : t
|
|
));
|
|
}}
|
|
placeholder="https://n8n.example.com/webhook/..."
|
|
className="border-2"
|
|
/>
|
|
</div>
|
|
|
|
{/* Relevant Shortcodes for this Template */}
|
|
<div className="p-3 bg-blue-50 border border-blue-200 rounded">
|
|
<h4 className="font-semibold text-sm mb-2">Shortcodes untuk template ini:</h4>
|
|
<div className="flex flex-wrap gap-1">
|
|
{RELEVANT_SHORTCODES[template.key as keyof typeof RELEVANT_SHORTCODES]?.map(shortcode => (
|
|
<code key={shortcode} className="bg-blue-100 px-2 py-1 rounded text-xs">
|
|
{shortcode}
|
|
</code>
|
|
)) || <span className="text-xs text-gray-500">Tidak ada shortcode khusus untuk template ini</span>}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
onClick={() => {
|
|
updateTemplate(template);
|
|
setPreviewTemplate(template);
|
|
setIsPreviewOpen(true);
|
|
}}
|
|
className="shadow-sm flex-1"
|
|
>
|
|
Simpan & Preview
|
|
</Button>
|
|
</div>
|
|
|
|
{template.last_payload_example && (
|
|
<div className="space-y-2">
|
|
<Label>Contoh Payload Terakhir</Label>
|
|
<pre className="p-3 bg-muted rounded-md text-xs overflow-x-auto">
|
|
{JSON.stringify(template.last_payload_example, null, 2)}
|
|
</pre>
|
|
</div>
|
|
)}
|
|
|
|
<div className="flex gap-2">
|
|
<Button
|
|
onClick={() => updateTemplate(template)}
|
|
className="shadow-sm flex-1"
|
|
>
|
|
Simpan Template
|
|
</Button>
|
|
</div>
|
|
</div>
|
|
</CollapsibleContent>
|
|
</div>
|
|
</Collapsible>
|
|
))}
|
|
</CardContent>
|
|
</Card>
|
|
|
|
{/* Modal Email Preview */}
|
|
{previewTemplate && (
|
|
<EmailTemplatePreview
|
|
template={previewTemplate}
|
|
open={isPreviewOpen}
|
|
onClose={() => setIsPreviewOpen(false)}
|
|
onTest={sendTestEmail}
|
|
isTestSending={testingTemplate === previewTemplate.id}
|
|
/>
|
|
)}
|
|
</div>
|
|
);
|
|
}
|