Public Access
feat(search): add game search page (#50)
Add /search to find games by name on IGDB. A game in the library opens its game page. Other hits open a detail view with an Add button. The query stays in the URL. To fit the new nav icon on 360 px phones, the header padding is smaller and the points chip hides while the Questions icon shows.
This commit is contained in:
1 parent
5be0d42bca
commit
60dcfe8992
13 files changed
+276
-16
No files matched your search
@@ -0,0 +1,50 @@
|
||||
import {useEffect, useState} from 'react';
|
||||
import {useNavigate, useSearchParams} from 'react-router-dom';
|
||||
import {useTranslation} from 'react-i18next';
|
||||
import {Search} from 'lucide-react';
|
||||
|
||||
import {Card, CardContent, CardHeader, CardTitle} from '@/components/ui/card';
|
||||
import GameSearch from '../components/GameSearch.jsx';
|
||||
import PageContainer from '../components/PageContainer.jsx';
|
||||
import SearchHitCard from '../components/SearchHitCard.jsx';
|
||||
|
||||
/**
|
||||
* Find games by name on IGDB. A game the user owns opens its page; any other hit opens a detail view with "Add".
|
||||
* The query lives in the URL, so it stays when the user comes back from a game page.
|
||||
*/
|
||||
export default function GameSearchPage() {
|
||||
const {t} = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const [params, setParams] = useSearchParams();
|
||||
const [query, setQuery] = useState(params.get('q') ?? '');
|
||||
const [picked, setPicked] = useState(null);
|
||||
|
||||
// The input keeps its own state: a router update per keystroke runs in a transition and drops keys of a burst.
|
||||
useEffect(() => {
|
||||
const timer = setTimeout(() => setParams(query ? {q: query} : {}, {replace: true}), 400);
|
||||
return () => clearTimeout(timer);
|
||||
}, [query, setParams]);
|
||||
const onQueryChange = (q) => {
|
||||
setPicked(null);
|
||||
setQuery(q);
|
||||
};
|
||||
const onPick = (hit) => (hit.inLibrary ? navigate(`/library/${hit.appid}`) : setPicked(hit));
|
||||
|
||||
return (
|
||||
<PageContainer className="space-y-4 px-4 py-6 md:px-8">
|
||||
{picked ? (
|
||||
<SearchHitCard hit={picked} onBack={() => setPicked(null)}/>
|
||||
) : (
|
||||
<Card>
|
||||
<CardHeader>
|
||||
<CardTitle className="flex items-center gap-2"><Search className="size-4 text-primary"/>{t('GAME_SEARCH.TITLE')}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent className="space-y-3">
|
||||
<p className="text-sm text-muted-foreground">{t('GAME_SEARCH.HINT')}</p>
|
||||
<GameSearch query={query} onQueryChange={onQueryChange} onPick={onPick} markInLibrary/>
|
||||
</CardContent>
|
||||
</Card>
|
||||
)}
|
||||
</PageContainer>
|
||||
);
|
||||
}
|
||||
Reference in new issue
Block a user