feat(web): login page with inline error handling
Add shadcn input/label/card primitives and implement the login page: email/password form using useLogin, navigates to /objects on success, shows inline i18n error on 401 (auth.invalid) or network failure. 2 new tests, 9 total green. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,66 @@
|
||||
import { useState, type FormEvent } from "react";
|
||||
import { useNavigate } from "react-router-dom";
|
||||
import { useTranslation } from "react-i18next";
|
||||
|
||||
import { useLogin } from "../api/queries";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import { Input } from "@/components/ui/input";
|
||||
import { Label } from "@/components/ui/label";
|
||||
|
||||
export function LoginPage() {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const login = useLogin();
|
||||
const [email, setEmail] = useState("");
|
||||
const [password, setPassword] = useState("");
|
||||
|
||||
const onSubmit = (event: FormEvent) => {
|
||||
event.preventDefault();
|
||||
login.mutate(
|
||||
{ email, password },
|
||||
{ onSuccess: () => navigate("/objects", { replace: true }) },
|
||||
);
|
||||
};
|
||||
|
||||
const errorKey = login.error
|
||||
? login.error.message === "invalid"
|
||||
? "auth.invalid"
|
||||
: "auth.networkError"
|
||||
: null;
|
||||
|
||||
return (
|
||||
<div className="flex min-h-screen items-center justify-center p-4">
|
||||
<form onSubmit={onSubmit} className="w-full max-w-sm space-y-4">
|
||||
<h1 className="text-2xl font-semibold">{t("app.name")}</h1>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="email">{t("auth.email")}</Label>
|
||||
<Input
|
||||
id="email"
|
||||
type="email"
|
||||
value={email}
|
||||
onChange={(event) => setEmail(event.target.value)}
|
||||
autoComplete="username"
|
||||
/>
|
||||
</div>
|
||||
<div className="space-y-2">
|
||||
<Label htmlFor="password">{t("auth.password")}</Label>
|
||||
<Input
|
||||
id="password"
|
||||
type="password"
|
||||
value={password}
|
||||
onChange={(event) => setPassword(event.target.value)}
|
||||
autoComplete="current-password"
|
||||
/>
|
||||
</div>
|
||||
{errorKey && (
|
||||
<p role="alert" className="text-sm text-red-600">
|
||||
{t(errorKey)}
|
||||
</p>
|
||||
)}
|
||||
<Button type="submit" className="w-full" disabled={login.isPending}>
|
||||
{t("auth.signIn")}
|
||||
</Button>
|
||||
</form>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user