Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 0 additions & 2 deletions src/App.css
Original file line number Diff line number Diff line change
Expand Up @@ -434,8 +434,6 @@
font-size: 14px;
}



.pokemon-info {
width: 100%;
margin-top: 16px;
Expand Down
84 changes: 5 additions & 79 deletions src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,35 +1,20 @@
import { useState } from 'react';
import type { FormEvent } from 'react';
import { useQuery } from '@tanstack/react-query';
import { useFavorites } from './useFavorites';
import FavoritesSidebar from './FavoritesSidebar';
import SearchBar, { SEARCH_STORAGE_KEY } from './SearchBar';
import { listAllPokemon } from './Api';
import PokemonDetails from './PokemonDetails';

import './App.css';

const SEARCH_STORAGE_KEY = 'pokemon-search';
const RECENT_SEARCHES_STORAGE_KEY = 'pokemon-recent-searches';
const MAX_RECENT_SEARCHES = 5;

function loadRecentSearches(): string[] {
try {
const raw = sessionStorage.getItem(RECENT_SEARCHES_STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}
const HOMEPAGE_POKEMON_COUNT = 8;

function capitalize(text: string): string {
return text.charAt(0).toUpperCase() + text.slice(1);
}

function App() {
const [query, setQuery] = useState(
() => sessionStorage.getItem(SEARCH_STORAGE_KEY) ?? ''
);
const [selectedPokemonId, setSelectedPokemonId] = useState<number | null>(1);

// TanStack Query håndterer henting og caching av Pokémon-lista.
Expand All @@ -48,31 +33,14 @@ function App() {
);

const [error, setError] = useState('');
const [recentSearches, setRecentSearches] = useState(loadRecentSearches);
const [showSuggestions, setShowSuggestions] = useState(false);

const { favorites, isFavorite, toggleFavorite } = useFavorites();

const pokemon = submittedQuery
? allPokemon
.filter((entry) => entry.name.includes(submittedQuery.toLowerCase()))
.slice(0, 24)
: [];

function addToRecentSearches(trimmedQuery: string) {
const withoutDuplicate = recentSearches.filter(
(entry) => entry.toLowerCase() !== trimmedQuery.toLowerCase()
);
const updated = [trimmedQuery, ...withoutDuplicate].slice(
0,
MAX_RECENT_SEARCHES
);
setRecentSearches(updated);
sessionStorage.setItem(
RECENT_SEARCHES_STORAGE_KEY,
JSON.stringify(updated)
);
}
: allPokemon.slice(0, HOMEPAGE_POKEMON_COUNT);

function runSearch(rawQuery: string) {
const trimmedQuery = rawQuery.trim();
Expand All @@ -86,18 +54,6 @@ function App() {
sessionStorage.setItem(SEARCH_STORAGE_KEY, trimmedQuery);
setSubmittedQuery(trimmedQuery);
setError('');
addToRecentSearches(trimmedQuery);
}

function handleSearch(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
runSearch(query);
}

function handleSelectRecentSearch(recentQuery: string) {
setQuery(recentQuery);
setShowSuggestions(false);
runSearch(recentQuery);
}

return (
Expand All @@ -117,38 +73,8 @@ function App() {
setSelectedPokemonId={setSelectedPokemonId}
/>

<section>
<form className="search-form" onSubmit={handleSearch}>
<div className="search-input-wrapper">
<input
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
onFocus={() => setShowSuggestions(true)}
onBlur={() => setShowSuggestions(false)}
placeholder="For eksempel: bulba"
aria-label="Søk etter Pokémon"
/>
{showSuggestions && recentSearches.length > 0 && (
<ul className="recent-searches">
{recentSearches.map((recentQuery) => (
<li key={recentQuery}>
<button
type="button"
onMouseDown={(event) => event.preventDefault()}
onClick={() => handleSelectRecentSearch(recentQuery)}
>
{recentQuery}
</button>
</li>
))}
</ul>
)}
</div>
<button type="submit" disabled={isPending}>
{isPending ? 'Laster Pokémon...' : 'Søk'}
</button>
</form>
<section className="search-bar">
<SearchBar onSearch={runSearch} isLoading={isPending} />
</section>

<section className="results" aria-live="polite">
Expand Down
4 changes: 3 additions & 1 deletion src/PokemonDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -87,7 +87,9 @@ function PokemonDetails({
<tbody>
<tr>
<td>
<span>{selectedPokemon.types.map(capitalize).join(', ')}</span>
<span>
{selectedPokemon.types.map(capitalize).join(', ')}
</span>
</td>
<td>
<span>
Expand Down
98 changes: 98 additions & 0 deletions src/SearchBar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import { useState, type FormEvent } from 'react';

export const SEARCH_STORAGE_KEY = 'pokemon-search';
const RECENT_SEARCHES_STORAGE_KEY = 'pokemon-recent-searches';
const MAX_RECENT_SEARCHES = 5;

type SearchBarProps = {
onSearch: (query: string) => void;
isLoading: boolean;
};

function loadRecentSearches(): string[] {
try {
const raw = sessionStorage.getItem(RECENT_SEARCHES_STORAGE_KEY);
const parsed = raw ? JSON.parse(raw) : [];
return Array.isArray(parsed) ? parsed : [];
} catch {
return [];
}
}

function SearchBar({ onSearch, isLoading }: SearchBarProps) {
const [query, setQuery] = useState(
() => sessionStorage.getItem(SEARCH_STORAGE_KEY) ?? ''
);
const [recentSearches, setRecentSearches] = useState(loadRecentSearches);
const [showSuggestions, setShowSuggestions] = useState(false);

function addToRecentSearches(trimmedQuery: string) {
const withoutDuplicate = recentSearches.filter(
(entry) => entry.toLowerCase() !== trimmedQuery.toLowerCase()
);
const updated = [trimmedQuery, ...withoutDuplicate].slice(
0,
MAX_RECENT_SEARCHES
);
setRecentSearches(updated);
sessionStorage.setItem(
RECENT_SEARCHES_STORAGE_KEY,
JSON.stringify(updated)
);
}

function submitSearch(rawQuery: string) {
const trimmedQuery = rawQuery.trim();
onSearch(trimmedQuery);
if (trimmedQuery) {
addToRecentSearches(trimmedQuery);
}
}

function handleSearch(event: FormEvent<HTMLFormElement>) {
event.preventDefault();
submitSearch(query);
}

function handleSelectRecentSearch(recentQuery: string) {
setQuery(recentQuery);
setShowSuggestions(false);
submitSearch(recentQuery);
}

return (
<form className="search-form" onSubmit={handleSearch}>
<div className="search-input-wrapper">
<input
type="search"
value={query}
onChange={(event) => setQuery(event.target.value)}
onFocus={() => setShowSuggestions(true)}
onBlur={() => setShowSuggestions(false)}
placeholder="For eksempel: bulba"
aria-label="Søk etter Pokémon"
/>
{showSuggestions && recentSearches.length > 0 && (
<ul className="recent-searches">
{recentSearches.map((recentQuery) => (
<li key={recentQuery}>
<button
type="button"
onMouseDown={(event) => event.preventDefault()}
onClick={() => handleSelectRecentSearch(recentQuery)}
>
{recentQuery}
</button>
</li>
))}
</ul>
)}
</div>
<button type="submit" disabled={isLoading}>
{isLoading ? 'Laster Pokémon...' : 'Søk'}
</button>
</form>
);
}

export default SearchBar;