import { useEffect, useState, type FormEvent } from "react"; import { useAuth } from "../hooks/useAuth"; import { get } from "../api/client"; import type { AuthConfig } from "../api/types"; export function LoginPage() { const { login } = useAuth(); const [username, setUsername] = useState(""); const [password, setPassword] = useState(""); const [error, setError] = useState(""); const [submitting, setSubmitting] = useState(false); const [oidcEnabled, setOidcEnabled] = useState(false); useEffect(() => { get("/api/auth/config") .then((cfg) => setOidcEnabled(cfg.oidc_enabled)) .catch(() => {}); }, []); const handleSubmit = async (e: FormEvent) => { e.preventDefault(); if (!username.trim() || !password) return; setError(""); setSubmitting(true); try { await login(username.trim(), password); } catch (err) { setError(err instanceof Error ? err.message : "Login failed"); } finally { setSubmitting(false); } }; return (

Silo

Product Lifecycle Management

{error &&
{error}
}
setUsername(e.target.value)} placeholder="Username or LDAP uid" autoFocus required style={inputStyle} />
setPassword(e.target.value)} placeholder="Password" required style={inputStyle} />
{oidcEnabled && ( <>
or
Sign in with Keycloak )}
); } const containerStyle: React.CSSProperties = { display: "flex", justifyContent: "center", alignItems: "center", minHeight: "100vh", backgroundColor: "var(--ctp-base)", }; const cardStyle: React.CSSProperties = { backgroundColor: "var(--ctp-surface0)", borderRadius: "1rem", padding: "2.5rem", width: "100%", maxWidth: 400, margin: "1rem", }; const titleStyle: React.CSSProperties = { color: "var(--ctp-mauve)", textAlign: "center", fontSize: "2rem", fontWeight: 600, marginBottom: "0.25rem", }; const subtitleStyle: React.CSSProperties = { color: "var(--ctp-subtext0)", textAlign: "center", fontSize: "var(--font-body)", marginBottom: "2rem", }; const errorStyle: React.CSSProperties = { color: "var(--ctp-red)", background: "rgba(243, 139, 168, 0.1)", border: "1px solid rgba(243, 139, 168, 0.2)", padding: "0.75rem 1rem", borderRadius: "0.5rem", marginBottom: "1rem", fontSize: "var(--font-body)", }; const formGroupStyle: React.CSSProperties = { marginBottom: "1.25rem", }; const labelStyle: React.CSSProperties = { display: "block", marginBottom: "0.5rem", fontWeight: 500, color: "var(--ctp-subtext1)", fontSize: "var(--font-body)", }; const inputStyle: React.CSSProperties = { width: "100%", padding: "0.75rem 1rem", backgroundColor: "var(--ctp-base)", border: "1px solid var(--ctp-surface1)", borderRadius: "0.5rem", color: "var(--ctp-text)", fontSize: "var(--font-body)", boxSizing: "border-box", }; const btnPrimaryStyle: React.CSSProperties = { display: "block", width: "100%", padding: "0.75rem 1.5rem", borderRadius: "0.25rem", fontWeight: 500, fontSize: "0.75rem", cursor: "pointer", border: "none", backgroundColor: "var(--ctp-mauve)", color: "var(--ctp-crust)", textAlign: "center", }; const dividerStyle: React.CSSProperties = { display: "flex", alignItems: "center", margin: "1.5rem 0", }; const dividerLineStyle: React.CSSProperties = { flex: 1, borderTop: "1px solid var(--ctp-surface1)", }; const btnOidcStyle: React.CSSProperties = { display: "block", width: "100%", padding: "0.75rem 1.5rem", borderRadius: "0.25rem", fontWeight: 500, fontSize: "0.75rem", cursor: "pointer", border: "none", backgroundColor: "var(--ctp-blue)", color: "var(--ctp-crust)", textAlign: "center", textDecoration: "none", boxSizing: "border-box", };