Finished sidebar

This commit is contained in:
2025-08-16 23:39:11 +02:00
parent 1921efc76a
commit c79d06f272
35 changed files with 1307 additions and 9 deletions

View File

@@ -0,0 +1,167 @@
import { Card, CardContent } from "@/components/ui/card"
import { getColor } from "@/data/colors"
import { formatDate, formatNumber } from "@/lib/formatters"
import { getGuildMember, getGuildRankTag, getMemberGEXP, getMemberWeeklyGEXP } from "@/lib/hypixel/guild"
import { getCoinMultiplier, getTotalChallenges, getTotalCoins, getTotalQuests, rewardClaimed } from "@/lib/hypixel/stats"
import { Guild } from "@/lib/schema/guild"
import { Player } from "@/lib/schema/player"
import { Separator } from "@radix-ui/react-separator"
import Link from "next/link"
import SocialIcons from "./SocialIcons"
type SidebarProps = {
level: number
ign: string
player: Player["player"]
guild: Guild["guild"] | undefined
}
export default function Sidebar({ level, ign, player, guild }: SidebarProps) {
return (
<Card className="w-1/4">
<CardContent>
<div className="flex justify-between px-8">
<div className="text-center">
<p>Hypixel level</p>
<p>{level.toFixed(2)}</p>
</div>
<div className="text-center">
<p>Karma</p>
<p className="text-mc-light-purple">{formatNumber(player.karma)}</p>
</div>
</div>
<Separator className="my-4" />
<div>
<p>
<span className="font-bold">{"Coin multiplier: "}</span>
<span>{`x${getCoinMultiplier(level)} (Level ${level.toFixed(1).split(".")[0]})`}</span>
</p>
<p>
<span className="font-bold">{"Total coins: "}</span>
<span className="text-mc-gold">{formatNumber(getTotalCoins(player.stats))}</span>
</p>
</div>
<Separator className="my-4" />
<div>
<p>
<span>
<Link href={`/achievements/${ign}`} className="font-bold underline">
Achievement Points
</Link>
</span>
<span className="font-bold">{": "}</span>
<span>{formatNumber(player.achievementPoints ?? 0)}</span>
</p>
<p>
<span>
<Link href={`/quests/${ign}`} className="font-bold underline">
Quests Completed
</Link>
</span>
<span className="font-bold">{": "}</span>
<span>{formatNumber(getTotalQuests(player.quests))}</span>
</p>
<p>
<span className="font-bold">{"Challenges Completed: "}</span>
<span>{formatNumber(getTotalChallenges(player.challenges.all_time))}</span>
</p>
</div>
<Separator className="my-4" />
<div>
<p>
<span className="font-bold">{"Today's Reward: "}</span>
<span>{rewardClaimed(player.lastClaimedReward) ? "Claimed" : "Unclaimed"}</span>
</p>
<p>
<span className="font-bold">{"Rewards Claimed: "}</span>
<span>{player.totalRewards}</span>
</p>
<p>
<span className="font-bold">{"Reward Streak: "}</span>
<span>{player.rewardStreak}</span>
</p>
<p>
<span className="font-bold">{"Top Reward Streak: "}</span>
<span>{player.rewardHighScore}</span>
</p>
</div>
<Separator className="my-4" />
<div>
<p>
<span className="font-bold">{"Gifts Given: "}</span>
<span>{player.giftingMeta?.giftsGiven ?? 0}</span>
</p>
<p>
<span className="font-bold">{"Ranks Given: "}</span>
<span>{player.giftingMeta?.ranksGiven ?? 0}</span>
</p>
</div>
<Separator className="my-4" />
<div>
<p>
<span className="font-bold">{"First Login: "}</span>
<span>{formatDate(player.firstLogin ?? 0)}</span>
</p>
<p>
<span className="font-bold">{"Last Login: "}</span>
<span>{formatDate(player.lastLogin ?? 0)}</span>
</p>
</div>
<Separator className="my-4" />
{guild && (
<>
<div className="flex flex-col gap-8">
<div>
<Link href={`/guild/${ign}`}>
<h1 className="text-xl font-bold underline">Guild</h1>
</Link>
<p>
<span className="font-bold">{"Name: "}</span>
<span className={getColor(guild.tagColor, "text", "gray")}>{guild.name}</span>
</p>
<p>
<span className="font-bold">{"Members: "}</span>
<span>{guild.members.length}</span>
</p>
</div>
<div>
<p>
<span className="font-bold">{"Rank: "}</span>
<span>{`${getGuildMember(guild, player.uuid)?.rank} `}</span>
<span className={getColor(guild.tagColor, "text", "gray")}>
{getGuildRankTag(guild, player.uuid)}
</span>
</p>
<p>
<span className="font-bold">{"Daily GEXP: "}</span>
<span>{formatNumber(getMemberGEXP(guild, player.uuid, 0) ?? 0)}</span>
</p>
<p>
<span className="font-bold">{"Weekly GEXP: "}</span>
<span>{formatNumber(getMemberWeeklyGEXP(guild, player.uuid) ?? 0)}</span>
</p>
<p>
<span className="font-bold">{"Joined: "}</span>
<span>{formatDate(getGuildMember(guild, player.uuid)?.joined ?? 0)}</span>
</p>
</div>
</div>
<Separator className="my-4" />
</>
)}
<div className="flex flex-col gap-2">
<h1 className="text-xl font-bold underline">Social Links</h1>
<div className="flex gap-2">
<SocialIcons
discord={player.socialMedia.links.DISCORD}
twitch={player.socialMedia.links.TWITCH}
youtube={player.socialMedia.links.YOUTUBE}
twitter={player.socialMedia.links.TWITCH}
hypixel={player.socialMedia.links.HYPIXEL}
/>
</div>
</div>
</CardContent>
</Card>
)
}

