Skip to content

Commit

Permalink
[feat]: Leaderboard page (#243)
Browse files Browse the repository at this point in the history
* initial leaderboard

* add pagination

* add skeleton loading and minor improvements

* fixed: minor pagination bug

* enhancement: add padding for smaller viewport
  • Loading branch information
dhairyathedev authored Jun 4, 2024
1 parent 97a3b59 commit e7bd899
Show file tree
Hide file tree
Showing 4 changed files with 400 additions and 0 deletions.
29 changes: 29 additions & 0 deletions app/leaderboard/action.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
"use server";
import { supabaseServer } from "@/utils/supabase/server";

const supabase = supabaseServer();

type LeaderboardProp = {
page?: number;
};

export const getLeaderboard = async ({ page = 1 }: LeaderboardProp) => {
try {
const itemsPerPage = 20;
const from = (page - 1) * itemsPerPage;
const to = from + itemsPerPage - 1;

const { data, error } = await supabase
.from("recent_users")
.select("*")
.neq("rating", "-0.1")
.order("rating", { ascending: false })
.range(from, to);

if (error) throw error;

return data;
} catch (error) {
throw error;
}
};
130 changes: 130 additions & 0 deletions app/leaderboard/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
"use client";
import React, { useEffect, useState } from "react";
import {
Table,
TableBody,
TableCaption,
TableCell,
TableHead,
TableHeader,
TableRow,
} from "@/components/ui/table";
import {
Pagination,
PaginationContent,
PaginationEllipsis,
PaginationItem,
PaginationLink,
PaginationNext,
PaginationPrevious,
} from "@/components/ui/pagination";

import { getLeaderboard } from "./action";
import { Avatar, AvatarImage, AvatarFallback } from "@/components/ui/avatar";
import Link from "next/link";
import { Skeleton } from "@/components/ui/skeleton";

interface LeaderboardProp {
avatar_url: string;
username: string;
rating: number;
}

export default function Leaderboard() {
const [leaderboard, setLeaderboard] = useState<LeaderboardProp[]>([]);
const [page, setPage] = useState(1);

useEffect(() => {
getLeaderboard({ page }).then((data) => setLeaderboard(data));
}, [page]);

const handlePrevious = () => {
if (page > 1) {
setPage(page - 1);
setLeaderboard([]);
}
};

const handleNext = () => {
setPage(page + 1);
setLeaderboard([]);
};

return (
<div className="max-w-screen-lg mx-auto px-4 lg:px-0">
<h1 className="text-2xl font-semibold mb-4 mt-4">Leaderboard</h1>
<Table>
<TableHeader>
<TableRow>
<TableHead className="w-[80px]">Rank</TableHead>
<TableHead className="w-[100px]">Avatar</TableHead>
<TableHead>Username</TableHead>
<TableHead>Rating</TableHead>
</TableRow>
</TableHeader>
{leaderboard.length > 0 ? (
<TableBody>
{leaderboard.map((user, index) => (
<TableRow key={user.username}>
<TableCell>{(page - 1) * 20 + index + 1}</TableCell>
<TableCell>
<Avatar>
<AvatarImage src={user.avatar_url} />
<AvatarFallback>CN</AvatarFallback>
</Avatar>
</TableCell>
<TableCell className="font-medium">
<Link
href={`${process.env.NEXT_PUBLIC_URL}/resume/${user.username}`}
>
{user.username}
</Link>
</TableCell>
<TableCell>{user.rating}</TableCell>
</TableRow>
))}
</TableBody>
) : (
<TableBody>
{Array.from({ length: 5 }, (_, index) => (
<TableRow key={index}>
<TableCell>
<Skeleton className="h-4 w-[100px]" />
</TableCell>
<TableCell>
<Skeleton className="h-8 w-8 rounded-full" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[250px]" />
</TableCell>
<TableCell>
<Skeleton className="h-4 w-[250px]" />
</TableCell>
</TableRow>
))}
</TableBody>
)}
</Table>
<Pagination className="mt-5">
<PaginationPrevious
onClick={handlePrevious}
className="cursor-pointer select-none"
>
Previous
</PaginationPrevious>
<PaginationContent>
<PaginationItem>
<PaginationLink>{page}</PaginationLink>
</PaginationItem>
</PaginationContent>

<PaginationNext
onClick={handleNext}
className="cursor-pointer select-none"
>
Next
</PaginationNext>
</Pagination>
</div>
);
}
121 changes: 121 additions & 0 deletions components/ui/pagination.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
import * as React from "react"
import {
ChevronLeftIcon,
ChevronRightIcon,
DotsHorizontalIcon,
} from "@radix-ui/react-icons"

import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button"

const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
<nav
role="navigation"
aria-label="pagination"
className={cn("mx-auto flex w-full justify-center", className)}
{...props}
/>
)
Pagination.displayName = "Pagination"

const PaginationContent = React.forwardRef<
HTMLUListElement,
React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
<ul
ref={ref}
className={cn("flex flex-row items-center gap-1", className)}
{...props}
/>
))
PaginationContent.displayName = "PaginationContent"

const PaginationItem = React.forwardRef<
HTMLLIElement,
React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
<li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"

type PaginationLinkProps = {
isActive?: boolean
} & Pick<ButtonProps, "size"> &
React.ComponentProps<"a">

const PaginationLink = ({
className,
isActive,
size = "icon",
...props
}: PaginationLinkProps) => (
<a
aria-current={isActive ? "page" : undefined}
className={cn(
buttonVariants({
variant: isActive ? "outline" : "ghost",
size,
}),
className
)}
{...props}
/>
)
PaginationLink.displayName = "PaginationLink"

const PaginationPrevious = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to previous page"
size="default"
className={cn("gap-1 pl-2.5", className)}
{...props}
>
<ChevronLeftIcon className="h-4 w-4" />
<span>Previous</span>
</PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"

const PaginationNext = ({
className,
...props
}: React.ComponentProps<typeof PaginationLink>) => (
<PaginationLink
aria-label="Go to next page"
size="default"
className={cn("gap-1 pr-2.5", className)}
{...props}
>
<span>Next</span>
<ChevronRightIcon className="h-4 w-4" />
</PaginationLink>
)
PaginationNext.displayName = "PaginationNext"

const PaginationEllipsis = ({
className,
...props
}: React.ComponentProps<"span">) => (
<span
aria-hidden
className={cn("flex h-9 w-9 items-center justify-center", className)}
{...props}
>
<DotsHorizontalIcon className="h-4 w-4" />
<span className="sr-only">More pages</span>
</span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"

export {
Pagination,
PaginationContent,
PaginationLink,
PaginationItem,
PaginationPrevious,
PaginationNext,
PaginationEllipsis,
}
Loading

0 comments on commit e7bd899

Please sign in to comment.