cleanup and start work on player page

This commit is contained in:
Lee 2024-09-09 09:24:16 +01:00
parent 527d18a422
commit b7ec3ebe0e
8 changed files with 195 additions and 140 deletions

@ -0,0 +1,119 @@
import { scoresaberLeaderboard } from "@/app/common/leaderboard/impl/scoresaber";
import { ScoreSort } from "@/app/common/leaderboard/sort";
import ScoreSaberPlayer from "@/app/common/leaderboard/types/scoresaber/scoresaber-player";
import { formatNumberWithCommas } from "@/app/common/number-utils";
import ClaimProfile from "@/app/components/player/claim-profile";
import PlayerSubName from "@/app/components/player/player-sub-name";
import { Avatar, AvatarFallback, AvatarImage } from "@/app/components/ui/avatar";
import { format } from "@formkit/tempo";
import { GlobeAmericasIcon } from "@heroicons/react/24/solid";
import { Metadata } from "next";
const playerSubNames = [
{
icon: <GlobeAmericasIcon className="h-5 w-5" />,
render: (player: ScoreSaberPlayer) => {
return <p>#{formatNumberWithCommas(player.rank)}</p>;
},
},
{
icon: <GlobeAmericasIcon className="h-5 w-5" />,
render: (player: ScoreSaberPlayer) => {
return <p>#{formatNumberWithCommas(player.countryRank)}</p>;
},
},
{
render: (player: ScoreSaberPlayer) => {
return <p className="tex-pp">{formatNumberWithCommas(player.pp)}pp</p>;
},
},
];
type Props = {
params: {
slug: string[];
};
};
export async function generateMetadata({ params: { slug } }: Props): Promise<Metadata> {
const id = slug[0]; // The players id
const player = await scoresaberLeaderboard.lookupPlayer(id, false);
if (player === undefined) {
return {
title: `Unknown Player`,
openGraph: {
title: `Unknown Player`,
},
};
}
return {
title: `${player.name}`,
openGraph: {
title: `ScoreSaber Reloaded - ${player.name}`,
description: `
PP: ${formatNumberWithCommas(player.pp)}pp
Rank: #${formatNumberWithCommas(player.rank)} (#${formatNumberWithCommas(player.countryRank)} ${player.country})
Joined ScoreSaber: ${format(player.firstSeen, { date: "medium", time: "short" })}
View the scores for ${player.name}!`,
},
};
}
export default async function Search({ params: { slug } }: Props) {
const id = slug[0]; // The players id
const sort: ScoreSort = (slug[1] as ScoreSort) || "recent"; // The sorting method
const page = slug[2] || 1; // The page number
const player = await scoresaberLeaderboard.lookupPlayer(id, false);
console.log("id", id);
console.log("sort", sort);
console.log("page", page);
return (
<div className="flex flex-col h-full w-full">
{player === undefined && (
<div>
<p>idek mate</p>
</div>
)}
{player !== undefined && (
<div className="flex flex-col gap-2">
<div className="flex flex-col bg-secondary p-2 rounded-md">
<div className="flex gap-3 flex-col items-center text-center sm:flex-row sm:items-start sm:text-start relative select-none">
<Avatar className="w-32 h-32 pointer-events-none">
<AvatarImage src={player.profilePicture} />
<AvatarFallback>{player.name}</AvatarFallback>
</Avatar>
<div>
<p className="font-bold text-2xl">{player.name}</p>
<div className="flex gap-2">
{playerSubNames.map((subName, index) => {
return (
<PlayerSubName icon={subName.icon} key={index}>
{subName.render(player)}
</PlayerSubName>
);
})}
{/* <PlayerSubName icon={<GlobeAmericasIcon className="size-6" />}>
<p>#{formatNumberWithCommas(player.rank)}</p>
</PlayerSubName>
<PlayerSubName icon={<GlobeAmericasIcon className="size-6" />}>
<p>#{formatNumberWithCommas(player.countryRank)}</p>
</PlayerSubName>
<PlayerSubName>
<p className="text-pp">{formatNumberWithCommas(player.pp)}pp</p>
</PlayerSubName> */}
</div>
<div className="absolute top-0 right-0">
<ClaimProfile playerId={id} />
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}

@ -1,80 +0,0 @@
import { scoresaberLeaderboard } from "@/app/common/leaderboard/impl/scoresaber";
import { ScoreSort } from "@/app/common/leaderboard/sort";
import { formatNumberWithCommas } from "@/app/common/number-utils";
import ClaimProfile from "@/app/components/claim-profile";
import { Avatar, AvatarFallback, AvatarImage } from "@/app/components/ui/avatar";
import { format } from "@formkit/tempo";
import { Metadata } from "next";
type Props = {
params: {
id: string;
sort: ScoreSort;
page: number;
};
};
export async function generateMetadata({ params: { id } }: Props): Promise<Metadata> {
const player = await scoresaberLeaderboard.lookupPlayer(id, false);
if (player === undefined) {
return {
title: `Unknown Player`,
openGraph: {
title: `Unknown Player`,
},
};
}
return {
title: `${player.name}`,
openGraph: {
title: `ScoreSaber Reloaded - ${player.name}`,
description: `
PP: ${formatNumberWithCommas(player.pp)}pp
Rank: #${formatNumberWithCommas(player.rank)} (#${formatNumberWithCommas(player.countryRank)} ${player.country})
Joined ScoreSaber: ${format(player.firstSeen, { date: "medium", time: "short" })}
View the scores for ${player.name}!`,
},
};
}
export default async function Search({ params: { id, sort, page } }: Props) {
const player = await scoresaberLeaderboard.lookupPlayer(id, false);
console.log("id", id);
console.log("sort", sort);
console.log("page", page);
return (
<div className="flex flex-col h-full w-full">
{player === undefined && (
<div>
<p>idek mate</p>
</div>
)}
{player !== undefined && (
<div className="flex flex-col gap-2">
<div className="flex flex-col bg-secondary p-2 rounded-md">
<div className="flex gap-3 flex-col items-center text-center sm:flex-row sm:items-start sm:text-start relative">
<Avatar className="w-32 h-32">
<AvatarImage src={player.profilePicture} />
<AvatarFallback>{player.name}</AvatarFallback>
</Avatar>
<div>
<p className="font-bold text-2xl">{player.name}</p>
<div className="flex gap-2">
<p className="text-xl text-gray-400">#{formatNumberWithCommas(player.rank)}</p>
<p className="text-xl text-gray-400">#{formatNumberWithCommas(player.countryRank)}</p>
<p className="text-xl text-blue-400">{formatNumberWithCommas(player.pp)}pp</p>
</div>
<div className="absolute top-0 right-0">
<ClaimProfile playerId={id} />
</div>
</div>
</div>
</div>
</div>
)}
</div>
);
}

@ -30,7 +30,7 @@ const renderNavbarItem = (item: NavbarItem) => (
export default function Navbar() { export default function Navbar() {
return ( return (
<div className="p-2"> <div className="w-full py-2">
<div className="px-2 h-10 rounded-md items-center flex justify-between bg-secondary"> <div className="px-2 h-10 rounded-md items-center flex justify-between bg-secondary">
{/* Left-aligned items */} {/* Left-aligned items */}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">

@ -2,11 +2,11 @@
import { CheckIcon } from "@heroicons/react/24/solid"; import { CheckIcon } from "@heroicons/react/24/solid";
import { useLiveQuery } from "dexie-react-hooks"; import { useLiveQuery } from "dexie-react-hooks";
import { setPlayerIdCookie } from "../common/website-utils"; import { setPlayerIdCookie } from "../../common/website-utils";
import useDatabase from "../hooks/use-database"; import useDatabase from "../../hooks/use-database";
import { useToast } from "../hooks/use-toast"; import { useToast } from "../../hooks/use-toast";
import { Button } from "./ui/button"; import { Button } from "../ui/button";
import { Tooltip, TooltipContent, TooltipTrigger } from "./ui/tooltip"; import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
type Props = { type Props = {
/** /**

@ -0,0 +1,13 @@
type Props = {
icon?: React.ReactNode;
children: React.ReactNode;
};
export default function PlayerSubName({ icon, children }: Props) {
return (
<div className="flex gap-1 items-center">
{icon}
{children}
</div>
);
}

@ -63,8 +63,10 @@ export default function RootLayout({
<BackgroundImage /> <BackgroundImage />
<TooltipProvider> <TooltipProvider>
<ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange> <ThemeProvider attribute="class" defaultTheme="dark" enableSystem disableTransitionOnChange>
<NavBar /> <main className="z-[9999] m-auto flex h-screen min-h-full flex-col items-center opacity-90 md:max-w-[1200px]">
<main className="m-2 z-50">{children}</main> <NavBar />
{children}
</main>
</ThemeProvider> </ThemeProvider>
</TooltipProvider> </TooltipProvider>
</DatabaseLoader> </DatabaseLoader>

@ -8,7 +8,7 @@ export function middleware(request: NextRequest) {
const playerIdCookie = cookies.get("playerId"); const playerIdCookie = cookies.get("playerId");
if (pathname == "/") { if (pathname == "/") {
if (playerIdCookie) { if (playerIdCookie) {
return NextResponse.redirect(new URL(`/player/${playerIdCookie.value}/top/1`, request.url)); return NextResponse.redirect(new URL(`/player/${playerIdCookie.value}`, request.url));
} else { } else {
return NextResponse.redirect(new URL("/search", request.url)); return NextResponse.redirect(new URL("/search", request.url));
} }

@ -1,62 +1,63 @@
import type { Config } from "tailwindcss"; import type { Config } from "tailwindcss";
const config: Config = { const config: Config = {
darkMode: ["class"], darkMode: ["class"],
content: [ content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}", "./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}", "./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}", "./src/app/**/*.{js,ts,jsx,tsx,mdx}",
], ],
theme: { theme: {
extend: { extend: {
colors: { colors: {
background: 'hsl(var(--background))', pp: "text-purple-400",
foreground: 'hsl(var(--foreground))', background: "hsl(var(--background))",
card: { foreground: "hsl(var(--foreground))",
DEFAULT: 'hsl(var(--card))', card: {
foreground: 'hsl(var(--card-foreground))' DEFAULT: "hsl(var(--card))",
}, foreground: "hsl(var(--card-foreground))",
popover: { },
DEFAULT: 'hsl(var(--popover))', popover: {
foreground: 'hsl(var(--popover-foreground))' DEFAULT: "hsl(var(--popover))",
}, foreground: "hsl(var(--popover-foreground))",
primary: { },
DEFAULT: 'hsl(var(--primary))', primary: {
foreground: 'hsl(var(--primary-foreground))' DEFAULT: "hsl(var(--primary))",
}, foreground: "hsl(var(--primary-foreground))",
secondary: { },
DEFAULT: 'hsl(var(--secondary))', secondary: {
foreground: 'hsl(var(--secondary-foreground))' DEFAULT: "hsl(var(--secondary))",
}, foreground: "hsl(var(--secondary-foreground))",
muted: { },
DEFAULT: 'hsl(var(--muted))', muted: {
foreground: 'hsl(var(--muted-foreground))' DEFAULT: "hsl(var(--muted))",
}, foreground: "hsl(var(--muted-foreground))",
accent: { },
DEFAULT: 'hsl(var(--accent))', accent: {
foreground: 'hsl(var(--accent-foreground))' DEFAULT: "hsl(var(--accent))",
}, foreground: "hsl(var(--accent-foreground))",
destructive: { },
DEFAULT: 'hsl(var(--destructive))', destructive: {
foreground: 'hsl(var(--destructive-foreground))' DEFAULT: "hsl(var(--destructive))",
}, foreground: "hsl(var(--destructive-foreground))",
border: 'hsl(var(--border))', },
input: 'hsl(var(--input))', border: "hsl(var(--border))",
ring: 'hsl(var(--ring))', input: "hsl(var(--input))",
chart: { ring: "hsl(var(--ring))",
'1': 'hsl(var(--chart-1))', chart: {
'2': 'hsl(var(--chart-2))', "1": "hsl(var(--chart-1))",
'3': 'hsl(var(--chart-3))', "2": "hsl(var(--chart-2))",
'4': 'hsl(var(--chart-4))', "3": "hsl(var(--chart-3))",
'5': 'hsl(var(--chart-5))' "4": "hsl(var(--chart-4))",
} "5": "hsl(var(--chart-5))",
}, },
borderRadius: { },
lg: 'var(--radius)', borderRadius: {
md: 'calc(var(--radius) - 2px)', lg: "var(--radius)",
sm: 'calc(var(--radius) - 4px)' md: "calc(var(--radius) - 2px)",
} sm: "calc(var(--radius) - 4px)",
} },
},
}, },
plugins: [require("tailwindcss-animate")], plugins: [require("tailwindcss-animate")],
}; };