feat(settings): link the Steam account through Steam
Build, Test & Push Frontend / quality-check (push) Successful in 16s
Build, Test & Push Frontend / docker (push) Successful in 25s

A Steam card in the settings links, verifies or unlinks the Steam
account through "Sign in through Steam". After the way back the page
shows the outcome and refreshes the session, so Core gets the new
Steam ID at once. Registration and settings no longer ask for a typed
Steam ID.
This commit is contained in:
lobo committed 2026-09-26 11:20:04 +02:00
1 parent 23ab5a91e7
commit ca75e6a530
13 files changed
+393 -86

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
import {useState} from 'react';
import {useTranslation} from 'react-i18next';
import {BadgeCheck, CircleAlert, Gamepad2, Link2, Loader2, Unlink} from 'lucide-react';
import {toast} from 'sonner';
import {Badge} from '@/components/ui/badge';
import {Button} from '@/components/ui/button';
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
import useSessionExpired from '../hooks/useSessionExpired.js';
import {markSession} from '../utils/auth.js';
import {startSteamLink, unlinkSteam} from '../utils/steamLink.js';
/**
* The linked Steam account. A Steam ID comes only from "Sign in through Steam"; one typed in before that is shown
* as not verified.
*/
export default function SteamAccountCard({steamId, verified, onUnlinked}) {
const {t} = useTranslation();
const expireSession = useSessionExpired();
const [busy, setBusy] = useState(false);
const link = async () => {
setBusy(true);
try {
const response = await startSteamLink();
if (response.ok) return; // on the way to Steam: the button stays busy
if (response.status === 401) {
expireSession();
} else {
toast.error(t('SETTINGS.STEAM_LINK.ERROR'));
}
} catch (err) {
console.error('Steam link error:', err);
toast.error(t('SETTINGS.STEAM_LINK.ERROR'));
}
setBusy(false);
};
const unlink = async () => {
setBusy(true);
try {
const response = await unlinkSteam();
if (response.status === 401) {
expireSession();
return;
}
if (!response.ok) {
toast.error(t('SETTINGS.STEAM_LINK.ERROR'));
return;
}
// The new access cookie has no Steam ID; the new stamp makes the header load again.
markSession();
toast.success(t('SETTINGS.STEAM_LINK.UNLINKED'));
onUnlinked();
} catch (err) {
console.error('Steam unlink error:', err);
toast.error(t('INFO.ERROR.NETWORK_FAILURE'));
} finally {
setBusy(false);
}
};
return (
<Card>
<CardHeader>
<CardTitle className="flex items-center gap-2"><Gamepad2 className="size-4 text-primary"/>{t('SETTINGS.STEAM')}</CardTitle>
</CardHeader>
<CardContent className="space-y-4">
{steamId ? (
<div className="flex flex-wrap items-center gap-2">
<span className="font-mono text-sm">{steamId}</span>
{verified ? (
<Badge variant="outline"><BadgeCheck className="size-3"/>{t('SETTINGS.STEAM_LINK.VERIFIED')}</Badge>
) : (
<Badge variant="outline"><CircleAlert className="size-3"/>{t('SETTINGS.STEAM_LINK.NOT_VERIFIED')}</Badge>
)}
</div>
) : (
<p className="text-sm text-muted-foreground">{t('SETTINGS.STEAM_LINK.NONE')}</p>
)}
{steamId && !verified && (
<p className="text-sm text-muted-foreground">{t('SETTINGS.STEAM_LINK.VERIFY_HINT')}</p>
)}
<div className="flex flex-wrap gap-2">
<Button type="button" variant={steamId && verified ? 'outline' : 'default'} onClick={link} disabled={busy}>
{busy ? <Loader2 className="animate-spin"/> : <Link2/>}
{t(!steamId ? 'SETTINGS.STEAM_LINK.LINK' : verified ? 'SETTINGS.STEAM_LINK.CHANGE' : 'SETTINGS.STEAM_LINK.VERIFY')}
</Button>
{steamId && (
<Button type="button" variant="outline" onClick={unlink} disabled={busy}>
<Unlink/>{t('SETTINGS.STEAM_LINK.UNLINK')}
</Button>
)}
</div>
</CardContent>
</Card>
);
}