const { useState, useEffect, useRef } = React; const SMTP_STORAGE_KEY = "cert_smtp_profiles_v1"; const FORM_STORAGE_KEY = "cert_form_data_v2"; function loadSmtpProfiles() { try { const saved = localStorage.getItem(SMTP_STORAGE_KEY); if (saved) { const parsed = JSON.parse(saved); if (Array.isArray(parsed) && parsed.length > 0) return parsed; } } catch (e) { console.error("Error loading SMTP profiles from localStorage", e); } return [{ id: "default_1", label: "Default Gmail", sender_email: "", password: "", smtp_server: "smtp.gmail.com", port: "587" }]; } function saveSmtpProfiles(profiles) { try { localStorage.setItem(SMTP_STORAGE_KEY, JSON.stringify(profiles)); } catch (e) { console.error("Error saving SMTP profiles to localStorage", e); } } function loadFormData() { try { const saved = localStorage.getItem(FORM_STORAGE_KEY); if (saved) { const parsed = JSON.parse(saved); if (parsed && typeof parsed === 'object') return parsed; } } catch (e) {} const today = new Date().toISOString().split('T')[0]; return { recipient_name: "", recipient_email: "", course_title: "AI IN ACTION", issue_date: today, sign_left: "Gouri Shankar", sign_right: "Arpit Shah", template_id: "ai_in_action" }; } function saveFormData(data) { try { localStorage.setItem(FORM_STORAGE_KEY, JSON.stringify(data)); } catch (e) {} } function App() { const [activeTab, setActiveTab] = useState("generator"); const [certHistory, setCertHistory] = useState([]); const [smtpProfiles, setSmtpProfiles] = useState(() => loadSmtpProfiles()); const [activeProfileId, setActiveProfileId] = useState(() => { return localStorage.getItem("active_smtp_profile_id") || (loadSmtpProfiles()[0]?.id || "default_1"); }); useEffect(() => { saveSmtpProfiles(smtpProfiles); }, [smtpProfiles]); useEffect(() => { localStorage.setItem("active_smtp_profile_id", activeProfileId); }, [activeProfileId]); const activeSmtpConfig = smtpProfiles.find(p => p.id === activeProfileId) || smtpProfiles[0] || { sender_email: "", password: "", smtp_server: "smtp.gmail.com", port: "587" }; // ========================================================================= // API CALL 1: GET /api/certificates/history // Fetches the complete list of issued certificates from database // ========================================================================= const fetchHistory = async () => { try { const res = await fetch('/api/certificates/history'); const data = await res.json(); if (data.history) setCertHistory(data.history); } catch (err) { console.error("Failed to fetch certificate history", err); } }; useEffect(() => { fetchHistory(); }, []); return (
{/* Header */}
AI

Certificate Automation & Dispatch System

Generate, Preview & Dispatch Verified Single-Page PDF Certificates

{activeSmtpConfig.sender_email ? (
SMTP Active: {activeSmtpConfig.sender_email}
) : (
SMTP Not Configured
)}
{/* Navigation Tabs Bar */} {/* Main Active Tab Content */}
{activeTab === 'generator' && ( )} {activeTab === 'batch' && ( )} {activeTab === 'records' && ( )} {activeTab === 'smtp' && ( )}
); } // ============================================================================ // TAB 1: Single Certificate Generator (NO AUTO-GENERATE ON REFRESH) // ============================================================================ function GeneratorTab({ smtpConfig, fetchHistory, setActiveTab }) { const [formData, setFormData] = useState(() => loadFormData()); const [currentCert, setCurrentCert] = useState(null); const [loading, setLoading] = useState(false); const [showPreviewModal, setShowPreviewModal] = useState(false); // Save form data to localStorage whenever updated useEffect(() => { saveFormData(formData); }, [formData]); // Explicit manual generate ONLY const handleGenerateSingle = async (e) => { if (e) e.preventDefault(); if (!formData.recipient_name.trim()) { alert("Please enter Recipient Name first!"); return; } setLoading(true); try { // ========================================================================= // API CALL 2: POST /api/certificates/generate // Generates single certificate (PDF + SVG) for the current form inputs // ========================================================================= const res = await fetch('/api/certificates/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await res.json(); setCurrentCert(data); setShowPreviewModal(true); fetchHistory(); } catch (err) { alert("Error generating certificate: " + err.message); } finally { setLoading(false); } }; const handleGenerateAndSendEmail = async (e) => { if (e) e.preventDefault(); if (!formData.recipient_name.trim()) { alert("Please enter Recipient Name first!"); return; } if (!formData.recipient_email.trim()) { alert("Please enter a valid Recipient Email Address!"); return; } if (!smtpConfig.sender_email || !smtpConfig.password) { alert("Please configure your SMTP Sender Email and App Password in SMTP Settings first!"); setActiveTab("smtp"); return; } setLoading(true); try { // ========================================================================= // API CALL 3A: POST /api/certificates/generate // Step 1: Generates the single certificate PDF & SVG files first // ========================================================================= const res = await fetch('/api/certificates/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(formData) }); const data = await res.json(); setCurrentCert(data); // ========================================================================= // API CALL 3B: POST /api/certificates/send_email // Step 2: Dispatches the 1-page PDF certificate email via SMTP // ========================================================================= const emailRes = await fetch('/api/certificates/send_email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cert_ids: [data.id], override_email: formData.recipient_email, smtp_config: smtpConfig }) }); const emailData = await emailRes.json(); const result = emailData.results[0]; if (result && result.success) { alert(`SUCCESS: Certificate generated & 1-page PDF emailed to ${formData.recipient_email}!`); setActiveTab("records"); } else { alert(`Certificate generated, but Email failed: ${result?.error || 'Check SMTP credentials'}`); } fetchHistory(); } catch (err) { alert("Error: " + err.message); } finally { setLoading(false); } }; const downloadSvg = () => { if (!currentCert) return; const blob = new Blob([currentCert.svg_content], { type: 'image/svg+xml' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; const clean_name = currentCert.recipient_name.replace(/[^a-zA-Z0-9]/g, '_'); a.download = `Certificate_${clean_name}.svg`; a.click(); }; const resetForm = () => { const today = new Date().toISOString().split('T')[0]; setFormData({ recipient_name: "", recipient_email: "", course_title: "AI IN ACTION", issue_date: today, sign_left: "Gouri Shankar", sign_right: "Arpit Shah", template_id: "ai_in_action" }); }; return (
Certificate Generator
setFormData({...formData, recipient_name: e.target.value})} required />
setFormData({...formData, recipient_email: e.target.value})} />
setFormData({...formData, course_title: e.target.value})} required />
setFormData({...formData, issue_date: e.target.value})} required />
setFormData({...formData, sign_left: e.target.value})} />
setFormData({...formData, sign_right: e.target.value})} />
{/* On-Demand Preview Modal */} {showPreviewModal && currentCert && (
setShowPreviewModal(false)}>
e.stopPropagation()}>
Generated Certificate Preview
)}
); } // ============================================================================ // TAB 2: Batch Recipient Import with Drag & Drop // ============================================================================ function BatchTab({ smtpConfig, fetchHistory, setActiveTab }) { const [parsedRecipients, setParsedRecipients] = useState([]); const [fileName, setFileName] = useState(""); const [loading, setLoading] = useState(false); const [isDragOver, setIsDragOver] = useState(false); const [mode, setMode] = useState("file"); const [batchText, setBatchText] = useState(""); const fileInputRef = useRef(null); const handleFileSelected = (file) => { if (!file) return; setFileName(file.name); setLoading(true); const reader = new FileReader(); reader.onload = async (e) => { const base64Data = e.target.result.split(',')[1]; try { const res = await fetch('/api/certificates/parse_file', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ filename: file.name, file_data: base64Data }) }); const data = await res.json(); if (data.success && data.recipients) { setParsedRecipients(data.recipients); } else { alert("Failed to parse file: " + (data.error || "Unknown error")); } } catch (err) { alert("File parse error: " + err.message); } finally { setLoading(false); } }; reader.readAsDataURL(file); }; const handleDragOver = (e) => { e.preventDefault(); setIsDragOver(true); }; const handleDragLeave = (e) => { e.preventDefault(); setIsDragOver(false); }; const handleDrop = (e) => { e.preventDefault(); setIsDragOver(false); if (e.dataTransfer.files && e.dataTransfer.files[0]) { handleFileSelected(e.dataTransfer.files[0]); } }; const handleProcessBatchImport = async (shouldSendEmail = false) => { let items = []; if (mode === "file") { items = parsedRecipients.map(r => ({ recipient_name: r.recipient_name, email: r.email, course_title: r.course_title || "AI IN ACTION" })); } else { if (!batchText.trim()) return; const lines = batchText.trim().split('\n'); items = lines.map(line => { const parts = line.split(','); return { recipient_name: parts[0]?.trim() || "Recipient", email: parts[1]?.trim() || "", course_title: parts[2]?.trim() || "AI IN ACTION" }; }); } if (items.length === 0) { alert("No recipients to process!"); return; } if (shouldSendEmail && (!smtpConfig?.sender_email || !smtpConfig?.password)) { alert("Please configure your SMTP Sender Email and App Password in SMTP Settings first!"); setActiveTab("smtp"); return; } setLoading(true); try { // Step 1: Batch generate certificates into folder & database const res = await fetch('/api/certificates/generate_to_folder', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ items, template_id: "ai_in_action" }) }); const data = await res.json(); const createdCerts = data.certificates || []; if (shouldSendEmail && createdCerts.length > 0) { // Step 2: Dispatch emails via SMTP for all created certificates const certIds = createdCerts.map(c => c.id || c.cert_id); const emailMap = {}; createdCerts.forEach((c, idx) => { const targetEmail = items[idx]?.email || c.recipient_email || ""; if (targetEmail) emailMap[c.id || c.cert_id] = targetEmail; }); const emailRes = await fetch('/api/certificates/send_email', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ cert_ids: certIds, email_map: emailMap, smtp_config: smtpConfig }) }); const emailData = await emailRes.json(); const successCount = (emailData.results || []).filter(r => r.success).length; alert(`SUCCESS: Generated ${data.count} certificate(s) & dispatched ${successCount} 1-page PDF email(s)!`); } else { alert(`Successfully generated & imported ${data.count} certificates into 'certificates/' folder.`); } setParsedRecipients([]); setFileName(""); setBatchText(""); fetchHistory(); setActiveTab("records"); } catch (err) { alert("Batch Process Error: " + err.message); } finally { setLoading(false); } }; return (
Batch Recipient Import
{mode === "file" ? ( <> e.target.files && handleFileSelected(e.target.files[0])} />
fileInputRef.current && fileInputRef.current.click()} >
📁

{fileName ? `Loaded: ${fileName}` : "Drag and drop your Excel or CSV file here"}

Supports Microsoft Excel (.xlsx, .xls) and CSV (.csv) files

{parsedRecipients.length > 0 && (

Parsed Recipients Preview ({parsedRecipients.length})

{parsedRecipients.map((r, idx) => ( ))}
# Recipient Name Target Email Address Course / Title
{idx + 1} {r.recipient_name} {r.email || No email set} {r.course_title}
)} ) : (

Paste CSV formatted text below. Format: Recipient Name, Recipient Email, Course Title per line.