feat(questions): list and restore removed games
Build, Test & Push Frontend / quality-check (push) Successful in 36s
Build, Test & Push Frontend / docker (push) Successful in 26s

Add a "Removed games" section on the questions page and an Undo
toast after removing a manual game (#39).
This commit is contained in:
lobo committed 2026-10-11 09:52:11 +02:00
1 parent c126592887
commit 84b16a2ca8
14 files changed
+280 -9

No files matched your search

+70
View File
@@ -0,0 +1,70 @@
import {useEffect, useState} from 'react';
import {useTranslation} from 'react-i18next';
import {toast} from 'sonner';
import {Button} from '@/components/ui/button';
import GameCover from './GameCover.jsx';
import {fetchRemovedGames, restoreGame} from '../utils/manualGames.js';
/**
* The games the user removed, in a closed `<details>`, one Restore button each. Shows nothing when there are none or
* the list does not load: it is a safety net, not a page.
*/
export default function RemovedGamesSection() {
const {t} = useTranslation();
const [games, setGames] = useState([]);
const [busy, setBusy] = useState(false);
useEffect(() => {
let active = true;
fetchRemovedGames().then(({ok, data}) => {
if (active && ok) setGames(data.results);
});
return () => {
active = false;
};
}, []);
const restore = async (appid) => {
setBusy(true);
try {
const response = await restoreGame(appid);
if (response.status === 401) return;
if (response.ok || response.status === 404) {
setGames((current) => current.filter((game) => game.appid !== appid));
if (response.ok) toast.success(t('REMOVED_GAMES.RESTORED'));
} else {
toast.error(t('BACKLOG.SAVE_FAILED'));
}
} catch (err) {
console.error('restore error:', err);
toast.error(t('INFO.ERROR.NETWORK_FAILURE'));
} finally {
setBusy(false);
}
};
if (games.length === 0) return null;
return (
<details className="rounded-lg border p-4">
<summary className="cursor-pointer font-medium">{t('REMOVED_GAMES.TITLE_COUNT', {count: games.length})}</summary>
<p className="mt-3 text-sm text-muted-foreground">{t('REMOVED_GAMES.HINT')}</p>
<ul className="mt-3 space-y-3">
{games.map((game) => (
<li key={game.appid} className="flex items-center gap-3">
<div className="w-16 shrink-0">
<GameCover appid={game.appid} imageUrl={game.imageUrl}/>
</div>
<div className="min-w-0 flex-1">
<p className="truncate font-medium">{game.name ?? `App ${game.appid}`}</p>
{game.platform && <p className="text-sm text-muted-foreground">{game.platform}</p>}
</div>
<Button type="button" size="sm" variant="outline" disabled={busy} onClick={() => restore(game.appid)}>
{t('REMOVED_GAMES.RESTORE')}
</Button>
</li>
))}
</ul>
</details>
);
}