import {useEffect, useMemo, useState} from 'react'; import {Link, useNavigate, useParams} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; import {ArrowLeft, Clock, ExternalLink, ShieldOff, Trophy} from 'lucide-react'; import {Badge} from '@/components/ui/badge'; import {Button} from '@/components/ui/button'; import {Card} from '@/components/ui/card'; import {Skeleton} from '@/components/ui/skeleton'; import {cn} from '@/lib/utils'; import BacklogCard from '../components/BacklogCard.jsx'; import GameCover from '../components/GameCover.jsx'; import GameStatusBadge from '../components/GameStatusBadge.jsx'; import {formatPlaytime} from '../utils/games.js'; const formatUnlockedAt = (instant, locale) => { if (!instant) return ''; try { return new Date(instant).toLocaleDateString(locale, {year: 'numeric', month: 'short', day: 'numeric'}); } catch { return ''; } }; export default function GameDetail() { const {t, i18n} = useTranslation(); const {appid} = useParams(); const navigate = useNavigate(); const [data, setData] = useState(null); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; const token = localStorage.getItem('tbp_jwt'); if (!token) { navigate('/login'); return; } setIsLoading(true); setError(null); (async () => { try { const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games/${appid}`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (cancelled) return; if (response.status === 401) { localStorage.removeItem('tbp_jwt'); navigate('/login'); return; } if (response.status === 404) { setError(t('GAME_DETAIL.NOT_FOUND')); return; } if (!response.ok) { setError(t('GAME_DETAIL.ERROR_LOAD')); return; } const json = await response.json(); setData(json); } catch (err) { if (!cancelled) { console.error('GameDetail load error:', err); setError(t('INFO.ERROR.NETWORK_FAILURE')); } } finally { if (!cancelled) setIsLoading(false); } })(); return () => { cancelled = true; }; }, [appid, navigate, t]); const sortedAchievements = useMemo(() => { if (!data?.achievements) return []; return [...data.achievements].sort((a, b) => { if (a.unlocked !== b.unlocked) return a.unlocked ? -1 : 1; if (a.unlocked && b.unlocked) { return (b.unlockedAt ?? '').localeCompare(a.unlockedAt ?? ''); } return (a.displayName ?? a.apiName).localeCompare(b.displayName ?? b.apiName); }); }, [data]); const stats = data ? [ [t('GAME_DETAIL.UNLOCKED'), data.achievementsUnlocked], [t('GAME_DETAIL.REMAINING'), data.achievementsRemaining], [t('GAME_DETAIL.COMPLETION'), `${data.completionPercent}%`], ] : []; return (
{isLoading && (
)} {error && !isLoading && (

{error}

)} {!isLoading && !error && data && ( <>

{data.name ?? `App ${data.appid}`}

{t('GAME_DETAIL.PLAYTIME', {time: formatPlaytime(data.playtimeMinutes)})}
setData((d) => ({...d, ...entry}))}/> {!data.hasAchievements && ( {t('GAME_DETAIL.NO_ACHIEVEMENTS')} )} {data.hasAchievements && !data.statsVisible && ( {t('GAME_DETAIL.STATS_HIDDEN')} )} {data.hasAchievements && data.statsVisible && ( <>

{t('GAME_DETAIL.ACHIEVEMENTS')}

{data.achievementsUnlocked} / {data.achievementsTotal}
{stats.map(([label, value]) => (
{value}
{label}
))}
    {sortedAchievements.map((a) => { const icon = a.unlocked ? a.iconUrl : a.iconGrayUrl; return (
  • {icon ? :
    }
    {a.displayName ?? a.apiName}
    {a.description && (
    {a.description}
    )} {a.unlocked && a.unlockedAt && (
    {formatUnlockedAt(a.unlockedAt, i18n.language)}
    )}
  • ); })}
)} )}
); }