import {useEffect, useState} from 'react'; import {useTranslation} from 'react-i18next'; import {toast} from 'sonner'; import {RefreshCw} from 'lucide-react'; import {Button} from '@/components/ui/button'; import {Card} from '@/components/ui/card'; import {Skeleton} from '@/components/ui/skeleton'; import AchievementsCard from '../components/AchievementsCard.jsx'; import ActivityCard from '../components/ActivityCard.jsx'; import CurrentlyPlayingCard from '../components/CurrentlyPlayingCard.jsx'; import DashboardHint from '../components/DashboardHint.jsx'; import DashboardWelcomeCard from '../components/DashboardWelcomeCard.jsx'; import MostPlayedCard from '../components/MostPlayedCard.jsx'; import PileOfShameCard from '../components/PileOfShameCard.jsx'; import SteamGameCount from '../components/SteamGameCount'; import UpNextCard from '../components/UpNextCard.jsx'; import SyncChangesBanner from '../components/SyncChangesBanner.jsx'; import WhatsNewDialog from '../components/WhatsNewDialog.jsx'; import useDashboardData from '../hooks/useDashboardData.js'; import useLibrarySync from '../hooks/useLibrarySync.js'; import useForceSync from '../hooks/useForceSync.js'; import PageContainer from '../components/PageContainer.jsx'; import useSteamProfile from '../hooks/useSteamProfile.js'; import useUserStats from '../hooks/useUserStats.js'; import useSaveResponse from '../hooks/useSaveResponse.js'; import useStatCharts from '../hooks/useStatCharts.js'; import {chartProps} from '../utils/statCharts.js'; import TileGrid from '../components/TileGrid.jsx'; import {StatDrillProvider} from '../components/stats/StatDrill.jsx'; import statGroupTile from '../components/stats/statGroupTile.jsx'; import { DASHBOARD_TILES, DASHBOARD_TITLES, DEFAULT_DASHBOARD, normalizeLayout, PLAYING_GAMES, saveTiles, statGroup, TOP_GAMES, UP_NEXT_GAMES, } from '../utils/tiles.js'; import {usePreferences} from '../context/PreferencesContext.jsx'; import {useCurrentUserContext} from '../context/CurrentUserContext.jsx'; const Dashboard = () => { const {t} = useTranslation(); const {user: userData, updateUser} = useCurrentUserContext(); const { steamData, topGames, playing, upNext, summary, summaryFailed, activity, achievements, isLoading, error, fetchSummary, reloadLibrary, } = useDashboardData(); const [shuffling, setShuffling] = useState(false); const [editing, setEditing] = useState(false); const runSave = useSaveResponse(); const {charts, change} = useStatCharts(); const layout = normalizeLayout(userData?.settings?.dashboardTiles, DASHBOARD_TILES, DEFAULT_DASHBOARD); // The stat groups of the shown stat tiles, and TREND for the pile card and the "pile grew" hint. Waits for the // saved layout in /me. const statGroups = [...new Set([...layout.map((tile) => statGroup(tile.id)).filter(Boolean), 'TREND'])].sort(); const stats = useUserStats(statGroups, userData != null); const shuffleSuggestion = async () => { setShuffling(true); if (!await fetchSummary(summary?.suggestion?.appid)) { toast.error(t('DASHBOARD.SUMMARY_FAILED')); } setShuffling(false); }; const hasSteamId = !!userData?.steamId; // A PlayStation link syncs too, also without a Steam ID. const canSync = hasSteamId || !!steamData.canSync; const showLibrary = canSync || steamData.count > 0; const {appearance, equipped} = usePreferences(); const steamProfile = useSteamProfile(hasSteamId); const bannerAppid = appearance.showBanner ? appearance.bannerAppid ?? activity?.recentGames?.[0]?.appid ?? null : null; const sync = useLibrarySync({ enabled: canSync, expectInitialSync: canSync && steamData.count === 0, onComplete: () => { reloadLibrary(); stats.reload(); }, }); const {watch} = sync; const forceSync = useForceSync(watch); useEffect(() => { if (steamData.syncStarted) watch(); }, [steamData, watch]); if (isLoading) { return ; } if (error) { return (
{t(error)}
); } const saveLayout = async (next) => { // The default is saved as "never changed", so a later change of the default reaches this user too. const tiles = JSON.stringify(next) === JSON.stringify(DEFAULT_DASHBOARD) ? null : next; if (!await runSave(() => saveTiles('dashboard-tiles', tiles))) return; updateUser((d) => ({...d, settings: {...d.settings, dashboardTiles: tiles}})); setEditing(false); }; const renderTile = (id, size) => { switch (id) { case 'GAME_COUNT': return showLibrary && ; case 'MOST_PLAYED': return showLibrary && ; case 'PLAYING': return showLibrary && ( ); case 'UP_NEXT': return showLibrary && ; case 'PILE_OF_SHAME': if (!showLibrary) return null; if (summary) { return ; } return summaryFailed && ( {t('DASHBOARD.SUMMARY_FAILED')} ); case 'ACTIVITY': return canSync && activity && ; case 'ACHIEVEMENTS': return hasSteamId && achievements && ; default: return statGroupTile(id, stats.stats, true, charts, change); } }; return ( setEditing(true)}/> {canSync && } setEditing(false)}/> {/* Last child: space-y gives every other child a bottom margin, which would move the fixed card. */} ); }; function DashboardSkeleton() { return (
); } export default Dashboard;