diff --git a/components/Footer.tsx b/components/Footer.tsx index 6a93661d..763153cf 100644 --- a/components/Footer.tsx +++ b/components/Footer.tsx @@ -7,12 +7,14 @@ import { FaGithub, FaInstagram, FaList, + FaTrophy, } from "react-icons/fa"; import { TbUfo } from "react-icons/tb"; import Link from "next/link"; import { MdAlternateEmail } from "react-icons/md"; import { HiStatusOnline } from "react-icons/hi"; import { useEffect, useState } from "react"; +import Leaderboard from '../pages/leaderboard'; export default function Footer() { const [swStatus, setSwStatus] = useState("Finding service worker..."); @@ -136,6 +138,16 @@ export default function Footer() { /> About Us + + + Leaderboard + Debug diff --git a/lib/Types.ts b/lib/Types.ts index e3848d99..f0880b62 100644 --- a/lib/Types.ts +++ b/lib/Types.ts @@ -578,3 +578,20 @@ export class WebhookHolder { this.url = url; } } + +export type LeaderboardUser = { + _id: string; + name: string; + image: string; + xp: number; + level: number; + teams: string[]; +}; + +export type LeaderboardTeam = { + _id: string; + name: string; + number: number; + league: League; + xp: number; +}; diff --git a/lib/api/ClientApi.ts b/lib/api/ClientApi.ts index fe92f229..c8093b0f 100644 --- a/lib/api/ClientApi.ts +++ b/lib/api/ClientApi.ts @@ -19,6 +19,8 @@ import { User, Report, WebhookHolder, + LeaderboardUser, + LeaderboardTeam, } from "@/lib/Types"; import { NotLinkedToTba, removeDuplicates } from "../client/ClientUtils"; import { @@ -2109,4 +2111,70 @@ export default class ClientApi extends NextApiTemplate { responseTime: Date.now() - times.responseTimestamp, })), ); + + getLeaderboard = createNextRoute< + [], + { users: LeaderboardUser[]; teams: LeaderboardTeam[] }, + ApiDependencies, + void + >({ + isAuthorized: AccessLevels.AlwaysAuthorized, + handler: async (req, res, { db: dbPromise }, authData, args) => { + const db = await dbPromise; + + const users = await db.findObjects(CollectionId.Users, { + xp: { $gt: 0 }, + }); + + const teams = await db.findObjects(CollectionId.Teams, { + _id: { + $in: users + .map((user) => user.teams.map((id) => new ObjectId(id))) + .flat(), + }, + }); + + const leaderboardTeams = teams.reduce( + (acc, team) => { + acc[team._id!.toString()] = { + _id: team._id!.toString(), + name: team.name, + number: team.number, + league: team.league, + xp: 0, + }; + + return acc; + }, + {} as { [_id: string]: LeaderboardTeam }, + ); + + const leaderboardUsers = users + .map((user) => ({ + _id: user._id!.toString(), + name: user.name?.split(" ")[0] ?? "Unknown", + image: user.image, + xp: user.xp, + level: user.level, + teams: user.teams + .map((id) => leaderboardTeams[id]) + .map((team) => `${team.league} ${team.number}`), + })) + .sort((a, b) => b.xp - a.xp); + + users.forEach((user) => { + user.teams.forEach((teamId) => { + leaderboardTeams[teamId].xp += user.xp; + }); + }); + + const leaderboardTeamsArray = Object.values(leaderboardTeams).sort( + (a, b) => b.xp - a.xp, + ); + + return res + .status(200) + .send({ users: leaderboardUsers, teams: leaderboardTeamsArray }); + }, + }); } diff --git a/pages/leaderboard.tsx b/pages/leaderboard.tsx new file mode 100644 index 00000000..1ad07218 --- /dev/null +++ b/pages/leaderboard.tsx @@ -0,0 +1,101 @@ +import Card from "@/components/Card"; +import Container from "@/components/Container"; +import ClientApi from "@/lib/api/ClientApi"; +import { useCurrentSession } from "@/lib/client/useCurrentSession"; +import { LeaderboardTeam, LeaderboardUser } from "@/lib/Types"; +import { useEffect, useState } from "react"; + +const api = new ClientApi(); + +export default function Leaderboard() { + const { session } = useCurrentSession(); + + const [usersOrTeam, setUsersOrTeam] = useState<"users" | "teams">("users"); + + const [userLeaderboard, setUserLeaderboard] = useState(); + const [teamLeaderboard, setTeamLeaderboard] = useState(); + + useEffect(() => { + api.getLeaderboard().then(({ users, teams }) => { + setUserLeaderboard(users); + setTeamLeaderboard(teams); + }); + }, []); + + return ( + + + Here is every user/team ranked by their level and XP: + + setUsersOrTeam("users")} + > + Users + + setUsersOrTeam("teams")} + > + Teams + + + + + Rank + {usersOrTeam == "users" ? "User" : "Team"} + {usersOrTeam == "users" && ( + <> + Teams + Level + > + )} + XP + + + {usersOrTeam == "users" && + userLeaderboard && + userLeaderboard.map((user, index) => ( + + {index + 1} + + {user.name} {session?.user?._id == user._id ? "(You)" : ""} + + {user.teams.join(", ")} + {user.level} + {user.xp} + + ))} + {usersOrTeam == "teams" && + teamLeaderboard && + teamLeaderboard.map((team, index) => ( + + {index + 1} + + {team.name}{" "} + {session?.user?.teams.includes(team._id) + ? "(Your Team)" + : ""} + + {team.xp} + + ))} + + + + + ); +}