Public Access
Registration shows the address used and can start over. Confirm page says an admin approves; no login link. Welcome puts next steps first; Steam link returns to the dashboard. Dashboard and library explain an empty or private library. First sync shows queued, polls slower up to 10 min, then a calm text. Login: simple checks, resend offer after 401, password toggles. Only a real expired link counts as invalid; typed passwords stay. Default language follows the browser; texts are platform-neutral.
56 lines
2.1 KiB
React
56 lines
2.1 KiB
React
import {useEffect, useState} from 'react';
|
|
import {Link} from 'react-router-dom';
|
|
import {useTranslation} from 'react-i18next';
|
|
import {ArrowRight, Gamepad2} from 'lucide-react';
|
|
|
|
import {Card} from '@/components/ui/card';
|
|
import useReducedMotion from '../hooks/useReducedMotion.js';
|
|
|
|
const DURATION_MS = 1000;
|
|
const COUNT_CLASS = 'block text-5xl font-bold tracking-tight tabular-nums';
|
|
|
|
const SteamGameCount = ({targetCount}) => {
|
|
const {t} = useTranslation();
|
|
const reduce = useReducedMotion();
|
|
const [count, setCount] = useState(0);
|
|
|
|
useEffect(() => {
|
|
if (reduce) {
|
|
setCount(targetCount);
|
|
return undefined;
|
|
}
|
|
let frame;
|
|
let startTime;
|
|
const animate = (timestamp) => {
|
|
startTime ??= timestamp;
|
|
const progress = Math.min((timestamp - startTime) / DURATION_MS, 1);
|
|
setCount(Math.floor(progress * targetCount));
|
|
if (progress < 1) {
|
|
frame = requestAnimationFrame(animate);
|
|
}
|
|
};
|
|
frame = requestAnimationFrame(animate);
|
|
return () => cancelAnimationFrame(frame);
|
|
}, [targetCount, reduce]);
|
|
|
|
return (
|
|
<Card className="relative justify-between overflow-hidden p-6">
|
|
<div className="absolute -right-6 -top-6 size-32 rounded-full bg-primary/10"/>
|
|
<div className="flex size-10 items-center justify-center rounded-lg bg-primary/10 text-primary">
|
|
<Gamepad2 className="size-5"/>
|
|
</div>
|
|
<div>
|
|
{targetCount > 0
|
|
? <Link to="/library" className={`${COUNT_CLASS} hover:text-primary`}>{count}</Link>
|
|
: <span className={COUNT_CLASS}>{count}</span>}
|
|
<div className="mt-1 text-sm text-muted-foreground">{t('DASHBOARD.GAMES')}</div>
|
|
</div>
|
|
<Link to="/library" className="inline-flex items-center gap-1 text-sm font-medium text-primary hover:underline">
|
|
{t('DASHBOARD.TO_LIBRARY')} <ArrowRight className="size-4"/>
|
|
</Link>
|
|
</Card>
|
|
);
|
|
};
|
|
|
|
export default SteamGameCount;
|