View File

@@ -0,0 +1,74 @@
"use client"
import { Button } from "@/components/ui/button"
import { CopyIcon } from "lucide-react"
import Link from "next/link"
import { CgWebsite } from "react-icons/cg"
import { FaDiscord, FaTwitch, FaYoutube } from "react-icons/fa"
import { FiTwitter } from "react-icons/fi"
import { toast } from "sonner"
export default function SocialIcons(
{ discord, twitch, youtube, twitter, hypixel }: { discord?: string, twitch?: string, youtube?: string, twitter?: string, hypixel?: string }
) {
return (
<>
<DiscordIcon username={discord} />
<SocialIcon href={twitch}>
<FaTwitch />
</SocialIcon>
<SocialIcon href={youtube}>
<FaYoutube />
</SocialIcon>
<SocialIcon href={twitter}>
<FiTwitter />
</SocialIcon>
<SocialIcon href={hypixel}>
<CgWebsite />
</SocialIcon>
</>
)
}
function DiscordIcon({ username }: { username?: string }) {
if (!username) return null
function handleClick() {
toast(
<div className="flex gap-8">
<h1 className="text-2xl">{username}</h1>
<button
onClick={() => {
navigator.clipboard.writeText(username!)
toast.dismiss("discord-username")
}}
>
<CopyIcon />
</button>
</div>,
{
position: "bottom-center",
id: "discord-username",
className: "flex justify-center items-center gap-4"
}
)
}
return (
<Button variant="ghost" className="transition-all hover:scale-125 hover:cursor-pointer" onClick={handleClick}>
<FaDiscord />
</Button>
)
}
function SocialIcon({ href, children }: { href?: string, children: React.ReactNode }) {
if (!href) return null
return (
<Button variant="ghost" className="transition-all hover:scale-125" asChild>
<Link href={href}>
{children}
</Link>
</Button>
)
}

View File

@@ -0,0 +1,63 @@
import DisplayName from "@/components/player/displayname"
import { getGuild } from "@/lib/hypixel/api/guild"
import { getUuid } from "@/lib/hypixel/api/mojang"
import { getPlayer } from "@/lib/hypixel/api/player"
import { getExactLevel } from "@/lib/hypixel/level"
import Sidebar from "./_components/Sidebar"
export default async function PlayerPage({
params
}: {
params: Promise<{ ign: string }>
}) {
const { ign: pign } = await params
const uuid = await getUuid(pign)
if (!uuid) {
return (
<div className="flex flex-col items-center min-h-screen">
<h1 className="mt-25">Player not found</h1>
</div>
)
}
const player = await getPlayer(uuid)
if (!player) {
return (
<div className="flex flex-col items-center min-h-screen">
<h1 className="mt-25">Player not found</h1>
</div>
)
}
const guild = await getGuild(uuid)
const level = getExactLevel(player.networkExp)
return (
<div className="flex flex-col items-center min-h-screen">
<h1 className="text-3xl font-bold mt-25">
<DisplayName
ign={player.displayname}
rank={player.newPackageRank}
monthly={player.monthlyPackageRank}
rankColor={player.monthlyRankColor}
plusColor={player.rankPlusColor}
guildTag={guild?.tag}
tagColor={guild?.tagColor}
/>
</h1>
<h1>
{player.uuid}
</h1>
<div className="flex gap-6 px-6 mt-8 w-full max-w-7xl">
<Sidebar level={level} ign={pign} player={player} guild={guild ?? undefined} />
<div className="p-6 w-3/4 rounded-xl border shadow-sm bg-card">
<h2 className="mb-4 text-xl font-semibold">Game Statistics</h2>
<p>Game stats will be displayed here...</p>
</div>
</div>
</div>
)
}