1
- import { useState , type ReactNode } from 'react' ;
1
+ import { type ReactNode , useState } from 'react' ;
2
2
import type {
3
3
LeadeboardUserDetails ,
4
4
ListLeaderboardStatsResponse ,
5
5
} from '../../api/leaderboard' ;
6
6
import { cn } from '../../lib/classname' ;
7
- import { FolderKanban , Zap , Trophy } from 'lucide-react' ;
8
- import { RankBadgeIcon } from '../ReactIcons/RankBadgeIcon' ;
7
+ import { FolderKanban , GitPullRequest , Trophy , Zap } from 'lucide-react' ;
9
8
import { TrophyEmoji } from '../ReactIcons/TrophyEmoji' ;
10
9
import { SecondPlaceMedalEmoji } from '../ReactIcons/SecondPlaceMedalEmoji' ;
11
10
import { ThirdPlaceMedalEmoji } from '../ReactIcons/ThirdPlaceMedalEmoji' ;
@@ -19,17 +18,25 @@ export function LeaderboardPage(props: LeaderboardPageProps) {
19
18
20
19
return (
21
20
< div className = "min-h-screen bg-gray-50" >
22
- < div className = "container py-10" >
21
+ < div className = "container py-5 sm:py- 10" >
23
22
< div className = "mb-8 text-center" >
24
- < div className = "mb-2 flex items-center justify-center gap-3" >
25
- < Trophy className = "size-8 text-yellow-500" />
26
- < h2 className = "text-2xl font-bold sm:text-3xl" > Leaderboard</ h2 >
23
+ < div className = "flex flex-col items-start sm:items-center justify-center" >
24
+ < img
25
+ src = { '/images/gifs/star.gif' }
26
+ alt = "party-popper"
27
+ className = "mb-4 mt-0 sm:mt-3 h-14 w-14 hidden sm:block"
28
+ />
29
+ < div className = "mb-0 sm:mb-4 flex flex-col items-start sm:items-center justify-start sm:justify-center" >
30
+ < h2 className = "mb-1.5 sm:mb-2 text-2xl font-semibold sm:text-4xl" >
31
+ Leaderboard
32
+ </ h2 >
33
+ < p className = "max-w-2xl text-left sm:text-center text-balance text-sm text-gray-500 sm:text-base" >
34
+ Top users based on their activity on roadmap.sh
35
+ </ p >
36
+ </ div >
27
37
</ div >
28
- < p className = "mx-auto max-w-2xl text-balance text-sm text-gray-500 sm:text-base" >
29
- Top users based on their activity on roadmap.sh
30
- </ p >
31
38
32
- < div className = "mt-8 grid gap-2 md:grid-cols-2" >
39
+ < div className = "mt-5 sm:mt- 8 grid gap-2 md:grid-cols-2" >
33
40
< LeaderboardLane
34
41
title = "Longest Visit Streak"
35
42
tabs = { [
@@ -64,6 +71,19 @@ export function LeaderboardPage(props: LeaderboardPageProps) {
64
71
} ,
65
72
] }
66
73
/>
74
+ < LeaderboardLane
75
+ title = "Top Contributors"
76
+ tabs = { [
77
+ {
78
+ title : 'This Month' ,
79
+ users : stats . githubContributors . currentMonth ,
80
+ emptyIcon : (
81
+ < GitPullRequest className = "size-16 text-gray-300" />
82
+ ) ,
83
+ emptyText : 'No contributors this month' ,
84
+ } ,
85
+ ] }
86
+ />
67
87
</ div >
68
88
</ div >
69
89
</ div >
@@ -88,8 +108,8 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
88
108
const { users : usersToShow , emptyIcon, emptyText } = activeTab ;
89
109
90
110
return (
91
- < div className = "overflow-hidden rounded-md border bg-white shadow-sm " >
92
- < div className = "flex items-center justify-between gap-2 bg-gray-100 px-3 py-3 mb -3" >
111
+ < div className = "flex flex-col overflow-hidden rounded-xl border bg-white min-h-[450px] " >
112
+ < div className = "mb-3 flex items-center justify-between gap-2 px-3 py-3" >
93
113
< h3 className = "text-base font-medium" > { title } </ h3 >
94
114
95
115
{ tabs . length > 1 && (
@@ -118,7 +138,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
118
138
</ div >
119
139
120
140
{ usersToShow . length === 0 && emptyText && (
121
- < div className = "flex flex-col items-center justify-center p-8" >
141
+ < div className = "flex flex-grow flex- col items-center justify-center p-8" >
122
142
{ emptyIcon }
123
143
< p className = "mt-4 text-sm text-gray-500" > { emptyText } </ p >
124
144
</ div >
@@ -128,19 +148,23 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
128
148
< ul className = "divide-y divide-gray-100 pb-4" >
129
149
{ usersToShow . map ( ( user , counter ) => {
130
150
const avatar = user ?. avatar
131
- ? `${ import . meta. env . PUBLIC_AVATAR_BASE_URL } /${ user . avatar } `
151
+ ? user ?. avatar ?. startsWith ( 'http' )
152
+ ? user ?. avatar
153
+ : `${ import . meta. env . PUBLIC_AVATAR_BASE_URL } /${ user . avatar } `
132
154
: '/images/default-avatar.png' ;
155
+
133
156
const rank = counter + 1 ;
157
+ const isGitHubUser = avatar ?. indexOf ( 'github' ) > - 1 ;
134
158
135
159
return (
136
160
< li
137
161
key = { user . id }
138
- className = "flex items-center justify-between gap-1 pl-2 pr-5 py-2.5 hover:bg-gray-50 "
162
+ className = "flex items-center justify-between gap-1 py-2.5 pl-2 pr-5 "
139
163
>
140
164
< div className = "flex min-w-0 items-center gap-2" >
141
165
< span
142
166
className = { cn (
143
- 'relative text-xs mr-1 flex size-6 shrink-0 items-center justify-center rounded-full tabular-nums' ,
167
+ 'relative mr-1 flex size-6 shrink-0 items-center justify-center rounded-full text-xs tabular-nums' ,
144
168
{
145
169
'text-black' : rank <= 3 ,
146
170
'text-gray-400' : rank > 3 ,
@@ -153,9 +177,19 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
153
177
< img
154
178
src = { avatar }
155
179
alt = { user . name }
156
- className = "size-7 shrink-0 rounded-full"
180
+ className = "mr-1 size-7 shrink-0 rounded-full"
157
181
/>
158
- < span className = "truncate" > { user . name } </ span >
182
+ { isGitHubUser ? (
183
+ < a
184
+ href = { `https://github.com/${ user . name } ` }
185
+ target = "_blank"
186
+ className = "truncate font-medium underline underline-offset-2"
187
+ >
188
+ { user . name }
189
+ </ a >
190
+ ) : (
191
+ < span className = "truncate" > { user . name } </ span >
192
+ ) }
159
193
{ rank === 1 ? (
160
194
< TrophyEmoji className = "size-5" />
161
195
) : rank === 2 ? (
@@ -167,7 +201,17 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
167
201
) }
168
202
</ div >
169
203
170
- < span className = "text-sm text-gray-500" > { user . count } </ span >
204
+ { isGitHubUser ? (
205
+ < a
206
+ target = { '_blank' }
207
+ href = { `https://github.com/kamranahmedse/developer-roadmap/pulls/${ user . name } ` }
208
+ className = "text-sm text-gray-500"
209
+ >
210
+ { user . count }
211
+ </ a >
212
+ ) : (
213
+ < span className = "text-sm text-gray-500" > { user . count } </ span >
214
+ ) }
171
215
</ li >
172
216
) ;
173
217
} ) }
0 commit comments