Displaying the game library with server side pagination

This commit is contained in:
Andreas Dahm committed 2026-05-04 11:48:08 +02:00
1 parent 994f4a516b
commit 8b290d5a13
8 files changed
+367 -5

No files matched your search

+8 -4
View File
@@ -1,4 +1,5 @@
import React, {useState, useEffect} from 'react';
import {Link} from 'react-router-dom';
import {useTranslation} from 'react-i18next';
const SteamGameCount = ({targetCount}) => {
@@ -7,13 +8,13 @@ const SteamGameCount = ({targetCount}) => {
useEffect(() => {
let startTime;
const duration = 1000; // 1 second
const duration = 1000;
const startCount = 0;
const animate = (timestamp) => {
if (!startTime) startTime = timestamp;
const progress = Math.min((timestamp - startTime) / duration, 1);
const currentCount = Math.floor(progress * (targetCount - startCount) + startCount);
setCount(currentCount);
@@ -26,14 +27,17 @@ const SteamGameCount = ({targetCount}) => {
}, [targetCount]);
return (
<div className="flex flex-col items-center justify-center p-6 bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700">
<Link
to="/library"
className="flex flex-col items-center justify-center p-6 bg-white dark:bg-slate-800 rounded-xl shadow-sm border border-slate-200 dark:border-slate-700 hover:border-blue-500 dark:hover:border-blue-500 transition-colors"
>
<div className="text-4xl font-bold text-blue-600 dark:text-blue-400 mb-2">
{count}
</div>
<div className="text-sm font-medium text-slate-500 dark:text-slate-400 uppercase tracking-wider">
{t('DASHBOARD.STEAM_GAMES')}
</div>
</div>
</Link>
);
};
+45
View File
@@ -0,0 +1,45 @@
import {useTranslation} from 'react-i18next';
const formatRelative = (iso, locale) => {
if (!iso) return null;
const then = new Date(iso).getTime();
const diffSec = Math.round((Date.now() - then) / 1000);
const rtf = new Intl.RelativeTimeFormat(locale, {numeric: 'auto'});
if (diffSec < 60) return rtf.format(-diffSec, 'second');
if (diffSec < 3600) return rtf.format(-Math.round(diffSec / 60), 'minute');
if (diffSec < 86400) return rtf.format(-Math.round(diffSec / 3600), 'hour');
return rtf.format(-Math.round(diffSec / 86400), 'day');
};
export default function SyncStatusBadge({status, lastSyncedAt, isRunning}) {
const {t, i18n} = useTranslation();
if (isRunning) {
return (
<div className="flex items-center gap-2 text-sm text-blue-600 dark:text-blue-400">
<i className="mdi mdi-sync mdi-spin"></i>
<span>{t('LIBRARY.SYNCING')}</span>
</div>
);
}
if (status === 'FAILED') {
return (
<div className="flex items-center gap-2 text-sm text-red-600 dark:text-red-400">
<i className="mdi mdi-alert-circle"></i>
<span>{t('LIBRARY.SYNC_FAILED')}</span>
</div>
);
}
if (lastSyncedAt) {
return (
<div className="flex items-center gap-2 text-sm text-slate-500 dark:text-slate-400">
<i className="mdi mdi-check-circle"></i>
<span>{t('LIBRARY.LAST_SYNCED', {when: formatRelative(lastSyncedAt, i18n.language)})}</span>
</div>
);
}
return null;
}