|
|
@ -1,10 +1,10 @@ |
|
|
|
import { type ReactNode, useState } from 'react'; |
|
|
|
import { type ReactNode, useState } from 'react'; |
|
|
|
import type { |
|
|
|
import type { |
|
|
|
LeadeboardUserDetails, |
|
|
|
LeaderboardUserDetails, |
|
|
|
ListLeaderboardStatsResponse, |
|
|
|
ListLeaderboardStatsResponse, |
|
|
|
} from '../../api/leaderboard'; |
|
|
|
} from '../../api/leaderboard'; |
|
|
|
import { cn } from '../../lib/classname'; |
|
|
|
import { cn } from '../../lib/classname'; |
|
|
|
import { FolderKanban, GitPullRequest, Trophy, Zap } from 'lucide-react'; |
|
|
|
import { FolderKanban, GitPullRequest, Users2, Zap } from 'lucide-react'; |
|
|
|
import { TrophyEmoji } from '../ReactIcons/TrophyEmoji'; |
|
|
|
import { TrophyEmoji } from '../ReactIcons/TrophyEmoji'; |
|
|
|
import { SecondPlaceMedalEmoji } from '../ReactIcons/SecondPlaceMedalEmoji'; |
|
|
|
import { SecondPlaceMedalEmoji } from '../ReactIcons/SecondPlaceMedalEmoji'; |
|
|
|
import { ThirdPlaceMedalEmoji } from '../ReactIcons/ThirdPlaceMedalEmoji'; |
|
|
|
import { ThirdPlaceMedalEmoji } from '../ReactIcons/ThirdPlaceMedalEmoji'; |
|
|
@ -17,74 +17,60 @@ export function LeaderboardPage(props: LeaderboardPageProps) { |
|
|
|
const { stats } = props; |
|
|
|
const { stats } = props; |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className="min-h-screen bg-gray-50"> |
|
|
|
<div className="min-h-screen bg-white"> |
|
|
|
<div className="container py-5 sm:py-10"> |
|
|
|
<div className="container pb-5 sm:pb-8"> |
|
|
|
<div className="mb-8 text-center"> |
|
|
|
<h1 className="my-5 flex items-center text-lg font-medium text-black sm:mb-4 sm:mt-8"> |
|
|
|
<div className="flex flex-col items-start sm:items-center justify-center"> |
|
|
|
<Users2 className="mr-2 size-5 text-black" /> |
|
|
|
<img |
|
|
|
Leaderboard |
|
|
|
src={'/images/gifs/star.gif'} |
|
|
|
</h1> |
|
|
|
alt="party-popper" |
|
|
|
|
|
|
|
className="mb-4 mt-0 sm:mt-3 h-14 w-14 hidden sm:block" |
|
|
|
<div className="grid gap-2 sm:gap-3 md:grid-cols-2"> |
|
|
|
/> |
|
|
|
<LeaderboardLane |
|
|
|
<div className="mb-0 sm:mb-4 flex flex-col items-start sm:items-center justify-start sm:justify-center"> |
|
|
|
title="Longest Visit Streak" |
|
|
|
<h2 className="mb-1.5 sm:mb-2 text-2xl font-semibold sm:text-4xl"> |
|
|
|
tabs={[ |
|
|
|
Leaderboard |
|
|
|
{ |
|
|
|
</h2> |
|
|
|
title: 'Active', |
|
|
|
<p className="max-w-2xl text-left sm:text-center text-balance text-sm text-gray-500 sm:text-base"> |
|
|
|
users: stats.streaks?.active || [], |
|
|
|
Top users based on their activity on roadmap.sh |
|
|
|
emptyIcon: <Zap className="size-16 text-gray-300" />, |
|
|
|
</p> |
|
|
|
emptyText: 'No users with streaks yet', |
|
|
|
</div> |
|
|
|
}, |
|
|
|
</div> |
|
|
|
{ |
|
|
|
|
|
|
|
title: 'Lifetime', |
|
|
|
<div className="mt-5 sm:mt-8 grid gap-2 md:grid-cols-2"> |
|
|
|
users: stats.streaks?.lifetime || [], |
|
|
|
<LeaderboardLane |
|
|
|
emptyIcon: <Zap className="size-16 text-gray-300" />, |
|
|
|
title="Longest Visit Streak" |
|
|
|
emptyText: 'No users with streaks yet', |
|
|
|
tabs={[ |
|
|
|
}, |
|
|
|
{ |
|
|
|
]} |
|
|
|
title: 'Active', |
|
|
|
/> |
|
|
|
users: stats.streaks?.active || [], |
|
|
|
<LeaderboardLane |
|
|
|
emptyIcon: <Zap className="size-16 text-gray-300" />, |
|
|
|
title="Projects Completed" |
|
|
|
emptyText: 'No users with streaks yet', |
|
|
|
tabs={[ |
|
|
|
}, |
|
|
|
{ |
|
|
|
{ |
|
|
|
title: 'This Month', |
|
|
|
title: 'Lifetime', |
|
|
|
users: stats.projectSubmissions.currentMonth, |
|
|
|
users: stats.streaks?.lifetime || [], |
|
|
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />, |
|
|
|
emptyIcon: <Zap className="size-16 text-gray-300" />, |
|
|
|
emptyText: 'No projects submitted this month', |
|
|
|
emptyText: 'No users with streaks yet', |
|
|
|
}, |
|
|
|
}, |
|
|
|
{ |
|
|
|
]} |
|
|
|
title: 'Lifetime', |
|
|
|
/> |
|
|
|
users: stats.projectSubmissions.lifetime, |
|
|
|
<LeaderboardLane |
|
|
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />, |
|
|
|
title="Projects Completed" |
|
|
|
emptyText: 'No projects submitted yet', |
|
|
|
tabs={[ |
|
|
|
}, |
|
|
|
{ |
|
|
|
]} |
|
|
|
title: 'This Month', |
|
|
|
/> |
|
|
|
users: stats.projectSubmissions.currentMonth, |
|
|
|
<LeaderboardLane |
|
|
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />, |
|
|
|
title="Top Contributors" |
|
|
|
emptyText: 'No projects submitted this month', |
|
|
|
subtitle="Past 2 weeks" |
|
|
|
}, |
|
|
|
tabs={[ |
|
|
|
{ |
|
|
|
{ |
|
|
|
title: 'Lifetime', |
|
|
|
title: 'This Month', |
|
|
|
users: stats.projectSubmissions.lifetime, |
|
|
|
users: stats.githubContributors.currentMonth, |
|
|
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />, |
|
|
|
emptyIcon: <GitPullRequest className="size-16 text-gray-300" />, |
|
|
|
emptyText: 'No projects submitted yet', |
|
|
|
emptyText: 'No contributors this month', |
|
|
|
}, |
|
|
|
}, |
|
|
|
]} |
|
|
|
]} |
|
|
|
/> |
|
|
|
/> |
|
|
|
<LeaderboardLane |
|
|
|
|
|
|
|
title="Top Contributors" |
|
|
|
|
|
|
|
tabs={[ |
|
|
|
|
|
|
|
{ |
|
|
|
|
|
|
|
title: 'This Month', |
|
|
|
|
|
|
|
users: stats.githubContributors.currentMonth, |
|
|
|
|
|
|
|
emptyIcon: ( |
|
|
|
|
|
|
|
<GitPullRequest className="size-16 text-gray-300" /> |
|
|
|
|
|
|
|
), |
|
|
|
|
|
|
|
emptyText: 'No contributors this month', |
|
|
|
|
|
|
|
}, |
|
|
|
|
|
|
|
]} |
|
|
|
|
|
|
|
/> |
|
|
|
|
|
|
|
</div> |
|
|
|
|
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
@ -93,24 +79,30 @@ export function LeaderboardPage(props: LeaderboardPageProps) { |
|
|
|
|
|
|
|
|
|
|
|
type LeaderboardLaneProps = { |
|
|
|
type LeaderboardLaneProps = { |
|
|
|
title: string; |
|
|
|
title: string; |
|
|
|
|
|
|
|
subtitle?: string; |
|
|
|
tabs: { |
|
|
|
tabs: { |
|
|
|
title: string; |
|
|
|
title: string; |
|
|
|
users: LeadeboardUserDetails[]; |
|
|
|
users: LeaderboardUserDetails[]; |
|
|
|
emptyIcon?: ReactNode; |
|
|
|
emptyIcon?: ReactNode; |
|
|
|
emptyText?: string; |
|
|
|
emptyText?: string; |
|
|
|
}[]; |
|
|
|
}[]; |
|
|
|
}; |
|
|
|
}; |
|
|
|
|
|
|
|
|
|
|
|
function LeaderboardLane(props: LeaderboardLaneProps) { |
|
|
|
function LeaderboardLane(props: LeaderboardLaneProps) { |
|
|
|
const { title, tabs } = props; |
|
|
|
const { title, subtitle, tabs } = props; |
|
|
|
|
|
|
|
|
|
|
|
const [activeTab, setActiveTab] = useState(tabs[0]); |
|
|
|
const [activeTab, setActiveTab] = useState(tabs[0]); |
|
|
|
const { users: usersToShow, emptyIcon, emptyText } = activeTab; |
|
|
|
const { users: usersToShow, emptyIcon, emptyText } = activeTab; |
|
|
|
|
|
|
|
|
|
|
|
return ( |
|
|
|
return ( |
|
|
|
<div className="flex flex-col overflow-hidden rounded-xl border bg-white min-h-[450px] "> |
|
|
|
<div className="flex min-h-[450px] flex-col overflow-hidden rounded-xl border bg-white"> |
|
|
|
<div className="mb-3 flex items-center justify-between gap-2 px-3 py-3"> |
|
|
|
<div className="mb-3 flex items-center justify-between gap-2 bg-gray-100 px-3 py-3"> |
|
|
|
<h3 className="text-base font-medium">{title}</h3> |
|
|
|
<h3 className="text-base font-medium"> |
|
|
|
|
|
|
|
{title}{' '} |
|
|
|
|
|
|
|
{subtitle && ( |
|
|
|
|
|
|
|
<span className="text-sm font-normal text-gray-400 ml-1">{subtitle}</span> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</h3> |
|
|
|
|
|
|
|
|
|
|
|
{tabs.length > 1 && ( |
|
|
|
{tabs.length > 1 && ( |
|
|
|
<div className="flex items-center gap-2"> |
|
|
|
<div className="flex items-center gap-2"> |
|
|
@ -181,7 +173,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) { |
|
|
|
/> |
|
|
|
/> |
|
|
|
{isGitHubUser ? ( |
|
|
|
{isGitHubUser ? ( |
|
|
|
<a |
|
|
|
<a |
|
|
|
href={`https://github.com/${user.name}`} |
|
|
|
href={`https://github.com/kamranahmedse/developer-roadmap/pulls?q=is%3Apr+is%3Aclosed+author%3A${user.name}`} |
|
|
|
target="_blank" |
|
|
|
target="_blank" |
|
|
|
className="truncate font-medium underline underline-offset-2" |
|
|
|
className="truncate font-medium underline underline-offset-2" |
|
|
|
> |
|
|
|
> |
|
|
@ -201,17 +193,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) { |
|
|
|
)} |
|
|
|
)} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
|
|
|
|
|
|
|
|
{isGitHubUser ? ( |
|
|
|
<span className="text-sm text-gray-500">{user.count}</span> |
|
|
|
<a |
|
|
|
|
|
|
|
target={'_blank'} |
|
|
|
|
|
|
|
href={`https://github.com/kamranahmedse/developer-roadmap/pulls/${user.name}`} |
|
|
|
|
|
|
|
className="text-sm text-gray-500" |
|
|
|
|
|
|
|
> |
|
|
|
|
|
|
|
{user.count} |
|
|
|
|
|
|
|
</a> |
|
|
|
|
|
|
|
) : ( |
|
|
|
|
|
|
|
<span className="text-sm text-gray-500">{user.count}</span> |
|
|
|
|
|
|
|
)} |
|
|
|
|
|
|
|
</li> |
|
|
|
</li> |
|
|
|
); |
|
|
|
); |
|
|
|
})} |
|
|
|
})} |
|
|
|