Login and forgot-password forms. AI output missed error state resets, hardcoded messages, and accessibility attributes. The production version fixes all six issues found in review.
1export function LoginForm() {2 const dispatch = useAppDispatch();3 const { status } = useAppSelector((s) => s.auth);45 const {6 register,7 handleSubmit,8 formState: { errors },9 } = useForm<LoginValues>({10 resolver: zodResolver(loginSchema),11 mode: "onBlur",12 });1314 function onSubmit(data: LoginValues) {15 dispatch(loginUser(data));16 }1718 const isLoading = status === "loading";1920 return (21 <form onSubmit={handleSubmit(onSubmit)} className="grid gap-4">22 {status === "failed" && (23 <div role="alert" className="text-sm text-destructive">24 Login failed. Please try again.25 </div>26 )}2728 <div className="grid gap-2">29 <Label htmlFor="password">Password</Label>30 <Input31 id="password"32 type="password"33 autoComplete="current-password"34 aria-invalid={!!errors.password}35 {...register("password")}36 />37 {errors.password && (38 <p className="text-sm text-destructive">39 {errors.password.message}40 </p>41 )}42 </div>4344 <Button type="submit" disabled={isLoading}>45 {isLoading ? "Signing in..." : "Sign in"}46 </Button>47 </form>48 );49}1export function LoginForm() {2 const dispatch = useAppDispatch();3 const { status, error } = useAppSelector((s) => s.auth);45 const {6 register,7 handleSubmit,8 formState: { errors },9 } = useForm<LoginValues>({10 resolver: zodResolver(loginSchema),11 mode: "onBlur",12 });1314 useEffect(() => {15 dispatch(clearError());16 }, [dispatch]);1718 function onSubmit(data: LoginValues) {19 dispatch(loginUser(data));20 }2122 const isLoading = status === "loading";2324 return (25 <form onSubmit={handleSubmit(onSubmit)} className="grid gap-4">26 {error && (27 <div role="alert" className="text-sm text-destructive">28 {error}29 </div>30 )}3132 <div className="grid gap-2">33 <Label htmlFor="password">Password</Label>34 <Input35 id="password"36 type="password"37 autoComplete="current-password"38 aria-invalid={!!errors.password}39 aria-describedby={errors.password ? "password-error" : undefined}40 {...register("password")}41 />42 {errors.password && (43 <p id="password-error" className="text-sm text-destructive">44 {errors.password.message}45 </p>46 )}47 </div>4849 <Button type="submit" disabled={isLoading}>50 {isLoading ? "Signing in..." : "Sign in"}51 </Button>52 </form>53 );54}1export function LoginForm() {2 const dispatch = useAppDispatch();3 const { status } = useAppSelector((s) => s.auth);45 const {6 register,7 handleSubmit,8 formState: { errors },9 } = useForm<LoginValues>({10 resolver: zodResolver(loginSchema),11 mode: "onBlur",12 });1314 function onSubmit(data: LoginValues) {15 dispatch(loginUser(data));16 }1718 const isLoading = status === "loading";1920 return (21 <form onSubmit={handleSubmit(onSubmit)} className="grid gap-4">22 {status === "failed" && (23 <div role="alert" className="text-sm text-destructive">24 Login failed. Please try again.25 </div>26 )}2728 <div className="grid gap-2">29 <Label htmlFor="password">Password</Label>30 <Input31 id="password"32 type="password"33 autoComplete="current-password"34 aria-invalid={!!errors.password}35 {...register("password")}36 />37 {errors.password && (38 <p className="text-sm text-destructive">39 {errors.password.message}40 </p>41 )}42 </div>4344 <Button type="submit" disabled={isLoading}>45 {isLoading ? "Signing in..." : "Sign in"}46 </Button>47 </form>48 );49}Navigating from /signup back to /login keeps a stale error banner because the Redux error state is never cleared. The AI did not add a useEffect to dispatch clearError().
The AI used a static "Login failed. Please try again." string instead of rendering the actual error from the API response stored in Redux state.
The password input has no aria-describedby linking it to the error message. Screen readers cannot associate the validation error with the field.
A useEffect dispatches clearError() when the component mounts, preventing stale errors from a previous screen from persisting.
The error message comes from the Redux store, which is populated from the server response via rejectWithValue. Users see the actual reason their login failed.
The password input links to "password-error" via aria-describedby, and the error paragraph has a matching id. Screen readers now announce the error when the field is focused.