Skip to content

Commit fbde312

Browse files
authored
Merge pull request #88 from CrackCode-dev/sasni-backup2
Added borders, badges, and styling to leaderboard table
2 parents be6422e + 5cf126b commit fbde312

4 files changed

Lines changed: 150 additions & 41 deletions

File tree

crackcode/client/package-lock.json

Lines changed: 12 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 131 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,164 @@
11
import React from "react";
22
import { UserRoundSearch, Flame } from "lucide-react";
33

4-
// ── Renders either an <img> or emoji depending on the avatar value ──
54
const Avatar = ({ avatar, name }) => {
65
const isImagePath =
76
typeof avatar === "string" &&
87
(avatar.startsWith("/") ||
98
avatar.startsWith("http") ||
109
avatar.match(/\.(png|jpg|jpeg|gif|webp|svg)$/i));
10+
1111
if (isImagePath) {
1212
return (
1313
<img
1414
src={avatar}
1515
alt={name}
16-
style={{ width: 32, height: 32, borderRadius: "50%", objectFit: "cover",
17-
border: "2px solid #334155", flexShrink: 0 }}
18-
onError={(e) => { e.target.replaceWith(Object.assign(document.createElement("span"), { textContent: "🕵️" })); }}
16+
style={{
17+
width: 32, height: 32, borderRadius: "50%", objectFit: "cover",
18+
border: "2px solid var(--border)", flexShrink: 0,
19+
}}
20+
onError={(e) => { e.target.style.display = "none"; }}
1921
/>
2022
);
2123
}
24+
2225
const isEmoji = typeof avatar === "string" && /\p{Emoji}/u.test(avatar);
23-
return <span className="text-xl">{isEmoji ? avatar : <UserRoundSearch className="w-6 h-6" />}</span>;
26+
return (
27+
<span style={{ fontSize: "1.3rem" }}>
28+
{isEmoji ? avatar : <UserRoundSearch style={{ width: 24, height: 24 }} />}
29+
</span>
30+
);
2431
};
2532

