From 612db19c92f714498f4490a442eb9cacab75d1c4 Mon Sep 17 00:00:00 2001 From: AlinaRyabova Date: Mon, 19 Jan 2026 17:06:12 +0200 Subject: [PATCH 1/2] fix: resolve accessibility issues (W3C, Lighthouse 100%, Keyboard nav, VoiceOver) --- frontend/app/[locale]/leaderboard/page.tsx | 6 +++ frontend/components/header/AppMobileMenu.tsx | 2 +- .../leaderboard/LeaderboardClient.tsx | 40 ++++++++------- .../leaderboard/LeaderboardPodium.tsx | 50 ++++++++----------- .../leaderboard/LeaderboardTabs.tsx | 5 +- frontend/components/shared/CookieBanner.tsx | 4 +- frontend/package-lock.json | 1 + 7 files changed, 56 insertions(+), 52 deletions(-) diff --git a/frontend/app/[locale]/leaderboard/page.tsx b/frontend/app/[locale]/leaderboard/page.tsx index af5fff5e..ffedf790 100644 --- a/frontend/app/[locale]/leaderboard/page.tsx +++ b/frontend/app/[locale]/leaderboard/page.tsx @@ -1,5 +1,11 @@ import { getLeaderboardData } from '@/db/queries/leaderboard'; import LeaderboardClient from '@/components/leaderboard/LeaderboardClient'; +import { Metadata } from 'next'; + +export const metadata: Metadata = { + title: 'Leaderboard | DevLovers', + description: 'Top performers of the community', +}; export const revalidate = 3600; diff --git a/frontend/components/header/AppMobileMenu.tsx b/frontend/components/header/AppMobileMenu.tsx index 46afcb3b..74fa8aea 100644 --- a/frontend/components/header/AppMobileMenu.tsx +++ b/frontend/components/header/AppMobileMenu.tsx @@ -54,7 +54,7 @@ export function AppMobileMenu({ className="flex h-9 w-9 items-center justify-center rounded-md text-muted-foreground transition-colors hover:bg-secondary hover:text-foreground" aria-label="Toggle menu" aria-expanded={open ? 'true' : 'false'} - aria-controls="app-mobile-nav" + aria-controls={open ? 'app-mobile-nav' : undefined} > {open ? : } diff --git a/frontend/components/leaderboard/LeaderboardClient.tsx b/frontend/components/leaderboard/LeaderboardClient.tsx index a3a90b96..c15eb595 100644 --- a/frontend/components/leaderboard/LeaderboardClient.tsx +++ b/frontend/components/leaderboard/LeaderboardClient.tsx @@ -5,7 +5,6 @@ import { LeaderboardTabs } from './LeaderboardTabs'; import { LeaderboardPodium } from './LeaderboardPodium'; import { LeaderboardTable } from './LeaderboardTable'; import { User } from './types'; -import { Trophy } from 'lucide-react'; interface LeaderboardClientProps { initialUsers: User[]; @@ -32,20 +31,24 @@ export default function LeaderboardClient({ className="pointer-events-none absolute inset-0 opacity-70 -z-10" aria-hidden="true" > -
-
-
+
+
+
-
-
-

-

+
); } diff --git a/frontend/components/leaderboard/LeaderboardPodium.tsx b/frontend/components/leaderboard/LeaderboardPodium.tsx index 41f51ae6..ee092718 100644 --- a/frontend/components/leaderboard/LeaderboardPodium.tsx +++ b/frontend/components/leaderboard/LeaderboardPodium.tsx @@ -22,69 +22,61 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) { key={user.id} className={cn( 'flex flex-col items-center transition-all duration-500 relative', - isFirst ? 'order-2 z-10 -mt-8' : 'z-0', isSecond ? 'order-1' : '', isThird ? 'order-3' : '' )} > -
+
{isFirst && (
-

+
{user.username} -

+
{user.points}{' '} @@ -105,11 +97,11 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) { ? 'h-24 border-slate-300/30 dark:border-slate-500/30' : '', isThird - ? 'h-16 border-orange-300/30 dark:border-orange-500/30' + ? 'h-20 border-orange-300/30 dark:border-orange-500/30' : '' )} /> -
+
); })} diff --git a/frontend/components/leaderboard/LeaderboardTabs.tsx b/frontend/components/leaderboard/LeaderboardTabs.tsx index 40b8086b..36a2b345 100644 --- a/frontend/components/leaderboard/LeaderboardTabs.tsx +++ b/frontend/components/leaderboard/LeaderboardTabs.tsx @@ -27,12 +27,13 @@ export function LeaderboardTabs({ onClick={() => onTabChange(tab)} role="tab" aria-selected={isActive} - aria-controls={`panel-${tab}`} + aria-controls="leaderboard-panel" + id={`tab-${tab.replace(/\s+/g, '-')}`} className={cn( 'px-5 py-2 text-sm font-semibold rounded-full transition-all duration-300 whitespace-nowrap', isActive ? 'bg-gradient-to-r from-sky-500 via-indigo-500 to-pink-500 text-white shadow-md' - : 'text-slate-600 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 hover:bg-white/50 dark:hover:bg-slate-700/50' + : 'text-slate-500 dark:text-slate-400 hover:text-slate-900 dark:hover:text-slate-200 hover:bg-white/50 dark:hover:bg-slate-700/50' )} > {tab} diff --git a/frontend/components/shared/CookieBanner.tsx b/frontend/components/shared/CookieBanner.tsx index 8f6acdd3..a777ff33 100644 --- a/frontend/components/shared/CookieBanner.tsx +++ b/frontend/components/shared/CookieBanner.tsx @@ -44,9 +44,9 @@ export function CookieBanner() {
-

+
{t('title')} -

+

{t('description')}{' '} Date: Mon, 19 Jan 2026 18:52:33 +0200 Subject: [PATCH 2/2] refactor(leaderboard): redesign UI, remove tabs & implement i18n --- .../leaderboard/LeaderboardClient.tsx | 42 ++++++------- .../leaderboard/LeaderboardPodium.tsx | 61 ++++++++----------- .../leaderboard/LeaderboardTable.tsx | 42 +++++++------ .../leaderboard/LeaderboardTabs.tsx | 45 -------------- frontend/messages/en.json | 10 +++ frontend/messages/pl.json | 10 +++ frontend/messages/uk.json | 10 +++ 7 files changed, 99 insertions(+), 121 deletions(-) delete mode 100644 frontend/components/leaderboard/LeaderboardTabs.tsx diff --git a/frontend/components/leaderboard/LeaderboardClient.tsx b/frontend/components/leaderboard/LeaderboardClient.tsx index c15eb595..de281e2a 100644 --- a/frontend/components/leaderboard/LeaderboardClient.tsx +++ b/frontend/components/leaderboard/LeaderboardClient.tsx @@ -1,7 +1,6 @@ 'use client'; -import { useState } from 'react'; -import { LeaderboardTabs } from './LeaderboardTabs'; +import { useTranslations } from 'next-intl'; import { LeaderboardPodium } from './LeaderboardPodium'; import { LeaderboardTable } from './LeaderboardTable'; import { User } from './types'; @@ -13,7 +12,7 @@ interface LeaderboardClientProps { export default function LeaderboardClient({ initialUsers, }: LeaderboardClientProps) { - const [activeTab, setActiveTab] = useState('Overall'); + const t = useTranslations('leaderboard'); const usersWithPoints = initialUsers.filter(user => user.points > 0); const hasResults = usersWithPoints.length > 0; @@ -37,7 +36,7 @@ export default function LeaderboardClient({

-
+
🏆 @@ -46,40 +45,35 @@ export default function LeaderboardClient({ Champions Arena
-

- Community{' '} + +

- Leaderboard + {t('title')}

-

- Top performers of the community. + +

+ {t('subtitle')}

-
- -
- -
-
+
+
{hasResults ? ( ) : (
-

- No results yet + {t('noResults')}

- Be the first to complete a quiz and claim the top spot! + {t('beFirst')}

)} @@ -88,7 +82,7 @@ export default function LeaderboardClient({
{hasResults && }
-
+
); diff --git a/frontend/components/leaderboard/LeaderboardPodium.tsx b/frontend/components/leaderboard/LeaderboardPodium.tsx index ee092718..94cb286e 100644 --- a/frontend/components/leaderboard/LeaderboardPodium.tsx +++ b/frontend/components/leaderboard/LeaderboardPodium.tsx @@ -8,7 +8,7 @@ import { User } from './types'; export function LeaderboardPodium({ topThree }: { topThree: User[] }) { return (
    {topThree.map(user => { @@ -21,35 +21,32 @@ export function LeaderboardPodium({ topThree }: { topThree: User[] }) {
  1. -
    -
    +
    +
    {isFirst && (