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.
80 lines
4.2 KiB
React
80 lines
4.2 KiB
React
import {Link} from 'react-router-dom';
|
|
import {useTranslation} from 'react-i18next';
|
|
import {Hash, LayoutGrid, Link2, RefreshCw} from 'lucide-react';
|
|
|
|
import {Badge} from '@/components/ui/badge';
|
|
import {Button} from '@/components/ui/button';
|
|
import {Card} from '@/components/ui/card';
|
|
import {cn} from '@/lib/utils';
|
|
import HeroBanner from './HeroBanner.jsx';
|
|
import FitText from './FitText.jsx';
|
|
import PointsBadge from './PointsBadge.jsx';
|
|
import SteamAvatar from './SteamAvatar.jsx';
|
|
import SyncStatusBadge from './SyncStatusBadge.jsx';
|
|
import {useCurrentUserContext} from '../context/CurrentUserContext.jsx';
|
|
import {bannerEffect, lookClass} from '../utils/rewards.js';
|
|
|
|
// canSync: a Steam ID or a PlayStation link; the sync controls follow it, the Steam ID badge only the Steam ID.
|
|
export default function DashboardWelcomeCard({bannerAppid, avatarUrl, sync, forceSync, canSync, editing, onCustomize, equipped, returning}) {
|
|
const {t} = useTranslation();
|
|
const {user: userData} = useCurrentUserContext();
|
|
const hasSteamId = !!userData?.steamId;
|
|
const showSync = canSync ?? hasSteamId;
|
|
const syncBusy = forceSync.busy || sync.isRunning;
|
|
|
|
return (
|
|
<Card className={cn('relative overflow-hidden p-6 md:p-8 flex flex-col md:flex-row md:items-center md:justify-between gap-6',
|
|
lookClass(equipped, 'BANNER_FRAME'))}>
|
|
{/* A picked game, else the game played most in the last two weeks. */}
|
|
<HeroBanner appid={bannerAppid} effect={bannerEffect(equipped)}/>
|
|
<div className="relative flex min-w-0 items-center gap-4 md:flex-1">
|
|
{/* The slot is there before the avatar loads, so the title does not jump. */}
|
|
{hasSteamId && (
|
|
<span className={cn('flex size-16 shrink-0 items-center justify-center rounded-full bg-muted',
|
|
lookClass(equipped, 'AVATAR_BORDER') ?? 'ring-2 ring-primary/60')}>
|
|
<SteamAvatar url={avatarUrl} className="size-16" iconClassName="size-8 text-muted-foreground"/>
|
|
</span>
|
|
)}
|
|
<div className="min-w-0 flex-1">
|
|
<p className="text-sm font-medium text-muted-foreground">{t(returning ? 'DASHBOARD.WELCOME_BACK' : 'DASHBOARD.WELCOME')}</p>
|
|
<h1 className="@container mt-1 text-3xl md:text-4xl font-bold tracking-tight">
|
|
<FitText text={[t('DASHBOARD.HELLO'), userData?.displayName ?? userData?.email?.split('@')[0], '👋'].filter(Boolean).join(' ')}/>
|
|
</h1>
|
|
<PointsBadge equipped={equipped} className="mt-2"/>
|
|
{hasSteamId ? (
|
|
<Badge variant="outline" className="mt-3 max-w-full gap-1.5 font-mono text-muted-foreground">
|
|
<Hash className="size-3"/>
|
|
<span className="min-w-0 truncate">{userData.steamId}</span>
|
|
</Badge>
|
|
) : (
|
|
<Button asChild variant="link" className="mt-2 h-auto p-0">
|
|
<Link to="/settings"><Link2 className="size-4"/>{t('DASHBOARD.NO_STEAM_ID')}</Link>
|
|
</Button>
|
|
)}
|
|
</div>
|
|
</div>
|
|
|
|
<div className="relative flex flex-col items-start md:items-end gap-3 shrink-0">
|
|
{showSync && (
|
|
<>
|
|
<SyncStatusBadge
|
|
status={sync.status}
|
|
lastSyncedAt={sync.lastSyncedAt}
|
|
isRunning={sync.isRunning}
|
|
queued={sync.queued}
|
|
stale={sync.stale}
|
|
/>
|
|
<Button onClick={forceSync.trigger} disabled={syncBusy}>
|
|
<RefreshCw className={syncBusy ? 'animate-spin' : ''}/>
|
|
{t('LIBRARY.RESYNC')}
|
|
</Button>
|
|
</>
|
|
)}
|
|
<Button variant="ghost" size="sm" disabled={editing} onClick={onCustomize}>
|
|
<LayoutGrid/>{t('TILES.CUSTOMIZE')}
|
|
</Button>
|
|
</div>
|
|
</Card>
|
|
);
|
|
}
|