86 lines
3.2 KiB
TypeScript
86 lines
3.2 KiB
TypeScript
"use client";
|
||
import { useState, Suspense } from "react";
|
||
import { useRouter, useSearchParams } from "next/navigation";
|
||
|
||
function LoginForm() {
|
||
const router = useRouter();
|
||
const params = useSearchParams();
|
||
const [email, setEmail] = useState("");
|
||
const [password, setPassword] = useState("");
|
||
const [error, setError] = useState("");
|
||
const [loading, setLoading] = useState(false);
|
||
|
||
const handleSubmit = async (e: React.FormEvent) => {
|
||
e.preventDefault();
|
||
setLoading(true);
|
||
setError("");
|
||
try {
|
||
const form = new URLSearchParams();
|
||
form.append("username", email);
|
||
form.append("password", password);
|
||
const r = await fetch("/api/auth/login", {
|
||
method: "POST",
|
||
headers: { "Content-Type": "application/x-www-form-urlencoded" },
|
||
body: form.toString(),
|
||
});
|
||
const data = await r.json();
|
||
if (!r.ok) { setError(data.detail || "登录失败"); return; }
|
||
localStorage.setItem("arb_token", data.access_token);
|
||
router.push("/dashboard");
|
||
} catch {
|
||
setError("网络错误,请重试");
|
||
} finally {
|
||
setLoading(false);
|
||
}
|
||
};
|
||
|
||
return (
|
||
<div className="min-h-[70vh] flex items-center justify-center">
|
||
<div className="w-full max-w-md rounded-xl border border-slate-200 bg-white p-8 space-y-6">
|
||
<div>
|
||
<h1 className="text-2xl font-bold text-slate-900">登录</h1>
|
||
{params.get("registered") && (
|
||
<p className="text-emerald-400 text-sm mt-1">✅ 注册成功,请登录</p>
|
||
)}
|
||
<p className="text-slate-500 text-sm mt-1">登录后查看信号和账户信息</p>
|
||
</div>
|
||
<form onSubmit={handleSubmit} className="space-y-4">
|
||
<div>
|
||
<label className="block text-sm text-slate-700 mb-1">邮箱</label>
|
||
<input
|
||
type="email" required value={email} onChange={e => setEmail(e.target.value)}
|
||
className="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-slate-900 text-sm focus:outline-none focus:border-cyan-500"
|
||
placeholder="your@email.com"
|
||
/>
|
||
</div>
|
||
<div>
|
||
<label className="block text-sm text-slate-700 mb-1">密码</label>
|
||
<input
|
||
type="password" required value={password} onChange={e => setPassword(e.target.value)}
|
||
className="w-full bg-white border border-slate-200 rounded-lg px-3 py-2 text-slate-900 text-sm focus:outline-none focus:border-cyan-500"
|
||
/>
|
||
</div>
|
||
{error && <p className="text-red-400 text-sm">{error}</p>}
|
||
<button
|
||
type="submit" disabled={loading}
|
||
className="w-full bg-cyan-600 hover:bg-cyan-500 disabled:opacity-50 text-white font-medium py-2 rounded-lg text-sm transition-colors"
|
||
>
|
||
{loading ? "登录中..." : "登录"}
|
||
</button>
|
||
</form>
|
||
<p className="text-center text-sm text-slate-500">
|
||
没有账号?<a href="/register" className="text-blue-600 hover:underline">注册</a>
|
||
</p>
|
||
</div>
|
||
</div>
|
||
);
|
||
}
|
||
|
||
export default function LoginPage() {
|
||
return (
|
||
<Suspense>
|
||
<LoginForm />
|
||
</Suspense>
|
||
);
|
||
}
|