All checks were successful
Deploy to Test Environment / deploy-to-test (push) Successful in 19m0s
88 lines
2.9 KiB
TypeScript
88 lines
2.9 KiB
TypeScript
// src/components/Leaderboard.tsx
|
|
import React from 'react';
|
|
import { useLeaderboardQuery } from '../hooks/queries/useLeaderboardQuery';
|
|
import { Award, Crown, ShieldAlert } from 'lucide-react';
|
|
|
|
/**
|
|
* Leaderboard component displaying top users by points.
|
|
*
|
|
* Refactored to use TanStack Query (ADR-0005 Phase 8).
|
|
*/
|
|
export const Leaderboard: React.FC = () => {
|
|
const { data: leaderboard = [], isLoading, error } = useLeaderboardQuery(10);
|
|
|
|
const getRankIcon = (rank: string) => {
|
|
switch (rank) {
|
|
case '1':
|
|
return <Crown className="w-6 h-6 text-yellow-400" />;
|
|
case '2':
|
|
return <Crown className="w-6 h-6 text-gray-400" />;
|
|
case '3':
|
|
return <Crown className="w-6 h-6 text-yellow-600" />;
|
|
default:
|
|
return <span className="font-semibold text-gray-500 dark:text-gray-400">{rank}</span>;
|
|
}
|
|
};
|
|
|
|
if (isLoading) {
|
|
return <div className="text-center p-8">Loading Leaderboard...</div>;
|
|
}
|
|
|
|
if (error) {
|
|
return (
|
|
<div
|
|
className="bg-red-100 border-l-4 border-red-500 text-red-700 p-4 rounded-md"
|
|
role="alert"
|
|
>
|
|
<div className="flex items-center">
|
|
<ShieldAlert className="h-6 w-6 mr-3" />
|
|
<p className="font-bold">Error: {error.message}</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<div className="bg-white dark:bg-gray-800 shadow-lg rounded-lg p-6 transition-colors hover:bg-gray-50 dark:hover:bg-gray-800/80">
|
|
<h2 className="text-2xl font-bold text-gray-900 dark:text-white mb-4 flex items-center">
|
|
<Award className="w-6 h-6 mr-2 text-blue-500" />
|
|
Top Users
|
|
</h2>
|
|
{leaderboard.length === 0 ? (
|
|
<p className="text-gray-500 dark:text-gray-400">
|
|
The leaderboard is currently empty. Be the first to earn points!
|
|
</p>
|
|
) : (
|
|
<ol className="space-y-4">
|
|
{leaderboard.map((user) => (
|
|
<li
|
|
key={user.user_id}
|
|
className="flex items-center space-x-4 p-3 bg-gray-50 dark:bg-gray-700 rounded-lg transition-colors hover:bg-brand-light/30 dark:hover:bg-brand-dark/20"
|
|
>
|
|
<div className="shrink-0 w-8 text-center">{getRankIcon(user.rank)}</div>
|
|
<img
|
|
src={
|
|
user.avatar_url ||
|
|
`https://api.dicebear.com/8.x/initials/svg?seed=${user.full_name || user.user_id}`
|
|
}
|
|
alt={user.full_name || 'User Avatar'}
|
|
className="w-12 h-12 rounded-full object-cover"
|
|
/>
|
|
<div className="flex-1">
|
|
<p className="font-semibold text-gray-800 dark:text-gray-100">
|
|
{user.full_name || 'Anonymous User'}
|
|
</p>
|
|
</div>
|
|
<div className="text-lg font-bold text-blue-600 dark:text-blue-400">
|
|
{user.points} pts
|
|
</div>
|
|
</li>
|
|
))}
|
|
</ol>
|
|
)}
|
|
</div>
|
|
);
|
|
};
|
|
|
|
export default Leaderboard;
|