import React, { useEffect, useState } from 'react'; import { Shield, Users, Server, Mail, Plus, Trash2, KeyRound, Copy, Check, ArrowLeft, Send, AlertCircle, UserPlus, } from 'lucide-react'; import { SystemSettings, User, UserInvite, UserRole, } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; interface AdminSettingsViewProps { currentUser: User; onBack: () => void; } type AdminSection = 'users' | 'server' | 'smtp'; export const AdminSettingsView: React.FC = ({ currentUser, onBack, }) => { const isServerAdmin = currentUser.role === 'admin'; const [section, setSection] = useState('users'); // --- Users & Invites State --- const [users, setUsers] = useState([]); const [invites, setInvites] = useState([]); const [newUserName, setNewUserName] = useState(''); const [newUserEmail, setNewUserEmail] = useState(''); const [newUserPassword, setNewUserPassword] = useState(''); const [newUserRole, setNewUserRole] = useState('member'); const [inviteEmail, setInviteEmail] = useState(''); const [inviteRole, setInviteRole] = useState('member'); const [copiedInviteId, setCopiedInviteId] = useState(null); const [resetPwdUserId, setResetPwdUserId] = useState(null); const [resetPwdVal, setResetPwdVal] = useState(''); // --- Server & SMTP Settings State --- const [settings, setSettings] = useState({ sharingMode: 'teams_only', allowRegistration: false, instanceName: 'MindSift', smtpHost: '', smtpPort: 587, smtpUser: '', smtpPass: '', smtpFrom: '', smtpTls: true, smtpConfiguredViaEnv: false, }); const [testSmtpEmail, setTestSmtpEmail] = useState(currentUser.email || ''); const [testingSmtp, setTestingSmtp] = useState(false); const [statusMsg, setStatusMsg] = useState<{ type: 'ok' | 'err'; text: string } | null>(null); const loadAll = async () => { try { const [uList, invList] = await Promise.all([ api.adminGetUsers(), api.adminGetInvites(), ]); setUsers(uList); setInvites(invList); if (isServerAdmin) { const sys = await api.adminGetSettings(); setSettings({ sharingMode: sys.sharingMode || 'teams_only', allowRegistration: Boolean(sys.allowRegistration), instanceName: sys.instanceName || 'MindSift', smtpHost: sys.smtpHost || '', smtpPort: sys.smtpPort || 587, smtpUser: sys.smtpUser || '', smtpPass: sys.smtpPass || '', smtpFrom: sys.smtpFrom || '', smtpTls: sys.smtpTls ?? true, smtpConfiguredViaEnv: Boolean(sys.smtpConfiguredViaEnv), }); } } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Laden der Administrationsdaten' }); } }; useEffect(() => { loadAll(); }, []); // ============================================================================ // HANDLERS: USERS & INVITES // ============================================================================ const handleCreateUser = async (e: React.FormEvent) => { e.preventDefault(); setStatusMsg(null); triggerHaptic(15); try { const created = await api.adminCreateUser({ email: newUserEmail.trim(), password: newUserPassword, displayName: newUserName.trim() || newUserEmail.split('@')[0], role: newUserRole, }); setUsers((prev) => [...prev, created]); setNewUserName(''); setNewUserEmail(''); setNewUserPassword(''); setNewUserRole('member'); setStatusMsg({ type: 'ok', text: `Benutzer "${created.displayName}" wurde angelegt.` }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Anlegen des Benutzers' }); } }; const handleCreateInvite = async (e: React.FormEvent) => { e.preventDefault(); setStatusMsg(null); triggerHaptic(15); try { const inv = await api.adminCreateInvite({ email: inviteEmail.trim(), role: inviteRole, }); setInvites((prev) => [inv, ...prev]); setInviteEmail(''); setStatusMsg({ type: 'ok', text: `Einladungscode ${inv.code} erfolgreich erstellt.`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Erstellen der Einladung' }); } }; const handleDeleteInvite = async (id: string) => { try { await api.adminDeleteInvite(id); setInvites((prev) => prev.filter((i) => i.id !== id)); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleCopyInviteCode = (inv: UserInvite) => { navigator.clipboard.writeText(inv.code); setCopiedInviteId(inv.id); setTimeout(() => setCopiedInviteId(null), 2000); }; const handleChangeUserRole = async (u: User, role: UserRole) => { setStatusMsg(null); try { const updated = await api.adminUpdateUser(u.id, { role }); setUsers((prev) => prev.map((item) => (item.id === u.id ? updated : item))); setStatusMsg({ type: 'ok', text: `Rolle für ${updated.displayName} auf "${role}" aktualisiert.`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleToggleUserActive = async (u: User) => { setStatusMsg(null); try { const updated = await api.adminUpdateUser(u.id, { isActive: !u.isActive }); setUsers((prev) => prev.map((item) => (item.id === u.id ? updated : item))); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleConfirmResetPassword = async (userId: string) => { if (!resetPwdVal.trim()) return; setStatusMsg(null); try { await api.adminResetPassword(userId, resetPwdVal.trim()); setResetPwdUserId(null); setResetPwdVal(''); setStatusMsg({ type: 'ok', text: 'Passwort erfolgreich zurückgesetzt.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; const handleDeleteUser = async (u: User) => { if ( !window.confirm( `Benutzer "${u.displayName}" (${u.email}) wirklich unwiderruflich löschen?` ) ) return; setStatusMsg(null); try { await api.adminDeleteUser(u.id); setUsers((prev) => prev.filter((item) => item.id !== u.id)); setStatusMsg({ type: 'ok', text: 'Benutzer gelöscht.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message }); } }; // ============================================================================ // HANDLERS: SERVER & SMTP SETTINGS // ============================================================================ const handleSaveSystemSettings = async (patch?: Partial) => { setStatusMsg(null); const next = { ...settings, ...(patch || {}) }; setSettings(next); try { const saved = await api.adminUpdateSettings(next); setSettings((prev) => ({ ...prev, ...saved })); setStatusMsg({ type: 'ok', text: 'Server-Einstellungen erfolgreich gespeichert.' }); } catch (err: any) { setStatusMsg({ type: 'err', text: err.message || 'Fehler beim Speichern' }); } }; const handleTestSmtp = async () => { if (!testSmtpEmail.trim()) return; setTestingSmtp(true); setStatusMsg(null); try { await api.adminUpdateSettings(settings); await api.adminTestSMTP(testSmtpEmail.trim()); setStatusMsg({ type: 'ok', text: `Test-E-Mail erfolgreich an ${testSmtpEmail.trim()} gesendet!`, }); } catch (err: any) { setStatusMsg({ type: 'err', text: `SMTP-Test fehlgeschlagen: ${err.message}`, }); } finally { setTestingSmtp(false); } }; return (
{/* Top Page Header */}

Server-Administration

Benutzerverwaltung, Einladungscodes, Isolationsmodus & Mail-Server

Server-Administrator
{statusMsg && (
{statusMsg.type === 'ok' ? ( ) : ( )} {statusMsg.text}
)} {/* Main 2-Column Layout */}
{/* Left Navigation Sidebar */} {/* Right Content Area */}
{/* ================================================================= */} {/* SECTION 1: BENUTZER, ROLLEN & EINLADUNGEN */} {/* ================================================================= */} {section === 'users' && (

Benutzersteuerung, Rollen & Einladungen

Lege Benutzerkonten direkt an oder erzeuge Einladungscodes, mit denen sich eingeladene Personen selbst registrieren können, auch wenn die öffentliche Registrierung gesperrt ist.

{/* Direct User Creation Form */}

Neues Benutzerkonto direkt anlegen

setNewUserName(e.target.value)} placeholder="z.B. Anna Beispiel" required className="w-full min-w-0 h-8 px-2.5 input-field" />
setNewUserEmail(e.target.value)} placeholder="anna@beispiel.de" required className="w-full min-w-0 h-8 px-2.5 input-field" />
setNewUserPassword(e.target.value)} placeholder="Mind. 4 Zeichen" minLength={4} required className="w-full min-w-0 h-8 px-2.5 input-field" />
{/* Invite Code Generator */}

