website refactor

This commit is contained in:
2026-01-15 19:55:46 +01:00
parent 5ef149b782
commit ce7be39155
154 changed files with 436 additions and 356 deletions

View File

@@ -0,0 +1,71 @@
import React from 'react';
import { Card } from '@/ui/Card';
import { RankBadge } from '@/ui/RankBadge';
import { DriverIdentity } from '@/components/drivers/DriverIdentity';
import { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { Stack } from '@/ui/Stack';
import { DriverStats } from '@/ui/DriverStats';
import { routes } from '@/lib/routing/RouteConfig';
export interface DriverCardProps {
id: string;
name: string;
rating: number;
skillLevel: 'beginner' | 'intermediate' | 'advanced' | 'pro';
nationality: string;
racesCompleted: number;
wins: number;
podiums: number;
rank: number;
onClick?: () => void;
}
export function DriverCard(props: DriverCardProps) {
const {
id,
name,
rating,
nationality,
racesCompleted,
wins,
podiums,
rank,
onClick,
} = props;
// Create a proper DriverViewModel instance
const driverViewModel = new DriverViewModel({
id,
name,
avatarUrl: null,
});
const winRate = racesCompleted > 0 ? ((wins / racesCompleted) * 100).toFixed(0) : '0';
return (
<Card
onClick={onClick}
transition
>
<Stack direction="row" align="center" justify="between">
<Stack direction="row" align="center" gap={4} flexGrow={1}>
<RankBadge rank={rank} size="lg" />
<DriverIdentity
driver={driverViewModel}
href={routes.driver.detail(id)}
meta={`${nationality}${racesCompleted} races`}
size="md"
/>
</Stack>
<DriverStats
rating={rating}
wins={wins}
podiums={podiums}
winRate={winRate}
/>
</Stack>
</Card>
);
}

View File

@@ -0,0 +1,118 @@
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
import { Zap } from 'lucide-react';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Icon } from '@/ui/Icon';
import { Image } from '@/ui/Image';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface DriverEntryRowProps {
index: number;
name: string;
avatarUrl: string;
country: string;
rating?: number | null;
isCurrentUser: boolean;
onClick: () => void;
}
export function DriverEntryRow({
index,
name,
avatarUrl,
country,
rating,
isCurrentUser,
onClick,
}: DriverEntryRowProps) {
return (
<Box
onClick={onClick}
display="flex"
alignItems="center"
gap={3}
p={3}
rounded="xl"
cursor="pointer"
transition
bg={isCurrentUser ? 'bg-primary-blue/10' : 'transparent'}
border
borderColor={isCurrentUser ? 'border-primary-blue/30' : 'transparent'}
hoverBorderColor={isCurrentUser ? 'primary-blue/40' : 'charcoal-outline/20'}
>
<Box
display="flex"
alignItems="center"
justifyContent="center"
w="8"
h="8"
rounded="lg"
bg="bg-iron-gray"
color="text-gray-500"
style={{ fontWeight: 'bold', fontSize: '0.875rem' }}
>
{index + 1}
</Box>
<Box position="relative" flexShrink={0}>
<Box
w="10"
h="10"
rounded="full"
overflow="hidden"
border={isCurrentUser}
borderColor={isCurrentUser ? 'border-primary-blue' : ''}
>
<Image
src={avatarUrl}
alt={name}
width={40}
height={40}
objectFit="cover"
fill
/>
</Box>
<Box
position="absolute"
bottom="-0.5"
right="-0.5"
w="5"
h="5"
rounded="full"
bg="bg-deep-graphite"
display="flex"
alignItems="center"
justifyContent="center"
style={{ fontSize: '0.625rem' }}
>
{CountryFlagDisplay.fromCountryCode(country).toString()}
</Box>
</Box>
<Box flexGrow={1} minWidth="0">
<Stack direction="row" align="center" gap={2}>
<Text
weight="semibold"
size="sm"
color={isCurrentUser ? 'text-primary-blue' : 'text-white'}
truncate
>
{name}
</Text>
{isCurrentUser && <Badge variant="primary">You</Badge>}
</Stack>
<Text size="xs" color="text-gray-500">{country}</Text>
</Box>
{rating != null && (
<Badge variant="warning">
<Icon icon={Zap} size={3} />
{rating}
</Badge>
)}
</Box>
);
}

