'use client'; import { useCallback, useEffect, useState, type FormEvent } from 'react'; import { api, errMsg } from '@/lib/api'; import { useSession } from '@/lib/session'; import { Alert, Field, fmt } from '@/components/ui'; interface Settings { host: string | null; port: number; secureMode: 'none' | 'starttls' | 'tls'; username: string | null; hasPassword: boolean; fromName: string | null; fromEmail: string | null; configured: boolean; updatedAt: string } interface Template { key: string; name: string; trigger: string; variables: string[]; subject: string; body: string; enabled: boolean; updatedAt: string } interface LogRow { id: number; to: string; template: string; status: string; error: string | null; createdAt: string } const SECURE_LABEL: Record = { none: 'Keine Verschlüsselung', starttls: 'STARTTLS (empfohlen, meist Port 587)', tls: 'TLS/SSL direkt (meist Port 465)' }; const STATUS: Record = { sent: { cls: 'ok', icon: '✓', label: 'Gesendet' }, failed: { cls: 'err', icon: '✕', label: 'Fehlgeschlagen' }, not_configured: { cls: 'warn', icon: '▲', label: 'SMTP nicht eingerichtet' }, disabled: { cls: 'lock', icon: '⊘', label: 'Vorlage deaktiviert' }, no_template: { cls: 'err', icon: '✕', label: 'Vorlage fehlt' }, }; const StatusBadge = ({ v }: { v: string }) => { const m = STATUS[v] ?? { cls: 'info', icon: '○', label: v }; return {m.label}; }; /** SMTP-Verbindung: Formular + Testmail. Leer gelassenes Passwortfeld lässt ein bereits gespeichertes Passwort unverändert. */ function SmtpForm({ s, w, onSaved }: { s: Settings; w: boolean; onSaved: () => void }) { const [busy, setBusy] = useState(false); const [msg, setMsg] = useState<{ k: 'ok' | 'err'; t: string } | null>(null); const [testTo, setTestTo] = useState(''); const [testBusy, setTestBusy] = useState(false); async function save(e: FormEvent) { e.preventDefault(); if (!w) return; setBusy(true); setMsg(null); const form = e.currentTarget; const f = new FormData(form); const v = (k: string) => (String(f.get(k) ?? '').trim() || null); const pw = String(f.get('password') ?? ''); try { await api('PUT', '/admin/email/settings', { host: v('host'), port: Number(f.get('port')) || 587, secureMode: f.get('secureMode'), username: v('username'), fromName: v('fromName'), fromEmail: v('fromEmail'), ...(pw ? { password: pw } : {}) }); setMsg({ k: 'ok', t: 'Gespeichert.' }); (form.elements.namedItem('password') as HTMLInputElement).value = ''; onSaved(); } catch (x) { setMsg({ k: 'err', t: errMsg(x) }); } finally { setBusy(false); } } async function sendTest() { setTestBusy(true); setMsg(null); try { const r = await api<{ status: string }>('POST', '/admin/email/settings/test', { to: testTo }); setMsg({ k: r.status === 'sent' ? 'ok' : 'err', t: r.status === 'sent' ? `Testmail an ${testTo} gesendet.` : `Status: ${STATUS[r.status]?.label ?? r.status}` }); } catch (x) { setMsg({ k: 'err', t: errMsg(x) }); } finally { setTestBusy(false); } } return (

SMTP-Verbindung

{!s.configured && Noch nicht eingerichtet. Solange Host und Absenderadresse fehlen, werden E-Mails nicht versendet, aber im Protokoll unten als „nicht eingerichtet“ vermerkt.} {msg && {msg.t}}
{w &&
}
{w &&
setTestTo(e.target.value)} style={{ maxWidth: 260 }} />
}

Zuletzt geändert: {fmt(s.updatedAt)}

); } /** Eine Vorlage: feste Definition (Auslöser, verfügbare Daten) + editierbarer Betreff/Text. */ function TemplateCard({ t, w, onSaved }: { t: Template; w: boolean; onSaved: () => void }) { const [open, setOpen] = useState(false); const [busy, setBusy] = useState(false); const [msg, setMsg] = useState<{ k: 'ok' | 'err'; t: string } | null>(null); const [testTo, setTestTo] = useState(''); const [testBusy, setTestBusy] = useState(false); async function save(e: FormEvent) { e.preventDefault(); if (!w) return; setBusy(true); setMsg(null); const f = new FormData(e.currentTarget); try { await api('PUT', `/admin/email/templates/${t.key}`, { subject: f.get('subject'), body: f.get('body'), enabled: f.get('enabled') === 'on' }); setMsg({ k: 'ok', t: 'Gespeichert.' }); onSaved(); } catch (x) { setMsg({ k: 'err', t: errMsg(x) }); } finally { setBusy(false); } } async function sendTest() { setTestBusy(true); setMsg(null); try { const r = await api<{ status: string }>('POST', `/admin/email/templates/${t.key}/test`, { to: testTo }); setMsg({ k: r.status === 'sent' ? 'ok' : 'err', t: r.status === 'sent' ? `Testmail mit Beispieldaten an ${testTo} gesendet.` : `Status: ${STATUS[r.status]?.label ?? r.status}` }); } catch (x) { setMsg({ k: 'err', t: errMsg(x) }); } finally { setTestBusy(false); } } return (

{t.name}

{t.trigger}

{!t.enabled && Deaktiviert}
{open &&
{msg && {msg.t}}

Verfügbare Daten für diese Vorlage: {t.variables.map((v) => {`{{${v}}}`})}