import type { FormEvent } from "react"; import { useEffect, useState } from "react"; import { api, errorMessage } from "../api"; import { AppBackground } from "../components/AppBackground"; import { Alert } from "../components/ui"; import { ThemeSwitch } from "../theme"; import type { AdminSession, PublicBranding } from "../types"; export function LoginPage({ onLogin }: { onLogin: (session: AdminSession) => void }) { // Deliberately not pre-filled: the built-in operator is a default name, not a // default identity, and typing it is the difference between choosing to use // it and drifting into it. The server rejects a blank username either way. const [username, setUsername] = useState(""); const [secret, setSecret] = useState(""); const [error, setError] = useState(""); const [busy, setBusy] = useState(false); // Whose server this is. Fetched without a session -- the name and icon are // already public from owpengram-server's client endpoints -- and left null on // failure so the panel simply keeps its own branding. const [branding, setBranding] = useState(null); const [iconFailed, setIconFailed] = useState(false); useEffect(() => { api.publicBranding().then(setBranding).catch(() => undefined); }, []); const serverName = branding?.name?.trim() || "OwpenGram"; const iconSrc = branding?.has_icon && !iconFailed ? api.publicIconURL() : "/logo.png"; async function submit(event: FormEvent) { event.preventDefault(); setBusy(true); setError(""); try { // The login answer carries the permission set and the CSRF token; api.login // remembers the token, the session state keeps the rights. const result = await api.login(secret, username); onLogin({ actor: result.actor, permissions: result.permissions ?? [] }); } catch (err) { setError(errorMessage(err)); } finally { setBusy(false); } } return (
{serverName} setIconFailed(true)} /> {serverName} {"Admin Console"}

{"Operations Admin"}

{"Enter credentials to open the console."}

{error && {error}}
); }