View File

@@ -0,0 +1,84 @@
import Link from 'next/link';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Badge } from '@/ui/Badge';
import { Image } from '@/ui/Image';
export interface DriverIdentityProps {
driver: {
id: string;
name: string;
avatarUrl: string | null;
};
href?: string;
contextLabel?: React.ReactNode;
meta?: React.ReactNode;
size?: 'sm' | 'md';
}
export function DriverIdentity(props: DriverIdentityProps) {
const { driver, href, contextLabel, meta, size = 'md' } = props;
const avatarSize = size === 'sm' ? 40 : 48;
const nameSize = size === 'sm' ? 'sm' : 'base';
const avatarUrl = driver.avatarUrl;
const content = (
<Box display="flex" alignItems="center" gap={{ base: 3, md: 4 }} flexGrow={1} minWidth="0">
<Box
rounded="full"
bg="bg-primary-blue/20"
overflow="hidden"
display="flex"
alignItems="center"
justifyContent="center"
flexShrink={0}
style={{ width: avatarSize, height: avatarSize }}
>
{avatarUrl ? (
<Image
src={avatarUrl}
alt={driver.name}
width={avatarSize}
height={avatarSize}
fullWidth
fullHeight
objectFit="cover"
/>
) : (
<PlaceholderImage size={avatarSize} />
)}
</Box>
<Box flexGrow={1} minWidth="0">
<Box display="flex" alignItems="center" gap={2} minWidth="0">
<Text size={nameSize} weight="medium" color="text-white" className="truncate">
{driver.name}
</Text>
{contextLabel && (
<Badge variant="default" className="bg-charcoal-outline/60 text-[10px] md:text-xs">
{contextLabel}
</Badge>
)}
</Box>
{meta && (
<Text size="xs" color="text-gray-400" mt={0.5} className="truncate" block>
{meta}
</Text>
)}
</Box>
</Box>
);
if (href) {
return (
<Link href={href} className="flex items-center gap-3 md:gap-4 flex-1 min-w-0">
{content}
</Link>
);
}
return <Box display="flex" alignItems="center" gap={{ base: 3, md: 4 }} flexGrow={1} minWidth="0">{content}</Box>;
}

View File

@@ -7,10 +7,10 @@ import { Text } from '@/ui/Text';
import { Heading } from '@/ui/Heading';
import { Stack } from '@/ui/Stack';
import { StatCard } from '@/ui/StatCard';
import { ProfileHeader } from '@/ui/ProfileHeader';
import { ProfileHeader } from '@/components/drivers/ProfileHeader';
import { ProfileStats } from './ProfileStats';
import { CareerHighlights } from '@/ui/CareerHighlights';
import { DriverRankings } from '@/ui/DriverRankings';
import { DriverRankings } from '@/components/drivers/DriverRankings';
import { PerformanceMetrics } from '@/ui/PerformanceMetrics';
import { useDriverProfile } from "@/hooks/driver/useDriverProfile";

View File

@@ -0,0 +1,52 @@
import React from 'react';
import { Card } from '@/ui/Card';
import { Heading } from '@/ui/Heading';
import { RankingListItem } from '@/components/leaderboards/RankingListItem';
import { RankingList } from '@/components/leaderboards/RankingList';
import { MinimalEmptyState } from '@/components/shared/state/EmptyState';
export interface DriverRanking {
type: 'overall' | 'league';
name: string;
rank: number;
totalDrivers: number;
percentile: number;
rating: number;
}
interface DriverRankingsProps {
rankings: DriverRanking[];
}
export function DriverRankings({ rankings }: DriverRankingsProps) {
if (!rankings || rankings.length === 0) {
return (
<Card bg="bg-iron-gray/60" borderColor="border-charcoal-outline/80" p={4}>
<Heading level={3} mb={2}>Rankings</Heading>
<MinimalEmptyState
title="No ranking data available yet"
description="Compete in leagues to earn your first results."
/>
</Card>
);
}
return (
<Card bg="bg-iron-gray/60" borderColor="border-charcoal-outline/80" p={4}>
<Heading level={3} mb={4}>Rankings</Heading>
<RankingList>
{rankings.map((ranking, index) => (
<RankingListItem
key={`${ranking.type}-${ranking.name}-${index}`}
name={ranking.name}
type={ranking.type}
rank={ranking.rank}
totalDrivers={ranking.totalDrivers}
percentile={ranking.percentile}
rating={ranking.rating}
/>
))}
</RankingList>
</Card>
);
}

