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
29 changes: 29 additions & 0 deletions src/components/Searchbar.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import SearchIcon from "../assets/search.svg";

//Values recived from CharactersPage as props
type SearchBarProps = {
searchQuery: string;
onSearchChange: (value: string) => void;
};

export default function Searchbar({
searchQuery,
onSearchChange,
}: SearchBarProps) {
return (
<form className="searchbar" onSubmit={(event) => event.preventDefault()}>
<input
type="search"
id="character-search"
name="search"
className="searchfield"
placeholder="Enter a character name..."
value={searchQuery}
onChange={(event) => onSearchChange(event.target.value)}
/>
<button type="submit" className="search-button" aria-label="Search">
<img src={SearchIcon} alt="" className="search-icon" />
</button>
</form>
);
}
100 changes: 41 additions & 59 deletions src/pages/CharactersPage.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
import "./CharactersPage.css";

import { useEffect, useState } from "react";
import { useQuery } from "@tanstack/react-query";

import CharacterCardSmall from "../components/CharacterCardSmall";
import CharacterDetailCard from "../components/CharacterDetailCard";
import SearchIcon from "../assets/search.svg";
import { useQuery } from '@tanstack/react-query';
import Searchbar from "../components/Searchbar";

import type { Character } from "../types/Character";

type FilterCategory = "gender" | "species" | "status";
import {
filterCharacters,
type FilterCategory,
type SelectedFilters,
} from "../utils/filterCharacters";

const filters: { title: string; key: FilterCategory; options: string[] }[] = [
{ title: "Gender", key: "gender", options: ["Female", "Male", "Unknown"] },
Expand All @@ -18,37 +23,41 @@ const filters: { title: string; key: FilterCategory; options: string[] }[] = [

// AI was used to create the fetchCharacters function
async function fetchCharacters(): Promise<Character[]> {
const response = await fetch("https://rickandmortyapi.com/api/character");
const response = await fetch("https://rickandmortyapi.com/api/character");

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

const data = await response.json();
return data.results;
const data = await response.json();
return data.results;
}

export default function CharactersPage() {
const {data: characters = []} = useQuery({queryKey: ['characters'], queryFn: fetchCharacters});
const { data: characters = [] } = useQuery({
queryKey: ["characters"],
queryFn: fetchCharacters,
});
const [selectedCharacter, setSelectedCharacter] = useState<Character | null>(
null,
);
const [searchQuery, setsearchQuery] = useState("");
const [selectedFilters, setSelectedFilters] = useState<
Record<FilterCategory, string[]>
>(() => {
const storedFilters = sessionStorage.getItem("selectedFilters");

if (storedFilters) {
return JSON.parse(storedFilters);
}

return {
gender: [],
species: [],
status: [],
};
});
const [searchQuery, setSearchQuery] = useState("");
const [selectedFilters, setSelectedFilters] = useState<SelectedFilters>(
() => {
const storedFilters = sessionStorage.getItem("selectedFilters");

if (storedFilters) {
return JSON.parse(storedFilters);
}

return {
gender: [],
species: [],
status: [],
};
},
);

const [favoriteCharacterIds, setFavoriteCharacterIds] = useState<number[]>(
() => {
const storedFavorites = localStorage.getItem("favoriteCharacterIds");
Expand All @@ -72,22 +81,11 @@ export default function CharactersPage() {
);
}, [favoriteCharacterIds]);

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;
});
const filteredCharacters = filterCharacters(
characters,
searchQuery,
selectedFilters,
);

function handleFilterChange(
category: FilterCategory,
Expand Down Expand Up @@ -145,23 +143,7 @@ export default function CharactersPage() {
return (
<section 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>
<Searchbar searchQuery={searchQuery} onSearchChange={setSearchQuery} />
<section className="character-cards">
{filteredCharacters.map((character) => (
<CharacterCardSmall
Expand Down
32 changes: 32 additions & 0 deletions src/utils/filterCharacters.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import type { Character } from "../types/Character";

export type FilterCategory = "gender" | "species" | "status";
export type SelectedFilters = Record<FilterCategory, string[]>;

export function filterCharacters(
// Takes in all characters, the value in the search field and the chosen filters
characters: Character[],
searchQuery: string,
selectedFilters: SelectedFilters,
): Character[] {
return characters.filter((character) => {
//Checks whether each character's name matches with the search and if the character's gender, specie or status is checked off in the filter
const matchesSearch = character.name
.toLowerCase()
.includes(searchQuery.toLowerCase());

const matchesGender =
selectedFilters.gender.length === 0 ||
selectedFilters.gender.includes(character.gender.toLowerCase());

const matchesSpecies =
selectedFilters.species.length === 0 ||
selectedFilters.species.includes(character.species.toLowerCase());

const matchesStatus =
selectedFilters.status.length === 0 ||
selectedFilters.status.includes(character.status.toLowerCase());
//Returns the result of the checks mentioned above
return matchesSearch && matchesGender && matchesSpecies && matchesStatus;
});
}