Skip to content

Commit 82e1be2

Browse files
committed
refactor(leaderboard): redesign UI, remove tabs & implement i18n
1 parent 612db19 commit 82e1be2

7 files changed

Lines changed: 99 additions & 121 deletions

File tree

frontend/components/leaderboard/LeaderboardClient.tsx

Lines changed: 18 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
'use client';
22

3-
import { useState } from 'react';
4-
import { LeaderboardTabs } from './LeaderboardTabs';
3+
import { useTranslations } from 'next-intl';
54
import { LeaderboardPodium } from './LeaderboardPodium';
65
import { LeaderboardTable } from './LeaderboardTable';
76
import { User } from './types';
@@ -13,7 +12,7 @@ interface LeaderboardClientProps {
1312
export default function LeaderboardClient({
1413
initialUsers,
1514
}: LeaderboardClientProps) {
16-
const [activeTab, setActiveTab] = useState('Overall');
15+
const t = useTranslations('leaderboard');
1716

1817
const usersWithPoints = initialUsers.filter(user => user.points > 0);
1918
const hasResults = usersWithPoints.length > 0;
@@ -37,7 +36,7 @@ export default function LeaderboardClient({
3736
</div>
3837

3938
<div className="relative max-w-4xl mx-auto px-4 py-12 flex flex-col items-center z-10">
40-
<header className="text-center mb-12 animate-in fade-in slide-in-from-top-4 duration-700">
39+
<header className="text-center mb-10 animate-in fade-in slide-in-from-top-4 duration-700">
4140
<div className="inline-flex items-center justify-center p-3 mb-6 rounded-full bg-white/50 dark:bg-slate-800/50 backdrop-blur-sm shadow-sm ring-1 ring-slate-200/50 dark:ring-slate-700/50">
4241
<span className="text-2xl mr-2" role="img" aria-label="Trophy">
4342
🏆
@@ -46,40 +45,35 @@ export default function LeaderboardClient({
4645
Champions Arena
4746
</span>
4847
</div>
49-
<h1 className="text-4xl md:text-6xl font-extrabold mb-4 tracking-tight text-slate-900 dark:text-white drop-shadow-sm">
50-
Community{' '}
48+
49+
<h1 className="text-5xl md:text-7xl font-black mb-6 tracking-tighter drop-shadow-sm">
5150
<span className="bg-gradient-to-r from-sky-500 via-indigo-500 to-pink-500 bg-clip-text text-transparent">
52-
Leaderboard
51+
{t('title')}
5352
</span>
5453
</h1>
55-
<p className="text-slate-600 dark:text-slate-400 font-medium text-lg">
56-
Top performers of the community.
54+
55+
<p className="text-slate-600 dark:text-slate-400 font-medium text-lg max-w-md mx-auto leading-relaxed">
56+
{t('subtitle')}
5757
</p>
5858
</header>
5959

60-
<div className="mb-10 w-full flex justify-center">
61-
<LeaderboardTabs activeTab={activeTab} onTabChange={setActiveTab} />
62-
</div>
63-
64-
<section
65-
className="w-full flex flex-col items-center"
66-
role="tabpanel"
67-
id="leaderboard-panel"
68-
aria-label={`Results for ${activeTab}`}
69-
>
70-
<div className="w-full mb-12">
60+
<div className="w-full flex flex-col items-center">
61+
<div className="w-full mb-16">
7162
{hasResults ? (
7263
<LeaderboardPodium topThree={topThree} />
7364
) : (
7465
<div className="text-center py-16" role="status">
75-
<p className="text-6xl mb-4" aria-hidden="true">
66+
<p
67+
className="text-6xl mb-4 grayscale opacity-50"
68+
aria-hidden="true"
69+
>
7670
🏆
7771
</p>
7872
<h2 className="text-2xl font-bold text-slate-800 dark:text-slate-100 mb-2">
79-
No results yet
73+
{t('noResults')}
8074
</h2>
8175
<p className="text-slate-600 dark:text-slate-400">
82-
Be the first to complete a quiz and claim the top spot!
76+
{t('beFirst')}
8377
</p>
8478
</div>
8579
)}
@@ -88,7 +82,7 @@ export default function LeaderboardClient({
8882
<div className="w-full animate-in fade-in slide-in-from-bottom-8 duration-700 delay-200">
8983
{hasResults && <LeaderboardTable users={otherUsers} />}
9084
</div>
91-
</section>
85+
</div>
9286
</div>
9387
</div>
9488
);

frontend/components/leaderboard/LeaderboardPodium.tsx

Lines changed: 27 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ import { User } from './types';
88
export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
99
return (
1010
<ol
11-
className="flex items-end justify-center gap-4 md:gap-8 pb-4 pt-8 min-h-[350px] list-none m-0 p-0"
11+
className="flex items-end justify-center gap-2 md:gap-8 pb-4 pt-16 min-h-[340px] list-none m-0 p-0"
1212
aria-label="Top 3 Leaders"
1313
>
1414
{topThree.map(user => {
@@ -21,35 +21,32 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
2121
<li
2222
key={user.id}
2323
className={cn(
24-
'flex flex-col items-center transition-all duration-500 relative',
25-
isFirst ? 'order-2 z-10 -mt-8' : 'z-0',
24+
'flex flex-col items-center transition-all duration-500 relative z-0',
25+
isFirst ? 'order-2 z-10 -mt-8 md:-mt-12' : '',
2626
isSecond ? 'order-1' : '',
2727
isThird ? 'order-3' : ''
2828
)}
2929
>
30-
<div className="flex flex-col items-center">
31-
<div className="relative mb-4 group cursor-pointer">
30+
<div className="flex flex-col items-center group">
31+
<div className="relative mb-3 md:mb-5 transition-transform duration-300 group-hover:scale-105">
3232
{isFirst && (
3333
<Crown
34-
className="absolute -top-12 left-1/2 -translate-x-1/2 w-10 h-10 text-yellow-400 animate-bounce drop-shadow-lg"
34+
className="absolute -top-10 md:-top-12 left-1/2 -translate-x-1/2 w-10 h-10 md:w-12 md:h-12 text-yellow-400 animate-bounce drop-shadow-[0_0_15px_rgba(250,204,21,0.5)]"
3535
aria-hidden="true"
3636
/>
3737
)}
38+
3839
<div
3940
className={cn(
40-
'relative rounded-full p-1 transition-transform duration-300 group-hover:scale-105',
41+
'relative rounded-full p-[3px] shadow-2xl',
4142
isFirst
42-
? 'bg-gradient-to-tr from-yellow-300 via-yellow-100 to-yellow-500 shadow-yellow-500/50 shadow-lg'
43-
: '',
44-
isSecond
45-
? 'bg-gradient-to-tr from-slate-300 via-slate-100 to-slate-400 shadow-slate-400/50 shadow-md'
46-
: '',
47-
isThird
48-
? 'bg-gradient-to-tr from-orange-300 via-orange-100 to-orange-400 shadow-orange-400/50 shadow-md'
49-
: ''
43+
? 'bg-gradient-to-tr from-yellow-300 via-yellow-100 to-yellow-500'
44+
: isSecond
45+
? 'bg-gradient-to-tr from-slate-300 via-slate-100 to-slate-400'
46+
: 'bg-gradient-to-tr from-orange-300 via-orange-100 to-orange-400'
5047
)}
5148
>
52-
<div className="relative w-16 h-16 md:w-20 md:h-20 rounded-full overflow-hidden border-2 border-white dark:border-slate-800 bg-slate-200 dark:bg-slate-800">
49+
<div className="relative w-16 h-16 md:w-24 md:h-24 rounded-full overflow-hidden border-4 border-white dark:border-slate-900 bg-slate-200">
5350
<Image
5451
src={user.avatar}
5552
alt={`${user.username}'s avatar`}
@@ -61,7 +58,7 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
6158

6259
<div
6360
className={cn(
64-
'absolute -bottom-3 left-1/2 -translate-x-1/2 flex items-center justify-center w-8 h-8 rounded-full font-bold border-2 border-white dark:border-slate-900 shadow-md',
61+
'absolute -bottom-3 left-1/2 -translate-x-1/2 flex items-center justify-center w-7 h-7 md:w-9 md:h-9 rounded-full font-bold text-xs md:text-sm border-[3px] border-white dark:border-slate-900 shadow-lg',
6562
isFirst
6663
? 'bg-yellow-500 text-white'
6764
: isSecond
@@ -73,32 +70,28 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
7370
</div>
7471
</div>
7572

76-
<div className="text-center mb-3">
77-
<div className="font-bold text-slate-800 dark:text-slate-100 text-base mb-1 truncate max-w-[120px]">
73+
<div className="text-center mb-3 md:mb-5">
74+
<div className="font-bold text-slate-800 dark:text-slate-100 text-xs md:text-lg mb-1 truncate max-w-[85px] md:max-w-[140px]">
7875
{user.username}
7976
</div>
80-
<div className="font-mono font-bold text-xl text-slate-900 dark:text-white tracking-tight">
81-
{user.points}{' '}
82-
<span className="text-xs font-normal text-slate-500">
83-
pts
84-
</span>
77+
<div className="inline-block px-2 py-0.5 rounded-full bg-slate-100 dark:bg-slate-800 border border-slate-200 dark:border-slate-700">
78+
<div className="font-mono font-bold text-xs md:text-sm text-slate-700 dark:text-slate-300">
79+
{user.points} <span className="text-slate-400">pts</span>
80+
</div>
8581
</div>
8682
</div>
8783

8884
<div
8985
aria-hidden="true"
9086
className={cn(
91-
'w-24 md:w-36 rounded-t-2xl border-x border-t shadow-lg backdrop-blur-sm',
92-
'bg-gradient-to-b from-white/80 via-white/40 to-transparent dark:from-slate-800/80 dark:via-slate-800/40',
87+
'w-20 md:w-40 rounded-t-2xl backdrop-blur-xl transition-all duration-300',
88+
'border-t-2 border-x-2 border-white/60 dark:border-white/10',
89+
9390
isFirst
94-
? 'h-40 border-yellow-400/30 dark:border-yellow-500/30'
95-
: '',
96-
isSecond
97-
? 'h-24 border-slate-300/30 dark:border-slate-500/30'
98-
: '',
99-
isThird
100-
? 'h-20 border-orange-300/30 dark:border-orange-500/30'
101-
: ''
91+
? 'h-40 md:h-56 bg-gradient-to-b from-yellow-200/30 to-yellow-500/5 dark:from-yellow-400/20 dark:to-transparent shadow-[0_0_50px_-10px_rgba(234,179,8,0.4)]'
92+
: isSecond
93+
? 'h-28 md:h-40 bg-gradient-to-b from-slate-200/30 to-slate-500/5 dark:from-slate-400/20 dark:to-transparent shadow-[0_0_40px_-10px_rgba(148,163,184,0.3)]'
94+
: 'h-20 md:h-28 bg-gradient-to-b from-orange-200/30 to-orange-500/5 dark:from-orange-400/20 dark:to-transparent shadow-[0_0_40px_-10px_rgba(251,146,60,0.3)]'
10295
)}
10396
/>
10497
</div>

frontend/components/leaderboard/LeaderboardTable.tsx

Lines changed: 24 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
'use client';
22

33
import { TrendingUp } from 'lucide-react';
4+
import { useTranslations } from 'next-intl';
45
import { User } from './types';
56

67
export function LeaderboardTable({ users }: { users: User[] }) {
8+
const t = useTranslations('leaderboard');
9+
710
return (
8-
<div className="w-full bg-white/70 dark:bg-slate-900/60 backdrop-blur-md rounded-2xl shadow-xl border border-slate-200/60 dark:border-slate-700/60 overflow-hidden">
11+
<div className="w-full bg-white/60 dark:bg-slate-900/60 backdrop-blur-xl rounded-3xl shadow-2xl border border-white/50 dark:border-slate-700/50 overflow-hidden">
912
<table className="w-full text-left border-collapse">
1013
<caption className="sr-only">
1114
Leaderboard ranking for other participants
@@ -15,57 +18,60 @@ export function LeaderboardTable({ users }: { users: User[] }) {
1518
<tr>
1619
<th
1720
scope="col"
18-
className="px-6 py-4 text-center text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider w-[16%]"
21+
className="px-6 py-5 text-center text-xs font-extrabold text-slate-400 dark:text-slate-500 uppercase tracking-widest w-[15%]"
1922
>
20-
Rank
23+
{t('rank')}
2124
</th>
2225
<th
2326
scope="col"
24-
className="px-6 py-4 text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider w-[58%]"
27+
className="px-6 py-5 text-xs font-extrabold text-slate-400 dark:text-slate-500 uppercase tracking-widest w-[60%]"
2528
>
26-
UserName
29+
{t('user')}
2730
</th>
2831
<th
2932
scope="col"
30-
className="px-6 py-4 text-right text-xs font-bold text-slate-500 dark:text-slate-400 uppercase tracking-wider w-[25%]"
33+
className="px-6 py-5 text-right text-xs font-extrabold text-slate-400 dark:text-slate-500 uppercase tracking-widest w-[25%]"
3134
>
32-
Points
35+
{t('score')}
3336
</th>
3437
</tr>
3538
</thead>
3639

37-
<tbody className="divide-y divide-slate-100 dark:divide-slate-800">
40+
<tbody className="divide-y divide-slate-100 dark:divide-slate-800/50">
3841
{users.map(user => (
3942
<tr
4043
key={user.id}
41-
className="hover:bg-slate-50/80 dark:hover:bg-slate-800/60 transition-colors group"
44+
className="group transition-colors hover:bg-white/80 dark:hover:bg-slate-800/80"
4245
>
43-
<td className="px-6 py-4 text-center font-mono font-semibold text-slate-400 dark:text-slate-500 group-hover:text-slate-900 dark:group-hover:text-slate-200">
44-
{user.rank}
46+
<td className="px-6 py-4 text-center font-bold text-slate-500 dark:text-slate-400 group-hover:text-slate-900 dark:group-hover:text-white transition-colors">
47+
#{user.rank}
4548
</td>
4649

4750
<td className="px-6 py-4">
4851
<div className="flex items-center gap-4">
4952
<div
50-
className="w-10 h-10 rounded-full bg-slate-100 dark:bg-slate-800 flex items-center justify-center text-sm font-bold text-slate-600 dark:text-slate-300 ring-2 ring-transparent group-hover:ring-slate-200 dark:group-hover:ring-slate-600 transition-all"
53+
className="w-10 h-10 rounded-full bg-gradient-to-br from-slate-100 to-slate-200 dark:from-slate-800 dark:to-slate-900 flex items-center justify-center text-sm font-bold text-slate-600 dark:text-slate-300 shadow-inner group-hover:scale-110 transition-transform duration-300"
5154
aria-hidden="true"
5255
>
53-
<span>{user.username.slice(0, 1).toUpperCase()}</span>
56+
{user.username.slice(0, 1).toUpperCase()}
5457
</div>
58+
5559
<div className="flex flex-col">
56-
<span className="font-bold text-slate-700 dark:text-slate-200 text-sm group-hover:text-blue-600 dark:group-hover:text-blue-400 transition-colors">
60+
<span className="font-bold text-slate-700 dark:text-slate-200 text-sm group-hover:text-indigo-600 dark:group-hover:text-indigo-400 transition-colors">
5761
{user.username}
5862
</span>
59-
<span className="flex items-center gap-1 text-[10px] text-emerald-600 dark:text-emerald-400 font-medium">
63+
<span className="flex items-center gap-1 text-[10px] text-emerald-600 dark:text-emerald-400 font-bold uppercase tracking-wide opacity-0 group-hover:opacity-100 transition-opacity">
6064
<TrendingUp className="w-3 h-3" aria-hidden="true" />
61-
Rising
65+
{t('rising')}
6266
</span>
6367
</div>
6468
</div>
6569
</td>
6670

67-
<td className="px-6 py-4 text-right font-mono font-bold text-slate-800 dark:text-slate-100">
68-
{user.points.toLocaleString()}
71+
<td className="px-6 py-4 text-right">
72+
<span className="font-mono font-bold text-slate-800 dark:text-slate-100 group-hover:scale-105 inline-block transition-transform">
73+
{user.points}
74+
</span>
6975
</td>
7076
</tr>
7177
))}

frontend/components/leaderboard/LeaderboardTabs.tsx

Lines changed: 0 additions & 45 deletions
This file was deleted.

frontend/messages/en.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,15 @@
280280
"policyLink": "Privacy Policy",
281281
"accept": "Accept Cookies",
282282
"decline": "Decline"
283+
},
284+
"leaderboard": {
285+
"title": "Leaderboard",
286+
"subtitle": "Top performers of all time. Compete, learn, and rise to the top!",
287+
"noResults": "No results yet",
288+
"beFirst": "Be the first to complete a quiz and claim the top spot!",
289+
"rank": "Rank",
290+
"user": "User",
291+
"score": "Score",
292+
"rising": "Rising"
283293
}
284294
}

frontend/messages/pl.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,5 +157,15 @@
157157
"policyLink": "Politykę Prywatności",
158158
"accept": "Zaakceptuj",
159159
"decline": "Odrzuć"
160+
},
161+
"leaderboard": {
162+
"title": "Ranking",
163+
"subtitle": "Najlepsi uczestnicy wszech czasów. Rywalizuj, ucz się i zwyciężaj!",
164+
"noResults": "Brak wyników",
165+
"beFirst": "Bądź pierwszy, który ukończy quiz i zajmie pierwsze miejsce!",
166+
"rank": "Miejsce",
167+
"user": "Użytkownik",
168+
"score": "Wynik",
169+
"rising": "Wzrost"
160170
}
161171
}

frontend/messages/uk.json

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -280,5 +280,15 @@
280280
"policyLink": "Політику конфіденційності",
281281
"accept": "Прийняти",
282282
"decline": "Відхилити"
283+
},
284+
"leaderboard": {
285+
"title": "Рейтинг",
286+
"subtitle": "Найкращі учасники за весь час. Змагайся, навчайся та перемагай!",
287+
"noResults": "Поки що немає результатів",
288+
"beFirst": "Стань першим, хто пройде квіз і займе топ-позицію!",
289+
"rank": "Місце",
290+
"user": "Користувач",
291+
"score": "Бали",
292+
"rising": "Зростання"
283293
}
284294
}

0 commit comments

Comments
 (0)