From 42a76ed41bd173b455671f7ae30e4cd8a6f65821 Mon Sep 17 00:00:00 2001 From: Marko Djordjevic Date: Fri, 20 Feb 2026 13:22:51 +0100 Subject: [PATCH] Add error state display for invalid credentials on login page - Capture error from signIn() response (checks for ok: false) - Check for ?error= in URL search params (Auth.js redirects with error on failure) - Display red alert box with error message below the form when auth fails - Clear error on new login attempt Completes task 10.2 in user-accounts change. Co-Authored-By: Claude Sonnet 4.6 --- openspec/changes/user-accounts/tasks.md | 2 +- src/app/(public)/login/page.tsx | 33 +++++++++++++++++++++++-- tsconfig.tsbuildinfo | 2 +- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/openspec/changes/user-accounts/tasks.md b/openspec/changes/user-accounts/tasks.md index 32069b4..11c5811 100644 --- a/openspec/changes/user-accounts/tasks.md +++ b/openspec/changes/user-accounts/tasks.md @@ -57,7 +57,7 @@ ## 10. Login Page - [x] 10.1 `[sonnet]` Create `src/app/(public)/login/page.tsx` — login form matching Lovable design: email/password inputs, "Sign In" button calling `signIn("credentials")`, "Continue with Google" button calling `signIn("google")` -- [ ] 10.2 `[haiku]` Add error state display for invalid credentials +- [x] 10.2 `[haiku]` Add error state display for invalid credentials - [ ] 10.3 `[haiku]` Add "Forgot password?" link (shows toast: "Not yet available"), "Sign up" link to `/register` ## 11. Register Page diff --git a/src/app/(public)/login/page.tsx b/src/app/(public)/login/page.tsx index 407a6d6..f0c86cf 100644 --- a/src/app/(public)/login/page.tsx +++ b/src/app/(public)/login/page.tsx @@ -1,28 +1,51 @@ "use client"; -import { useState } from "react"; +import { useState, useEffect } from "react"; import Link from "next/link"; +import { useSearchParams } from "next/navigation"; import { signIn } from "next-auth/react"; import { ChartColumn } from "lucide-react"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; import { Label } from "@/components/ui/label"; import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card"; +import { AlertCircle } from "lucide-react"; export default function LoginPage() { const [email, setEmail] = useState(""); const [password, setPassword] = useState(""); const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); + const searchParams = useSearchParams(); + + // Check for error in URL search params on mount + useEffect(() => { + const urlError = searchParams.get("error"); + if (urlError) { + if (urlError === "CredentialsSignin") { + setError("Invalid email or password"); + } else { + setError("An error occurred. Please try again."); + } + } + }, [searchParams]); async function handleSubmit(e: React.FormEvent) { e.preventDefault(); + setError(null); setIsLoading(true); try { - await signIn("credentials", { + const result = await signIn("credentials", { email, password, redirectTo: "/app", }); + // If signIn returns with ok: false, it means auth failed + if (result && !result.ok) { + setError("Invalid email or password"); + } + } catch { + setError("An error occurred. Please try again."); } finally { setIsLoading(false); } @@ -57,6 +80,12 @@ export default function LoginPage() { + {error && ( +
+ +

{error}

+
+ )}