import {useCallback, useEffect, useState} from 'react'; import {useNavigate, useSearchParams} from 'react-router-dom'; import {useTranslation} from 'react-i18next'; import useLibrarySync from '../hooks/useLibrarySync.js'; import SyncStatusBadge from '../components/SyncStatusBadge.jsx'; const PAGE_SIZES = [20, 40, 80]; const DEFAULT_SIZE = 20; const iconUrl = (appid, hash) => hash ? `https://cdn.cloudflare.steamstatic.com/steamcommunity/public/images/apps/${appid}/${hash}.jpg` : null; const formatPlaytime = (minutes) => { if (!minutes || minutes <= 0) return '0h'; const hours = minutes / 60; return hours >= 10 ? `${Math.round(hours)}h` : `${hours.toFixed(1)}h`; }; export default function GameLibrary() { const {t} = useTranslation(); const navigate = useNavigate(); const [searchParams, setSearchParams] = useSearchParams(); const page = Math.max(0, parseInt(searchParams.get('page') ?? '0', 10) || 0); const sizeParam = parseInt(searchParams.get('size') ?? String(DEFAULT_SIZE), 10); const size = PAGE_SIZES.includes(sizeParam) ? sizeParam : DEFAULT_SIZE; const [data, setData] = useState({content: [], page: 0, size, totalElements: 0, totalPages: 0}); const [isLoading, setIsLoading] = useState(true); const [error, setError] = useState(null); const load = useCallback(async () => { const token = localStorage.getItem('tbp_jwt'); if (!token) { navigate('/login'); return; } setIsLoading(true); setError(null); try { const url = `${import.meta.env.VITE_CORE_API_URL}/api/library/games?page=${page}&size=${size}`; const response = await fetch(url, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json', }, }); if (response.status === 401) { localStorage.removeItem('tbp_jwt'); navigate('/login'); return; } if (!response.ok) { setError(t('LIBRARY.ERROR_LOAD')); return; } const json = await response.json(); setData(json); } catch (err) { console.error('Library load error:', err); setError(t('INFO.ERROR.NETWORK_FAILURE')); } finally { setIsLoading(false); } }, [navigate, page, size, t]); useEffect(() => { load(); }, [load]); const expectInitialSync = !isLoading && !error && data.totalElements === 0; const sync = useLibrarySync({ enabled: true, expectInitialSync, onComplete: load, }); const updateParams = (next) => { const params = new URLSearchParams(searchParams); Object.entries(next).forEach(([k, v]) => params.set(k, String(v))); setSearchParams(params); }; const onSizeChange = (event) => { updateParams({size: event.target.value, page: 0}); }; const goToPage = (target) => { if (target < 0 || target >= data.totalPages) return; updateParams({page: target}); }; return (