← back to Govarbitrage
src/app/login/page.tsx
72 lines
"use client";
import { useState, Suspense } from "react";
import { useRouter, useSearchParams } from "next/navigation";
import { Card, CardContent, CardHeader, CardTitle } from "@/components/ui/card";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
function LoginForm() {
const router = useRouter();
const search = useSearchParams();
const next = search.get("next") || "/";
const [username, setUsername] = useState("");
const [password, setPassword] = useState("");
const [error, setError] = useState("");
const [loading, setLoading] = useState(false);
async function submit(e: React.FormEvent) {
e.preventDefault();
setLoading(true);
setError("");
try {
const res = await fetch("/api/auth/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ username, password }),
});
if (res.ok) {
router.push(next);
router.refresh();
} else {
const d = await res.json().catch(() => ({}));
setError(d.error || "Login failed");
}
} catch {
setError("Network error");
} finally {
setLoading(false);
}
}
return (
<Card className="w-full max-w-sm">
<CardHeader>
<CardTitle className="text-foreground text-lg">
Gov<span className="text-primary">Arbitrage</span> — Sign in
</CardTitle>
</CardHeader>
<CardContent>
<form onSubmit={submit} className="space-y-3">
<Input type="text" autoCapitalize="none" autoCorrect="off" placeholder="Username" value={username} onChange={(e) => setUsername(e.target.value)} required autoFocus />
<Input type="password" placeholder="Password" value={password} onChange={(e) => setPassword(e.target.value)} required />
{error && <p className="text-sm text-[var(--negative)]">{error}</p>}
<Button type="submit" className="w-full" disabled={loading}>
{loading ? "Signing in…" : "Sign in"}
</Button>
</form>
</CardContent>
</Card>
);
}
export default function LoginPage() {
return (
<main className="flex min-h-screen items-center justify-center p-5">
<Suspense fallback={null}>
<LoginForm />
</Suspense>
</main>
);
}