33+
const RANK_MEDAL = { 1: "🥇", 2: "🥈", 3: "🥉" };
34+
2635
const LeaderboardTable = ({ data = [] }) => {
2736
return (
28-
<div className="table-container" style={{ width: "100%" }}>
37+
<div
38+
style={{
39+
width: "100%",
40+
borderRadius: "1rem",
41+
border: "1px solid var(--border)",
42+
background: "var(--surface)",
43+
overflow: "hidden",
44+
marginTop: "2rem",
45+
}}
46+
>
2947
<table style={{ width: "100%", tableLayout: "fixed", borderSpacing: 0, borderCollapse: "collapse" }}>
48+
49+
{/* Header */}
3050
<thead>
31-
<tr>
32-
<th style={{ width: "6%", padding: "18px 20px", textAlign: "left" }}>Rank</th>
33-
<th style={{ width: "20%", padding: "18px 20px", textAlign: "left" }}>Detective</th>
34-
<th style={{ width: "14%", padding: "18px 20px", textAlign: "left" }}>Title</th>
35-
<th style={{ width: "18%", padding: "18px 20px", textAlign: "left" }}>Specialization</th>
36-
<th style={{ width: "16%", padding: "18px 20px", textAlign: "left" }}>Investigation Points</th>
37-
<th style={{ width: "13%", padding: "18px 20px", textAlign: "left" }}>Cases Solved</th>
38-
<th style={{ width: "13%", padding: "18px 20px", textAlign: "left" }}>Streak</th>
51+
<tr style={{ borderBottom: "1px solid var(--border)" }}>
52+
{[
53+
{ label: "Rank", width: "7%" },
54+
{ label: "Detective", width: "22%" },
55+
{ label: "Title", width: "13%" },
56+
{ label: "Specialization", width: "16%" },
57+
{ label: "Investigation Points", width: "17%" },
58+
{ label: "Cases Solved", width: "13%" },
59+
{ label: "Streak", width: "12%" },
60+
].map(({ label, width }) => (
61+
<th
62+
key={label}
63+
style={{
64+
width,
65+
padding: "14px 20px",
66+
textAlign: "left",
67+
fontSize: "0.7rem",
68+
fontWeight: 700,
69+
letterSpacing: "0.08em",
70+
textTransform: "uppercase",
71+
color: "var(--muted)",
72+
}}
73+
>
74+
{label}
75+
</th>
76+
))}
3977
</tr>
4078
</thead>
79+
80+
{/* Body */}
4181
<tbody>
42-
{data.map((user) => (
43-
<tr key={user.rank} style={{ borderTop: "1px solid rgba(255,255,255,0.06)" }}>
44-
<td style={{ padding: "20px 20px" }}>#{user.rank}</td>
45-
<td style={{ padding: "20px 20px" }}>
46-
<div style={{ display: "flex", alignItems: "center", gap: "12px" }}>
47-
<Avatar avatar={user.avatar} name={user.name} />
48-
<span>{user.name}</span>
49-
</div>
50-
</td>
51-
<td style={{ padding: "20px 20px" }}>{user.title}</td>
52-
<td style={{ padding: "20px 20px" }}>{user.specialization}</td>
53-
<td style={{ padding: "20px 20px" }} className="green">{user.points.toLocaleString()}</td>
54-
<td style={{ padding: "20px 20px" }}>{user.cases}</td>
55-
<td style={{ padding: "20px 20px" }} className="orange">
56-
<div className="flex items-center gap-1">
57-
{user.streak} <Flame className="w-4 h-4" />
58-
</div>
59-
</td>
60-
</tr>
61-
))}
82+
{data.map((user, i) => {
83+
const isTop3 = user.rank <= 3;
84+
return (
85+
<tr
86+
key={user.rank}
87+
style={{
88+
borderTop: i === 0 ? "none" : "1px solid var(--border)",
89+
background: isTop3 ? "rgba(var(--brand-rgb, 202,138,4), 0.04)" : "transparent",
90+
transition: "background 0.15s",
91+
}}
92+
onMouseEnter={(e) => (e.currentTarget.style.background = "rgba(128,128,128,0.07)")}
93+
onMouseLeave={(e) => (e.currentTarget.style.background = isTop3 ? "rgba(202,138,4,0.04)" : "transparent")}
94+
>
95+
{/* Rank */}
96+
<td style={{ padding: "18px 20px", fontWeight: 700, color: "var(--text)" }}>
97+
<span style={{ display: "flex", alignItems: "center", gap: 6 }}>
98+
#{user.rank}
99+
{RANK_MEDAL[user.rank] && (
100+
<span style={{ fontSize: "1rem" }}>{RANK_MEDAL[user.rank]}</span>
101+
)}
102+
</span>
103+
</td>
104+
105+
{/* Detective */}
106+
<td style={{ padding: "18px 20px" }}>
107+
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
108+
<Avatar avatar={user.avatar} name={user.name} />
109+
<span style={{ fontWeight: 600, color: "var(--text)" }}>{user.name}</span>
110+
</div>
111+
</td>
112+
113+
{/* Title badge */}
114+
<td style={{ padding: "18px 20px" }}>
115+
<span
116+
style={{
117+
display: "inline-block",
118+
padding: "3px 10px",
119+
borderRadius: 999,
120+
fontSize: "0.72rem",
121+
fontWeight: 700,
122+
letterSpacing: "0.05em",
123+
textTransform: "uppercase",
124+
border: "1px solid var(--border)",
125+
color: "var(--brand)",
126+
background: "rgba(202,138,4,0.08)",
127+
}}
128+
>
129+
{user.title}
130+
</span>
131+
</td>
132+
133+
{/* Specialization */}
134+
<td style={{ padding: "18px 20px", color: "var(--muted)" }}>
135+
{user.specialization}
136+
</td>
137+
138+
{/* Investigation Points */}
139+
<td style={{ padding: "18px 20px", fontWeight: 700, color: "var(--brand)" }}>
140+
{user.points.toLocaleString()}
141+
</td>
142+
143+
{/* Cases Solved */}
144+
<td style={{ padding: "18px 20px", color: "var(--muted)" }}>
145+
{user.cases}
146+
</td>
147+
148+
{/* Streak */}
149+
<td style={{ padding: "18px 20px" }}>
150+
<div style={{ display: "flex", alignItems: "center", gap: 4 }}>
151+
<Flame style={{ width: 16, height: 16, color: "#ea580c" }} />
152+
<span style={{ fontWeight: 600, color: "#ea580c" }}>{user.streak}</span>
153+
</div>
154+
</td>
155+
</tr>
156+
);
157+
})}
62158
</tbody>
63159
</table>
64160
</div>
65161
);
66162
};
67163

68-
export default LeaderboardTable;
164+
export default LeaderboardTable;

crackcode/client/src/pages/leaderboard/leaderboardPage.jsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -77,10 +77,10 @@ const LeaderboardPage = () => {
7777
<div className="min-h-screen flex flex-col" style={{ background: 'var(--bg)', color: 'var(--text)' }}>
7878
<Header variant="empty" showBackBtn={false}/>
7979

80-
<main className="flex-1 px-6 sm:px-10 py-10 mt-20">
80+
<main className="flex-1 px-6 sm:px-10 py-8 mt-15">
8181

8282
{/* Title + Filter buttons */}
83-
<div className="flex items-center justify-between max-w-5xl mx-auto mt-20 mb-12">
83+
<div className="flex items-center justify-between max-w-5xl mx-auto mt-12 mb-10">
8484
<div className="text-center flex-1">
8585
<div className="flex flex-col gap-5">
8686
<h1

crackcode/server/package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)