122 lines
4.0 KiB
TypeScript
122 lines
4.0 KiB
TypeScript
"use client";
|
|
|
|
import { useState, useEffect } from "react";
|
|
import { useRouter } from "next/navigation";
|
|
|
|
export default function LoginPage() {
|
|
const router = useRouter();
|
|
const [username, setUsername] = useState("");
|
|
const [password, setPassword] = useState("");
|
|
const [error, setError] = useState("");
|
|
const [loading, setLoading] = useState(false);
|
|
|
|
useEffect(() => {
|
|
// 已登录则跳转
|
|
const token = localStorage.getItem("wauth_token");
|
|
if (token) {
|
|
fetch("/api/auth/login", {
|
|
headers: { "X-Auth-User": token },
|
|
}).then(r => {
|
|
if (r.ok) router.replace("/");
|
|
else localStorage.removeItem("wauth_token");
|
|
}).catch(() => localStorage.removeItem("wauth_token"));
|
|
}
|
|
}, [router]);
|
|
|
|
const handleLogin = async (e: React.FormEvent) => {
|
|
e.preventDefault();
|
|
setError("");
|
|
setLoading(true);
|
|
|
|
try {
|
|
const res = await fetch("/api/auth/login", {
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ username, password }),
|
|
});
|
|
|
|
const data = await res.json();
|
|
|
|
if (!res.ok) {
|
|
setError(data.error || "登录失败");
|
|
return;
|
|
}
|
|
|
|
// 存储用户信息
|
|
localStorage.setItem("wauth_token", data.user.username);
|
|
localStorage.setItem("wauth_user", JSON.stringify(data.user));
|
|
|
|
// 注册 Service Worker
|
|
if ("serviceWorker" in navigator) {
|
|
const reg = await navigator.serviceWorker.register("/sw.js");
|
|
if (reg.installing) {
|
|
await new Promise<void>(resolve => {
|
|
reg.installing!.addEventListener("statechange", function () {
|
|
if (this.state === "activated") resolve();
|
|
});
|
|
});
|
|
}
|
|
const sw = reg.active || reg.waiting || reg.installing;
|
|
if (sw) {
|
|
sw.postMessage({ type: "SET_TOKEN", token: data.user.username });
|
|
}
|
|
if (navigator.serviceWorker.controller) {
|
|
navigator.serviceWorker.controller.postMessage({ type: "SET_TOKEN", token: data.user.username });
|
|
}
|
|
}
|
|
|
|
router.replace("/");
|
|
} catch {
|
|
setError("网络错误,请重试");
|
|
} finally {
|
|
setLoading(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="fixed inset-0 z-50 flex items-center justify-center bg-bg">
|
|
<div className="bg-surface rounded-2xl shadow-lg p-10 w-full max-w-sm mx-4">
|
|
<h1 className="text-xl font-bold text-accent mb-1">写作后台</h1>
|
|
<p className="text-[13px] text-muted mb-8">优世界 · Blog Admin</p>
|
|
|
|
{error && (
|
|
<div className="bg-red-50 text-red-600 text-[13px] px-4 py-2.5 rounded-lg mb-5">
|
|
{error}
|
|
</div>
|
|
)}
|
|
|
|
<form onSubmit={handleLogin} className="space-y-5">
|
|
<div>
|
|
<label className="block text-[13px] text-text font-medium mb-1.5">用户名</label>
|
|
<input
|
|
type="text"
|
|
value={username}
|
|
onChange={e => setUsername(e.target.value)}
|
|
className="w-full px-3.5 py-2.5 rounded-lg border border-line bg-bg text-text text-sm outline-none focus:border-accent transition-colors"
|
|
autoFocus
|
|
required
|
|
/>
|
|
</div>
|
|
<div>
|
|
<label className="block text-[13px] text-text font-medium mb-1.5">密码</label>
|
|
<input
|
|
type="password"
|
|
value={password}
|
|
onChange={e => setPassword(e.target.value)}
|
|
className="w-full px-3.5 py-2.5 rounded-lg border border-line bg-bg text-text text-sm outline-none focus:border-accent transition-colors"
|
|
required
|
|
/>
|
|
</div>
|
|
<button
|
|
type="submit"
|
|
disabled={loading}
|
|
className="w-full py-2.5 bg-accent text-white rounded-lg text-sm font-semibold hover:opacity-90 disabled:opacity-50 transition-opacity"
|
|
>
|
|
{loading ? "登录中..." : "登 录"}
|
|
</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|