Files
chlova/web/src/pages/Login.tsx
T
Kantin-Petit 9de0132676 feat: scaffold UI web (React/Vite/Tailwind) + Login (v0.22.0)
Package web/ : React 19 + Vite 8 + Tailwind 4 + react-router 7 + PWA.
Tokens dark HUD Jarvis-red, client API, contexte auth JWT, shell + garde
de route, écran Login (mot de passe + TOTP). Chat/Review en stubs. Build
OK, 0 vuln. docs/ui-design.md.

Palier de risque : reversible (front statique, aucun accès infra direct).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-23 02:20:16 +02:00

51 lines
2.1 KiB
TypeScript

import { useState, type FormEvent } from "react";
import { useAuth } from "../auth";
import { ApiError } from "../api";
export function Login() {
const { login } = useAuth();
const [user, setUser] = useState("");
const [password, setPassword] = useState("");
const [totp, setTotp] = useState("");
const [busy, setBusy] = useState(false);
const [error, setError] = useState<string | null>(null);
const submit = async (e: FormEvent): Promise<void> => {
e.preventDefault();
setBusy(true);
setError(null);
try {
await login(user, password, totp);
} catch (err) {
setError(err instanceof ApiError && err.status === 429 ? "Trop de tentatives, patiente." : "Identifiants invalides.");
} finally {
setBusy(false);
}
};
const field = "w-full rounded-md bg-surface-2 border border-border px-3 py-2 text-fg placeholder:text-muted ring-accent";
return (
<div className="min-h-dvh grid place-items-center px-4">
<form onSubmit={submit} className="w-full max-w-sm space-y-4 rounded-xl border border-border bg-surface p-6">
<h1 className="text-2xl font-bold tracking-wide glow text-accent">CHLOVA</h1>
<p className="text-sm text-muted">Accès propriétaire authentification forte.</p>
<input className={field} placeholder="Utilisateur" autoComplete="username" value={user} onChange={(e) => setUser(e.target.value)} />
<input className={field} type="password" placeholder="Mot de passe" autoComplete="current-password" value={password} onChange={(e) => setPassword(e.target.value)} />
<input className={field} inputMode="numeric" placeholder="Code 2FA (TOTP)" autoComplete="one-time-code" value={totp} onChange={(e) => setTotp(e.target.value)} />
{error && <p role="alert" className="text-sm text-danger">{error}</p>}
<button
type="submit"
disabled={busy}
className="w-full rounded-md bg-accent px-3 py-2 font-medium text-bg disabled:opacity-50 ring-accent cursor-pointer"
>
{busy ? "Connexion…" : "Se connecter"}
</button>
</form>
</div>
);
}