arbitrage-engine/frontend/app/dashboard/page.tsx

113 lines
5.2 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

"use client";
import { useEffect, useState } from "react";
import { useRouter } from "next/navigation";
interface UserInfo {
id: number;
email: string;
discord_id: string | null;
tier: string;
expires_at: string | null;
}
export default function DashboardPage() {
const router = useRouter();
const [user, setUser] = useState<UserInfo | null>(null);
const [discordId, setDiscordId] = useState("");
const [saving, setSaving] = useState(false);
const [msg, setMsg] = useState("");
useEffect(() => {
const token = localStorage.getItem("arb_token");
if (!token) { router.push("/login"); return; }
fetch("/api/user/me", { headers: { Authorization: `Bearer ${token}` } })
.then(r => { if (!r.ok) { router.push("/login"); return null; } return r.json(); })
.then(d => { if (d) { setUser(d); setDiscordId(d.discord_id || ""); } });
}, [router]);
const bindDiscord = async () => {
setSaving(true); setMsg("");
const token = localStorage.getItem("arb_token");
const r = await fetch("/api/user/bind-discord", {
method: "POST",
headers: { Authorization: `Bearer ${token}`, "Content-Type": "application/json" },
body: JSON.stringify({ discord_id: discordId }),
});
const d = await r.json();
setMsg(r.ok ? "✅ 绑定成功" : d.detail || "绑定失败");
setSaving(false);
if (r.ok && user) setUser({ ...user, discord_id: discordId });
};
const logout = () => { localStorage.removeItem("arb_token"); router.push("/"); };
if (!user) return <div className="text-slate-500 p-8">...</div>;
const tierLabel: Record<string, string> = { free: "免费版", pro: "Pro", premium: "Premium" };
return (
<div className="space-y-6 max-w-2xl">
<div className="flex items-center justify-between">
<h1 className="text-2xl font-bold text-slate-900"></h1>
<button onClick={logout} className="text-sm text-slate-500 hover:text-slate-800">退</button>
</div>
<div className="rounded-xl border border-slate-200 bg-white p-6 space-y-3">
<h2 className="text-slate-800 font-semibold"></h2>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="text-slate-500"></div>
<div className="text-slate-800">{user.email}</div>
<div className="text-slate-500"></div>
<div className="text-blue-600 font-medium">{tierLabel[user.tier] || user.tier}</div>
<div className="text-slate-500"></div>
<div className="text-slate-800">{user.expires_at ? new Date(user.expires_at).toLocaleDateString("zh-CN") : "永久免费"}</div>
</div>
</div>
<div className="rounded-xl border border-slate-200 bg-white p-6 space-y-4">
<h2 className="text-slate-800 font-semibold">Discord </h2>
<p className="text-slate-500 text-sm">Discord ID后@你</p>
<div className="flex gap-2">
<input
value={discordId} onChange={e => setDiscordId(e.target.value)}
className="flex-1 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="Discord用户ID18位数字"
/>
<button
onClick={bindDiscord} disabled={saving || !discordId}
className="bg-cyan-600 hover:bg-cyan-500 disabled:opacity-50 text-white px-4 py-2 rounded-lg text-sm"
>
{saving ? "保存中..." : "绑定"}
</button>
</div>
{msg && <p className={`text-sm ${msg.startsWith("✅") ? "text-emerald-400" : "text-red-400"}`}>{msg}</p>}
<p className="text-slate-400 text-xs">Discord ID ID</p>
</div>
<div className="rounded-xl border border-slate-200 bg-white p-6">
<h2 className="text-slate-800 font-semibold mb-3"></h2>
<div className="grid grid-cols-3 gap-3 text-sm">
{[
{ tier: "free", label: "免费版", price: "¥0", features: ["实时费率面板"] },
{ tier: "pro", label: "Pro", price: "¥99/月", features: ["实时费率面板", "信号Discord推送", "历史数据"] },
{ tier: "premium", label: "Premium", price: "¥299/月", features: ["Pro全部功能", "定制阈值", "优先客服"] },
].map(p => (
<div key={p.tier} className={`rounded-lg border p-4 space-y-2 ${user.tier === p.tier ? "border-cyan-500 bg-cyan-950/30" : "border-slate-200"}`}>
<div className="font-medium text-slate-800">{p.label}</div>
<div className="text-blue-600 font-bold">{p.price}</div>
<ul className="space-y-1">
{p.features.map(f => <li key={f} className="text-slate-400 text-xs"> {f}</li>)}
</ul>
{user.tier !== p.tier && p.tier !== "free" && (
<button className="w-full mt-2 bg-slate-700 hover:bg-slate-600 text-slate-800 py-1 rounded text-xs">
</button>
)}
</div>
))}
</div>
</div>
</div>
);
}