import React, { useEffect, useState } from 'react'; import { User as UserIcon, Bell, Key, Plus, Trash2, Terminal, Download, ArrowLeft, Check, Mail, MessageSquare, Globe, Send, AlertCircle, Palette, } from 'lucide-react'; import { APIKey, User } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; import { AVAILABLE_THEMES, ThemeId } from '../../lib/theme'; interface UserSettingsViewProps { currentUser: User; activeTheme: ThemeId; onThemeChange: (themeId: ThemeId) => void; onUserUpdated: (u: User) => void; onBack: () => void; } type UserSettingsSection = 'profile' | 'appearance' | 'alarms' | 'webhooks'; export const UserSettingsView: React.FC = ({ currentUser, activeTheme, onThemeChange, onUserUpdated, onBack, }) => { const [section, setSection] = useState('profile'); // --- Profile State --- const [displayName, setDisplayName] = useState(currentUser.displayName || ''); const [email, setEmail] = useState(currentUser.email || ''); const [newPassword, setNewPassword] = useState(''); const [profileMsg, setProfileMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); const [savingProfile, setSavingProfile] = useState(false); // --- Multi-Channel Alarms State --- const [alarmChannels, setAlarmChannels] = useState( Array.isArray(currentUser.alarmChannels) ? currentUser.alarmChannels : [] ); const [alarmEmail, setAlarmEmail] = useState(currentUser.alarmEmail || currentUser.email || ''); const [discordWebhookUrl, setDiscordWebhookUrl] = useState(currentUser.discordWebhookUrl || ''); const [customWebhookUrl, setCustomWebhookUrl] = useState(currentUser.customWebhookUrl || ''); const [notifyBeforeMinutes, setNotifyBeforeMinutes] = useState( currentUser.notifyBeforeMinutes ?? 30 ); const [alarmMsg, setAlarmMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); const [savingAlarms, setSavingAlarms] = useState(false); const [testingChannel, setTestingChannel] = useState(null); // --- Webhooks & API Keys State --- const [apiKeys, setApiKeys] = useState([]); const [newKeyName, setNewKeyName] = useState(''); const [newlyCreatedKey, setNewlyCreatedKey] = useState(null); const [copiedLabel, setCopiedLabel] = useState(null); useEffect(() => { setDisplayName(currentUser.displayName || ''); setEmail(currentUser.email || ''); setAlarmChannels(Array.isArray(currentUser.alarmChannels) ? currentUser.alarmChannels : []); setAlarmEmail(currentUser.alarmEmail || currentUser.email || ''); setDiscordWebhookUrl(currentUser.discordWebhookUrl || ''); setCustomWebhookUrl(currentUser.customWebhookUrl || ''); setNotifyBeforeMinutes(currentUser.notifyBeforeMinutes ?? 30); }, [currentUser]); useEffect(() => { loadKeys(); }, []); const loadKeys = async () => { try { const keys = await api.getAPIKeys(); setApiKeys(keys); } catch { // ignore } }; const toggleAlarmChannel = (ch: string) => { setAlarmChannels((prev) => prev.includes(ch) ? prev.filter((item) => item !== ch) : [...prev, ch] ); }; // --- Handlers: Profile --- const handleSaveProfile = async (e: React.FormEvent) => { e.preventDefault(); setProfileMsg(null); setSavingProfile(true); try { const patch: any = { displayName: displayName.trim(), email: email.trim(), }; if (newPassword.trim()) { patch.password = newPassword.trim(); } const updated = await api.updateSettings(patch); onUserUpdated(updated); setNewPassword(''); setProfileMsg({ type: 'ok', text: 'Profil und Anmeldedaten erfolgreich gespeichert.' }); } catch (err: any) { setProfileMsg({ type: 'err', text: err.message || 'Fehler beim Speichern' }); } finally { setSavingProfile(false); } }; // --- Handlers: Alarms --- const handleSaveAlarms = async (e: React.FormEvent) => { e.preventDefault(); setAlarmMsg(null); setSavingAlarms(true); try { const updated = await api.updateSettings({ alarmChannels, alarmEmail: alarmEmail.trim(), discordWebhookUrl: discordWebhookUrl.trim(), customWebhookUrl: customWebhookUrl.trim(), notifyBeforeMinutes: Number(notifyBeforeMinutes), }); onUserUpdated(updated); setAlarmMsg({ type: 'ok', text: 'Alarm-Kanäle und Vorwarnzeit erfolgreich gespeichert.' }); } catch (err: any) { setAlarmMsg({ type: 'err', text: err.message || 'Fehler beim Speichern der Alarme' }); } finally { setSavingAlarms(false); } }; const handleTestChannel = async (channel: 'email' | 'discord' | 'webhook') => { setAlarmMsg(null); let target = ''; if (channel === 'email') { target = (alarmEmail || currentUser.email || '').trim(); if (!target) { setAlarmMsg({ type: 'err', text: 'Bitte gib zuerst eine Empfänger-E-Mail-Adresse an.' }); return; } } else if (channel === 'discord') { target = discordWebhookUrl.trim(); if (!target) { setAlarmMsg({ type: 'err', text: 'Bitte gib zuerst eine Discord Webhook URL ein.' }); return; } } else if (channel === 'webhook') { target = customWebhookUrl.trim(); if (!target) { setAlarmMsg({ type: 'err', text: 'Bitte gib zuerst eine eigene Webhook URL ein.' }); return; } } setTestingChannel(channel); try { await api.testAlarmChannel(channel, target); const label = channel === 'email' ? 'E-Mail' : channel === 'discord' ? 'Discord' : 'Eigener Webhook'; setAlarmMsg({ type: 'ok', text: `Test-Alarm über "${label}" wurde erfolgreich vom Server versendet!`, }); } catch (err: any) { setAlarmMsg({ type: 'err', text: `Test fehlgeschlagen: ${err.message || 'Unbekannter Fehler'}`, }); } finally { setTestingChannel(null); } }; // --- Handlers: Webhooks & Export --- const handleCreateAPIKey = async (e: React.FormEvent) => { e.preventDefault(); triggerHaptic(15); try { const created = await api.createAPIKey(newKeyName.trim() || 'Eigenes Skript / Webhook'); if (created.rawKey) { setNewlyCreatedKey(created.rawKey); } setNewKeyName(''); loadKeys(); } catch (err: any) { alert(err.message); } }; const handleDeleteAPIKey = async (id: string) => { if (!window.confirm('Diesen API-Schlüssel wirklich widerrufen?')) return; await api.deleteAPIKey(id); loadKeys(); }; const handleCopy = (text: string, label: string) => { navigator.clipboard.writeText(text); setCopiedLabel(label); setTimeout(() => setCopiedLabel(null), 2000); }; const handleExportData = async () => { const data = await api.exportAll(); const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json;charset=utf-8', }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = `mindsift-backup-${new Date().toISOString().slice(0, 10)}.json`; a.click(); URL.revokeObjectURL(url); }; const baseUrl = window.location.origin; const activeKeyPlaceholder = newlyCreatedKey || (apiKeys[0]?.keyPrefix ? apiKeys[0].keyPrefix + '••••••••' : 'DEIN_API_KEY'); const curlTaskExample = `curl -X POST ${baseUrl}/api/v1/inbox \\ -H "X-API-Key: ${activeKeyPlaceholder}" \\ -H "Content-Type: application/json" \\ -d '{"content": "Server-Alarm prüfen #monitoring", "priority": "urgent", "target": "task"}'`; const roleBadgeLabel = currentUser.role === 'admin' ? 'Server-Admin' : currentUser.role === 'team_admin' ? 'Team-Admin' : 'Mitglied'; return (
{/* Top Page Header */}

Kontoeinstellungen

Persönliches Profil, Design-Themes, Aufgaben-Alarme, Private Webhooks & Daten-Backup

Rolle: {roleBadgeLabel}
{/* Main 2-Column Layout */}
{/* Left Navigation Sidebar */} {/* Right Content Area */}
{/* ================================================================= */} {/* SECTION 1: PROFIL & SICHERHEIT */} {/* ================================================================= */} {section === 'profile' && (

Profil & Anmeldedaten

Verwalte deinen Anzeigenamen, deine E-Mail-Adresse und dein persönliches Passwort.

{profileMsg && (
{profileMsg.type === 'ok' ? ( ) : ( )} {profileMsg.text}
)}
setDisplayName(e.target.value)} required className="w-full h-9 px-3 input-field" />
setEmail(e.target.value)} required className="w-full h-9 px-3 input-field" />
setNewPassword(e.target.value)} placeholder="Mindestens 4 Zeichen" className="w-full h-9 px-3 input-field" />
)} {/* ================================================================= */} {/* SECTION 2: DARSTELLUNG & THEMES (SINGLE-FILE CSS THEMING) */} {/* ================================================================= */} {section === 'appearance' && (

Darstellung & Single-File CSS Themes

Jedes Theme ist eine einzige, eigenständige CSS-Datei, in der alle Farben, Oberflächen, Kontraste und Ecken-Rundungen in 6 festen Gruppen gebündelt sind.

{AVAILABLE_THEMES.map((t) => { const isSelected = activeTheme === t.id; return ( ); })}

Eigenes Theme anpassen oder hinzufügen:

Alle Theme-Dateien liegen unter{' '} frontend/src/themes/ (inklusive der Kopiervorlage{' '} theme-template.css). Dort sind alle Eigenschaften in 6 Blöcken gruppiert: 1. Akzentfarbe,{' '} 2. Oberflächen, 3. Textfarben,{' '} 4. Rahmen & Schatten, 5. Statusfarben und{' '} 6. Rundungen.

)} {/* ================================================================= */} {/* SECTION 3: ALARME (MULTI-CHANNEL: EMAIL, DISCORD, CUSTOM WEBHOOK) */} {/* ================================================================= */} {section === 'alarms' && (

Aufgaben-Alarme & Benachrichtigungsziele

Wähle aus, über welche Kanäle du benachrichtigt werden möchtest, sobald eine Aufgabe mit Fälligkeitsdatum ansteht. Mehrere Ziele können gleichzeitig aktiv sein.

{alarmMsg && (
{alarmMsg.type === 'ok' ? ( ) : ( )} {alarmMsg.text}
)}
{/* Lead Time Selector */}

Wann soll MindSift den Alarm vor dem Fälligkeitszeitpunkt auslösen?

{/* Channel 1: E-Mail */}
setAlarmEmail(e.target.value)} placeholder={currentUser.email} className="w-full h-8 px-2.5 input-field" />
{/* Channel 2: Discord Webhook */}
setDiscordWebhookUrl(e.target.value)} placeholder="https://discord.com/api/webhooks/123456789/token..." className="w-full h-8 px-2.5 input-field font-mono" />
{/* Channel 3: Custom HTTP Webhook */}
setCustomWebhookUrl(e.target.value)} placeholder="https://dein-server.local/webhook/mindsift-alarm" className="w-full h-8 px-2.5 input-field font-mono" />
)} {/* ================================================================= */} {/* SECTION 4: PRIVATE WEBHOOKS & EXPORT */} {/* ================================================================= */} {section === 'webhooks' && (

Private Webhooks (Eingehend) & Daten-Export

Erstelle persönliche API-Schlüssel, um aus externen Skripten, Server-Monitoring oder Discord-Bots automatisch Aufgaben und Gedanken in dein MindSift einzuspeisen.

{/* Create API Key */}
setNewKeyName(e.target.value)} placeholder="Bezeichnung des Schlüssels (z.B. Proxmox Monitoring, Discord-Forwarder)…" className="flex-1 h-9 px-3 input-field" />
{newlyCreatedKey && (

Neuer API-Schlüssel erstellt (bitte jetzt kopieren, wird nur einmal im Klartext angezeigt):

{newlyCreatedKey}
)} {/* Existing Keys List */}
{apiKeys.length === 0 ? (
Noch keine persönlichen API-Schlüssel vorhanden.
) : ( apiKeys.map((k) => (

{k.name}

Präfix: {k.keyPrefix}•••••••• · Erstellt:{' '} {new Date(k.createdAt).toLocaleDateString()}

)) )}
{/* Curl Example */}
Curl-Beispiel: Direkt als Aufgabe einspeisen
                  {curlTaskExample}
                
{/* Full Data Backup */}

Vollständiger Daten-Export (JSON Backup)

Lädt alle deine Gedanken, Aufgaben, Markdown-Dokumente und Listen als portable JSON-Datei herunter.

)}
); };