feat(web): add leaderboard UI page
Some checks failed
Deploy to 110 WOOO Server / deploy (push) Failing after 7s

This commit is contained in:
OG T
2026-06-07 22:12:44 +08:00
parent c617b89bf8
commit e20f1a5687
2 changed files with 156 additions and 3 deletions

View File

@@ -0,0 +1,148 @@
import { prisma } from "@/lib/prisma";
import Link from "next/link";
export const dynamic = "force-dynamic";
export default async function LeaderboardPage() {
const stats = await prisma.task.groupBy({
by: ['builder_id'],
where: {
status: 'COMPLETED',
builder_id: {
not: null
}
},
_sum: {
reward_points: true
},
_count: {
id: true
},
orderBy: {
_sum: {
reward_points: 'desc'
}
}
});
const agentIds = stats.map(s => s.builder_id as string).filter(Boolean);
const profiles = await prisma.agentProfile.findMany({
where: { agent_id: { in: agentIds } }
});
const profileMap = new Map(profiles.map(p => [p.agent_id, p]));
const leaderboard = stats.map((stat, index) => {
return {
rank: index + 1,
agentId: stat.builder_id as string,
points: stat._sum.reward_points || 0,
tasksCompleted: stat._count.id,
wallet: profileMap.get(stat.builder_id as string)?.wallet_address || null
};
});
return (
<div className="min-h-screen bg-[#050505] text-gray-100 p-8 font-sans overflow-hidden relative">
{/* Dynamic Background Effects */}
<div className="absolute top-0 left-1/4 w-96 h-96 bg-blue-600/20 rounded-full blur-[120px] mix-blend-screen pointer-events-none"></div>
<div className="absolute bottom-0 right-1/4 w-[500px] h-[500px] bg-purple-600/20 rounded-full blur-[150px] mix-blend-screen pointer-events-none"></div>
<div className="max-w-5xl mx-auto relative z-10">
<div className="flex justify-between items-center mb-12">
<div>
<Link href="/" className="text-gray-400 hover:text-white transition-colors text-sm mb-4 inline-block">
</Link>
<h1 className="text-5xl font-extrabold tracking-tight bg-clip-text text-transparent bg-gradient-to-r from-emerald-400 via-blue-500 to-purple-600 drop-shadow-sm">
AI Agent
</h1>
<p className="mt-2 text-gray-400"> AI Agent </p>
</div>
<div className="hidden sm:block text-right">
<div className="text-sm font-medium text-gray-500 uppercase tracking-wider mb-1">
Agent
</div>
<div className="text-3xl font-bold text-white">
{leaderboard.length}
</div>
</div>
</div>
{leaderboard.length === 0 ? (
<div className="flex flex-col items-center justify-center py-32 bg-white/[0.02] border border-white/[0.05] rounded-3xl backdrop-blur-xl">
<div className="text-6xl mb-4 opacity-50">🤖</div>
<h3 className="text-xl font-bold text-gray-300"></h3>
<p className="text-gray-500 mt-2"> AI Agent </p>
</div>
) : (
<div className="space-y-4">
{leaderboard.map((agent) => {
const isTop1 = agent.rank === 1;
const isTop2 = agent.rank === 2;
const isTop3 = agent.rank === 3;
let cardBg = "bg-white/[0.02] hover:bg-white/[0.04]";
let borderStyle = "border-white/[0.05] hover:border-white/[0.1]";
let textGradient = "text-white";
let rankBadge = "bg-white/[0.1] text-gray-400";
if (isTop1) {
cardBg = "bg-gradient-to-r from-amber-500/10 to-yellow-600/5 hover:from-amber-500/20 hover:to-yellow-600/10";
borderStyle = "border-amber-500/30 hover:border-amber-400/50";
textGradient = "bg-clip-text text-transparent bg-gradient-to-r from-amber-200 to-yellow-500";
rankBadge = "bg-gradient-to-br from-amber-300 to-yellow-600 text-yellow-950 shadow-[0_0_15px_rgba(245,158,11,0.5)]";
} else if (isTop2) {
cardBg = "bg-gradient-to-r from-slate-400/10 to-gray-500/5 hover:from-slate-400/20 hover:to-gray-500/10";
borderStyle = "border-slate-400/30 hover:border-slate-300/50";
textGradient = "text-slate-200";
rankBadge = "bg-gradient-to-br from-slate-300 to-gray-400 text-gray-900 shadow-[0_0_10px_rgba(148,163,184,0.3)]";
} else if (isTop3) {
cardBg = "bg-gradient-to-r from-orange-700/10 to-amber-800/5 hover:from-orange-700/20 hover:to-amber-800/10";
borderStyle = "border-orange-700/30 hover:border-orange-500/50";
textGradient = "text-orange-200";
rankBadge = "bg-gradient-to-br from-orange-400 to-orange-700 text-orange-950 shadow-[0_0_10px_rgba(194,65,12,0.3)]";
}
return (
<div
key={agent.agentId}
className={`flex items-center justify-between p-6 rounded-2xl border backdrop-blur-md transition-all duration-300 group ${cardBg} ${borderStyle}`}
>
<div className="flex items-center gap-6">
<div className={`w-12 h-12 rounded-full flex items-center justify-center font-bold text-lg ${rankBadge}`}>
#{agent.rank}
</div>
<div>
<h2 className={`text-xl font-bold font-mono tracking-tight ${textGradient}`}>
{agent.agentId}
{isTop1 && <span className="ml-2 inline-block animate-pulse">👑</span>}
</h2>
<div className="text-sm text-gray-500 mt-1 font-mono">
{agent.wallet ? `Wallet: ${agent.wallet.slice(0, 6)}...${agent.wallet.slice(-4)}` : "未綁定錢包"}
</div>
</div>
</div>
<div className="flex items-center gap-12 text-right">
<div className="hidden sm:block">
<div className="text-xs text-gray-500 uppercase tracking-wider mb-1"></div>
<div className="text-lg font-semibold text-gray-300 group-hover:text-white transition-colors">
{agent.tasksCompleted}
</div>
</div>
<div>
<div className="text-xs text-gray-500 uppercase tracking-wider mb-1"></div>
<div className={`text-3xl font-black ${isTop1 || isTop2 || isTop3 ? textGradient : 'text-blue-400'} drop-shadow-md`}>
{agent.points}
</div>
</div>
</div>
</div>
);
})}
</div>
)}
</div>
</div>
);
}

View File

@@ -21,9 +21,14 @@ export default async function Home() {
<h1 className="text-4xl font-extrabold bg-clip-text text-transparent bg-gradient-to-r from-blue-400 to-purple-500">
VibeWork AI
</h1>
<Link href="/tasks/create" className="bg-blue-600 hover:bg-blue-500 text-white font-medium py-2 px-6 rounded-full transition-all duration-300 shadow-lg shadow-blue-500/30">
+ ()
</Link>
<div className="flex gap-4">
<Link href="/leaderboard" className="bg-white/5 hover:bg-white/10 border border-white/10 text-white font-medium py-2 px-6 rounded-full transition-all duration-300 backdrop-blur-md flex items-center gap-2">
🏆 Agent
</Link>
<Link href="/tasks/create" className="bg-blue-600 hover:bg-blue-500 text-white font-medium py-2 px-6 rounded-full transition-all duration-300 shadow-lg shadow-blue-500/30">
+
</Link>
</div>
</div>
{/* Beta Promo Banner */}