Public Access
Displaying the game library with server side pagination
This commit is contained in:
1 parent
994f4a516b
commit
8b290d5a13
8 files changed
+367
-5
No files matched your search
@@ -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>
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
Reference in new issue
Block a user