feat(play-next): show why each game was picked
Build, Test & Push Frontend / quality-check (push) Successful in 1m15s
Build, Test & Push Frontend / docker (push) Successful in 2m33s

Each card shows a reason line under the game name. Issue #28.
This commit is contained in:
lobo committed 2026-10-05 16:13:20 +02:00
1 parent 2b20b389b7
commit edeea5a43d
5 files changed
+55 -5

No files matched your search

+18 -1
View File
@@ -12,6 +12,7 @@ import GameCover from '../components/GameCover.jsx';
import FitText from '../components/FitText.jsx';
import PageContainer from '../components/PageContainer.jsx';
import {saveGameStatus} from '../utils/backlog.js';
import {formatDate} from '../utils/format.js';
import {formatPlaytime} from '../utils/games.js';
import {request} from '../utils/api.js';
import useAbortPrevious from '../hooks/useAbortPrevious.js';
@@ -174,6 +175,20 @@ export default function PlayNextPage() {
);
}
const reasonText = (reason, t, language) => {
if (!reason) return null;
if (reason.source === 'SIMILAR') {
return t('PLAY_NEXT.REASON.SIMILAR', {
name: reason.anchorName ?? `App ${reason.anchorAppid}`,
playtime: formatPlaytime(reason.anchorPlaytimeMinutes),
});
}
if (reason.source === 'LONGEST') {
return t('PLAY_NEXT.REASON.LONGEST', {date: formatDate(reason.firstSeenAt, language, {dateStyle: 'medium'})});
}
return t('PLAY_NEXT.REASON.RANDOM');
};
const emptyKey = (budget, mood) => {
// With a length limit, waiting for the background check may never help.
if (mood === 'SPEEDRUN' && !budget.maxMinutes) return 'PLAY_NEXT.EMPTY_SPEEDRUN';
@@ -182,9 +197,10 @@ const emptyKey = (budget, mood) => {
};
function PickCard({game}) {
const {t} = useTranslation();
const {t, i18n} = useTranslation();
const [busy, setBusy] = useState(false);
const [started, setStarted] = useState(false);
const reason = reasonText(game.reason, t, i18n.language);
const startPlaying = async () => {
setBusy(true);
@@ -214,6 +230,7 @@ function PickCard({game}) {
<Link to={`/library/${game.appid}`} className="@container block min-w-0 font-semibold hover:text-primary transition-colors">
<FitText text={game.name ?? `App ${game.appid}`}/>
</Link>
{reason && <p className="text-sm text-muted-foreground">{reason}</p>}
<div className="flex flex-wrap items-center gap-2 text-sm text-muted-foreground">
{game.timeToBeatMinutes > 0 && (
<span className="flex items-center gap-1">