Skip to content

Commit 9702bcb

Browse files
committed
Implemented leaderboard
1 parent 4147dc0 commit 9702bcb

2 files changed

Lines changed: 105 additions & 14 deletions

File tree

.gitignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,4 +13,7 @@
1313

1414
npm-debug.log*
1515
yarn-debug.log*
16-
yarn-error.log*
16+
yarn-error.log*
17+
18+
*.ps1
19+
*.bat

src/pages/leaderboard.js

Lines changed: 101 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,25 +4,50 @@ import '../App.css';
44

55
export function Leaderboard() {
66
const [leaderboard, setLeaderboard] = useState([]);
7+
const [searchTerm, setSearchTerm] = useState('');
8+
const [userData, setUserData] = useState(null);
79

810
async function FetchLeaderboard() {
911
try {
1012
const response = await fetch(`http://${GetBackendHost()}/data/leaderboard`, {
1113
method: "GET",
12-
credentials: 'include' // ensures cookies are sent
14+
credentials: 'include'
1315
});
14-
16+
1517
const data = await response.json();
1618
setLeaderboard(data);
1719
} catch (error) {
1820
console.error("Error sending request:", error);
1921
}
2022
}
2123

22-
// runs periodically
2324
useEffect(() => {
24-
FetchLeaderboard()
25-
}, []); // [] means execute this once on page-load
25+
FetchLeaderboard();
26+
}, []);
27+
28+
useEffect(() => {
29+
async function GetInfo() {
30+
try {
31+
const response = await fetch(`http://${GetBackendHost()}/user/info`, {
32+
method: "GET",
33+
credentials: 'include' // ensures cookies are sent
34+
});
35+
36+
const data = await response.json();
37+
if (data) {
38+
setUserData({"username": data.username, "team": data.team})
39+
}
40+
} catch (error) {
41+
console.error("Error sending request:", error);
42+
}
43+
}
44+
GetInfo();
45+
}, []); // run once on page-load
46+
47+
// Filter leaderboard by search term (case-sensitive)
48+
const filteredLeaderboard = leaderboard.filter(item =>
49+
item.name.includes(searchTerm)
50+
);
2651

2752
return (
2853
<div className="App">
@@ -36,17 +61,81 @@ export function Leaderboard() {
3661
}}>
3762
<h1>KHI Leaderboard</h1>
3863

39-
<div className="container mt-4 d-flex justify-content-center">
64+
{/* Search Bar */}
65+
<div className="mb-3 mt-3 d-flex justify-content-center">
66+
<input
67+
type="text"
68+
className="form-control"
69+
placeholder="Search by name..."
70+
value={searchTerm}
71+
onChange={e => setSearchTerm(e.target.value)}
72+
style={{ maxWidth: '400px', width: '100%' }}
73+
/>
74+
</div>
75+
76+
<div className="container mt-2 d-flex justify-content-center gap-3">
77+
{/* THIS CARD ONLY SHOWS WHEN A USER IS AUTHENTICATED */}
78+
{userData && (
79+
<div
80+
className="card shadow-sm"
81+
style={{ minWidth: '200px', maxWidth: '300px', width: '100%' }}
82+
>
83+
<div className="card-header bg-primary text-white text-center fw-bold">
84+
{userData.username}
85+
</div>
86+
87+
{(() => {
88+
const actualIndex = leaderboard.findIndex(
89+
(entry) => entry.name === userData.username || entry.name === userData.team
90+
);
91+
92+
if (actualIndex === -1) return null;
93+
94+
const userEntry = leaderboard[actualIndex];
95+
let badgeColor = "secondary";
96+
if (actualIndex === 0) badgeColor = "warning";
97+
else if (actualIndex === 1) badgeColor = "secondary";
98+
else if (actualIndex === 2) badgeColor = "danger";
99+
100+
return (
101+
<div className="card-body">
102+
<div className="d-flex justify-content-between align-items-center mb-2">
103+
<h6 className="mb-0">Your Placement</h6>
104+
<span className={`badge bg-${badgeColor} ms-2`}>
105+
#{actualIndex + 1}
106+
</span>
107+
</div>
108+
109+
<div
110+
className="d-flex align-items-center d-flex justify-content-between px-4 py-2"
111+
style={{ fontSize: "1.25rem" }}>
112+
<span className="fw-semibold">{userEntry.name}</span>
113+
<span className="text-muted fw-bold">{userEntry.points} pts</span>
114+
</div>
115+
</div>
116+
);
117+
})()}
118+
</div>
119+
)}
120+
40121
<div className="card shadow-sm" style={{ minWidth: '200px', maxWidth: '500px', width: '100%' }}>
41122
<div className="card-header bg-primary text-white text-center fw-bold">
42123
Leaderboard
43124
</div>
44-
<ul className="list-group list-group-flush">
45-
{leaderboard.map((item, index) => {
125+
<ul
126+
className="list-group list-group-flush"
127+
style={{
128+
maxHeight: '400px',
129+
overflowY: 'auto',
130+
scrollbarWidth: 'thin',
131+
WebkitOverflowScrolling: 'touch',
132+
}}>
133+
{filteredLeaderboard.map((item, index) => {
134+
const actualIndex = leaderboard.findIndex(original => original.name === item.name);
46135
let badgeColor = "secondary";
47-
if (index === 0) badgeColor = "warning"; // Gold
48-
else if (index === 1) badgeColor = "secondary"; // Silver
49-
else if (index === 2) badgeColor = "danger"; // Bronze
136+
if (actualIndex === 0) badgeColor = "warning";
137+
else if (actualIndex === 1) badgeColor = "secondary";
138+
else if (actualIndex === 2) badgeColor = "danger";
50139

51140
return (
52141
<li
@@ -56,7 +145,7 @@ export function Leaderboard() {
56145
>
57146
<span>
58147
<span className={`badge bg-${badgeColor} me-2`}>
59-
{index + 1}
148+
{actualIndex + 1}
60149
</span>
61150
{item.name}
62151
</span>
@@ -67,7 +156,6 @@ export function Leaderboard() {
67156
</ul>
68157
</div>
69158
</div>
70-
71159
</header>
72160
</div>
73161
);

0 commit comments

Comments
 (0)