From 5aa4dac475bfba1713401bdb9b67ba8bfc619b74 Mon Sep 17 00:00:00 2001 From: Gurveer Singh Date: Fri, 11 Sep 2026 10:01:33 +0200 Subject: [PATCH 1/3] feat: :sparkles: Search is now implemented Search works as expected and you can write both upper and lower case --- src/pages/CharactersPage.tsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index b654f7d..c020487 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -23,6 +23,7 @@ const filters = [ export default function CharactersPage(){ const [characters, setCharacters] = useState([]); + const [searchQuery, setsearchQuery] = useState(""); useEffect(() => { async function fetchCharacters(){ @@ -43,17 +44,19 @@ export default function CharactersPage(){ fetchCharacters() }, []) + const filteredCharacters = characters.filter((character) => character.name.toLowerCase().includes(searchQuery.toLowerCase())); + return(
- + setsearchQuery(event.target.value)}/>
- {characters.map((character) => ( + {filteredCharacters.map((character) => ( Date: Fri, 11 Sep 2026 10:33:04 +0200 Subject: [PATCH 2/3] feat: :sparkles: Filters now work Filters works both alone and with search --- src/pages/CharactersPage.tsx | 122 +++++++++++++++++++++-------------- 1 file changed, 75 insertions(+), 47 deletions(-) diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index c020487..6252628 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -14,71 +14,99 @@ 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([]); const [searchQuery, setsearchQuery] = useState(""); - + const [selectedFilters, setSelectedFilters] = useState>({ + gender: [], + species: [], + status: [] + }); + 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() }, []) - const filteredCharacters = characters.filter((character) => character.name.toLowerCase().includes(searchQuery.toLowerCase())); + 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( -
-
-
- setsearchQuery(event.target.value)}/> - -
-
- {filteredCharacters.map((character) => ( - - ))} -
-
-
- +
+
+) } \ No newline at end of file From f66490cc981aa172b605e101ecdf56d81d1e9fed Mon Sep 17 00:00:00 2001 From: Gurveer Singh Date: Fri, 11 Sep 2026 10:51:45 +0200 Subject: [PATCH 3/3] feat: :sparkles: Favorite system works You can now favorite characters, and the star will change color accordingly --- src/components/CharacterCardSmall.css | 4 ++ src/components/CharacterCardSmall.tsx | 10 +-- src/pages/CharactersPage.tsx | 99 +++++++++++++++------------ 3 files changed, 65 insertions(+), 48 deletions(-) diff --git a/src/components/CharacterCardSmall.css b/src/components/CharacterCardSmall.css index b5c2fbf..c91cd7a 100644 --- a/src/components/CharacterCardSmall.css +++ b/src/components/CharacterCardSmall.css @@ -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; diff --git a/src/components/CharacterCardSmall.tsx b/src/components/CharacterCardSmall.tsx index 756030a..a92ab15 100644 --- a/src/components/CharacterCardSmall.tsx +++ b/src/components/CharacterCardSmall.tsx @@ -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(
@@ -16,11 +18,11 @@ export default function CharacterCardSmall({name, image}: CharacterCardSmallProp

{name}

-
) -} \ No newline at end of file +} diff --git a/src/pages/CharactersPage.tsx b/src/pages/CharactersPage.tsx index 6252628..af0ee1f 100644 --- a/src/pages/CharactersPage.tsx +++ b/src/pages/CharactersPage.tsx @@ -31,6 +31,7 @@ export default function CharactersPage() { species: [], status: [] }); + const [favoriteCharacterIds, setFavoriteCharacterIds] = useState([]); useEffect(() => { async function fetchCharacters() { @@ -55,7 +56,7 @@ export default function CharactersPage() { const matchesSearch = character.name.toLowerCase().includes(searchQuery.toLowerCase()); const matchesFilters = filters.every((filter) => { const selectedOptions = selectedFilters[filter.key]; - + if (selectedOptions.length === 0) { return true; } @@ -66,47 +67,57 @@ export default function CharactersPage() { 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) - })); -} -return ( -
-
-
- setsearchQuery(event.target.value)} /> - -
-
- {filteredCharacters.map((character) => ( - - ))} + 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 ( +
+
+
event.preventDefault()}> + setsearchQuery(event.target.value)} /> + +
+
+ {filteredCharacters.map((character) => ( + handleToggleFavorite(character.id)} + /> + ))} +
-
-
- -
-
-) -} \ No newline at end of file +
+ +
+
+ ) +}