");
}
const result = await Email.send({
SecureToken: server.secureToken,
To: to,
From: server.fromEmail || branding.senderEmail,
Subject: subject,
Body: htmlBody,
});
if (result !== "OK") throw new Error(result);
return { success: true, provider: "smtpjs" };
} else if (server.provider === "api") {
// Custom API endpoint (POST JSON)
const resp = await fetch(server.endpoint, {
method: "POST",
headers: { "Content-Type": "application/json", ...(server.apiKey ? { "Authorization": `Bearer ${server.apiKey}` } : {}) },
body: JSON.stringify({ to, from: server.fromEmail || branding.senderEmail, fromName: branding.senderName, subject, html: htmlBody, text: body })
});
if (!resp.ok) throw new Error(`API error: ${resp.status} ${resp.statusText}`);
return { success: true, provider: "api" };
} else if (server.provider === "mailto") {
// Fallback: open mailto link
const mailtoUrl = `mailto:${to}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.open(mailtoUrl, "_blank");
return { success: true, provider: "mailto", note: "Opened in email client" };
} else {
throw new Error("Unknown provider: " + server.provider);
}
}
async function sendTemplateEmail(templateId, recipientData, onToast) {
const templates = (await S.get("emailTemplates")) || DEFAULT_EMAIL_TEMPLATES;
const branding = (await S.get("emailBranding")) || DEFAULT_BRANDING;
const servers = await loadSmtpServers();
const tpl = templates.find(t => t.id === templateId);
if (!tpl) { onToast({ type: "error", msg: "Template nicht gefunden" }); return false; }
if (!tpl.a) { onToast({ type: "error", msg: `Template "${tpl.n}" ist deaktiviert` }); return false; }
// Fill variables
let subject = tpl.subject;
let body = tpl.body;
const vars = { firma: branding.senderName || "ConsultPro", ...recipientData };
Object.keys(vars).forEach(k => {
subject = subject.replaceAll(`{{${k}}}`, vars[k] || "");
body = body.replaceAll(`{{${k}}}`, vars[k] || "");
});
if (!recipientData.email) { onToast({ type: "error", msg: "Keine E-Mail-Adresse" }); return false; }
// Find active server
const activeServer = servers.find(s => s.active);
if (!activeServer) {
// Fallback to mailto
const mailtoUrl = `mailto:${recipientData.email}?subject=${encodeURIComponent(subject)}&body=${encodeURIComponent(body)}`;
window.open(mailtoUrl, "_blank");
onToast({ type: "success", msg: `E-Mail in Mail-App geöffnet (kein SMTP konfiguriert)` });
return true;
}
try {
await sendEmailViaSmtp(activeServer, recipientData.email, subject, body, branding);
// Log sent email
const logId = `emaillog_${Date.now()}`;
const log = { id: logId, templateId, templateName: tpl.n, to: recipientData.email, toName: recipientData.name, subject, sentAt: new Date().toISOString(), server: activeServer.name, status: "sent" };
await S.set(`emaillog:${logId}`, log);
const idx = (await S.get("emaillog:index")) || [];
idx.unshift(logId);
if (idx.length > 100) idx.length = 100;
await S.set("emaillog:index", idx);
onToast({ type: "success", msg: `✓ E-Mail an ${recipientData.email} gesendet via ${activeServer.name}` });
return true;
} catch (err) {
onToast({ type: "error", msg: `E-Mail-Fehler: ${err.message}` });
return false;
}
}
function Settings({ onToast }) {
const [settings, setSettings] = useState({ firma: "ConsultPro GmbH", primary: "#3b82f6", telegram: "", whatsapp: "", apiKey: "sk-••••••••••" });
const [smtpServers, setSmtpServers] = useState([]);
const [editSmtp, setEditSmtp] = useState(null);
const [newSmtp, setNewSmtp] = useState(null);
const [emailLogs, setEmailLogs] = useState([]);
const [showLogs, setShowLogs] = useState(false);
const [testEmail, setTestEmail] = useState("");
const [sending, setSending] = useState(false);
useEffect(() => {
(async () => {
const s = await S.get("settings:global");
if (s) setSettings(s);
setSmtpServers(await loadSmtpServers());
const logIdx = (await S.get("emaillog:index")) || [];
const logs = [];
for (const id of logIdx.slice(0, 20)) { const l = await S.get(`emaillog:${id}`); if (l) logs.push(l); }
setEmailLogs(logs);
})();
}, []);
const save = async () => { await S.set("settings:global", settings); onToast({ type: "success", msg: "Einstellungen gespeichert" }); };
const addSmtp = async () => {
if (!newSmtp || !newSmtp.name) { onToast({ type: "error", msg: "Name ist Pflicht" }); return; }
const id = `smtp_${Date.now()}`;
const server = { id, ...newSmtp, active: smtpServers.length === 0 };
const updated = [...smtpServers, server];
setSmtpServers(updated);
await saveSmtpServers(updated);
setNewSmtp(null);
onToast({ type: "success", msg: `SMTP "${server.name}" hinzugefügt` });
};
const updateSmtp = async () => {
if (!editSmtp) return;
const updated = smtpServers.map(s => s.id === editSmtp.id ? editSmtp : s);
setSmtpServers(updated);
await saveSmtpServers(updated);
setEditSmtp(null);
onToast({ type: "success", msg: "SMTP aktualisiert" });
};
const deleteSmtp = async (id) => {
if (!confirm("SMTP-Server löschen?")) return;
const updated = smtpServers.filter(s => s.id !== id);
setSmtpServers(updated);
await saveSmtpServers(updated);
onToast({ type: "success", msg: "SMTP gelöscht" });
};
const toggleSmtpActive = async (id) => {
const updated = smtpServers.map(s => ({ ...s, active: s.id === id }));
setSmtpServers(updated);
await saveSmtpServers(updated);
onToast({ type: "success", msg: "Aktiver SMTP geändert" });
};
const sendTest = async () => {
if (!testEmail || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(testEmail)) { onToast({ type: "error", msg: "Gültige E-Mail eingeben" }); return; }
setSending(true);
await sendTemplateEmail("welcome", { name: "Test User", email: testEmail, password: "TestPass123", loginUrl: "https://consultpro.de/login" }, onToast);
setSending(false);
// Refresh logs
const logIdx = (await S.get("emaillog:index")) || [];
const logs = [];
for (const id of logIdx.slice(0, 20)) { const l = await S.get(`emaillog:${id}`); if (l) logs.push(l); }
setEmailLogs(logs);
};
const emptySmtp = { name: "", provider: "api", endpoint: "", apiKey: "", fromEmail: "", secureToken: "" };
const SmtpForm = ({ data, onChange, onSave, onCancel, title }) => (
React.createElement(Modal, { onClose: onCancel, title: title, wide: true,}
, React.createElement(Field, { label: "Server-Name *" , value: data.name, onChange: v => onChange({ ...data, name: v }), ph: "z.B. Postmark, SendGrid, Brevo..." ,} )
, React.createElement(Select, { label: "Provider-Typ", value: data.provider, onChange: v => onChange({ ...data, provider: v }), options: [
{ value: "api", label: "API Endpoint (Postmark, SendGrid, Brevo, etc.)" },
{ value: "smtpjs", label: "SmtpJS.com (Browser-SMTP via Secure Token)" },
{ value: "mailto", label: "Mailto-Fallback (öffnet Mail-App)" }
],} )
, data.provider === "api" && React.createElement(React.Fragment, null
, React.createElement(Field, { label: "API Endpoint URL *" , value: data.endpoint, onChange: v => onChange({ ...data, endpoint: v }), ph: "https://api.postmarkapp.com/email",} )
, React.createElement(Field, { label: "API Key / Token" , value: data.apiKey, onChange: v => onChange({ ...data, apiKey: v }), ph: "Dein API-Key..." ,} )
, React.createElement('div', { style: { padding: 12, background: C.primaryL, borderRadius: 10, fontSize: 12, color: "#1e40af", marginBottom: 14 },}, "💡 "
, React.createElement('strong', null, "API-Format:"), " Der Request geht als POST mit JSON: " , React.createElement('code', null, `{to, from, fromName, subject, html, text}`), ". Header: " , React.createElement('code', null, "Authorization: Bearer [API-Key]" ), ".", React.createElement('br', null), React.createElement('br', null)
, React.createElement('strong', null, "Postmark:"), " Endpoint: " , React.createElement('code', null, "https://api.postmarkapp.com/email"), " — Token als API-Key" , React.createElement('br', null)
, React.createElement('strong', null, "SendGrid:"), " Endpoint: " , React.createElement('code', null, "https://api.sendgrid.com/v3/mail/send"), " — API-Key" , React.createElement('br', null)
, React.createElement('strong', null, "Brevo:"), " Endpoint: " , React.createElement('code', null, "https://api.brevo.com/v3/smtp/email"), " — API-Key" , React.createElement('br', null)
, React.createElement('strong', null, "Eigener Server:" ), " Jeder Endpoint, der JSON akzeptiert"
)
)
, data.provider === "smtpjs" && React.createElement(React.Fragment, null
, React.createElement(Field, { label: "Secure Token *" , value: data.secureToken, onChange: v => onChange({ ...data, secureToken: v }), ph: "Dein SmtpJS Secure Token" ,} )
, React.createElement('div', { style: { padding: 12, background: C.warningBg, borderRadius: 10, fontSize: 12, color: "#92400e", marginBottom: 14 },}, "⚠️ "
, React.createElement('strong', null, "SmtpJS"), " nutzt einen Relay-Service. Erstelle deinen Secure Token auf " , React.createElement('a', { href: "https://smtpjs.com", target: "_blank", style: { color: "#92400e" },}, "smtpjs.com"), " mit deinen SMTP-Daten (Host, Port, User, Password). Der Token verbirgt deine Zugangsdaten."
)
)
, data.provider === "mailto" && React.createElement('div', { style: { padding: 12, background: C.successBg, borderRadius: 10, fontSize: 12, color: "#065f46", marginBottom: 14 },}, "ℹ️ "
, React.createElement('strong', null, "Mailto-Fallback:"), " Öffnet die E-Mail im lokalen Mail-Programm des Benutzers (Outlook, Thunderbird, Apple Mail). Kein Server nötig, aber nicht automatisiert."
)
, React.createElement(Field, { label: "Absender-E-Mail", value: data.fromEmail, onChange: v => onChange({ ...data, fromEmail: v }), ph: "noreply@deinedomain.de (überschreibt Branding)" ,} )
, React.createElement('div', { style: { display: "flex", gap: 8, marginTop: 16 },}
, React.createElement('button', { onClick: onSave, style: btn("primary"),}, "Speichern")
, React.createElement('button', { onClick: onCancel, style: btn("ghost"),}, "Abbrechen")
)
)
);
return (
React.createElement('div', null
, React.createElement(PageHeader, { title: "Einstellungen", sub: "System-Konfiguration",} )
, React.createElement('div', { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20, marginBottom: 20 },}
, React.createElement(Card, null, React.createElement('h3', { style: { fontSize: 15, fontWeight: 700, color: C.text, marginBottom: 16, display: "flex", alignItems: "center", gap: 8 },}, React.createElement(Ic, { n: "settings", s: 18, c: C.primary,} ), " Branding" )
, React.createElement(Field, { label: "Firmenname", value: settings.firma, onChange: v => setSettings({...settings, firma: v}),} )
, React.createElement(Field, { label: "Primärfarbe", type: "color", value: settings.primary, onChange: v => setSettings({...settings, primary: v}),} )
, React.createElement('button', { onClick: save, style: btn("primary", "sm"),}, "Speichern")
)
, React.createElement(Card, null, React.createElement('h3', { style: { fontSize: 15, fontWeight: 700, color: C.text, marginBottom: 16, display: "flex", alignItems: "center", gap: 8 },}, React.createElement(Ic, { n: "chat", s: 18, c: C.accent,} ), " Integrationen" )
, React.createElement(Field, { label: "Telegram Bot Token" , value: settings.telegram, onChange: v => setSettings({...settings, telegram: v}), ph: "Bot-Token...",} )
, React.createElement(Field, { label: "WhatsApp API Key" , value: settings.whatsapp, onChange: v => setSettings({...settings, whatsapp: v}), ph: "API-Key...",} )
, React.createElement('button', { onClick: save, style: btn("primary", "sm"),}, "Speichern")
)
)
, React.createElement(Card, null
, React.createElement('div', { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 },}
, React.createElement('h3', { style: { fontSize: 15, fontWeight: 700, color: C.text, display: "flex", alignItems: "center", gap: 8, margin: 0 },}, React.createElement(Ic, { n: "email", s: 18, c: C.primary,} ), " SMTP / E-Mail-Server" )
, React.createElement('button', { onClick: () => setNewSmtp({ ...emptySmtp }), style: btn("primary", "sm"),}, React.createElement(Ic, { n: "plus", s: 13,} ), " Server hinzufügen" )
)
, smtpServers.length === 0 ? (
React.createElement('div', { style: { padding: 32, textAlign: "center", background: C.subtle, borderRadius: 12 },}
, React.createElement(Ic, { n: "email", s: 32, c: C.textDim,} )
, React.createElement('div', { style: { fontSize: 14, fontWeight: 600, color: C.textMid, marginTop: 12 },}, "Kein E-Mail-Server konfiguriert" )
, React.createElement('div', { style: { fontSize: 12, color: C.textDim, marginTop: 4, marginBottom: 16 },}, "E-Mails werden über die Mail-App geöffnet (mailto). Für automatischen Versand einen Server hinzufügen." )
, React.createElement('button', { onClick: () => setNewSmtp({ ...emptySmtp }), style: btn("primary"),}, React.createElement(Ic, { n: "plus", s: 14,} ), " Ersten Server hinzufügen" )
)
) : (
React.createElement('div', { style: { display: "flex", flexDirection: "column", gap: 10 },}
, smtpServers.map(s => (
React.createElement('div', { key: s.id, style: { display: "flex", alignItems: "center", gap: 14, padding: 16, background: s.active ? "#f0fdf4" : C.subtle, border: `1.5px solid ${s.active ? "#86efac" : C.border}`, borderRadius: 12 },}
, React.createElement('div', { onClick: () => toggleSmtpActive(s.id), style: { width: 20, height: 20, borderRadius: "50%", border: `2px solid ${s.active ? C.success : C.textDim}`, background: s.active ? C.success : "transparent", cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 },}
, s.active && React.createElement(Ic, { n: "check", s: 12, c: "#fff",} )
)
, React.createElement('div', { style: { flex: 1 },}
, React.createElement('div', { style: { fontSize: 14, fontWeight: 700, color: C.text },}, s.name, " " , s.active && React.createElement('span', { style: { fontSize: 11, color: C.success, fontWeight: 600 },}, " — Aktiv" ))
, React.createElement('div', { style: { fontSize: 12, color: C.textDim },}, s.provider === "api" ? s.endpoint : s.provider === "smtpjs" ? "SmtpJS Relay" : "Mailto Fallback", " " , s.fromEmail ? `· ${s.fromEmail}` : "")
)
, React.createElement('div', { style: { display: "flex", gap: 6 },}
, React.createElement('button', { onClick: () => setEditSmtp({ ...s }), style: btn("ghost", "sm"),}, React.createElement(Ic, { n: "edit", s: 12,} ))
, React.createElement('button', { onClick: () => deleteSmtp(s.id), style: btn("danger", "sm"),}, React.createElement(Ic, { n: "trash", s: 12,} ))
)
)
))
)
)
, React.createElement('div', { style: { marginTop: 20, padding: 16, background: C.subtle, borderRadius: 12 },}
, React.createElement('div', { style: { fontSize: 12, fontWeight: 700, color: C.textMid, textTransform: "uppercase", letterSpacing: 1, marginBottom: 10 },}, "Test-E-Mail senden" )
, React.createElement('div', { style: { display: "flex", gap: 8 },}
, React.createElement('input', { type: "email", value: testEmail, onChange: e => setTestEmail(e.target.value), placeholder: "test@beispiel.de", style: { flex: 1, padding: "10px 14px", background: "#fff", border: `1.5px solid ${C.border}`, borderRadius: 10, fontSize: 13, outline: "none", fontFamily: "inherit", boxSizing: "border-box" },} )
, React.createElement('button', { onClick: sendTest, disabled: sending, style: btn("primary"),}, sending ? "Sende..." : "Test senden")
)
, React.createElement('div', { style: { fontSize: 11, color: C.textDim, marginTop: 6 },}, "Sendet die Willkommens-Vorlage an die angegebene Adresse" , smtpServers.length === 0 ? " (via Mail-App)" : ` via ${(smtpServers.find(s => s.active) || {}).name || "Mail-App"}`)
)
)
, React.createElement('div', { style: { display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20, marginTop: 20 },}
, React.createElement(Card, null, React.createElement('h3', { style: { fontSize: 15, fontWeight: 700, color: C.text, marginBottom: 16, display: "flex", alignItems: "center", gap: 8 },}, React.createElement(Ic, { n: "dl", s: 18, c: C.warning,} ), " Datenexport" )
, React.createElement('div', { style: { display: "flex", flexDirection: "column", gap: 8 },}
, React.createElement('button', { onClick: () => onToast({ type: "success", msg: "CSV export gestartet" }), style: btn("ghost"),}, React.createElement(Ic, { n: "dl", s: 14,} ), " Mitarbeiter (CSV)" )
, React.createElement('button', { onClick: () => onToast({ type: "success", msg: "JSON export" }), style: btn("ghost"),}, React.createElement(Ic, { n: "dl", s: 14,} ), " Bewerbungen (JSON)" )
, React.createElement('button', { onClick: () => onToast({ type: "success", msg: "ZIP erstellt" }), style: btn("ghost"),}, React.createElement(Ic, { n: "dl", s: 14,} ), " Alle Ausweise (ZIP)" )
)
)
, React.createElement(Card, null
, React.createElement('div', { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 },}
, React.createElement('h3', { style: { fontSize: 15, fontWeight: 700, color: C.text, display: "flex", alignItems: "center", gap: 8, margin: 0 },}, React.createElement(Ic, { n: "email", s: 18, c: C.accent,} ), " E-Mail-Log" )
, React.createElement('span', { style: { fontSize: 11, color: C.textDim },}, emailLogs.length, " Einträge" )
)
, emailLogs.length === 0 ? React.createElement('div', { style: { padding: 24, textAlign: "center", color: C.textDim, fontSize: 13 },}, "Noch keine E-Mails versendet" )
: React.createElement('div', { style: { maxHeight: 240, overflow: "auto" },}, emailLogs.map(l => (
React.createElement('div', { key: l.id, style: { display: "flex", alignItems: "center", gap: 10, padding: "10px 0", borderBottom: `1px solid ${C.borderL}` },}
, React.createElement('div', { style: { width: 28, height: 28, borderRadius: 8, background: l.status === "sent" ? C.successBg : C.dangerBg, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 },}, React.createElement(Ic, { n: l.status === "sent" ? "check" : "x", s: 12, c: l.status === "sent" ? C.success : C.danger,} ))
, React.createElement('div', { style: { flex: 1, minWidth: 0 },}
, React.createElement('div', { style: { fontSize: 12, fontWeight: 600, color: C.text },}, l.templateName)
, React.createElement('div', { style: { fontSize: 11, color: C.textDim, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },}, l.to, " · " , l.server, " · " , new Date(l.sentAt).toLocaleString("de-DE"))
)
)
)))
)
)
, newSmtp && React.createElement(SmtpForm, { data: newSmtp, onChange: setNewSmtp, onSave: addSmtp, onCancel: () => setNewSmtp(null), title: "Neuen E-Mail-Server hinzufügen" ,} )
, editSmtp && React.createElement(SmtpForm, { data: editSmtp, onChange: setEditSmtp, onSave: updateSmtp, onCancel: () => setEditSmtp(null), title: `Server bearbeiten: ${editSmtp.name}`,} )
)
);
}
// ══════ MAIN APP ══════
function App() {
const [toast, setToast] = useState(null);
const [ready, setReady] = useState(false);
useEffect(function(){ seedData().then(function(){ setReady(true); }); }, []);
if (!ready) return React.createElement("div", {style:{height:"100vh",display:"flex",alignItems:"center",justifyContent:"center",background:C.subtle,fontFamily:"sans-serif",fontSize:14,color:C.textSoft}}, "Wird geladen...");
return React.createElement(React.Fragment, null, React.createElement(Website, {onToast:setToast}), React.createElement(Toast, {t:toast,onClose:function(){setToast(null)}}));
}
var root=ReactDOM.createRoot(document.getElementById("root"));
root.render(React.createElement(App,null));