init: 初始化 AssetX 项目仓库
包含 webapp(Next.js 用户端)、webapp-back(Go 后端)、 antdesign(管理后台)、landingpage(营销落地页)、 数据库 SQL 和配置文件。
This commit is contained in:
159
webapp/components/points/TopPerformers.tsx
Normal file
159
webapp/components/points/TopPerformers.tsx
Normal file
@@ -0,0 +1,159 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import Image from "next/image";
|
||||
import { fetchLeaderboard, formatPoints, type LeaderboardData } from "@/lib/api/points";
|
||||
import { useApp } from "@/contexts/AppContext";
|
||||
|
||||
export default function TopPerformers() {
|
||||
const { t } = useApp();
|
||||
const [leaderboard, setLeaderboard] = useState<LeaderboardData | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
|
||||
useEffect(() => {
|
||||
async function loadLeaderboard() {
|
||||
setLoading(true);
|
||||
const data = await fetchLeaderboard(5);
|
||||
setLeaderboard(data);
|
||||
setLoading(false);
|
||||
}
|
||||
loadLeaderboard();
|
||||
}, []);
|
||||
|
||||
const getRowStyle = (rank: number) => {
|
||||
switch (rank) {
|
||||
case 1:
|
||||
return {
|
||||
bg: "#fff5ef",
|
||||
borderColor: "#ff6900",
|
||||
rankColor: "#ff6900",
|
||||
textColor: "#111827",
|
||||
pointsSize: "text-body-large",
|
||||
};
|
||||
case 2:
|
||||
return {
|
||||
bg: "#fffbf5",
|
||||
borderColor: "#ffb933",
|
||||
rankColor: "#ffb933",
|
||||
textColor: "#111827",
|
||||
pointsSize: "text-body-large",
|
||||
};
|
||||
case 3:
|
||||
return {
|
||||
bg: "#f3f4f6",
|
||||
borderColor: "#6b7280",
|
||||
rankColor: "#6b7280",
|
||||
textColor: "#111827",
|
||||
pointsSize: "text-body-large",
|
||||
};
|
||||
default:
|
||||
return {
|
||||
bg: "transparent",
|
||||
borderColor: "transparent",
|
||||
rankColor: "#d1d5db",
|
||||
textColor: "#9ca1af",
|
||||
pointsSize: "text-body-small",
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
if (loading) {
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 flex flex-col overflow-hidden h-full animate-pulse">
|
||||
<div className="p-6 border-b border-[#f1f5f9] dark:border-gray-700">
|
||||
<div className="h-6 bg-gray-200 dark:bg-gray-700 rounded w-32" />
|
||||
</div>
|
||||
<div className="flex-1 flex flex-col">
|
||||
{[1, 2, 3, 4, 5].map((i) => (
|
||||
<div key={i} className="h-[72px] border-l-4 border-transparent px-6 flex items-center">
|
||||
<div className="h-4 bg-gray-200 dark:bg-gray-700 rounded w-full" />
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
if (!leaderboard) {
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 p-8">
|
||||
<p className="text-text-tertiary dark:text-gray-400">{t("points.failedToLoadLeaderboard")}</p>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="bg-bg-surface dark:bg-gray-800 rounded-3xl border border-border-gray dark:border-gray-700 flex flex-col overflow-hidden h-full animate-fade-in">
|
||||
{/* Header */}
|
||||
<div className="border-b border-[#f1f5f9] dark:border-gray-700 px-6 pt-8 pb-8">
|
||||
<h3 className="text-heading-h4 font-bold text-text-primary dark:text-white leading-[140%]">
|
||||
{t("points.topPerformers")}
|
||||
</h3>
|
||||
</div>
|
||||
|
||||
{/* Performers List */}
|
||||
<div className="flex-1 flex flex-col">
|
||||
{leaderboard.topUsers.map((performer) => {
|
||||
const style = getRowStyle(performer.rank);
|
||||
const isTopThree = performer.rank <= 3;
|
||||
const height = performer.rank <= 3 ? "h-[72px]" : "h-[68px]";
|
||||
|
||||
return (
|
||||
<div
|
||||
key={performer.rank}
|
||||
className={`flex items-center justify-between px-6 ${height} border-l-4`}
|
||||
style={{
|
||||
backgroundColor: style.bg,
|
||||
borderLeftColor: style.borderColor,
|
||||
}}
|
||||
>
|
||||
{/* Left - Rank and Address */}
|
||||
<div className="flex items-center gap-5">
|
||||
{/* Rank Number */}
|
||||
<span
|
||||
className={`font-black italic leading-[133%] ${
|
||||
isTopThree ? "text-2xl tracking-[0.07px]" : "text-lg tracking-[-0.44px]"
|
||||
}`}
|
||||
style={{ color: style.rankColor }}
|
||||
>
|
||||
{performer.rank}
|
||||
</span>
|
||||
|
||||
{/* Address */}
|
||||
<span
|
||||
className="text-body-small font-bold font-inter leading-[150%]"
|
||||
style={{ color: style.textColor }}
|
||||
>
|
||||
{performer.address}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Right - Points */}
|
||||
<span
|
||||
className={`${style.pointsSize} font-bold leading-[150%]`}
|
||||
style={{
|
||||
color: isTopThree ? "#111827" : "#4b5563",
|
||||
}}
|
||||
>
|
||||
{formatPoints(performer.points)}
|
||||
</span>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
{/* Footer - My Rank */}
|
||||
<div className="border-t border-border-gray dark:border-gray-700 px-6 py-6 flex items-center justify-between">
|
||||
<div className="flex items-center gap-1">
|
||||
<Image src="/icons/ui/icon-chart.svg" alt="Chart" width={16} height={16} />
|
||||
<span className="text-[10px] font-medium text-text-tertiary dark:text-gray-400 leading-[150%] tracking-[0.01em]">
|
||||
{t("points.myRank")}
|
||||
</span>
|
||||
</div>
|
||||
<span className="text-body-small font-bold text-text-primary dark:text-white leading-[150%]">
|
||||
{leaderboard.myPoints.toLocaleString()}
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user