2023-10-22 02:34:10 +00:00
|
|
|
import { ScoresaberPlayer } from "@/schemas/scoresaber/player";
|
|
|
|
import { usePlayerScoresStore } from "@/store/playerScoresStore";
|
|
|
|
import { useSettingsStore } from "@/store/settingsStore";
|
|
|
|
import { formatNumber } from "@/utils/number";
|
2023-10-22 04:34:08 +00:00
|
|
|
import {
|
|
|
|
calcPpBoundary,
|
|
|
|
getAveragePp,
|
|
|
|
getHighestPpPlay,
|
|
|
|
} from "@/utils/scoresaber/scores";
|
2023-10-22 04:14:37 +00:00
|
|
|
import {
|
|
|
|
GlobeAsiaAustraliaIcon,
|
|
|
|
HomeIcon,
|
|
|
|
UserIcon,
|
|
|
|
} from "@heroicons/react/20/solid";
|
2023-10-22 02:34:10 +00:00
|
|
|
import { useRef } from "react";
|
|
|
|
import ReactCountryFlag from "react-country-flag";
|
|
|
|
import { toast } from "react-toastify";
|
|
|
|
import { useStore } from "zustand";
|
2023-10-22 02:47:22 +00:00
|
|
|
import Avatar from "../Avatar";
|
|
|
|
import Card from "../Card";
|
|
|
|
import Label from "../Label";
|
2023-10-22 02:34:10 +00:00
|
|
|
import PlayerChart from "./PlayerChart";
|
|
|
|
|
|
|
|
type PlayerInfoProps = {
|
|
|
|
playerData: ScoresaberPlayer;
|
|
|
|
};
|
|
|
|
|
|
|
|
export default function PlayerInfo({ playerData }: PlayerInfoProps) {
|
|
|
|
const playerId = playerData.id;
|
|
|
|
const settingsStore = useStore(useSettingsStore, (store) => store);
|
|
|
|
const playerScoreStore = useStore(usePlayerScoresStore, (store) => store);
|
|
|
|
|
|
|
|
// Whether we have scores for this player in the local database
|
|
|
|
const hasLocalScores = playerScoreStore?.exists(playerId);
|
|
|
|
|
|
|
|
const toastId: any = useRef(null);
|
|
|
|
|
|
|
|
async function claimProfile() {
|
|
|
|
settingsStore?.setUserId(playerId);
|
2023-10-22 04:14:37 +00:00
|
|
|
addProfile(false);
|
|
|
|
}
|
2023-10-22 02:34:10 +00:00
|
|
|
|
2023-10-22 04:14:37 +00:00
|
|
|
async function addFriend() {
|
|
|
|
const friend = await settingsStore?.addFriend(playerData.id);
|
|
|
|
if (!friend) {
|
|
|
|
toast.error(`Failed to add ${playerData.name} as a friend`);
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
addProfile(true);
|
|
|
|
}
|
2023-10-22 02:34:10 +00:00
|
|
|
|
2023-10-22 04:14:37 +00:00
|
|
|
async function addProfile(isFriend: boolean) {
|
|
|
|
if (!usePlayerScoresStore.getState().exists(playerId)) {
|
|
|
|
const reponse = await playerScoreStore?.addPlayer(
|
|
|
|
playerId,
|
|
|
|
(page, totalPages) => {
|
|
|
|
const autoClose = page == totalPages ? 5000 : false;
|
|
|
|
|
|
|
|
if (page == 1) {
|
|
|
|
toastId.current = toast.info(
|
|
|
|
`Fetching scores ${page}/${totalPages}`,
|
|
|
|
{
|
|
|
|
autoClose: autoClose,
|
|
|
|
progress: page / totalPages,
|
|
|
|
},
|
|
|
|
);
|
|
|
|
} else {
|
|
|
|
toast.update(toastId.current, {
|
2023-10-22 02:34:10 +00:00
|
|
|
progress: page / totalPages,
|
2023-10-22 04:14:37 +00:00
|
|
|
render: `Fetching scores ${page}/${totalPages}`,
|
|
|
|
autoClose: autoClose,
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
console.log(
|
|
|
|
`Fetching scores for ${playerId} (${page}/${totalPages})`,
|
2023-10-22 02:34:10 +00:00
|
|
|
);
|
2023-10-22 04:14:37 +00:00
|
|
|
},
|
|
|
|
);
|
|
|
|
if (reponse?.error) {
|
|
|
|
toast.error("Failed to fetch scores");
|
|
|
|
console.log(reponse.message);
|
|
|
|
return;
|
|
|
|
}
|
2023-10-22 02:34:10 +00:00
|
|
|
}
|
|
|
|
|
2023-10-22 04:14:37 +00:00
|
|
|
if (!isFriend) {
|
|
|
|
toast.success(`Successfully set ${playerData.name} as your profile`);
|
|
|
|
} else {
|
|
|
|
toast.success(`Successfully added ${playerData.name} as a friend`);
|
|
|
|
}
|
2023-10-22 02:34:10 +00:00
|
|
|
}
|
|
|
|
|
2023-10-22 04:14:37 +00:00
|
|
|
const isOwnProfile = settingsStore?.userId == playerId;
|
|
|
|
|
2023-10-22 02:34:10 +00:00
|
|
|
return (
|
|
|
|
<Card className="mt-2">
|
|
|
|
{/* Player Info */}
|
|
|
|
<div className="flex flex-col items-center gap-3 md:flex-row md:items-start">
|
|
|
|
<div className="min-w-fit">
|
|
|
|
{/* Avatar */}
|
|
|
|
<div className="flex flex-col items-center gap-2">
|
|
|
|
<Avatar url={playerData.profilePicture} label="Avatar" />
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Settings Buttons */}
|
|
|
|
<div className="absolute right-3 top-20 flex flex-col justify-end gap-2 md:relative md:right-0 md:top-0 md:mt-2 md:flex-row md:justify-center">
|
2023-10-22 04:14:37 +00:00
|
|
|
{!isOwnProfile && (
|
|
|
|
<button
|
|
|
|
className="h-fit w-fit rounded-md bg-blue-500 p-1 hover:bg-blue-600"
|
|
|
|
onClick={claimProfile}
|
|
|
|
>
|
|
|
|
<HomeIcon title="Set as your Profile" width={24} height={24} />
|
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
|
|
|
|
{!settingsStore?.isFriend(playerId) && !isOwnProfile && (
|
|
|
|
<button
|
|
|
|
className="rounded-md bg-blue-500 p-1 hover:bg-blue-600"
|
|
|
|
onClick={addFriend}
|
|
|
|
>
|
|
|
|
<UserIcon title="Add as Friend" width={24} height={24} />
|
2023-10-22 02:34:10 +00:00
|
|
|
</button>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
<div className="mt-1 flex w-full flex-col items-center gap-2 md:items-start">
|
|
|
|
{/* Name */}
|
|
|
|
<p className="text-2xl">{playerData.name}</p>
|
|
|
|
|
|
|
|
<div className="flex gap-3 text-xl">
|
|
|
|
{/* Global Rank */}
|
|
|
|
<div className="flex items-center gap-1 text-gray-300">
|
|
|
|
<GlobeAsiaAustraliaIcon width={32} height={32} />
|
|
|
|
<p>#{playerData.rank}</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Country Rank */}
|
|
|
|
<div className="flex items-center gap-1 text-gray-300">
|
|
|
|
<ReactCountryFlag
|
|
|
|
countryCode={playerData.country}
|
|
|
|
svg
|
|
|
|
className="!h-7 !w-7"
|
|
|
|
/>
|
|
|
|
<p>#{playerData.countryRank}</p>
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* PP */}
|
|
|
|
<div className="flex items-center text-gray-300">
|
|
|
|
<p>{formatNumber(playerData.pp)}pp</p>
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
{/* Labels */}
|
|
|
|
<div className="flex flex-wrap justify-center gap-2 md:justify-start">
|
|
|
|
<Label
|
|
|
|
title="Total play count"
|
|
|
|
className="bg-blue-500"
|
2023-10-22 02:47:22 +00:00
|
|
|
hoverValue="Total ranked song play count"
|
2023-10-22 02:34:10 +00:00
|
|
|
value={formatNumber(playerData.scoreStats.totalPlayCount)}
|
|
|
|
/>
|
|
|
|
<Label
|
|
|
|
title="Total score"
|
|
|
|
className="bg-blue-500"
|
2023-10-22 02:47:22 +00:00
|
|
|
hoverValue="Total score of all your plays"
|
2023-10-22 02:34:10 +00:00
|
|
|
value={formatNumber(playerData.scoreStats.totalScore)}
|
|
|
|
/>
|
|
|
|
<Label
|
|
|
|
title="Avg ranked acc"
|
|
|
|
className="bg-blue-500"
|
2023-10-22 02:47:22 +00:00
|
|
|
hoverValue="Average accuracy of all your ranked plays"
|
2023-10-22 02:34:10 +00:00
|
|
|
value={`${playerData.scoreStats.averageRankedAccuracy.toFixed(
|
|
|
|
2,
|
|
|
|
)}%`}
|
|
|
|
/>
|
|
|
|
|
|
|
|
{hasLocalScores && (
|
|
|
|
<>
|
|
|
|
<Label
|
|
|
|
title="Top PP"
|
|
|
|
className="bg-[#8992e8]"
|
2023-10-22 02:47:22 +00:00
|
|
|
hoverValue="Highest pp play"
|
2023-10-22 02:34:10 +00:00
|
|
|
value={`${formatNumber(
|
|
|
|
getHighestPpPlay(playerId)?.toFixed(2),
|
|
|
|
)}pp`}
|
|
|
|
/>
|
2023-10-22 04:34:08 +00:00
|
|
|
<Label
|
|
|
|
title="Avg PP"
|
|
|
|
className="bg-[#8992e8]"
|
|
|
|
hoverValue="Average amount of pp per play (best 20 scores)"
|
|
|
|
value={`${formatNumber(
|
|
|
|
getAveragePp(playerId)?.toFixed(2),
|
|
|
|
)}pp`}
|
|
|
|
/>
|
2023-10-22 02:34:10 +00:00
|
|
|
<Label
|
|
|
|
title="+ 1pp"
|
|
|
|
className="bg-[#8992e8]"
|
2023-10-22 02:47:22 +00:00
|
|
|
hoverValue="Amount of raw pp required to increase your pp by 1pp"
|
2023-10-22 02:34:10 +00:00
|
|
|
value={`${formatNumber(
|
|
|
|
calcPpBoundary(playerId, 1)?.toFixed(2),
|
2023-10-22 02:47:22 +00:00
|
|
|
)}pp per raw`}
|
2023-10-22 02:34:10 +00:00
|
|
|
/>
|
|
|
|
</>
|
|
|
|
)}
|
|
|
|
</div>
|
|
|
|
|
|
|
|
{/* Chart */}
|
|
|
|
<PlayerChart scoresaber={playerData} />
|
|
|
|
</div>
|
|
|
|
</div>
|
|
|
|
</Card>
|
|
|
|
);
|
|
|
|
}
|