This commit is contained in:
parent
17c40edc3a
commit
e8d864e531
@ -5,6 +5,7 @@ import Card from "../card";
|
|||||||
import CountryFlag from "../country-flag";
|
import CountryFlag from "../country-flag";
|
||||||
import { Avatar, AvatarImage } from "../ui/avatar";
|
import { Avatar, AvatarImage } from "../ui/avatar";
|
||||||
import ClaimProfile from "./claim-profile";
|
import ClaimProfile from "./claim-profile";
|
||||||
|
import PlayerStats from "./player-stats";
|
||||||
|
|
||||||
const playerData = [
|
const playerData = [
|
||||||
{
|
{
|
||||||
@ -40,33 +41,38 @@ type Props = {
|
|||||||
export default function PlayerHeader({ player }: Props) {
|
export default function PlayerHeader({ player }: Props) {
|
||||||
return (
|
return (
|
||||||
<Card>
|
<Card>
|
||||||
<div className="flex gap-3 flex-col items-center text-center sm:flex-row sm:items-start sm:text-start relative select-none">
|
<div className="flex gap-3 flex-col items-center text-center lg:flex-row lg:items-start lg:text-start relative select-none">
|
||||||
<Avatar className="w-32 h-32 pointer-events-none">
|
<Avatar className="w-32 h-32 pointer-events-none">
|
||||||
<AvatarImage fetchPriority="high" src={player.profilePicture} />
|
<AvatarImage fetchPriority="high" src={player.profilePicture} />
|
||||||
</Avatar>
|
</Avatar>
|
||||||
<div>
|
<div className="w-full flex gap-2 flex-col justify-center items-center lg:justify-start lg:items-start">
|
||||||
<p className="font-bold text-2xl">{player.name}</p>
|
<div>
|
||||||
<div className="flex flex-col">
|
<p className="font-bold text-2xl">{player.name}</p>
|
||||||
<div>
|
<div className="flex flex-col">
|
||||||
{player.inactive && <p className="text-gray-400">Inactive Account</p>}
|
<div>
|
||||||
{player.banned && <p className="text-red-500">Banned Account</p>}
|
{player.inactive && <p className="text-gray-400">Inactive Account</p>}
|
||||||
</div>
|
{player.banned && <p className="text-red-500">Banned Account</p>}
|
||||||
<div className="flex gap-2">
|
</div>
|
||||||
{playerData.map((subName, index) => {
|
<div className="flex gap-2">
|
||||||
// Check if the player is inactive or banned and if the data should be shown
|
{playerData.map((subName, index) => {
|
||||||
if (!subName.showWhenInactiveOrBanned && (player.inactive || player.banned)) {
|
// Check if the player is inactive or banned and if the data should be shown
|
||||||
return null;
|
if (!subName.showWhenInactiveOrBanned && (player.inactive || player.banned)) {
|
||||||
}
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div key={index} className="flex gap-1 items-center">
|
<div key={index} className="flex gap-1 items-center">
|
||||||
{subName.icon && subName.icon(player)}
|
{subName.icon && subName.icon(player)}
|
||||||
{subName.render && subName.render(player)}
|
{subName.render && subName.render(player)}
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
})}
|
})}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<PlayerStats player={player} />
|
||||||
|
|
||||||
<div className="absolute top-0 right-0">
|
<div className="absolute top-0 right-0">
|
||||||
<ClaimProfile playerId={player.id} />
|
<ClaimProfile playerId={player.id} />
|
||||||
</div>
|
</div>
|
||||||
|
@ -97,7 +97,7 @@ export default function PlayerRankChart({ player }: Props) {
|
|||||||
lineTension: 0.5,
|
lineTension: 0.5,
|
||||||
data: playerRankHistory,
|
data: playerRankHistory,
|
||||||
label: "Rank",
|
label: "Rank",
|
||||||
borderColor: "#c084fc",
|
borderColor: "#a147fa",
|
||||||
fill: false,
|
fill: false,
|
||||||
color: "#fff",
|
color: "#fff",
|
||||||
},
|
},
|
||||||
|
71
src/components/player/player-stats.tsx
Normal file
71
src/components/player/player-stats.tsx
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
import ScoreSaberPlayer from "@/common/data-fetcher/types/scoresaber/scoresaber-player";
|
||||||
|
import { formatNumberWithCommas } from "@/common/number-utils";
|
||||||
|
import StatValue from "@/components/stat-value";
|
||||||
|
import { ClassValue } from "clsx";
|
||||||
|
|
||||||
|
type Badge = {
|
||||||
|
name: string;
|
||||||
|
color?: ClassValue;
|
||||||
|
create: (player: ScoreSaberPlayer) => string | React.ReactNode | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
const badges: Badge[] = [
|
||||||
|
{
|
||||||
|
name: "Ranked Play Count",
|
||||||
|
color: "bg-pp",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return formatNumberWithCommas(player.scoreStats.rankedPlayCount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Total Ranked Score",
|
||||||
|
color: "bg-pp",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return formatNumberWithCommas(player.scoreStats.totalRankedScore);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Average Ranked Accuracy",
|
||||||
|
color: "bg-pp",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return player.scoreStats.averageRankedAccuracy.toFixed(2) + "%";
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Total Play Count",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return formatNumberWithCommas(player.scoreStats.totalPlayCount);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Total Score",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return formatNumberWithCommas(player.scoreStats.totalScore);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Total Replays Watched",
|
||||||
|
create: (player: ScoreSaberPlayer) => {
|
||||||
|
return formatNumberWithCommas(player.scoreStats.replaysWatched);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
player: ScoreSaberPlayer;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default function PlayerStats({ player }: Props) {
|
||||||
|
return (
|
||||||
|
<div className={`flex flex-wrap gap-2 w-full justify-center lg:justify-start`}>
|
||||||
|
{badges.map((badge, index) => {
|
||||||
|
const toRender = badge.create(player);
|
||||||
|
if (toRender === undefined) {
|
||||||
|
return <div key={index} />;
|
||||||
|
}
|
||||||
|
|
||||||
|
return <StatValue key={index} color={badge.color} name={badge.name} value={toRender} />;
|
||||||
|
})}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
@ -1,18 +1,30 @@
|
|||||||
|
import clsx, { ClassValue } from "clsx";
|
||||||
|
|
||||||
type Props = {
|
type Props = {
|
||||||
/**
|
/**
|
||||||
* The stat name.
|
* The stat name.
|
||||||
*/
|
*/
|
||||||
name?: string;
|
name?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The background color of the stat.
|
||||||
|
*/
|
||||||
|
color?: ClassValue;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The value of the stat.
|
* The value of the stat.
|
||||||
*/
|
*/
|
||||||
value: React.ReactNode;
|
value: React.ReactNode;
|
||||||
};
|
};
|
||||||
|
|
||||||
export default function StatValue({ name, value }: Props) {
|
export default function StatValue({ name, color, value }: Props) {
|
||||||
return (
|
return (
|
||||||
<div className="flex min-w-16 gap-2 bg-accent h-[28px] p-1 items-center justify-center rounded-md text-sm">
|
<div
|
||||||
|
className={clsx(
|
||||||
|
"flex min-w-16 gap-2 h-[28px] p-1 items-center justify-center rounded-md text-sm",
|
||||||
|
color ? color : "bg-accent"
|
||||||
|
)}
|
||||||
|
>
|
||||||
{name && (
|
{name && (
|
||||||
<>
|
<>
|
||||||
<p>{name}</p>
|
<p>{name}</p>
|
||||||
|
@ -10,7 +10,7 @@ const config: Config = {
|
|||||||
theme: {
|
theme: {
|
||||||
extend: {
|
extend: {
|
||||||
colors: {
|
colors: {
|
||||||
pp: "#c084fc",
|
pp: "#a147fa",
|
||||||
background: "hsl(var(--background))",
|
background: "hsl(var(--background))",
|
||||||
foreground: "hsl(var(--foreground))",
|
foreground: "hsl(var(--foreground))",
|
||||||
card: {
|
card: {
|
||||||
|
Reference in New Issue
Block a user