arbitrage-engine/frontend/app/page.tsx

96 lines
3.7 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, useCallback } from "react";
import { api, RatesResponse, StatsResponse, HistoryResponse } from "@/lib/api";
import RateCard from "@/components/RateCard";
import StatsCard from "@/components/StatsCard";
import FundingChart from "@/components/FundingChart";
export default function Dashboard() {
const [rates, setRates] = useState<RatesResponse | null>(null);
const [stats, setStats] = useState<StatsResponse | null>(null);
const [history, setHistory] = useState<HistoryResponse | null>(null);
const [status, setStatus] = useState<"loading" | "running" | "error">("loading");
const [lastUpdate, setLastUpdate] = useState<string>("");
const fetchRates = useCallback(async () => {
try {
const r = await api.rates();
setRates(r);
setStatus("running");
setLastUpdate(new Date().toLocaleTimeString("zh-CN"));
} catch {
setStatus("error");
}
}, []);
const fetchAll = useCallback(async () => {
try {
const [s, h] = await Promise.all([api.stats(), api.history()]);
setStats(s);
setHistory(h);
} catch {
// stats/history 失败不影响主界面
}
}, []);
useEffect(() => {
fetchRates();
fetchAll();
const rateInterval = setInterval(fetchRates, 2_000);
const slowInterval = setInterval(fetchAll, 120_000);
return () => { clearInterval(rateInterval); clearInterval(slowInterval); };
}, [fetchRates, fetchAll]);
return (
<div className="space-y-6">
{/* Header */}
<div className="flex items-center justify-between">
<div>
<h1 className="text-2xl font-bold text-slate-900"></h1>
<p className="text-slate-500 text-sm mt-1"> BTC / ETH </p>
</div>
<div className="flex items-center gap-2 text-sm">
<span className={`w-2 h-2 rounded-full ${
status === "running" ? "bg-emerald-500 animate-pulse"
: status === "error" ? "bg-red-500" : "bg-slate-300"
}`} />
<span className={status === "running" ? "text-emerald-600" : status === "error" ? "text-red-600" : "text-slate-400"}>
{status === "running" ? "运行中" : status === "error" ? "连接失败" : "加载中..."}
</span>
{lastUpdate && <span className="text-slate-400 ml-2"> {lastUpdate}</span>}
</div>
</div>
{/* Rate Cards */}
<div className="grid grid-cols-1 md:grid-cols-2 gap-4">
<RateCard asset="BTC" data={rates?.BTC ?? null} />
<RateCard asset="ETH" data={rates?.ETH ?? null} />
</div>
{/* Stats Cards */}
{stats && (
<div className="grid grid-cols-1 sm:grid-cols-3 gap-4">
<StatsCard title="BTC 套利" mean7d={stats.BTC.mean7d} annualized={stats.BTC.annualized} accent="blue" />
<StatsCard title="ETH 套利" mean7d={stats.ETH.mean7d} annualized={stats.ETH.annualized} accent="indigo" />
<StatsCard title="50/50 组合" mean7d={stats.combo.mean7d} annualized={stats.combo.annualized} accent="green" />
</div>
)}
{/* History Chart */}
{history && (
<div className="rounded-xl border border-slate-200 bg-white shadow-sm p-6">
<h2 className="text-slate-800 font-semibold mb-4">7</h2>
<FundingChart history={history} />
</div>
)}
{/* Strategy note */}
<div className="rounded-lg border border-blue-100 bg-blue-50 px-5 py-3 text-sm text-slate-600">
<span className="text-blue-600 font-medium"></span>
+ 8
</div>
</div>
);
}