Public Access
feat(library): search IGDB and add games by hand
This commit is contained in:
1 parent
d9a39dda22
commit
444b03acee
17 files changed
+699
-35
No files matched your search
@@ -1,7 +1,7 @@
|
||||
import {useEffect, useMemo, useState} from 'react';
|
||||
import {Link, useNavigate, useParams} from 'react-router-dom';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {ArrowLeft, Clock, ExternalLink, Image as ImageIcon, ImageOff, PanelTop, RotateCcw, ShieldOff, Trophy} from 'lucide-react';
|
||||
import {ArrowLeft, Clock, ExternalLink, Gamepad2, Image as ImageIcon, ImageOff, PanelTop, RotateCcw, ShieldOff, Trophy} from 'lucide-react';
|
||||
|
||||
import {Badge} from '@/components/ui/badge';
|
||||
import {Button} from '@/components/ui/button';
|
||||
@@ -11,6 +11,7 @@ import {cn} from '@/lib/utils';
|
||||
import BacklogCard from '../components/BacklogCard.jsx';
|
||||
import GameCover from '../components/GameCover.jsx';
|
||||
import GameStatusBadge from '../components/GameStatusBadge.jsx';
|
||||
import ManualGameCard from '../components/ManualGameCard.jsx';
|
||||
import CompletedHint from '../components/CompletedHint.jsx';
|
||||
import PlayingHint from '../components/PlayingHint.jsx';
|
||||
import TimeToBeat from '../components/TimeToBeat.jsx';
|
||||
@@ -21,6 +22,7 @@ import {usePreferences} from '../context/PreferencesContext.jsx';
|
||||
import PageContainer from '../components/PageContainer.jsx';
|
||||
import {apiFetch} from '../utils/api.js';
|
||||
import {hasSession} from '../utils/auth.js';
|
||||
import {isSteamKey} from '../utils/manualGames.js';
|
||||
|
||||
const formatUnlockedAt = (instant, locale) => {
|
||||
if (!instant) return '';
|
||||
@@ -99,6 +101,8 @@ export default function GameDetail() {
|
||||
});
|
||||
}, [data]);
|
||||
|
||||
const manual = data?.source === 'MANUAL';
|
||||
const onSteam = !!data && isSteamKey(data.appid);
|
||||
const isBackground = !!data && appearance.backgroundAppid === data.appid;
|
||||
const isBanner = !!data && appearance.showBanner && appearance.bannerAppid === data.appid;
|
||||
|
||||
@@ -131,7 +135,7 @@ export default function GameDetail() {
|
||||
{!isLoading && !error && data && (
|
||||
<>
|
||||
<Card className="relative overflow-hidden p-0 gap-0 md:flex-row">
|
||||
<GameCover appid={data.appid} className="w-full rounded-none md:w-96 md:shrink-0"/>
|
||||
<GameCover appid={data.appid} imageUrl={data.imageUrl} className="w-full rounded-none md:w-96 md:shrink-0"/>
|
||||
<div className="flex flex-1 flex-col justify-between gap-4 p-6">
|
||||
<div>
|
||||
<h1 className="text-2xl md:text-3xl font-bold tracking-tight">
|
||||
@@ -143,12 +147,16 @@ export default function GameDetail() {
|
||||
{t('GAME_DETAIL.PLAYTIME', {time: formatPlaytime(data.playtimeMinutes)})}
|
||||
</Badge>
|
||||
<GameStatusBadge status={data.status}/>
|
||||
{data.platform && (
|
||||
<Badge variant="outline"><Gamepad2 className="size-3"/>{data.platform}</Badge>
|
||||
)}
|
||||
</div>
|
||||
<div className="mt-3">
|
||||
<TimeToBeat time={data.timeToBeat}/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{/* Store page, background and banner need Steam art, so a game off Steam has none of them. */}
|
||||
{onSteam && <div className="flex flex-wrap gap-2">
|
||||
<Button asChild variant="outline" size="sm">
|
||||
<a href={`https://store.steampowered.com/app/${data.appid}`} target="_blank" rel="noopener noreferrer">
|
||||
{t('GAME_DETAIL.OPEN_IN_STEAM')}<ExternalLink/>
|
||||
@@ -173,7 +181,7 @@ export default function GameDetail() {
|
||||
<PanelTop/>{t('GAME_DETAIL.SET_BANNER')}
|
||||
</Button>
|
||||
)}
|
||||
</div>
|
||||
</div>}
|
||||
</div>
|
||||
</Card>
|
||||
|
||||
@@ -183,10 +191,15 @@ export default function GameDetail() {
|
||||
<BacklogCard key={data.appid} game={data}
|
||||
onSaved={(entry) => setData((d) => ({...d, ...entry}))}/>
|
||||
|
||||
{manual && (
|
||||
<ManualGameCard key={`manual-${data.appid}`} game={data}
|
||||
onSaved={(changes) => setData((d) => ({...d, ...changes}))}/>
|
||||
)}
|
||||
|
||||
{!data.hasAchievements && (
|
||||
<Card className="items-center py-10 text-center text-sm text-muted-foreground">
|
||||
<Trophy className="size-8"/>
|
||||
{t('GAME_DETAIL.NO_ACHIEVEMENTS')}
|
||||
{t(manual ? 'MANUAL_GAME.NO_ACHIEVEMENTS' : 'GAME_DETAIL.NO_ACHIEVEMENTS')}
|
||||
</Card>
|
||||
)}
|
||||
|
||||
|
||||
Reference in new issue
Block a user