import React, { useEffect, useState } from 'react'; import { Brain, Shield, Lock, KeyRound, UserPlus, LogIn, Server, Users, Check, AlertCircle, } from 'lucide-react'; import { AuthStatus, SharingMode, User } from '../../lib/types'; import { api, triggerHaptic } from '../../lib/api'; import { ToggleSwitch } from '../../components/ToggleSwitch'; import { LegalLinks } from '../../components/LegalLinks'; import { passwordProblem } from '../../lib/password'; interface SetupAndAuthViewProps { authStatus: AuthStatus; onAuthenticated: (user: User) => void; resetToken?: string | null; onResetDone?: () => void; onResetCancel?: () => void; } export const SetupAndAuthView: React.FC = ({ authStatus, onAuthenticated, resetToken, onResetDone, onResetCancel, }) => { // --- Setup Wizard State --- const [setupName, setSetupName] = useState(''); const [setupEmail, setSetupEmail] = useState(''); const [setupPassword, setSetupPassword] = useState(''); const [setupPasswordConfirm, setSetupPasswordConfirm] = useState(''); const [instanceName, setInstanceName] = useState(authStatus.instanceName || 'MindSift'); const [sharingMode, setSharingMode] = useState('organizations'); const [allowRegistration, setAllowRegistration] = useState(false); const [createSampleData, setCreateSampleData] = useState(false); // --- Login / Invite / Register State --- // An invite link (?invite=CODE) opens the invite form with the code filled in const [linkInviteCode] = useState(() => new URLSearchParams(window.location.search).get('invite')?.trim().toUpperCase() || ''); const [authTab, setAuthTab] = useState<'login' | 'invite' | 'register' | 'forgot' | 'reset'>( resetToken ? 'reset' : linkInviteCode ? 'invite' : 'login' ); const [email, setEmail] = useState(''); const [password, setPassword] = useState(''); const [displayName, setDisplayName] = useState(''); const [inviteCode, setInviteCode] = useState(linkInviteCode); // Link from the confirmation mail (?verifyToken=…): confirm right away and say what happens next useEffect(() => { const url = new URL(window.location.href); const token = url.searchParams.get('verifyToken'); if (!token) return; url.searchParams.delete('verifyToken'); window.history.replaceState({}, '', url.pathname + url.search + url.hash); setAuthTab('login'); api .verifyEmail(token) .then((res) => setNotice(res.message)) .catch((err) => setError(err.message || 'Der Bestätigungslink ist ungültig oder abgelaufen.')); }, []); // Login refused because the address is not confirmed: offer to send the link again const [resendState, setResendState] = useState<'idle' | 'sending' | 'sent'>('idle'); const resendVerification = async () => { setResendState('sending'); try { await api.resendVerification(email.trim()); setResendState('sent'); setError(null); setNotice('Falls die Adresse noch auf Bestätigung wartet, haben wir dir den Link erneut geschickt.'); } catch (err: any) { setResendState('idle'); setError(err.message || 'Senden fehlgeschlagen'); } }; // The code is in the form now; it should not stay in the address bar (history, screenshots) useEffect(() => { if (!linkInviteCode) return; const url = new URL(window.location.href); url.searchParams.delete('invite'); window.history.replaceState({}, '', url.pathname + url.search + url.hash); }, [linkInviteCode]); // Hint on the sign-up form: which addresses get in, and whether an admin approves first const registrationHint = (() => { const domains = (authStatus.registrationDomains || []).map((d) => '@' + d.replace(/^\*\./, '')).join(', '); switch (authStatus.registrationMode) { case 'approval': return 'Nach der Registrierung gibt ein Administrator dein Konto frei.'; case 'open': return authStatus.emailVerification ? 'Du bekommst eine E-Mail, um deine Adresse zu bestätigen.' : ''; case 'domain': return domains ? `Registrierung mit E-Mail-Adressen von ${domains}.` : ''; case 'domain+approval': return domains ? `Mit Adressen von ${domains} geht es sofort, andere Konten gibt ein Administrator frei.` : 'Nach der Registrierung gibt ein Administrator dein Konto frei.'; } return ''; })(); const registrationHintFull = registrationHint && authStatus.emailVerification && authStatus.registrationMode !== 'open' ? `${registrationHint} Vorher bestätigst du deine Adresse per E-Mail.` : registrationHint; const [submitting, setSubmitting] = useState(false); const [error, setError] = useState(null); const [notice, setNotice] = useState(null); const [newPassword, setNewPassword] = useState(''); const [newPasswordConfirm, setNewPasswordConfirm] = useState(''); const handleForgotPassword = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSubmitting(true); try { await api.requestPasswordReset(email.trim()); setNotice('Falls ein Konto mit dieser E-Mail-Adresse existiert, haben wir dir einen Link zum Zurücksetzen geschickt. Er gilt eine Stunde.'); setAuthTab('login'); } catch (err: any) { setError(err.message || 'Anfrage fehlgeschlagen'); } finally { setSubmitting(false); } }; const handleResetPassword = async (e: React.FormEvent) => { e.preventDefault(); setError(null); const resetProblem = passwordProblem(newPassword); if (resetProblem) { setError(resetProblem); triggerHaptic(25); return; } if (newPassword !== newPasswordConfirm) { setError('Die eingegebenen Passwörter stimmen nicht überein.'); triggerHaptic(25); return; } setSubmitting(true); try { await api.resetPassword(resetToken || '', newPassword); setNewPassword(''); setNewPasswordConfirm(''); setPassword(''); setNotice('Dein Passwort wurde geändert. Melde dich jetzt mit dem neuen Passwort an.'); setAuthTab('login'); onResetDone?.(); } catch (err: any) { setError(err.message || 'Passwort konnte nicht geändert werden'); } finally { setSubmitting(false); } }; const handleCompleteSetup = async (e: React.FormEvent) => { e.preventDefault(); setError(null); const setupProblem = passwordProblem(setupPassword); if (setupProblem) { setError(setupProblem); triggerHaptic(25); return; } if (setupPassword !== setupPasswordConfirm) { setError('Die eingegebenen Passwörter stimmen nicht überein.'); triggerHaptic(25); return; } setSubmitting(true); triggerHaptic(15); try { const res = await api.completeSetup({ email: setupEmail.trim(), password: setupPassword, displayName: setupName.trim(), instanceName: instanceName.trim() || 'MindSift', sharingMode, allowRegistration, createSampleData, }); onAuthenticated(res.user); } catch (err: any) { setError(err.message || 'Fehler bei der Ersteinrichtung'); } finally { setSubmitting(false); } }; const handleLogin = async (e: React.FormEvent) => { e.preventDefault(); setError(null); setSubmitting(true); triggerHaptic(15); try { const res = await api.login(email.trim(), password); onAuthenticated(res.user); } catch (err: any) { setError(err.message || 'Ungültige Anmeldedaten'); } finally { setSubmitting(false); } }; const handleRegisterOrInvite = async (e: React.FormEvent) => { e.preventDefault(); setError(null); const registerProblem = passwordProblem(password); if (registerProblem) { setError(registerProblem); triggerHaptic(25); return; } setSubmitting(true); triggerHaptic(15); try { const code = authTab === 'invite' ? inviteCode.trim() : ''; const res = await api.register(email.trim(), password, displayName.trim(), code); if (res.pending || !res.user) { // Waiting for approval: back to the login form with the explanation setNotice(res.message || 'Dein Konto wartet auf die Freigabe durch einen Administrator.'); setPassword(''); setAuthTab('login'); return; } onAuthenticated(res.user); } catch (err: any) { setError(err.message || 'Registrierung fehlgeschlagen'); } finally { setSubmitting(false); } }; // ============================================================================ // FIRST-BOOT SERVER SETUP WIZARD // ============================================================================ if (authStatus.needsSetup) { return (
{/* Top Brand Bar */}

MindSift Ersteinrichtung

Initiales Administrator-Konto & Server-Konfiguration

First Boot

Willkommen auf deiner neuen MindSift-Instanz. Es existiert noch kein Benutzerkonto in der Datenbank. Lege jetzt das erste Server-Admin-Konto an und bestimme die Sicherheitseinstellungen.

{error && (
{error}
)} {/* Section 1: Admin Account */}

1. Server-Administrator anlegen

setSetupName(e.target.value)} placeholder="z.B. Admin / Dein Name" required autoFocus className="w-full h-8 px-2.5 input-field" />
setSetupEmail(e.target.value)} placeholder="admin@deinedomain.de" required className="w-full h-8 px-2.5 input-field" />
setSetupPassword(e.target.value)} placeholder="Mindestens 8 Zeichen" required className="w-full h-8 px-2.5 input-field" />
setSetupPasswordConfirm(e.target.value)} placeholder="Passwort erneut eingeben" required className={`w-full h-8 px-2.5 input-field ${ setupPasswordConfirm && setupPassword !== setupPasswordConfirm ? 'border-status-err focus:border-status-err' : '' }`} /> {setupPasswordConfirm && setupPassword !== setupPasswordConfirm && ( Passwörter stimmen nicht überein )}
{/* Section 2: Server & Isolation */}

