feat: add empty and error pages

feat/leaderboard
Arik Chakma 3 months ago
parent b4edb07510
commit 87da9d038a
  1. 46
      src/components/Leaderboard/LeaderboardPage.tsx

@ -1,9 +1,10 @@
import { useState } from 'react'; import { useState, type ReactNode } 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 } from 'lucide-react';
type LeaderboardPageProps = { type LeaderboardPageProps = {
stats: ListLeaderboardStatsResponse; stats: ListLeaderboardStatsResponse;
@ -22,18 +23,32 @@ export function LeaderboardPage(props: LeaderboardPageProps) {
Top users based on their activity on roadmap.sh Top users based on their activity on roadmap.sh
</p> </p>
<div className="mt-8 grid grid-cols-2 gap-2"> <div className="mt-8 grid gap-2 md:grid-cols-2">
<LeaderboardLane <LeaderboardLane
title="Most Streaks" title="Most Streaks"
tabs={[{ title: 'All Time', users: stats.longestStreaks }]} tabs={[
{
title: 'All Time',
users: stats.longestStreaks,
emptyIcon: <Zap className="size-16 text-gray-300" />,
emptyText: 'No users with streaks yet',
},
]}
/> />
<LeaderboardLane <LeaderboardLane
title="Projects" title="Projects"
tabs={[ tabs={[
{ title: 'Lifetime', users: stats.projectSubmissions.lifetime },
{ {
title: 'This Month', title: 'This Month',
users: stats.projectSubmissions.currentMonth, users: stats.projectSubmissions.currentMonth,
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
emptyText: 'No projects submitted this month',
},
{
title: 'Lifetime',
users: stats.projectSubmissions.lifetime,
emptyIcon: <FolderKanban className="size-16 text-gray-300" />,
emptyText: 'No projects submitted yet',
}, },
]} ]}
/> />
@ -48,6 +63,8 @@ type LeaderboardLaneProps = {
tabs: { tabs: {
title: string; title: string;
users: LeadeboardUserDetails[]; users: LeadeboardUserDetails[];
emptyIcon?: ReactNode;
emptyText?: string;
}[]; }[];
}; };
@ -55,7 +72,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
const { title, tabs } = props; const { title, tabs } = props;
const [activeTab, setActiveTab] = useState(tabs[0]); const [activeTab, setActiveTab] = useState(tabs[0]);
const usersToShow = activeTab.users; const { users: usersToShow, emptyIcon, emptyText } = activeTab;
return ( return (
<div className="rounded-md border bg-white shadow-sm"> <div className="rounded-md border bg-white shadow-sm">
@ -84,11 +101,20 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
)} )}
</div> </div>
{usersToShow.length === 0 && emptyText && (
<div className="flex flex-col items-center justify-center p-8">
{emptyIcon}
<p className="mt-4 text-sm text-gray-500">{emptyText}</p>
</div>
)}
{usersToShow.length > 0 && (
<ul className="divide-y"> <ul className="divide-y">
{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}` ? `${import.meta.env.PUBLIC_AVATAR_BASE_URL}/${user.avatar}`
: '/images/default-avatar.png'; : '/images/default-avatar.png';
const rank = counter + 1;
return ( return (
<li <li
@ -96,8 +122,13 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
className="flex items-center justify-between gap-1 p-2 px-4" className="flex items-center justify-between gap-1 p-2 px-4"
> >
<div className="flex min-w-0 items-center gap-2"> <div className="flex min-w-0 items-center gap-2">
<span className="flex size-7 shrink-0 items-center justify-center tabular-nums"> <span
{counter + 1} className={cn(
'flex size-7 shrink-0 items-center justify-center font-medium tabular-nums',
rank <= 3 ? 'text-black' : 'text-gray-400',
)}
>
{rank}
</span> </span>
<img <img
@ -113,6 +144,7 @@ function LeaderboardLane(props: LeaderboardLaneProps) {
); );
})} })}
</ul> </ul>
)}
</div> </div>
); );
} }

Loading…
Cancel
Save