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 <noreply@anthropic.com>
This commit is contained in:
parent
954dd27c88
commit
42a76ed41b
3 changed files with 33 additions and 4 deletions
|
|
@ -57,7 +57,7 @@
|
||||||
## 10. Login Page
|
## 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")`
|
- [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`
|
- [ ] 10.3 `[haiku]` Add "Forgot password?" link (shows toast: "Not yet available"), "Sign up" link to `/register`
|
||||||
|
|
||||||
## 11. Register Page
|
## 11. Register Page
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,51 @@
|
||||||
"use client";
|
"use client";
|
||||||
|
|
||||||
import { useState } from "react";
|
import { useState, useEffect } from "react";
|
||||||
import Link from "next/link";
|
import Link from "next/link";
|
||||||
|
import { useSearchParams } from "next/navigation";
|
||||||
import { signIn } from "next-auth/react";
|
import { signIn } from "next-auth/react";
|
||||||
import { ChartColumn } from "lucide-react";
|
import { ChartColumn } from "lucide-react";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
|
||||||
|
import { AlertCircle } from "lucide-react";
|
||||||
|
|
||||||
export default function LoginPage() {
|
export default function LoginPage() {
|
||||||
const [email, setEmail] = useState("");
|
const [email, setEmail] = useState("");
|
||||||
const [password, setPassword] = useState("");
|
const [password, setPassword] = useState("");
|
||||||
const [isLoading, setIsLoading] = useState(false);
|
const [isLoading, setIsLoading] = useState(false);
|
||||||
|
const [error, setError] = useState<string | null>(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) {
|
async function handleSubmit(e: React.FormEvent) {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
|
setError(null);
|
||||||
setIsLoading(true);
|
setIsLoading(true);
|
||||||
try {
|
try {
|
||||||
await signIn("credentials", {
|
const result = await signIn("credentials", {
|
||||||
email,
|
email,
|
||||||
password,
|
password,
|
||||||
redirectTo: "/app",
|
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 {
|
} finally {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
}
|
}
|
||||||
|
|
@ -57,6 +80,12 @@ export default function LoginPage() {
|
||||||
</CardDescription>
|
</CardDescription>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent>
|
<CardContent>
|
||||||
|
{error && (
|
||||||
|
<div className="mb-4 flex gap-3 rounded-md bg-destructive/10 p-3 border border-destructive/20">
|
||||||
|
<AlertCircle className="h-5 w-5 text-destructive flex-shrink-0 mt-0.5" />
|
||||||
|
<p className="text-sm text-destructive font-mono">{error}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<form onSubmit={handleSubmit} className="space-y-4">
|
<form onSubmit={handleSubmit} className="space-y-4">
|
||||||
<div className="space-y-2">
|
<div className="space-y-2">
|
||||||
<Label htmlFor="email" className="text-xs font-mono">
|
<Label htmlFor="email" className="text-xs font-mono">
|
||||||
|
|
|
||||||
File diff suppressed because one or more lines are too long
Loading…
Add table
Add a link
Reference in a new issue