@@ -4,25 +4,50 @@ import '../App.css';
44
55export 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