generated from muhtadeetaron/nextjs-template
171 lines
6.3 KiB
TypeScript
171 lines
6.3 KiB
TypeScript
"use client";
|
|
|
|
import BackgroundWrapper from "@/components/BackgroundWrapper";
|
|
import Header from "@/components/Header";
|
|
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
|
|
import { API_URL, getToken } from "@/lib/auth";
|
|
import { BoardData, getLeaderboard, getUserData } from "@/lib/leaderboard";
|
|
import { UserData } from "@/types/auth";
|
|
import Image from "next/image";
|
|
import React, { useEffect, useState } from "react";
|
|
|
|
const LeaderboardPage = () => {
|
|
const [boardError, setBoardError] = useState<string | null>(null);
|
|
const [boardData, setBoardData] = useState<BoardData[]>([]);
|
|
const [userData, setUserData] = useState<UserData>();
|
|
const [loading, setLoading] = useState(true);
|
|
|
|
useEffect(() => {
|
|
async function fetchUser() {
|
|
try {
|
|
const token = await getToken();
|
|
if (!token) throw new Error("User is not authenticated");
|
|
|
|
const response = await fetch(`${API_URL}/me`, {
|
|
method: "get",
|
|
headers: { Authorization: `Bearer ${token}` },
|
|
});
|
|
|
|
if (!response.ok) throw new Error("Failed to fetch user data");
|
|
|
|
const fetchedUserData = await response.json();
|
|
setLoading(false);
|
|
setUserData(fetchedUserData);
|
|
} catch (error) {
|
|
console.error(error);
|
|
setUserData(undefined);
|
|
}
|
|
}
|
|
|
|
async function fetchBoardData() {
|
|
try {
|
|
const boardResponse = await fetch(`${API_URL}/leaderboard`, {
|
|
method: "GET",
|
|
});
|
|
|
|
if (!boardResponse.ok) {
|
|
throw new Error("Failed to fetch leaderboard data");
|
|
}
|
|
|
|
const fetchedBoardData = await boardResponse.json();
|
|
if (Array.isArray(fetchedBoardData) && fetchedBoardData.length > 0) {
|
|
setBoardData(fetchedBoardData);
|
|
} else {
|
|
setBoardError("No leaderboard data available.");
|
|
setBoardData([]);
|
|
}
|
|
} catch (error) {
|
|
console.error(error);
|
|
setBoardError("Something went wrong. Please try again.");
|
|
setBoardData([]);
|
|
}
|
|
}
|
|
|
|
fetchUser();
|
|
fetchBoardData();
|
|
}, []);
|
|
|
|
const getTopThree = (boardData: BoardData[]) => {
|
|
if (!boardData || !Array.isArray(boardData)) return [];
|
|
const sortedData = boardData
|
|
.filter((player) => player?.points !== undefined) // Ensure `points` exists
|
|
.sort((a, b) => b.points - a.points);
|
|
|
|
const topThree = sortedData.slice(0, 3).map((player, index) => ({
|
|
...player,
|
|
rank: index + 1,
|
|
height: index === 0 ? 280 : index === 1 ? 250 : 220,
|
|
}));
|
|
|
|
return [topThree[1], topThree[0], topThree[2]].filter(Boolean); // Handle missing players
|
|
};
|
|
|
|
return (
|
|
<BackgroundWrapper>
|
|
<section>
|
|
<Header displayTabTitle={"Leaderboard"} />
|
|
<section className="flex flex-col mx-10 pt-10 space-y-4">
|
|
<section className="flex justify-evenly items-end">
|
|
{getTopThree(boardData).map((student, idx) =>
|
|
student ? (
|
|
<div
|
|
key={idx}
|
|
className="w-[100px] flex flex-col bg-[#113768] rounded-t-xl items-center justify-start pt-4 space-y-3"
|
|
style={{ height: student.height }}
|
|
>
|
|
<h3 className="font-bold text-xl text-white">
|
|
{student.rank}
|
|
</h3>
|
|
<Avatar className="bg-slate-300 w-12 h-12">
|
|
<AvatarFallback className="text-xl font-semibold">
|
|
{student.name.charAt(0).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<p className="font-bold text-md text-center text-white">
|
|
{student.name}
|
|
</p>
|
|
<p className="text-sm text-white">({student.points}pt)</p>
|
|
</div>
|
|
) : null
|
|
)}
|
|
</section>
|
|
<div className="w-full border-[0.5px] border-[#c5dbf8] bg-[#c5dbf8]"></div>
|
|
<section className="border-[1px] border-[#c0dafc] w-full rounded-3xl p-6 space-y-4 mb-20">
|
|
<section>
|
|
{getUserData(boardData, userData!.name).map((user, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="flex bg-[#113768] rounded-[8] py-2 px-4 justify-between items-center"
|
|
>
|
|
<div className=" flex gap-3 items-center">
|
|
<h2 className="font-medium text-sm text-white">
|
|
{user.rank}
|
|
</h2>
|
|
<Avatar className="bg-slate-300 w-6 h-6">
|
|
<AvatarFallback className="text-sm font-semibold">
|
|
{user.name.charAt(0).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<h3 className="font-medium text-sm text-white">You</h3>
|
|
</div>
|
|
<p className="font-medium text-white/70 text-sm">
|
|
{user.points}pt
|
|
</p>
|
|
</div>
|
|
))}
|
|
</section>
|
|
<div className="w-full border-[0.5px] border-[#c5dbf8] bg-[#c5dbf8]"></div>
|
|
<section className="space-y-4">
|
|
{getLeaderboard(boardData)
|
|
.slice(0, 10)
|
|
.map((user, idx) => (
|
|
<div
|
|
key={idx}
|
|
className="flex border-2 border-[#c5dbf8] rounded-[8] py-2 px-4 justify-between items-center"
|
|
>
|
|
<div className="flex gap-3 items-center">
|
|
<h2 className="font-medium text-sm">{idx + 1}</h2>
|
|
<Avatar className="bg-slate-300 w-6 h-6">
|
|
<AvatarFallback className="text-sm font-semibold">
|
|
{user.name.charAt(0).toUpperCase()}
|
|
</AvatarFallback>
|
|
</Avatar>
|
|
<h3 className="font-medium text-sm">
|
|
{user.name.split(" ").slice(0, 2).join(" ")}
|
|
</h3>
|
|
</div>
|
|
<p className="font-medium text-[#000]/40 text-sm">
|
|
{user.points}pt
|
|
</p>
|
|
</div>
|
|
))}
|
|
</section>
|
|
</section>
|
|
</section>
|
|
</section>
|
|
</BackgroundWrapper>
|
|
);
|
|
};
|
|
|
|
export default LeaderboardPage;
|