Public Access
feat(questions): list and restore removed games
Add a "Removed games" section on the questions page and an Undo toast after removing a manual game (#39).
This commit is contained in:
1 parent
c126592887
commit
84b16a2ca8
14 files changed
+280
-9
No files matched your search
@@ -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>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user