import { useEffect, useRef, useState } from "react"; import type { FormEvent } from "react"; import { AlertTriangle, ArrowRight, LockKeyhole } from "lucide-react"; import { ApiError } from "../api/client"; import { loginLocal } from "../api/auth"; import type { AuthenticatedUser, AuthPublicConfig } from "../api/types"; import { Button } from "../components/ui/button"; interface LoginPageProps { config: AuthPublicConfig; onAuthenticated: (user: AuthenticatedUser) => void; onRetry?: () => void; } function loginError(error: unknown): { message: string; retry: boolean } { if (error instanceof ApiError && error.status === 503) { return { message: "Authentication is temporarily unavailable. Try again.", retry: true }; } if (error instanceof ApiError && error.status === 403) { return { message: "This sign-in request was rejected. Open ThothII from its configured address and try again.", retry: false }; } return { message: "Invalid username or password.", retry: false }; } export function LoginPage({ config, onAuthenticated, onRetry }: LoginPageProps) { const localLogin = config.mode === "local" && config.localLogin; const formRef = useRef(null); const passwordRef = useRef(null); const mountedRef = useRef(true); const submittingRef = useRef(false); const attemptRef = useRef(0); const [submitting, setSubmitting] = useState(false); const [error, setError] = useState<{ message: string; retry: boolean }>(); useEffect(() => { mountedRef.current = true; return () => { mountedRef.current = false; }; }, []); async function submit(event: FormEvent) { event.preventDefault(); if (submittingRef.current) return; const form = event.currentTarget; const values = new FormData(form); const username = String(values.get("username") ?? ""); const password = String(values.get("password") ?? ""); const remember = values.get("remember") === "on"; const attempt = ++attemptRef.current; submittingRef.current = true; setError(undefined); setSubmitting(true); try { const user = await loginLocal(username, password, remember); if (mountedRef.current && attemptRef.current === attempt) onAuthenticated(user); } catch (failure) { if (mountedRef.current && attemptRef.current === attempt) setError(loginError(failure)); } finally { submittingRef.current = false; if (!mountedRef.current || attemptRef.current !== attempt) return; // Passwords never enter React state and are cleared after every attempt, // including operational failures and successful authentication. form.reset(); setSubmitting(false); passwordRef.current?.focus(); } } return (

Secure re-entry · governed workspace

Return to the analytical ledger.

ThothII keeps each question, review decision, and SQL artifact inside a traceable human-in-the-loop workflow.

Sign in to ThothII

Use your installation account to continue.

{error && (

{error.message}

{error.retry && onRetry && ( )}
)} {localLogin && (
)} {config.oidcLogin && ( Continue with single sign-on )} {!localLogin && !config.oidcLogin && (

No browser sign-in method is enabled for this installation.

)}
); }