feat(stats): add stats page and pile trend
Build, Test & Push Frontend / quality-check (push) Successful in 15s
Build, Test & Push Frontend / docker (push) Successful in 22s

Show the stat groups on /stats, the pile trend on the dashboard card,
one switch per group in the settings, and the shared groups on the
public profile.
This commit is contained in:
lobo committed 2026-09-27 07:16:10 +02:00
1 parent 83bbc2730d
commit 9d1c7bb40d
31 files changed
+978 -11

No files matched your search

+31
View File
@@ -16,12 +16,14 @@ import {Skeleton} from '@/components/ui/skeleton';
import {Switch} from '@/components/ui/switch';
import FormField from '../components/FormField.jsx';
import SteamAccountCard from '../components/SteamAccountCard.jsx';
import ProfileStatsSettings from '../components/stats/ProfileStatsSettings.jsx';
import {settingsSchema} from '../utils/validation.js';
import {usePreferences} from '../context/PreferencesContext.jsx';
import useSessionExpired from '../hooks/useSessionExpired.js';
import useSaveAppearance from '../hooks/useSaveAppearance.js';
import {ACCENTS, CORNERS} from '../utils/appearance.js';
import {coverUrl} from '../utils/games.js';
import {saveProfileStats} from '../utils/stats.js';
import {cn} from '@/lib/utils';
import {apiFetch, refreshSession} from '../utils/api.js';
import {hasSession, markSession} from '../utils/auth.js';
@@ -59,6 +61,7 @@ export default function SettingsPage() {
const [profile, setProfile] = useState(null);
const [isLoading, setIsLoading] = useState(true);
const [loadError, setLoadError] = useState(null);
const [savingProfileStats, setSavingProfileStats] = useState(false);
const [searchParams, setSearchParams] = useSearchParams();
// Read once: removing the parameter below must not load the profile again.
const [steamOutcome] = useState(() => searchParams.get('steam'));
@@ -197,6 +200,31 @@ export default function SettingsPage() {
}
};
// Shown at once; the switches wait for the answer, so the saves reach Auth one after the other.
const handleProfileStatsChange = async (groups) => {
const previous = profile?.settings?.profileStats ?? [];
const showProfileStats = (next) => setProfile((p) => ({...p, settings: {...p.settings, profileStats: next}}));
showProfileStats(groups);
setSavingProfileStats(true);
try {
const response = await saveProfileStats(groups);
if (response.status === 401) {
expireSession();
return;
}
if (!response.ok) {
showProfileStats(previous);
toast.error(t('SETTINGS.ERROR_SAVE'));
}
} catch (err) {
console.error('Profile stats save error:', err);
showProfileStats(previous);
toast.error(t('INFO.ERROR.NETWORK_FAILURE'));
} finally {
setSavingProfileStats(false);
}
};
const copyProfileLink = async (link) => {
try {
await navigator.clipboard.writeText(link);
@@ -429,6 +457,9 @@ export default function SettingsPage() {
</Button>
</div>
)}
<ProfileStatsSettings selected={profile?.settings?.profileStats ?? []}
disabled={!publicProfile} saving={savingProfileStats}
onChange={handleProfileStatsChange}/>
</div>
</CardContent>
</Card>