"use client"; import { useCallback, useEffect, useState } from "react"; import type { ReactNode } from "react"; import { ExternalLink, MailCheck, ReceiptText, Save, Send, ShieldCheck } from "lucide-react"; import { useToast } from "@/components/common/ToastProvider"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { api } from "@/lib/api"; import type { LexwareSettings, LexwareSettingsPayload, LexwareTestConnectionResponse, } from "@/types/lexware"; import type { PublicLinksSettings, SettingsSource, SmtpSettings, SmtpSettingsPayload, SmtpTestResponse, } from "@/types/system-settings"; type SettingsTab = "smtp" | "public-links" | "lexware"; const sourceLabels: Record = { database: "Admin-Konfiguration", environment: "Umgebung", missing: "Nicht konfiguriert", }; function getErrorMessage(error: unknown, fallback: string) { if (typeof error === "object" && error !== null && "response" in error) { const response = (error as { response?: { data?: { detail?: string; message?: string } } }).response; return response?.data?.detail ?? response?.data?.message ?? fallback; } return fallback; } function emptySmtpSettings(): SmtpSettings { return { host: "", port: 587, username: "", password_is_set: false, from_email: "", from_name: "Funktechnik Schubert", use_tls: true, enabled: false, source: "missing", }; } function emptyLexwareSettings(): LexwareSettings { return { enabled: false, api_base_url: "https://api.lexware.io", api_key_is_set: false, organization_name: "", default_tax_rate: "19.00", default_payment_terms_days: 14, source: "missing", }; } export default function SettingsPage() { const { showToast } = useToast(); const [activeTab, setActiveTab] = useState("smtp"); const [smtp, setSmtp] = useState(emptySmtpSettings()); const [newPassword, setNewPassword] = useState(""); const [testRecipient, setTestRecipient] = useState(""); const [publicLinks, setPublicLinks] = useState({ repair_status_base_url: "", source: "missing", }); const [lexware, setLexware] = useState(emptyLexwareSettings()); const [newLexwareApiKey, setNewLexwareApiKey] = useState(""); const [loading, setLoading] = useState(true); const [savingSmtp, setSavingSmtp] = useState(false); const [sendingTest, setSendingTest] = useState(false); const [savingPublicLinks, setSavingPublicLinks] = useState(false); const [savingLexware, setSavingLexware] = useState(false); const [testingLexware, setTestingLexware] = useState(false); const [error, setError] = useState(""); const loadSettings = useCallback(async () => { setError(""); setLoading(true); try { const [smtpResponse, publicLinksResponse, lexwareResponse] = await Promise.all([ api.get("/system-settings/smtp"), api.get("/system-settings/public-links"), api.get("/lexware/settings"), ]); setSmtp(smtpResponse.data); setPublicLinks(publicLinksResponse.data); setLexware(lexwareResponse.data); } catch (err) { setError(getErrorMessage(err, "Einstellungen konnten nicht geladen werden.")); } finally { setLoading(false); } }, []); useEffect(() => { queueMicrotask(() => { void loadSettings(); }); }, [loadSettings]); async function saveSmtp() { setSavingSmtp(true); try { const payload: SmtpSettingsPayload = { host: smtp.host, port: smtp.port, username: smtp.username, from_email: smtp.from_email, from_name: smtp.from_name, use_tls: smtp.use_tls, enabled: smtp.enabled, }; if (newPassword) { payload.password = newPassword; } const response = await api.put("/system-settings/smtp", payload); setSmtp(response.data); setNewPassword(""); showToast({ type: "success", title: "SMTP-Konfiguration gespeichert" }); } catch (err) { showToast({ type: "error", title: "SMTP konnte nicht gespeichert werden", description: getErrorMessage(err, "Bitte prüfe die Eingaben."), }); } finally { setSavingSmtp(false); } } async function sendTestMail() { setSendingTest(true); try { const response = await api.post("/system-settings/smtp/test", { recipient: testRecipient, }); showToast({ type: response.data.success ? "success" : "error", title: response.data.success ? "Testmail versendet" : "Testmail fehlgeschlagen", description: response.data.message, }); } catch (err) { showToast({ type: "error", title: "Testmail fehlgeschlagen", description: getErrorMessage(err, "Die Testmail konnte nicht versendet werden."), }); } finally { setSendingTest(false); } } async function savePublicLinks() { setSavingPublicLinks(true); try { const response = await api.put("/system-settings/public-links", { repair_status_base_url: publicLinks.repair_status_base_url, }); setPublicLinks(response.data); showToast({ type: "success", title: "Öffentliche Links gespeichert" }); } catch (err) { showToast({ type: "error", title: "Öffentliche Links konnten nicht gespeichert werden", description: getErrorMessage(err, "Bitte prüfe die Basis-URL."), }); } finally { setSavingPublicLinks(false); } } async function saveLexware() { setSavingLexware(true); try { const payload: LexwareSettingsPayload = { enabled: lexware.enabled, api_base_url: lexware.api_base_url, organization_name: lexware.organization_name, default_tax_rate: lexware.default_tax_rate, default_payment_terms_days: lexware.default_payment_terms_days, }; if (newLexwareApiKey) { payload.api_key = newLexwareApiKey; } const response = await api.put("/lexware/settings", payload); setLexware(response.data); setNewLexwareApiKey(""); showToast({ type: "success", title: "Lexware-Konfiguration gespeichert" }); } catch (err) { showToast({ type: "error", title: "Lexware konnte nicht gespeichert werden", description: getErrorMessage(err, "Bitte prüfe die Lexware-Einstellungen."), }); } finally { setSavingLexware(false); } } async function testLexwareConnection() { setTestingLexware(true); try { const response = await api.post("/lexware/test-connection"); showToast({ type: response.data.success ? "success" : "error", title: response.data.success ? "Lexware-Verbindung erfolgreich" : "Lexware-Verbindung fehlgeschlagen", description: response.data.message, }); } catch (err) { showToast({ type: "error", title: "Lexware-Verbindung fehlgeschlagen", description: getErrorMessage(err, "Die Verbindung konnte nicht geprüft werden."), }); } finally { setTestingLexware(false); } } if (loading) { return
Einstellungen werden geladen...
; } if (error) { return
{error}
; } return (

Einstellungen

SMTP-Versand, öffentliche Statuslinks und Lexware Office verwalten

setActiveTab("smtp")}> SMTP setActiveTab("public-links")}> Öffentliche Links setActiveTab("lexware")}> Lexware Office
{activeTab === "smtp" ? (

SMTP

Quelle: {sourceLabels[smtp.source]} {smtp.password_is_set ? " · Passwort ist gesetzt" : ""}

setSmtp((current) => ({ ...current, host: event.target.value }))} /> setSmtp((current) => ({ ...current, port: Number(event.target.value) || 587 }))} /> setSmtp((current) => ({ ...current, username: event.target.value }))} /> setNewPassword(event.target.value)} /> setSmtp((current) => ({ ...current, from_email: event.target.value }))} /> setSmtp((current) => ({ ...current, from_name: event.target.value }))} />
Für Apple Mail/iCloud: smtp.mail.me.com, Port 587, TLS/STARTTLS aktiv, Benutzername vollständige Mailadresse, Passwort = app-spezifisches Passwort.

