"use client"; import { useEffect, useRef, useState } from "react"; import { Item, ItemContent, ItemHeader, ItemTitle } from "@/components/ui/item"; import { Progress } from "@/components/ui/progress"; import { SparkleIcon } from "lucide-react"; import type { ResolvedLevel } from "@/utils/xp/resolveLevel"; import { PROFILE_UPDATE_EVENT, type ProfileUpdateDetail } from "@/hooks/useGameTick"; import { Tooltip, TooltipContent, TooltipTrigger } from "@/components/ui/tooltip"; const XPDisplay = ({ xpLevel }: { xpLevel: ResolvedLevel }) => { const { levelName, levelDesc, progress, xp, nextLevelXP } = xpLevel; const [xpGain, setXpGain] = useState(null); const [xpGainId, setXpGainId] = useState(0); const lastGainRef = useRef<{ amount: number; at: number } | null>(null); useEffect(() => { let clearTimer: ReturnType | null = null; const handleProfileUpdate = (event: Event) => { const detail = (event as CustomEvent).detail; if (!detail || typeof detail.xpGained !== "number" || detail.xpGained <= 0) return; const now = Date.now(); if (lastGainRef.current?.amount === detail.xpGained && now - lastGainRef.current.at < 500) { return; } lastGainRef.current = { amount: detail.xpGained, at: now }; setXpGain(detail.xpGained); setXpGainId((id) => id + 1); if (clearTimer) clearTimeout(clearTimer); clearTimer = setTimeout(() => setXpGain(null), 1500); }; window.addEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate); return () => { if (clearTimer) clearTimeout(clearTimer); window.removeEventListener(PROFILE_UPDATE_EVENT, handleProfileUpdate); }; }, []); return (
{levelName}

{levelDesc ?? "Missing content!"}

{xp.toLocaleString()} {xpGain !== null && ( +{xpGain.toLocaleString()} XP )}
{nextLevelXP !== null && ( {Math.floor(progress)}% to next level )}
); }; export default XPDisplay;