Einladungscode erzeugen (gültig für 7 Tage)

setInviteEmail(e.target.value)} placeholder="Leer lassen für beliebige E-Mail" className="w-full min-w-0 h-8 px-2.5 input-field" />
{/* Active Invites List */} {invites.length > 0 && (

Aktive & bisherige Einladungscodes:

{invites.map((inv) => (
{inv.code} Rolle: {inv.role} {inv.email ? ` · Für: ${inv.email}` : ''} {inv.usedBy ? ( Eingelöst ) : ( Offen )}
{!inv.usedBy && ( )}
))}
)}
{/* Users List */}

Registrierte Benutzer ({users.length})

{users.map((u) => (
{u.displayName} {!u.isActive && ( Gesperrt )}

{u.email}

{/* Role selector */} {isServerAdmin && ( <> {u.id !== currentUser.id && ( <> )} )}
{resetPwdUserId === u.id && (
setResetPwdVal(e.target.value)} placeholder="Neues Passwort eingeben…" className="flex-1 h-7 px-2 input-field" />
)}
))}
)} {/* ================================================================= */} {/* SECTION 3: SERVER & SHARING-ISOLATION (SERVER-ADMIN ONLY) */} {/* ================================================================= */} {section === 'server' && isServerAdmin && (

Server-Grundeinstellungen & Benutzer-Sichtbarkeit

Als Server-Admin bestimmst du zentral für alle Nutzer, ob sie komplett isoliert arbeiten, sich nur innerhalb ihrer Teams sehen oder ob alle Nutzer für alle sichtbar sind.

setSettings((prev) => ({ ...prev, instanceName: e.target.value })) } className="flex-1 h-9 px-3 input-field" />
{/* Sharing Isolation Cards */}
{/* Public Registration Toggle Switch */}

Öffentliche Selbstregistrierung erlauben

Wenn deaktiviert, können sich fremde Personen auf dem Login-Bildschirm nicht selbst registrieren. Neue Konten erfordern dann zwingend einen Einladungscode oder müssen von einem Admin angelegt werden.

)} {/* ================================================================= */} {/* SECTION 4: E-MAIL / SMTP-SERVER (SERVER-ADMIN ONLY) */} {/* ================================================================= */} {section === 'smtp' && isServerAdmin && (

E-Mail / SMTP-Server für Aufgaben-Alarme

Konfiguriere den Postausgangsserver per Docker-Compose Environment-Variablen oder direkt hier über die Oberfläche (wird persistent in der SQLite-Datenbank gespeichert).

{settings.smtpConfiguredViaEnv && ( Docker Env erkannt )}
setSettings((prev) => ({ ...prev, smtpHost: e.target.value })) } placeholder="z.B. smtp.example.com oder mail.deinedomain.de" className="w-full h-9 px-3 input-field font-mono" />
setSettings((prev) => ({ ...prev, smtpPort: Number(e.target.value) })) } placeholder="587" className="w-full h-9 px-3 input-field font-mono" />
setSettings((prev) => ({ ...prev, smtpUser: e.target.value })) } placeholder="alerts@deinedomain.de" className="w-full h-9 px-3 input-field font-mono" />
setSettings((prev) => ({ ...prev, smtpPass: e.target.value })) } placeholder="••••••••••••" className="w-full h-9 px-3 input-field font-mono" />
setSettings((prev) => ({ ...prev, smtpFrom: e.target.value })) } placeholder="MindSift " className="w-full h-9 px-3 input-field" />
{/* Test SMTP Delivery */}

SMTP-Verbindung testen

Sendet sofort eine Test-E-Mail mit den oben eingetragenen Daten.

setTestSmtpEmail(e.target.value)} placeholder="empfaenger@beispiel.de" className="flex-1 h-8 px-3 input-field" />
)}
); };