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

113 lines
5.2 KiB
TypeScript
Raw 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-400 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-100"></h1>
<button onClick={logout} className="text-sm text-slate-400 hover:text-slate-200">退</button>
</div>
<div className="rounded-xl border border-slate-700 bg-slate-800/50 p-6 space-y-3">
<h2 className="text-slate-200 font-semibold"></h2>
<div className="grid grid-cols-2 gap-3 text-sm">
<div className="text-slate-400"></div>
<div className="text-slate-200">{user.email}</div>
<div className="text-slate-400"></div>
<div className="text-cyan-400 font-medium">{tierLabel[user.tier] || user.tier}</div>
<div className="text-slate-400"></div>
<div className="text-slate-200">{user.expires_at ? new Date(user.expires_at).toLocaleDateString("zh-CN") : "永久免费"}</div>
</div>
</div>
<div className="rounded-xl border border-slate-700 bg-slate-800/50 p-6 space-y-4">
<h2 className="text-slate-200 font-semibold">Discord </h2>
<p className="text-slate-400 text-sm">Discord ID后@你</p>
<div className="flex gap-2">
<input
value={discordId} onChange={e => setDiscordId(e.target.value)}
className="flex-1 bg-slate-900 border border-slate-600 rounded-lg px-3 py-2 text-slate-100 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-500 text-xs">Discord ID ID</p>
</div>
<div className="rounded-xl border border-slate-700 bg-slate-800/50 p-6">
<h2 className="text-slate-200 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-600"}`}>
<div className="font-medium text-slate-200">{p.label}</div>
<div className="text-cyan-400 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-200 py-1 rounded text-xs">
</button>
)}
</div>
))}
</div>
</div>
</div>
);
}