import { adminLoginSchema, ErrorCode } from "@batch-cooking/shared"; import { type FormEvent, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { ApiError } from "../../api/client"; import { useAdminAuth } from "../../features/auth/AdminAuthContext"; import "../../features/auth/admin-auth.scss"; import { fieldErrorsFrom } from "../../lib/zod-errors"; import { errorMessageService } from "../../services/error-message.service"; /** * The admin login screen — the only unauthenticated route. Client-side * validation via the shared `adminLoginSchema` (same rules the API * enforces), then `POST /admin/auth/login`; any API failure is translated * to a localized label via {@link ErrorMessageService}. Same structure as * apps/web's `LoginPage`. */ export function LoginPage() { const { login } = useAdminAuth(); const navigate = useNavigate(); const { t } = useTranslation(); const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [fieldErrors, setFieldErrors] = useState>({}); const [formError, setFormError] = useState(null); const [isSubmitting, setIsSubmitting] = useState(false); async function handleSubmit(e: FormEvent) { e.preventDefault(); setFormError(null); const result = adminLoginSchema.safeParse({ email, password }); if (!result.success) { setFieldErrors(fieldErrorsFrom(result.error)); return; } setFieldErrors({}); setIsSubmitting(true); try { await login(result.data); void navigate("/"); } catch (err) { const code = err instanceof ApiError ? err.code : ErrorCode.INTERNAL_ERROR; setFormError(errorMessageService.getLabel(code)); } finally { setIsSubmitting(false); } } return (

{t("admin.login.title")}

setEmail(e.target.value)} autoComplete="email" /> {fieldErrors.email &&

{fieldErrors.email}

} setPassword(e.target.value)} autoComplete="current-password" /> {fieldErrors.password &&

{fieldErrors.password}

} {formError &&

{formError}

}
); }