From 70b03a9de4a784ac035087b380628321f8b81610 Mon Sep 17 00:00:00 2001
From: Sanjay Krishnan K <196655178+Sanjaykris20@users.noreply.github.com>
Date: Sat, 17 Oct 2026 21:48:50 +0530
Subject: [PATCH] fix(leaderboard): render empty search state with clear filter
button
---
.../components/leaderboard-table.tsx | 175 ++++++++++--------
.../tests/leaderboard-table.test.tsx | 66 ++++++-
src/locales/ar.json | 2 +
src/locales/en.json | 2 +
4 files changed, 167 insertions(+), 78 deletions(-)
diff --git a/src/features/leaderboard/components/leaderboard-table.tsx b/src/features/leaderboard/components/leaderboard-table.tsx
index 278edee..5b43bf5 100644
--- a/src/features/leaderboard/components/leaderboard-table.tsx
+++ b/src/features/leaderboard/components/leaderboard-table.tsx
@@ -7,6 +7,7 @@ import { AlertTriangle, ExternalLink, ArrowUpDown, ArrowUp, ArrowDown } from "lu
import { Avatar } from "@/components/layout/avatar";
import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip";
import { Card, CardContent, CardHeader, CardTitle, CardDescription } from "@/components/ui/card";
+import { Button } from "@/components/ui/button";
import { useTranslation } from "@/components/providers/language-provider";
import { cn } from "@/utils/cn";
import type { ScoredLeaderboardEntry as LeaderboardEntry } from "../types";
@@ -266,86 +267,108 @@ export function LeaderboardTable({
- {filtered.map((user) => (
-
- |
-
- {user.impactRank}
-
+ {filtered.length === 0 && search.trim() ? (
+ |
+
+
+
+ {t("leaderboard.noDevelopersSearch", {
+ query: search,
+ })}
+
+
+
|
-
- {(() => {
- const profileUrl = (
- countrySlug
- ? `/user/${user.username}?country=${encodeURIComponent(countrySlug)}`
- : `/user/${user.username}`
- ) as Route;
+ |
+ ) : (
+ filtered.map((user) => (
+
+ |
+
+ {user.impactRank}
+
+ |
+
+ {(() => {
+ const profileUrl = (
+ countrySlug
+ ? `/user/${user.username}?country=${encodeURIComponent(countrySlug)}`
+ : `/user/${user.username}`
+ ) as Route;
- return (
-
-
-
-
-
-
-
- {user.name || user.username}
-
-
-
-
+ return (
+
+
+
+
+
+
+
+ {user.name || user.username}
+
+
+
+
+
+
+ @{user.username}
+
-
- @{user.username}
-
-
- );
- })()}
- |
-
- {user.finalScore}
- |
- {user.repoScore} |
- {user.prScore} |
-
- {user.contributionScore}
- |
-
- ))}
+ );
+ })()}
+
+
+ {user.finalScore}
+ |
+ {user.repoScore} |
+ {user.prScore} |
+
+ {user.contributionScore}
+ |
+
+ ))
+ )}
diff --git a/src/features/leaderboard/tests/leaderboard-table.test.tsx b/src/features/leaderboard/tests/leaderboard-table.test.tsx
index ade36d2..df015f3 100644
--- a/src/features/leaderboard/tests/leaderboard-table.test.tsx
+++ b/src/features/leaderboard/tests/leaderboard-table.test.tsx
@@ -17,7 +17,7 @@ vi.mock("next/link", () => ({
// Mock translations
vi.mock("@/components/providers/language-provider", () => ({
useTranslation: () => ({
- t: (key: string) => {
+ t: (key: string, params?: Record) => {
const translations: Record = {
"leaderboard.title": "Leaderboard",
"leaderboard.description": "Leaderboard description",
@@ -25,6 +25,8 @@ vi.mock("@/components/providers/language-provider", () => ({
"leaderboard.impactRank": "Impact Rank",
"leaderboard.search": "Search",
"leaderboard.partialErrors": "Partial Errors",
+ "leaderboard.noDevelopersSearch": 'No developers found matching "{query}".',
+ "leaderboard.clearSearch": "Clear search",
"comparison.final.score": "Final Score",
"comparison.repo.score": "Repo Score",
"comparison.pr.score": "PR Score",
@@ -32,7 +34,12 @@ vi.mock("@/components/providers/language-provider", () => ({
"a11y.openProfile": "Open profile",
};
- return translations[key] ?? key;
+ const template = translations[key] ?? key;
+ if (!params) return template;
+ return Object.keys(params).reduce(
+ (acc, k) => acc.split(`{${k}}`).join(String(params[k])),
+ template,
+ );
},
}),
}));
@@ -277,3 +284,58 @@ describe("LeaderboardTable keyboard shortcuts", () => {
expect(document.activeElement).not.toBe(search);
});
});
+
+describe("LeaderboardTable empty search state", () => {
+ afterEach(() => {
+ cleanup();
+ });
+
+ const users = [
+ {
+ username: "alice",
+ name: "Alice",
+ avatarUrl: "https://example.com/alice.png",
+ impactRank: 1,
+ finalScore: 80,
+ repoScore: 30,
+ prScore: 20,
+ contributionScore: 40,
+ },
+ ];
+
+ const renderLeaderboard = () =>
+ render(
+ ,
+ );
+
+ test("shows empty search state when query matches no developers", async () => {
+ const user = userEvent.setup();
+ renderLeaderboard();
+
+ const searchInput = screen.getByPlaceholderText("Search");
+ await user.type(searchInput, "nonexistent");
+
+ expect(screen.getByText('No developers found matching "nonexistent".')).toBeDefined();
+ expect(screen.getByRole("button", { name: /clear search/i })).toBeDefined();
+ });
+
+ test("clicking clear search resets the search input and restores developers", async () => {
+ const user = userEvent.setup();
+ renderLeaderboard();
+
+ const searchInput = screen.getByPlaceholderText("Search") as HTMLInputElement;
+ await user.type(searchInput, "nonexistent");
+
+ const clearButton = screen.getByRole("button", { name: /clear search/i });
+ await user.click(clearButton);
+
+ expect(searchInput.value).toBe("");
+ expect(screen.getByText("Alice")).toBeDefined();
+ });
+});
diff --git a/src/locales/ar.json b/src/locales/ar.json
index b440516..5a3b87b 100644
--- a/src/locales/ar.json
+++ b/src/locales/ar.json
@@ -258,6 +258,8 @@
"leaderboard.empty.title": "لم يتم العثور على دول",
"leaderboard.empty.description": "ستظهر الدول المتاحة هنا.",
"leaderboard.noCountriesSearch": "لا توجد دول تطابق بحثك.",
+ "leaderboard.noDevelopersSearch": "لم يتم العثور على مطورين يطابقون \"{query}\".",
+ "leaderboard.clearSearch": "مسح البحث",
"leaderboard.noDevelopersFor": "لم يتم حساب قائمة المتصدرين في هذه الدولة بعد.",
"leaderboard.error.retry": "حاول مرة أخرى",
"leaderboard.viewCountry": "عرض لوحة المتصدرين",
diff --git a/src/locales/en.json b/src/locales/en.json
index 30a903f..3218e28 100644
--- a/src/locales/en.json
+++ b/src/locales/en.json
@@ -258,6 +258,8 @@
"leaderboard.empty.title": "No countries found",
"leaderboard.empty.description": "Available countries will appear here.",
"leaderboard.noCountriesSearch": "No countries match your search.",
+ "leaderboard.noDevelopersSearch": "No developers found matching \"{query}\".",
+ "leaderboard.clearSearch": "Clear search",
"leaderboard.noDevelopersFor": "No leaderboard has been calculated for this country yet.",
"leaderboard.error.retry": "Try again",
"leaderboard.viewCountry": "View leaderboard",