From 5cf126bf16b7a2905f6ac1c4eb979d8694e78480 Mon Sep 17 00:00:00 2001 From: Sasni Lasadi Date: Thu, 16 Apr 2026 17:54:38 +0530 Subject: [PATCH] Added borders, badges, and styling to leaderboard table --- crackcode/client/package-lock.json | 16 +- .../leaderboard/leaderboardTable.jsx | 166 ++++++++++++++---- .../src/pages/leaderboard/leaderboardPage.jsx | 4 +- crackcode/server/package-lock.json | 5 + 4 files changed, 150 insertions(+), 41 deletions(-) diff --git a/crackcode/client/package-lock.json b/crackcode/client/package-lock.json index 1386af12..69b6f52f 100644 --- a/crackcode/client/package-lock.json +++ b/crackcode/client/package-lock.json @@ -74,6 +74,7 @@ "integrity": "sha512-e7jT4DxYvIDLk1ZHmU/m/mB19rex9sv0c2ftBtjSBv+kVM/902eh0fINUzD7UwLLNR+jU585GxUJ8/EBfAM5fw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.5", @@ -1776,6 +1777,7 @@ "integrity": "sha512-z9VXpC7MWrhfWipitjNdgCauoMLRdIILQsAEV+ZesIzBq/oUlxk0m3ApZuMFCXdnS4U7KrI+l3WRUEGQ8K1QKw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@types/prop-types": "*", "csstype": "^3.2.2" @@ -1796,8 +1798,7 @@ "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT", - "optional": true, - "peer": true + "optional": true }, "node_modules/@vitejs/plugin-react": { "version": "5.1.2", @@ -2007,6 +2008,7 @@ "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", "dev": true, "license": "MIT", + "peer": true, "bin": { "acorn": "bin/acorn" }, @@ -2218,6 +2220,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "baseline-browser-mapping": "^2.9.0", "caniuse-lite": "^1.0.30001759", @@ -2671,7 +2674,6 @@ "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "license": "(MPL-2.0 OR Apache-2.0)", - "peer": true, "optionalDependencies": { "@types/trusted-types": "^2.0.7" } @@ -2859,6 +2861,7 @@ "integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@eslint-community/eslint-utils": "^4.8.0", "@eslint-community/regexpp": "^4.12.1", @@ -3937,6 +3940,7 @@ "integrity": "sha512-/9AVW7xNbsBv6GfWho4TTNjEo9fe6Zhf9O7s0Fhhr3u+awPwAJMKwAMXnkk5vBxflqLW9hTHX/0cs+P3gW+cQw==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "abab": "^2.0.6", "cssstyle": "^3.0.0", @@ -4399,7 +4403,6 @@ "resolved": "https://registry.npmjs.org/marked/-/marked-14.0.0.tgz", "integrity": "sha512-uIj4+faQ+MgHgwUW1l2PsPglZLOLOT1uErt06dAPtx2kjteLAkbsd/0FiYg/MGS+i7ZKLb7w2WClxHkzOOuryQ==", "license": "MIT", - "peer": true, "bin": { "marked": "bin/marked.js" }, @@ -4802,6 +4805,7 @@ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz", "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==", "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -4945,6 +4949,7 @@ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0" }, @@ -4957,6 +4962,7 @@ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz", "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==", "license": "MIT", + "peer": true, "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" @@ -5671,6 +5677,7 @@ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.0.tgz", "integrity": "sha512-dZwN5L1VlUBewiP6H9s2+B3e3Jg96D0vzN+Ry73sOefebhYr9f94wwkMNN/9ouoU8pV1BqA1d1zGk8928cx0rg==", "license": "MIT", + "peer": true, "dependencies": { "esbuild": "^0.27.0", "fdir": "^6.5.0", @@ -7039,6 +7046,7 @@ "integrity": "sha512-0wZ1IRqGGhMP76gLqz8EyfBXKk0J2qo2+H3fi4mcUP/KtTocoX08nmIAHl1Z2kJIZbZee8KOpBCSNPRgauucjw==", "dev": true, "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } diff --git a/crackcode/client/src/components/leaderboard/leaderboardTable.jsx b/crackcode/client/src/components/leaderboard/leaderboardTable.jsx index b5e2228f..ffd0d213 100644 --- a/crackcode/client/src/components/leaderboard/leaderboardTable.jsx +++ b/crackcode/client/src/components/leaderboard/leaderboardTable.jsx @@ -1,68 +1,164 @@ import React from "react"; import { UserRoundSearch, Flame } from "lucide-react"; -// ── Renders either an or emoji depending on the avatar value ── const Avatar = ({ avatar, name }) => { const isImagePath = typeof avatar === "string" && (avatar.startsWith("/") || avatar.startsWith("http") || avatar.match(/\.(png|jpg|jpeg|gif|webp|svg)$/i)); + if (isImagePath) { return ( {name} { e.target.replaceWith(Object.assign(document.createElement("span"), { textContent: "🕵️" })); }} + style={{ + width: 32, height: 32, borderRadius: "50%", objectFit: "cover", + border: "2px solid var(--border)", flexShrink: 0, + }} + onError={(e) => { e.target.style.display = "none"; }} /> ); } + const isEmoji = typeof avatar === "string" && /\p{Emoji}/u.test(avatar); - return {isEmoji ? avatar : }; + return ( + + {isEmoji ? avatar : } + + ); }; +const RANK_MEDAL = { 1: "🥇", 2: "🥈", 3: "🥉" }; + const LeaderboardTable = ({ data = [] }) => { return ( -
+
+ + {/* Header */} - - - - - - - - + + {[ + { label: "Rank", width: "7%" }, + { label: "Detective", width: "22%" }, + { label: "Title", width: "13%" }, + { label: "Specialization", width: "16%" }, + { label: "Investigation Points", width: "17%" }, + { label: "Cases Solved", width: "13%" }, + { label: "Streak", width: "12%" }, + ].map(({ label, width }) => ( + + ))} + + {/* Body */} - {data.map((user) => ( - - - - - - - - - - ))} + {data.map((user, i) => { + const isTop3 = user.rank <= 3; + return ( + (e.currentTarget.style.background = "rgba(128,128,128,0.07)")} + onMouseLeave={(e) => (e.currentTarget.style.background = isTop3 ? "rgba(202,138,4,0.04)" : "transparent")} + > + {/* Rank */} + + + {/* Detective */} + + + {/* Title badge */} + + + {/* Specialization */} + + + {/* Investigation Points */} + + + {/* Cases Solved */} + + + {/* Streak */} + + + ); + })}
RankDetectiveTitleSpecializationInvestigation PointsCases SolvedStreak
+ {label} +
#{user.rank} -
- - {user.name} -
-
{user.title}{user.specialization}{user.points.toLocaleString()}{user.cases} -
- {user.streak} -
-
+ + #{user.rank} + {RANK_MEDAL[user.rank] && ( + {RANK_MEDAL[user.rank]} + )} + + +
+ + {user.name} +
+
+ + {user.title} + + + {user.specialization} + + {user.points.toLocaleString()} + + {user.cases} + +
+ + {user.streak} +
+
); }; -export default LeaderboardTable; +export default LeaderboardTable; \ No newline at end of file diff --git a/crackcode/client/src/pages/leaderboard/leaderboardPage.jsx b/crackcode/client/src/pages/leaderboard/leaderboardPage.jsx index 4160924f..a80b9db8 100644 --- a/crackcode/client/src/pages/leaderboard/leaderboardPage.jsx +++ b/crackcode/client/src/pages/leaderboard/leaderboardPage.jsx @@ -77,10 +77,10 @@ const LeaderboardPage = () => {
-
+
{/* Title + Filter buttons */} -
+