Testmail senden

setTestRecipient(event.target.value)} />
) : activeTab === "public-links" ? (

Öffentliche Links

Quelle: {sourceLabels[publicLinks.source]}

setPublicLinks((current) => ({ ...current, repair_status_base_url: event.target.value }))} />

Statusmails erzeugen weiterhin sichere Einmal-Token in Hermes. Die Basis-URL bestimmt nur, auf welche öffentliche Website-Route der Link zeigt.

) : (

Lexware Office

Quelle: {sourceLabels[lexware.source]} {lexware.api_key_is_set ? " · API-Key ist gesetzt" : ""}

setLexware((current) => ({ ...current, organization_name: event.target.value }))} /> setLexware((current) => ({ ...current, api_base_url: event.target.value }))} /> setNewLexwareApiKey(event.target.value)} /> setLexware((current) => ({ ...current, default_tax_rate: event.target.value }))} /> setLexware((current) => ({ ...current, default_payment_terms_days: Number(event.target.value) || 0 }))} />

Olympus bereitet Werkstatt- und KV-Daten vor. Lexware Office bleibt führend für Buchhaltung, Rechnungen, Steuer und DATEV/EÜR. Der API-Key wird nicht angezeigt und nicht an den Browser zurückgegeben.

)}
); } function TabButton({ active, onClick, children, }: { active: boolean; onClick: () => void; children: ReactNode; }) { return ( ); } function Field({ label, children }: { label: string; children: ReactNode }) { return ( ); }