mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-08-30 12:40:03 +02:00
UI for leaderboard
This commit is contained in:
@@ -1,7 +1,7 @@
|
|||||||
import { type APIContext } from 'astro';
|
import { type APIContext } from 'astro';
|
||||||
import { api } from './api.ts';
|
import { api } from './api.ts';
|
||||||
|
|
||||||
export type LeadeboardUserDetails = {
|
export type LeaderboardUserDetails = {
|
||||||
id: string;
|
id: string;
|
||||||
name: string;
|
name: string;
|
||||||
avatar?: string;
|
avatar?: string;
|
||||||
@@ -10,15 +10,15 @@ export type LeadeboardUserDetails = {
|
|||||||
|
|
||||||
export type ListLeaderboardStatsResponse = {
|
export type ListLeaderboardStatsResponse = {
|
||||||
streaks: {
|
streaks: {
|
||||||
active: LeadeboardUserDetails[];
|
active: LeaderboardUserDetails[];
|
||||||
lifetime: LeadeboardUserDetails[];
|
lifetime: LeaderboardUserDetails[];
|
||||||
};
|
};
|
||||||
projectSubmissions: {
|
projectSubmissions: {
|
||||||
currentMonth: LeadeboardUserDetails[];
|
currentMonth: LeaderboardUserDetails[];
|
||||||
lifetime: LeadeboardUserDetails[];
|
lifetime: LeaderboardUserDetails[];
|
||||||
};
|
};
|
||||||
githubContributors: {
|
githubContributors: {
|
||||||
currentMonth: LeadeboardUserDetails[];
|
currentMonth: LeaderboardUserDetails[];
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@@ -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="mb-0 sm:mb-4 flex flex-col items-start sm:items-center justify-start sm:justify-center">
|
|
||||||
<h2 className="mb-1.5 sm:mb-2 text-2xl font-semibold sm:text-4xl">
|
|
||||||
Leaderboard
|
|
||||||
</h2>
|
|
||||||
<p className="max-w-2xl text-left sm:text-center text-balance text-sm text-gray-500 sm:text-base">
|
|
||||||
Top users based on their activity on roadmap.sh
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="mt-5 sm:mt-8 grid gap-2 md:grid-cols-2">
|
<div className="grid gap-2 sm:gap-3 md:grid-cols-2">
|
||||||
<LeaderboardLane
|
<LeaderboardLane
|
||||||
title="Longest Visit Streak"
|
title="Longest Visit Streak"
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
title: 'Active',
|
title: 'Active',
|
||||||
users: stats.streaks?.active || [],
|
users: stats.streaks?.active || [],
|
||||||
emptyIcon: <Zap className="size-16 text-gray-300" />,
|
emptyIcon: <Zap className="size-16 text-gray-300" />,
|
||||||
emptyText: 'No users with streaks yet',
|
emptyText: 'No users with streaks yet',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Lifetime',
|
title: 'Lifetime',
|
||||||
users: stats.streaks?.lifetime || [],
|
users: stats.streaks?.lifetime || [],
|
||||||
emptyIcon: <Zap className="size-16 text-gray-300" />,
|
emptyIcon: <Zap className="size-16 text-gray-300" />,
|
||||||
emptyText: 'No users with streaks yet',
|
emptyText: 'No users with streaks yet',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<LeaderboardLane
|
<LeaderboardLane
|
||||||
title="Projects Completed"
|
title="Projects Completed"
|
||||||
tabs={[
|
tabs={[
|
||||||
{
|
{
|
||||||
title: 'This Month',
|
title: 'This Month',
|
||||||
users: stats.projectSubmissions.currentMonth,
|
users: stats.projectSubmissions.currentMonth,
|
||||||
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
|
||||||
emptyText: 'No projects submitted this month',
|
emptyText: 'No projects submitted this month',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'Lifetime',
|
title: 'Lifetime',
|
||||||
users: stats.projectSubmissions.lifetime,
|
users: stats.projectSubmissions.lifetime,
|
||||||
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
|
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
|
||||||
emptyText: 'No projects submitted yet',
|
emptyText: 'No projects submitted yet',
|
||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
<LeaderboardLane
|
<LeaderboardLane
|
||||||
title="Top Contributors"
|
title="Top Contributors"
|
||||||
tabs={[
|
subtitle="Past 2 weeks"
|
||||||
{
|
tabs={[
|
||||||
title: 'This Month',
|
{
|
||||||
users: stats.githubContributors.currentMonth,
|
title: 'This Month',
|
||||||
emptyIcon: (
|
users: stats.githubContributors.currentMonth,
|
||||||
<GitPullRequest className="size-16 text-gray-300" />
|
emptyIcon: <GitPullRequest className="size-16 text-gray-300" />,
|
||||||
),
|
emptyText: 'No contributors this month',
|
||||||
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>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
Reference in New Issue
Block a user