View File

@@ -0,0 +1,28 @@
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { DriverRatingPill } from '@/ui/DriverRatingPill';
import { DriverSummaryPill as UiDriverSummaryPill } from '@/ui/DriverSummaryPill';
export interface DriverSummaryPillProps {
driver: DriverViewModel;
rating: number | null;
rank: number | null;
avatarSrc?: string | null;
onClick?: () => void;
href?: string;
}
export function DriverSummaryPill(props: DriverSummaryPillProps) {
const { driver, rating, rank, avatarSrc, onClick, href } = props;
return (
<UiDriverSummaryPill
name={driver.name}
avatarSrc={avatarSrc}
onClick={onClick}
href={href}
ratingComponent={<DriverRatingPill rating={rating} rank={rank} />}
/>
);
}

View File

@@ -0,0 +1,161 @@
import { mediaConfig } from '@/lib/config/mediaConfig';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Heading } from '@/ui/Heading';
import { Icon } from '@/ui/Icon';
import { Image } from '@/ui/Image';
import { MedalBadge } from '@/ui/MedalBadge';
import { MiniStat } from '@/ui/MiniStat';
import { Text } from '@/ui/Text';
import { Flag, Shield, Star, TrendingUp } from 'lucide-react';
const SKILL_LEVELS = [
{ id: 'pro', label: 'Pro', icon: Star, color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30', description: 'Elite competition level' },
{ id: 'advanced', label: 'Advanced', icon: Star, color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30', description: 'Highly competitive' },
{ id: 'intermediate', label: 'Intermediate', icon: TrendingUp, color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30', description: 'Developing skills' },
{ id: 'beginner', label: 'Beginner', icon: Shield, color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30', description: 'Learning the ropes' },
];
const CATEGORIES = [
{ id: 'beginner', label: 'Beginner', color: 'text-green-400', bgColor: 'bg-green-400/10', borderColor: 'border-green-400/30' },
{ id: 'intermediate', label: 'Intermediate', color: 'text-primary-blue', bgColor: 'bg-primary-blue/10', borderColor: 'border-primary-blue/30' },
{ id: 'advanced', label: 'Advanced', color: 'text-purple-400', bgColor: 'bg-purple-400/10', borderColor: 'border-purple-400/30' },
{ id: 'pro', label: 'Pro', color: 'text-yellow-400', bgColor: 'bg-yellow-400/10', borderColor: 'border-yellow-400/30' },
{ id: 'endurance', label: 'Endurance', color: 'text-orange-400', bgColor: 'bg-orange-400/10', borderColor: 'border-orange-400/30' },
{ id: 'sprint', label: 'Sprint', color: 'text-red-400', bgColor: 'bg-red-400/10', borderColor: 'border-red-400/30' },
];
interface FeaturedDriverCardProps {
driver: {
id: string;
name: string;
nationality: string;
avatarUrl?: string;
rating: number;
wins: number;
podiums: number;
skillLevel?: string;
category?: string;
};
position: number;
onClick: () => void;
}
export function FeaturedDriverCard({ driver, position, onClick }: FeaturedDriverCardProps) {
const levelConfig = SKILL_LEVELS.find((l) => l.id === driver.skillLevel);
const categoryConfig = CATEGORIES.find((c) => c.id === driver.category);
const getBorderColor = (pos: number) => {
switch (pos) {
case 1: return 'border-yellow-400/50';
case 2: return 'border-gray-300/50';
case 3: return 'border-amber-600/50';
default: return 'border-charcoal-outline';
}
};
const getHoverBorderColor = (pos: number) => {
switch (pos) {
case 1: return 'yellow-400';
case 2: return 'gray-300';
case 3: return 'amber-600';
default: return 'primary-blue';
}
};
return (
<Box
as="button"
type="button"
onClick={onClick}
p={5}
rounded="xl"
bg="bg-iron-gray/60"
border
borderColor={getBorderColor(position)}
hoverBorderColor={getHoverBorderColor(position)}
transition
textAlign="left"
cursor="pointer"
hoverScale
group
>
{/* Header with Position */}
<Box display="flex" alignItems="start" justifyContent="between" mb={4}>
<MedalBadge position={position} />
<Box display="flex" gap={2}>
{categoryConfig && (
<Badge
bg={categoryConfig.bgColor}
color={categoryConfig.color}
borderColor={categoryConfig.borderColor}
>
{categoryConfig.label}
</Badge>
)}
{levelConfig && (
<Badge
bg={levelConfig.bgColor}
color={levelConfig.color}
borderColor={levelConfig.borderColor}
>
{levelConfig.label}
</Badge>
)}
</Box>
</Box>
{/* Avatar & Name */}
<Box display="flex" alignItems="center" gap={4} mb={4}>
<Box
position="relative"
w="16"
h="16"
rounded="full"
overflow="hidden"
border
borderColor="border-charcoal-outline"
groupHoverBorderColor="primary-blue"
transition
>
<Image
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
alt={driver.name}
objectFit="cover"
fill
/>
</Box>
<Box>
<Heading level={3} groupHoverColor="primary-blue" transition>
{driver.name}
</Heading>
<Box display="flex" alignItems="center" gap={2}>
<Icon icon={Flag} size={3.5} color="rgb(107, 114, 128)" />
<Text size="sm" color="text-gray-500">{driver.nationality}</Text>
</Box>
</Box>
</Box>
{/* Stats */}
<Box display="grid" gridCols={3} gap={3}>
<MiniStat
label="Rating"
value={driver.rating.toLocaleString()}
color="text-primary-blue"
/>
<MiniStat
label="Wins"
value={driver.wins}
color="text-performance-green"
/>
<MiniStat
label="Podiums"
value={driver.podiums}
color="text-warning-amber"
/>
</Box>
</Box>
);
}

View File

@@ -0,0 +1,98 @@
import type { DriverViewModel } from '@/lib/view-models/DriverViewModel';
import { Badge } from '@/ui/Badge';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { CountryFlag } from '@/ui/CountryFlag';
import { DriverRatingPill } from '@/ui/DriverRatingPill';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { PlaceholderImage } from '@/ui/PlaceholderImage';
import { Stack } from '@/ui/Stack';
import { Text } from '@/ui/Text';
interface ProfileHeaderProps {
driver: DriverViewModel;
rating?: number | null;
rank?: number | null;
isOwnProfile?: boolean;
onEditClick?: () => void;
teamName?: string | null;
teamTag?: string | null;
}
export function ProfileHeader({
driver,
rating,
rank,
isOwnProfile = false,
onEditClick,
teamName,
teamTag,
}: ProfileHeaderProps) {
return (
<Box display="flex" alignItems="start" justifyContent="between">
<Box display="flex" alignItems="start" gap={4}>
<Box
w="20"
h="20"
rounded="full"
bg="bg-gradient-to-br from-primary-blue to-purple-600"
overflow="hidden"
display="flex"
alignItems="center"
justifyContent="center"
>
{driver.avatarUrl ? (
<Image
src={driver.avatarUrl}
alt={driver.name}
width={80}
height={80}
className="w-full h-full object-cover"
/>
) : (
<PlaceholderImage size={80} />
)}
</Box>
<Box>
<Box display="flex" alignItems="center" gap={3} mb={2}>
<Heading level={1}>{driver.name}</Heading>
{driver.country && <CountryFlag countryCode={driver.country} size="lg" />}
{teamTag && (
<Badge variant="primary">
{teamTag}
</Badge>
)}
</Box>
<Box display="flex" alignItems="center" gap={4}>
<Text size="sm" color="text-gray-400">iRacing ID: {driver.iracingId}</Text>
{teamName && (
<Stack direction="row" align="center" gap={2}>
<Text size="sm" color="text-gray-400"></Text>
<Text size="sm" color="text-primary-blue">
{teamTag ? `[${teamTag}] ${teamName}` : teamName}
</Text>
</Stack>
)}
</Box>
{(typeof rating === 'number' || typeof rank === 'number') && (
<Box mt={2}>
<DriverRatingPill rating={rating ?? null} rank={rank ?? null} />
</Box>
)}
</Box>
</Box>
{isOwnProfile && (
<Button variant="secondary" onClick={onEditClick}>
Edit Profile
</Button>
)}
</Box>
);
}

View File

@@ -0,0 +1,171 @@
import { mediaConfig } from '@/lib/config/mediaConfig';
import { CountryFlagDisplay } from '@/lib/display-objects/CountryFlagDisplay';
import { Box } from '@/ui/Box';
import { Button } from '@/ui/Button';
import { Heading } from '@/ui/Heading';
import { Image } from '@/ui/Image';
import { Link } from '@/ui/Link';
import { Stack } from '@/ui/Stack';
import { Surface } from '@/ui/Surface';
import { Text } from '@/ui/Text';
import { Calendar, Clock, ExternalLink, Globe, Star, Trophy, UserPlus } from 'lucide-react';
interface ProfileHeroProps {
driver: {
name: string;
avatarUrl?: string;
country: string;
iracingId: number;
joinedAt: string | Date;
};
stats: {
rating: number;
} | null;
globalRank: number;
timezone: string;
socialHandles: {
platform: string;
handle: string;
url: string;
}[];
onAddFriend: () => void;
friendRequestSent: boolean;
}
function getSocialIcon(platform: string) {
const { Twitter, Youtube, Twitch, MessageCircle } = require('lucide-react');
switch (platform) {
case 'twitter': return Twitter;
case 'youtube': return Youtube;
case 'twitch': return Twitch;
case 'discord': return MessageCircle;
default: return Globe;
}
}
export function ProfileHero({
driver,
stats,
globalRank,
timezone,
socialHandles,
onAddFriend,
friendRequestSent,
}: ProfileHeroProps) {
return (
<Surface variant="muted" rounded="2xl" border padding={6} style={{ background: 'linear-gradient(to bottom right, rgba(38, 38, 38, 0.8), rgba(38, 38, 38, 0.6), #0f1115)', borderColor: '#262626' }}>
<Stack direction="row" align="start" gap={6} wrap>
{/* Avatar */}
<Box style={{ position: 'relative' }}>
<Box style={{ width: '7rem', height: '7rem', borderRadius: '1rem', background: 'linear-gradient(to bottom right, #3b82f6, #9333ea)', padding: '0.25rem', boxShadow: '0 20px 25px -5px rgba(59, 130, 246, 0.2)' }}>
<Box style={{ width: '100%', height: '100%', borderRadius: '0.75rem', overflow: 'hidden', backgroundColor: '#262626' }}>
<Image
src={driver.avatarUrl || mediaConfig.avatars.defaultFallback}
alt={driver.name}
width={144}
height={144}
style={{ width: '100%', height: '100%', objectFit: 'cover' }}
/>
</Box>
</Box>
</Box>
{/* Driver Info */}
<Box style={{ flex: 1, minWidth: 0 }}>
<Stack direction="row" align="center" gap={3} wrap mb={2}>
<Heading level={1}>{driver.name}</Heading>
<Text size="4xl" aria-label={`Country: ${driver.country}`}>
{CountryFlagDisplay.fromCountryCode(driver.country).toString()}
</Text>
</Stack>
{/* Rating and Rank */}
<Stack direction="row" align="center" gap={4} wrap mb={4}>
{stats && (
<>
<Surface variant="muted" rounded="lg" padding={1} style={{ backgroundColor: 'rgba(59, 130, 246, 0.1)', border: '1px solid rgba(59, 130, 246, 0.3)', paddingLeft: '0.75rem', paddingRight: '0.75rem' }}>
<Stack direction="row" align="center" gap={2}>
<Star style={{ width: '1rem', height: '1rem', color: '#3b82f6' }} />
<Text font="mono" weight="bold" color="text-primary-blue">{stats.rating}</Text>
<Text size="xs" color="text-gray-400">Rating</Text>
</Stack>
</Surface>
<Surface variant="muted" rounded="lg" padding={1} style={{ backgroundColor: 'rgba(250, 204, 21, 0.1)', border: '1px solid rgba(250, 204, 21, 0.3)', paddingLeft: '0.75rem', paddingRight: '0.75rem' }}>
<Stack direction="row" align="center" gap={2}>
<Trophy style={{ width: '1rem', height: '1rem', color: '#facc15' }} />
<Text font="mono" weight="bold" style={{ color: '#facc15' }}>#{globalRank}</Text>
<Text size="xs" color="text-gray-400">Global</Text>
</Stack>
</Surface>
</>
)}
</Stack>
{/* Meta info */}
<Stack direction="row" align="center" gap={4} wrap style={{ fontSize: '0.875rem', color: '#9ca3af' }}>
<Stack direction="row" align="center" gap={1.5}>
<Globe style={{ width: '1rem', height: '1rem' }} />
<Text size="sm">iRacing: {driver.iracingId}</Text>
</Stack>
<Stack direction="row" align="center" gap={1.5}>
<Calendar style={{ width: '1rem', height: '1rem' }} />
<Text size="sm">
Joined{' '}
{new Date(driver.joinedAt).toLocaleDateString('en-US', {
month: 'short',
year: 'numeric',
})}
</Text>
</Stack>
<Stack direction="row" align="center" gap={1.5}>
<Clock style={{ width: '1rem', height: '1rem' }} />
<Text size="sm">{timezone}</Text>
</Stack>
</Stack>
</Box>
{/* Action Buttons */}
<Box>
<Button
variant="primary"
onClick={onAddFriend}
disabled={friendRequestSent}
icon={<UserPlus style={{ width: '1rem', height: '1rem' }} />}
>
{friendRequestSent ? 'Request Sent' : 'Add Friend'}
</Button>
</Box>
</Stack>
{/* Social Handles */}
{socialHandles.length > 0 && (
<Box mt={6} pt={6} style={{ borderTop: '1px solid rgba(38, 38, 38, 0.5)' }}>
<Stack direction="row" align="center" gap={2} wrap>
<Text size="sm" color="text-gray-500" style={{ marginRight: '0.5rem' }}>Connect:</Text>
{socialHandles.map((social) => {
const Icon = getSocialIcon(social.platform);
return (
<Box key={social.platform}>
<Link
href={social.url}
target="_blank"
rel="noopener noreferrer"
variant="ghost"
>
<Surface variant="muted" rounded="lg" padding={1} style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', paddingLeft: '0.75rem', paddingRight: '0.75rem', backgroundColor: 'rgba(38, 38, 38, 0.5)', border: '1px solid #262626', color: '#9ca3af' }}>
<Icon style={{ width: '1rem', height: '1rem' }} />
<Text size="sm">{social.handle}</Text>
<ExternalLink style={{ width: '0.75rem', height: '0.75rem', opacity: 0.5 }} />
</Surface>
</Link>
</Box>
);
})}
</Stack>
</Box>
)}
</Surface>
);
}

View File

@@ -6,8 +6,8 @@ import { Button } from '@/ui/Button';
import { Box } from '@/ui/Box';
import { Text } from '@/ui/Text';
import { Stack } from '@/ui/Stack';
import { LoadingWrapper } from '@/ui/LoadingWrapper';
import { EmptyState } from '@/ui/EmptyState';
import { LoadingWrapper } from '@/components/shared/state/LoadingWrapper';
import { EmptyState } from '@/components/shared/state/EmptyState';
import { Pagination } from '@/ui/Pagination';
import { Trophy } from 'lucide-react';