mirror of
https://github.com/kamranahmedse/developer-roadmap.git
synced 2025-01-17 14:18:17 +01:00
feat: add github contributors leaderboard (#7277)
* feat: add github contributors leaderboard * Improve UI for leaderboard --------- Co-authored-by: Kamran Ahmed <kamranahmed.se@gmail.com>
This commit is contained in:
parent
47936801fd
commit
55f0eff569
BIN
public/images/gifs/party-popper.gif
Normal file
BIN
public/images/gifs/party-popper.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 386 KiB |
Before Width: | Height: | Size: 256 KiB After Width: | Height: | Size: 256 KiB |
BIN
public/images/gifs/star.gif
Normal file
BIN
public/images/gifs/star.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 145 KiB |
BIN
public/images/gifs/starstruck.gif
Normal file
BIN
public/images/gifs/starstruck.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.0 MiB |
BIN
public/images/gifs/sunglasses.gif
Normal file
BIN
public/images/gifs/sunglasses.gif
Normal file
Binary file not shown.
After Width: | Height: | Size: 1013 KiB |
@ -17,6 +17,9 @@ export type ListLeaderboardStatsResponse = {
|
|||||||
currentMonth: LeadeboardUserDetails[];
|
currentMonth: LeadeboardUserDetails[];
|
||||||
lifetime: LeadeboardUserDetails[];
|
lifetime: LeadeboardUserDetails[];
|
||||||
};
|
};
|
||||||
|
githubContributors: {
|
||||||
|
currentMonth: LeadeboardUserDetails[];
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export function leaderboardApi(context: APIContext) {
|
export function leaderboardApi(context: APIContext) {
|
||||||
|
@ -23,7 +23,7 @@ const formattedDate = DateTime.fromISO('2024-09-13').toFormat('dd LLL, yyyy');
|
|||||||
<div
|
<div
|
||||||
class='flex flex-col items-center justify-center gap-2 sm:gap-2 rounded-xl border bg-white px-8 py-12 text-center'
|
class='flex flex-col items-center justify-center gap-2 sm:gap-2 rounded-xl border bg-white px-8 py-12 text-center'
|
||||||
>
|
>
|
||||||
<img src='/images/rocket.gif' class='w-[70px] mb-4' />
|
<img src='/images/gifs/rocket.gif' class='w-[70px] mb-4' />
|
||||||
<h2 class='text-balance text-xl font-medium'>Changelog page is launched</h2>
|
<h2 class='text-balance text-xl font-medium'>Changelog page is launched</h2>
|
||||||
<p class='font-normal text-balance text-gray-400 text-sm sm:text-base'>
|
<p class='font-normal text-balance text-gray-400 text-sm sm:text-base'>
|
||||||
We will be sharing a selected list of updates, improvements, and fixes made to
|
We will be sharing a selected list of updates, improvements, and fixes made to
|
||||||
|
@ -10,7 +10,7 @@ const top10Changelogs = allChangelogs.slice(0, 10);
|
|||||||
<div class='container !max-w-[650px]'>
|
<div class='container !max-w-[650px]'>
|
||||||
<p class='text-2xl font-bold sm:text-5xl'>
|
<p class='text-2xl font-bold sm:text-5xl'>
|
||||||
<img
|
<img
|
||||||
src='/images/rocket.gif'
|
src='/images/gifs/rocket.gif'
|
||||||
alt='Rocket'
|
alt='Rocket'
|
||||||
class='mr-2 hidden sm:inline h-12 w-12'
|
class='mr-2 hidden sm:inline h-12 w-12'
|
||||||
/>
|
/>
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { useState, type ReactNode } from 'react';
|
import { type ReactNode, useState } from 'react';
|
||||||
import type {
|
import type {
|
||||||
LeadeboardUserDetails,
|
LeadeboardUserDetails,
|
||||||
ListLeaderboardStatsResponse,
|
ListLeaderboardStatsResponse,
|
||||||
} from '../../api/leaderboard';
|
} from '../../api/leaderboard';
|
||||||
import { cn } from '../../lib/classname';
|
import { cn } from '../../lib/classname';
|
||||||
import { FolderKanban, Zap, Trophy } from 'lucide-react';
|
import { FolderKanban, GitPullRequest, Trophy, Zap } from 'lucide-react';
|
||||||
import { RankBadgeIcon } from '../ReactIcons/RankBadgeIcon';
|
|
||||||
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';
|
||||||
@ -19,17 +18,25 @@ export function LeaderboardPage(props: LeaderboardPageProps) {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-gray-50">
|
<div className="min-h-screen bg-gray-50">
|
||||||
<div className="container py-10">
|
<div className="container py-5 sm:py-10">
|
||||||
<div className="mb-8 text-center">
|
<div className="mb-8 text-center">
|
||||||
<div className="mb-2 flex items-center justify-center gap-3">
|
<div className="flex flex-col items-start sm:items-center justify-center">
|
||||||
<Trophy className="size-8 text-yellow-500" />
|
<img
|
||||||
<h2 className="text-2xl font-bold sm:text-3xl">Leaderboard</h2>
|
src={'/images/gifs/star.gif'}
|
||||||
</div>
|
alt="party-popper"
|
||||||
<p className="mx-auto max-w-2xl text-balance text-sm text-gray-500 sm:text-base">
|
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
|
Top users based on their activity on roadmap.sh
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="mt-8 grid gap-2 md:grid-cols-2">
|
<div className="mt-5 sm:mt-8 grid gap-2 md:grid-cols-2">
|
||||||
<LeaderboardLane
|
<LeaderboardLane
|
||||||
title="Longest Visit Streak"
|
title="Longest Visit Streak"
|
||||||
tabs={[
|
tabs={[
|
||||||
@ -64,6 +71,19 @@ export function LeaderboardPage(props: LeaderboardPageProps) {
|
|||||||
},
|
},
|
||||||
]}
|
]}
|
||||||
/>
|
/>
|
||||||
|
<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>
|
||||||
@ -88,8 +108,8 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
|
|||||||
const { users: usersToShow, emptyIcon, emptyText } = activeTab;
|
const { users: usersToShow, emptyIcon, emptyText } = activeTab;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="overflow-hidden rounded-md border bg-white shadow-sm">
|
<div className="flex flex-col overflow-hidden rounded-xl border bg-white min-h-[450px] ">
|
||||||
<div className="flex items-center justify-between gap-2 bg-gray-100 px-3 py-3 mb-3">
|
<div className="mb-3 flex items-center justify-between gap-2 px-3 py-3">
|
||||||
<h3 className="text-base font-medium">{title}</h3>
|
<h3 className="text-base font-medium">{title}</h3>
|
||||||
|
|
||||||
{tabs.length > 1 && (
|
{tabs.length > 1 && (
|
||||||
@ -118,7 +138,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{usersToShow.length === 0 && emptyText && (
|
{usersToShow.length === 0 && emptyText && (
|
||||||
<div className="flex flex-col items-center justify-center p-8">
|
<div className="flex flex-grow flex-col items-center justify-center p-8">
|
||||||
{emptyIcon}
|
{emptyIcon}
|
||||||
<p className="mt-4 text-sm text-gray-500">{emptyText}</p>
|
<p className="mt-4 text-sm text-gray-500">{emptyText}</p>
|
||||||
</div>
|
</div>
|
||||||
@ -128,19 +148,23 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
|
|||||||
<ul className="divide-y divide-gray-100 pb-4">
|
<ul className="divide-y divide-gray-100 pb-4">
|
||||||
{usersToShow.map((user, counter) => {
|
{usersToShow.map((user, counter) => {
|
||||||
const avatar = user?.avatar
|
const avatar = user?.avatar
|
||||||
? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
|
? user?.avatar?.startsWith('http')
|
||||||
|
? user?.avatar
|
||||||
|
: `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
|
||||||
: '/images/default-avatar.png';
|
: '/images/default-avatar.png';
|
||||||
|
|
||||||
const rank = counter + 1;
|
const rank = counter + 1;
|
||||||
|
const isGitHubUser = avatar?.indexOf('github') > -1;
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li
|
<li
|
||||||
key={user.id}
|
key={user.id}
|
||||||
className="flex items-center justify-between gap-1 pl-2 pr-5 py-2.5 hover:bg-gray-50"
|
className="flex items-center justify-between gap-1 py-2.5 pl-2 pr-5"
|
||||||
>
|
>
|
||||||
<div className="flex min-w-0 items-center gap-2">
|
<div className="flex min-w-0 items-center gap-2">
|
||||||
<span
|
<span
|
||||||
className={cn(
|
className={cn(
|
||||||
'relative text-xs mr-1 flex size-6 shrink-0 items-center justify-center rounded-full tabular-nums',
|
'relative mr-1 flex size-6 shrink-0 items-center justify-center rounded-full text-xs tabular-nums',
|
||||||
{
|
{
|
||||||
'text-black': rank <= 3,
|
'text-black': rank <= 3,
|
||||||
'text-gray-400': rank > 3,
|
'text-gray-400': rank > 3,
|
||||||
@ -153,9 +177,19 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
|
|||||||
<img
|
<img
|
||||||
src={avatar}
|
src={avatar}
|
||||||
alt={user.name}
|
alt={user.name}
|
||||||
className="size-7 shrink-0 rounded-full"
|
className="mr-1 size-7 shrink-0 rounded-full"
|
||||||
/>
|
/>
|
||||||
|
{isGitHubUser ? (
|
||||||
|
<a
|
||||||
|
href={`https://github.com/${user.name}`}
|
||||||
|
target="_blank"
|
||||||
|
className="truncate font-medium underline underline-offset-2"
|
||||||
|
>
|
||||||
|
{user.name}
|
||||||
|
</a>
|
||||||
|
) : (
|
||||||
<span className="truncate">{user.name}</span>
|
<span className="truncate">{user.name}</span>
|
||||||
|
)}
|
||||||
{rank === 1 ? (
|
{rank === 1 ? (
|
||||||
<TrophyEmoji className="size-5" />
|
<TrophyEmoji className="size-5" />
|
||||||
) : rank === 2 ? (
|
) : rank === 2 ? (
|
||||||
@ -167,7 +201,17 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
|
|||||||
)}
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{isGitHubUser ? (
|
||||||
|
<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>
|
<span className="text-sm text-gray-500">{user.count}</span>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user