Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 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
4 changes: 4 additions & 0 deletions src/components/CharacterCardSmall.css
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,10 @@
cursor: pointer;
}

.star-favorite {
filter: brightness(0) saturate(100%) invert(84%) sepia(85%) saturate(829%) hue-rotate(359deg) brightness(105%) contrast(104%);
}

.details-button{
background-color: white;
color: black;
Expand Down
10 changes: 6 additions & 4 deletions src/components/CharacterCardSmall.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,11 @@ import StarShape from '../assets/star.svg'
type CharacterCardSmallProps = {
name: string;
image: string;
isFavorite: boolean;
onToggleFavorite: () => void;
};

export default function CharacterCardSmall({name, image}: CharacterCardSmallProps){
export default function CharacterCardSmall({name, image, isFavorite, onToggleFavorite}: CharacterCardSmallProps){
return(
<article className='card-content'>
<img src={CardShape} alt="" className='card-shape'/>
Expand All @@ -16,11 +18,11 @@ export default function CharacterCardSmall({name, image}: CharacterCardSmallProp
<p className='character-name'>{name}</p>
<div className='card-buttons'>
<button type='button' className='details-button'>Details</button>
<button type='button' className='favorite-button'>
<img src={StarShape} alt="" className='star' aria-label={`Add ${name} to favorites`}/>
<button type='button' className='favorite-button' onClick={onToggleFavorite} aria-label={isFavorite ? `Remove ${name} from favorites` : `Add ${name} to favorites`}>
<img src={StarShape} alt="" className={`star ${isFavorite ? 'star-favorite' : ''}`}/>
</button>
</div>
</div>
</article>
)
}
}
134 changes: 88 additions & 46 deletions src/pages/CharactersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,68 +14,110 @@ type Character = {
image: string;
};

const filters = [
{title: 'Gender', options: ['Female', 'Male', 'Unknown'] },
{title: 'Species', options: ['Human', 'Aileen', 'Unknown']},
{title: 'Status', options: ['Alive', 'Dead', 'Unknown']}
type FilterCategory = "gender" | "species" | "status";

const filters: { title: string, key: FilterCategory, options: string[] }[] = [
{ title: 'Gender', key: 'gender', options: ['Female', 'Male', 'Unknown'] },
{ title: 'Species', key: 'species', options: ['Human', 'Alien', 'Unknown'] },
{ title: 'Status', key: 'status', options: ['Alive', 'Dead', 'Unknown'] }
];

export default function CharactersPage(){
export default function CharactersPage() {

const [characters, setCharacters] = useState<Character[]>([]);

const [searchQuery, setsearchQuery] = useState("");
const [selectedFilters, setSelectedFilters] = useState<Record<FilterCategory, string[]>>({
gender: [],
species: [],
status: []
});
const [favoriteCharacterIds, setFavoriteCharacterIds] = useState<number[]>([]);

useEffect(() => {
async function fetchCharacters(){
try{
async function fetchCharacters() {
try {
const response = await fetch("https://rickandmortyapi.com/api/character");

if (!response.ok) {
throw new Error(`Could not fetch characters: ${response.status}`)
}

const data = await response.json();
setCharacters(data.results);
}
catch(error) {
}
catch (error) {
console.error(error);
}
}
fetchCharacters()
}, [])

return(
<main className="content">
<section className='search-area' aria-label='Characters'>
<form className="searchbar">
<input type="search" id='character-search' name='search' className="searchfield" placeholder="Enter character name..."/>
<button type="submit" className="search-button">
<img src={SearchIcon} alt="" className='search-icon'/>
</button>
</form>
<section className='character-cards'>
{characters.map((character) => (
<CharacterCardSmall
key={character.id}
name={character.name}
image={character.image}
/>
))}
</section>
const filteredCharacters = characters.filter((character) => {
const matchesSearch = character.name.toLowerCase().includes(searchQuery.toLowerCase());
const matchesFilters = filters.every((filter) => {
const selectedOptions = selectedFilters[filter.key];

if (selectedOptions.length === 0) {
return true;
}

return selectedOptions.includes(character[filter.key].toLowerCase());
});

return matchesSearch && matchesFilters;
});

function handleFilterChange(category: FilterCategory, value: string, checked: boolean) {
setSelectedFilters((previousFilters) => ({
...previousFilters,
[category]: checked
? [...previousFilters[category], value]
: previousFilters[category].filter((selectedValue) => selectedValue !== value)
}));
}

function handleToggleFavorite(characterId: number) {
setFavoriteCharacterIds((previousFavorites) => previousFavorites.includes(characterId)
? previousFavorites.filter((id) => id !== characterId)
: [...previousFavorites, characterId]
);
}

return (
<main className="content">
<section className='search-area' aria-label='Characters'>
<form className="searchbar" onSubmit={(event) => event.preventDefault()}>
<input type="search" id='character-search' name='search' className="searchfield" placeholder="Enter character name..." value={searchQuery} onChange={(event) => setsearchQuery(event.target.value)} />
<button type="submit" className="search-button">
<img src={SearchIcon} alt="" className='search-icon' />
</button>
</form>
<section className='character-cards'>
{filteredCharacters.map((character) => (
<CharacterCardSmall
key={character.id}
name={character.name}
image={character.image}
isFavorite={favoriteCharacterIds.includes(character.id)}
onToggleFavorite={() => handleToggleFavorite(character.id)}
/>
))}
</section>
<div className='filter-area' aria-label='Filters'>
<aside className='filter-group'>
{filters.map((filter) => (
<fieldset key={filter.title} className='filter-group'>
<legend>{filter.title}</legend>
{filter.options.map((option) => (
<label key={option} className='checkbox-option'>
<input type="checkbox" name={filter.title.toLowerCase()} value={option.toLowerCase()}/> {option}
</label>
))}
</fieldset>
))}
</aside>
</div>
</main>
</section>
<div className='filter-area' aria-label='Filters'>
<aside className='filter-group'>
{filters.map((filter) => (
<fieldset key={filter.title} className='filter-group'>
<legend>{filter.title}</legend>
{filter.options.map((option) => (
<label key={option} className='checkbox-option'>
<input type="checkbox" name={filter.key} value={option.toLowerCase()} checked={selectedFilters[filter.key].includes(option.toLowerCase())} onChange={(event) => handleFilterChange(filter.key, event.target.value, event.target.checked)} /> {option}
</label>
))}
</fieldset>
))}
</aside>
</div>
</main>
)
}
}