cleanup top scores and add timeframes to them
This commit is contained in:
@ -0,0 +1,23 @@
|
||||
import { Metadata } from "next";
|
||||
import { Timeframe } from "@ssr/common/timeframe";
|
||||
import { TopScoresData } from "@/components/score/top/top-scores-data";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Top Scores",
|
||||
openGraph: {
|
||||
title: "ScoreSaber Reloaded - Top Scores",
|
||||
description: "View the top 50 scores set by players on ScoreSaber.",
|
||||
},
|
||||
};
|
||||
|
||||
type TopScoresPageProps = {
|
||||
params: Promise<{
|
||||
timeframe: Timeframe;
|
||||
}>;
|
||||
};
|
||||
|
||||
export default async function TopScoresPage({ params }: TopScoresPageProps) {
|
||||
const { timeframe } = await params;
|
||||
|
||||
return <TopScoresData timeframe={timeframe} />;
|
||||
}
|
@ -1,59 +0,0 @@
|
||||
import { Metadata } from "next";
|
||||
import Card from "@/components/card";
|
||||
import { kyFetch } from "@ssr/common/utils/utils";
|
||||
import { Config } from "@ssr/common/config";
|
||||
import { TopScoresResponse } from "@ssr/common/response/top-scores-response";
|
||||
import Score from "@/components/score/score";
|
||||
import Link from "next/link";
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title: "Top Scores",
|
||||
openGraph: {
|
||||
title: "ScoreSaber Reloaded - Top Scores",
|
||||
description: "View the top 100 scores set by players on ScoreSaber.",
|
||||
},
|
||||
};
|
||||
|
||||
export default async function TopScoresPage() {
|
||||
const scores = await kyFetch<TopScoresResponse>(`${Config.apiUrl}/scores/top`);
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col gap-2 w-full xl:w-[75%]">
|
||||
<div>
|
||||
<p className="font-semibold'">Top 100 ScoreSaber Scores</p>
|
||||
<p className="text-gray-400">This will only show scores that have been tracked.</p>
|
||||
</div>
|
||||
|
||||
{!scores ? (
|
||||
<p>No scores found</p>
|
||||
) : (
|
||||
<div className="flex flex-col gap-2 divide-y divide-border">
|
||||
{scores.scores.map(({ score, leaderboard, beatSaver }, index) => {
|
||||
const player = score.playerInfo;
|
||||
const name = score.playerInfo ? player.name || player.id : score.playerId;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex flex-col pt-2">
|
||||
<p className="text-sm">
|
||||
Set by{" "}
|
||||
<Link href={`/player/${player.id}`}>
|
||||
<span className="text-ssr hover:brightness-[66%] transition-all transform-gpu">{name}</span>
|
||||
</Link>
|
||||
</p>
|
||||
<Score
|
||||
score={score}
|
||||
leaderboard={leaderboard}
|
||||
beatSaverMap={beatSaver}
|
||||
settings={{
|
||||
hideLeaderboardDropdown: true,
|
||||
hideAccuracyChanger: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -44,7 +44,7 @@ const items: NavbarItem[] = [
|
||||
},
|
||||
{
|
||||
name: "Top Scores",
|
||||
link: "/scores/top",
|
||||
link: "/scores/top/weekly",
|
||||
openInNewTab: false,
|
||||
},
|
||||
];
|
||||
|
6
projects/website/src/components/loading-icon.tsx
Normal file
6
projects/website/src/components/loading-icon.tsx
Normal file
@ -0,0 +1,6 @@
|
||||
import { ArrowPathIcon } from "@heroicons/react/24/solid";
|
||||
import * as React from "react";
|
||||
|
||||
export function LoadingIcon() {
|
||||
return <ArrowPathIcon className="w-5 h-5 animate-spin" />;
|
||||
}
|
125
projects/website/src/components/score/top/top-scores-data.tsx
Normal file
125
projects/website/src/components/score/top/top-scores-data.tsx
Normal file
@ -0,0 +1,125 @@
|
||||
"use client";
|
||||
|
||||
import Card from "@/components/card";
|
||||
import Link from "next/link";
|
||||
import { Button } from "@/components/ui/button";
|
||||
import Score from "@/components/score/score";
|
||||
import { useEffect, useState } from "react";
|
||||
import { Timeframe } from "@ssr/common/timeframe";
|
||||
import { TopScoresResponse } from "@ssr/common/response/top-scores-response";
|
||||
import { Config } from "@ssr/common/config";
|
||||
import { kyFetch } from "@ssr/common/utils/utils";
|
||||
import { useQuery } from "@tanstack/react-query";
|
||||
import { LoadingIcon } from "@/components/loading-icon";
|
||||
import { capitalizeFirstLetter } from "@/common/string-utils";
|
||||
|
||||
type TimeframesType = {
|
||||
timeframe: Timeframe;
|
||||
display: string;
|
||||
};
|
||||
const timeframes: TimeframesType[] = [
|
||||
{
|
||||
timeframe: "daily",
|
||||
display: "Today",
|
||||
},
|
||||
{
|
||||
timeframe: "weekly",
|
||||
display: "This Week",
|
||||
},
|
||||
{
|
||||
timeframe: "monthly",
|
||||
display: "This Month",
|
||||
},
|
||||
{
|
||||
timeframe: "all",
|
||||
display: "All Time",
|
||||
},
|
||||
];
|
||||
|
||||
type TopScoresDataProps = {
|
||||
timeframe: Timeframe;
|
||||
};
|
||||
|
||||
export function TopScoresData({ timeframe }: TopScoresDataProps) {
|
||||
const [selectedTimeframe, setSelectedTimeframe] = useState<Timeframe>(timeframe);
|
||||
const [scores, setScores] = useState<TopScoresResponse | null>(null);
|
||||
|
||||
const { data, isLoading } = useQuery({
|
||||
queryKey: ["top-scores", selectedTimeframe],
|
||||
queryFn: async () => {
|
||||
return kyFetch<TopScoresResponse>(`${Config.apiUrl}/scores/top?limit=50&timeframe=${selectedTimeframe}`);
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
// Update the URL
|
||||
window.history.replaceState(null, "", `/scores/top/${selectedTimeframe}`);
|
||||
}, [selectedTimeframe]);
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setScores(data);
|
||||
}
|
||||
}, [data]);
|
||||
|
||||
return (
|
||||
<Card className="flex flex-col gap-2 w-full xl:w-[75%] justify-center">
|
||||
<div className="flex flex-row flex-wrap gap-2 justify-center">
|
||||
{timeframes.map((timeframe, index) => {
|
||||
return (
|
||||
<Button
|
||||
key={index}
|
||||
className="w-32"
|
||||
variant={selectedTimeframe === timeframe.timeframe ? "default" : "outline"}
|
||||
onClick={() => {
|
||||
setScores(null);
|
||||
setSelectedTimeframe(timeframe.timeframe);
|
||||
}}
|
||||
>
|
||||
{timeframe.display}
|
||||
</Button>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
|
||||
<div className="flex justify-center flex-col text-center">
|
||||
<p className="font-semibold'">Top 50 ScoreSaber Scores ({capitalizeFirstLetter(selectedTimeframe)})</p>
|
||||
<p className="text-gray-400">This will only show scores that have been tracked.</p>
|
||||
</div>
|
||||
|
||||
{(isLoading || !scores) && (
|
||||
<div className="flex justify-center items-center">
|
||||
<LoadingIcon />
|
||||
</div>
|
||||
)}
|
||||
{scores && !isLoading && (
|
||||
<div className="flex flex-col gap-2 divide-y divide-border">
|
||||
{scores.scores.map(({ score, leaderboard, beatSaver }, index) => {
|
||||
const player = score.playerInfo;
|
||||
const name = score.playerInfo ? player.name || player.id : score.playerId;
|
||||
|
||||
return (
|
||||
<div key={index} className="flex flex-col pt-2">
|
||||
<p className="text-sm">
|
||||
Set by{" "}
|
||||
<Link href={`/player/${player.id}`}>
|
||||
<span className="text-ssr hover:brightness-[66%] transition-all transform-gpu">{name}</span>
|
||||
</Link>
|
||||
</p>
|
||||
<Score
|
||||
score={score}
|
||||
leaderboard={leaderboard}
|
||||
beatSaverMap={beatSaver}
|
||||
settings={{
|
||||
hideLeaderboardDropdown: true,
|
||||
hideAccuracyChanger: true,
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
)}
|
||||
</Card>
|
||||
);
|
||||
}
|
@ -3,8 +3,8 @@ import { formatNumberWithCommas, formatPp } from "@ssr/common/utils/number-utils
|
||||
import { capitalizeFirstLetter } from "@/common/string-utils";
|
||||
import Tooltip from "@/components/tooltip";
|
||||
import { ReactElement } from "react";
|
||||
import { ChangeRange } from "@ssr/common/player/player";
|
||||
import { PlayerStatValue } from "@ssr/common/player/player-stat-change";
|
||||
import { Timeframe } from "@ssr/common/timeframe";
|
||||
|
||||
type ChangeOverTimeProps = {
|
||||
/**
|
||||
@ -40,7 +40,7 @@ export function ChangeOverTime({ player, type, children }: ChangeOverTimeProps)
|
||||
};
|
||||
|
||||
// Renders the change for a given time frame
|
||||
const renderChange = (value: number | undefined, range: ChangeRange) => (
|
||||
const renderChange = (value: number | undefined, range: Timeframe) => (
|
||||
<p>
|
||||
{capitalizeFirstLetter(range)} Change:{" "}
|
||||
<span className={value === undefined ? "" : value >= 0 ? (value === 0 ? "" : "text-green-500") : "text-red-500"}>
|
||||
|
Reference in New Issue
Block a user