2. Instanz & Berechtigungen

setInstanceName(e.target.value)} placeholder="MindSift" className="w-full h-8 px-2.5 input-field" />
Öffentliche Selbstregistrierung erlauben Wenn deaktiviert, können neue Nutzer nur per Einladungscode oder durch einen Admin angelegt werden.
Beispieldaten anlegen Füllt dein Konto mit Beispiel-Gedanken, Notizen, Aufgabenlisten und Einkaufslisten zum Ausprobieren. Es werden keine weiteren Benutzer angelegt.
); } // ============================================================================ // STANDARD LOGIN / INVITE CODE / REGISTRATION VIEW // ============================================================================ return (
{/* Top Header */}

{authStatus.instanceName || 'MindSift'}

{/* Navigation Tabs (not while choosing a new password) */}
{authStatus.allowRegistration && ( )}
{/* Form Body */}
{error && (
{error} {/bestätige zuerst deine E-Mail/i.test(error) && email.trim() && ( )}
)} {notice && !error && (
{notice}
)} {authTab === 'forgot' && (

Passwort vergessen?

Gib die E-Mail-Adresse deines Kontos ein. Wir schicken dir einen Link, mit dem du ein neues Passwort festlegst.

setEmail(e.target.value)} placeholder="name@beispiel.de" required autoFocus autoComplete="email" className="w-full h-11 sm:h-8 px-2.5 input-field text-base sm:text-xs" />
)} {authTab === 'reset' && (

Neues Passwort festlegen

Danach bist du überall abgemeldet und meldest dich mit dem neuen Passwort an.

setNewPassword(e.target.value)} placeholder="Mindestens 8 Zeichen" required autoFocus autoComplete="new-password" className="w-full h-11 sm:h-8 px-2.5 input-field text-base sm:text-xs" />
setNewPasswordConfirm(e.target.value)} required autoComplete="new-password" className="w-full h-11 sm:h-8 px-2.5 input-field text-base sm:text-xs" />
)} {authTab === 'login' && (
{authStatus.demoMode && (
🧪 Demo-Modus aktiv Klick zum Ausfüllen:
)}
setEmail(e.target.value)} placeholder="admin oder name@beispiel.de" required autoFocus className="w-full h-8 px-2.5 input-field" />
{authStatus.passwordReset && ( )}
setPassword(e.target.value)} placeholder="••••••••" required className="w-full h-8 px-2.5 input-field" />
{!authStatus.allowRegistration && (

Öffentliche Registrierung ist deaktiviert. Zugang nur mit Konto oder{' '} .

)}
)} {(authTab === 'invite' || authTab === 'register') && (
{authTab === 'register' && registrationHintFull && (

{registrationHintFull}

)} {authTab === 'invite' && (
setInviteCode(e.target.value.toUpperCase())} placeholder="MS-XXXXXXXX" required autoFocus className="w-full h-8 px-2.5 input-field font-mono uppercase" />
)}
setDisplayName(e.target.value)} placeholder="Dein Name" required className="w-full h-8 px-2.5 input-field" />
setEmail(e.target.value)} placeholder="name@beispiel.de" required className="w-full h-8 px-2.5 input-field" />
setPassword(e.target.value)} placeholder="Mindestens 8 Zeichen" required className="w-full h-8 px-2.5 input-field" />
)}
); };