diff --git a/src/app/ranking/global/page.tsx b/src/app/ranking/global/page.tsx new file mode 100644 index 0000000..999dde7 --- /dev/null +++ b/src/app/ranking/global/page.tsx @@ -0,0 +1,179 @@ +"use client"; + +import Avatar from "@/components/Avatar"; +import Card from "@/components/Card"; +import Container from "@/components/Container"; +import Pagination from "@/components/Pagination"; +import { Spinner } from "@/components/Spinner"; +import { ScoresaberPlayer } from "@/schemas/scoresaber/player"; +import { formatNumber } from "@/utils/number"; +import { fetchTopPlayers } from "@/utils/scoresaber/api"; +import Image from "next/image"; +import Link from "next/link"; +import { useRouter, useSearchParams } from "next/navigation"; +import { useCallback, useEffect, useState } from "react"; + +type PageInfo = { + loading: boolean; + page: number; + totalPages: number; + players: ScoresaberPlayer[]; +}; + +export default function Player({ params }: { params: { id: string } }) { + const searchParams = useSearchParams(); + const router = useRouter(); + + let page; + const pageString = searchParams.get("page"); + if (pageString == null) { + page = 1; + } else { + page = Number.parseInt(pageString) || 1; + } + + const [error, setError] = useState(false); + const [errorMessage, setErrorMessage] = useState(""); + + const [pageInfo, setPageInfo] = useState({ + loading: true, + page: page, + totalPages: 1, + players: [], + }); + + const updatePage = useCallback( + (page: any) => { + console.log("Switching page to", page); + fetchTopPlayers(page).then((response) => { + if (!response) { + setError(true); + setErrorMessage("No players found"); + setPageInfo({ ...pageInfo, loading: false }); + return; + } + setPageInfo({ + ...pageInfo, + players: response.players, + totalPages: response.pageInfo.totalPages, + loading: false, + page: page, + }); + if (page > 1) { + router.push(`/ranking/global?page=${page}`); + } else { + router.push(`/ranking/global`); + } + }); + }, + [pageInfo, router], + ); + + useEffect(() => { + if (!pageInfo.loading || error) return; + + updatePage(pageInfo.page); + }, [error, params.id, updatePage, pageInfo.page, pageInfo.loading]); + + if (pageInfo.loading || error) { + return ( +
+ + +
+
+ {pageInfo.loading && } + + {error && ( +
+

{errorMessage}

+ + Sad cat +
+ )} +
+
+
+
+
+ ); + } + + const players = pageInfo.players; + + return ( +
+ + + {pageInfo.loading ? ( +
+ +
+ ) : ( + + + + + + + + + + + + + {players.map((player) => ( + + + + + + + + + ))} + +
RankProfilePerformance PointsTotal PlaysTotal Ranked PlaysAvg Ranked Accuracy
#{formatNumber(player.rank)} + {" "} + +

{player.name}

+ +
{formatNumber(player.pp)}pp + {formatNumber(player.scoreStats.totalPlayCount)} + + {formatNumber(player.scoreStats.rankedPlayCount)} + + {player.scoreStats.averageRankedAccuracy.toFixed(2) + "%"} +
+ )} + + {/* Pagination */} +
+
+ { + updatePage(page); + }} + /> +
+
+
+
+
+ ); +} diff --git a/src/utils/scoresaber/api.ts b/src/utils/scoresaber/api.ts index 4377c8c..cfc6ae5 100644 --- a/src/utils/scoresaber/api.ts +++ b/src/utils/scoresaber/api.ts @@ -13,6 +13,7 @@ const SEARCH_PLAYER_URL = const PLAYER_SCORES = API_URL + "/player/{}/scores?limit={}&sort={}&page={}&withMetadata=true"; const GET_PLAYER_DATA_FULL = API_URL + "/player/{}/full"; +const GET_PLAYERS_URL = API_URL + "/players?page={}"; const SearchType = { RECENT: "recent", @@ -141,3 +142,42 @@ export async function fetchAllScores( return scores as ScoresaberPlayerScore[]; } + +/** + * Get the top players + * + * @param page the page to get the players from + * @returns a list of players + */ +export async function fetchTopPlayers(page: number = 1): Promise< + | { + players: ScoresaberPlayer[]; + pageInfo: { + totalPlayers: number; + page: number; + totalPages: number; + }; + } + | undefined +> { + const response = await fetchQueue.fetch( + formatString(GET_PLAYERS_URL, true, page), + ); + const json = await response.json(); + + // Check if there was an error fetching the user data + if (json.errorMessage) { + return undefined; + } + + const players = json.players as ScoresaberPlayer[]; + const metadata = json.metadata; + return { + players: players, + pageInfo: { + totalPlayers: metadata.total, + page: metadata.page, + totalPages: Math.ceil(metadata.total / metadata.itemsPerPage), + }, + }; +}