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
27 changes: 27 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
"preview": "vite preview"
},
"dependencies": {
"@tanstack/react-query": "^5.103.0",
"react": "^19.2.8",
"react-dom": "^19.2.8"
},
Expand Down
8 changes: 7 additions & 1 deletion src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
import "./App.css";
import HomePage from "./pages/HomePage";
import CharactersPage from "./pages/CharactersPage";

function App() {
return <HomePage />;
return (
<main>
<HomePage />
<CharactersPage />
</main>
);
}

export default App;
48 changes: 25 additions & 23 deletions src/components/CharacterDetailCard.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import "./CharacterDetailCard.css";
import { useEffect, useState } from "react";
import { useQuery } from '@tanstack/react-query'

import type { Character } from "../types/Character";
import DetailCardShape from "../assets/detail-card-shape.svg";
Expand All @@ -23,6 +23,25 @@ type Episode = {
episode: string;
};

// AI was used to create the fetchEpisodes function
async function fetchEpisodes(character: Character): Promise<Episode[]> {
const episodeIds = character.episode
.map((episodeUrl) => episodeUrl.split('/').pop())
.join(',');

const response = await fetch(
`https://rickandmortyapi.com/api/episode/${episodeIds}`
);

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

const data = await response.json();

return Array.isArray(data) ? data : [data];
}

export default function CharacterDetailCard({
character,
onClose,
Expand All @@ -31,28 +50,11 @@ export default function CharacterDetailCard({
isFavorite,
onToggleFavorite,
}: CharacterDetailCardProps) {
const [episodes, setEpisodes] = useState<Episode[]>([]);

// AI was used to create this useEffect function.
useEffect(() => {
async function fetchEpisodes() {
try {
const episodeData = await Promise.all(
character.episode.map(async (episodeUrl) => {
const response = await fetch(episodeUrl);
if (!response.ok) {
throw new Error(`Could not fetch episode: ${response.status}`);
}
return response.json();
}),
);
setEpisodes(episodeData);
} catch (error) {
console.error(error);
}
}
fetchEpisodes();
}, [character]);

const { data: episodes = [] } = useQuery({
queryKey: ['episodes', character.id],
queryFn: () => fetchEpisodes(character)
});

return (
<div className="detail-overlay">
Expand Down
9 changes: 6 additions & 3 deletions src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
import { StrictMode } from "react";
import { createRoot } from "react-dom/client";
import { QueryClient, QueryClientProvider } from '@tanstack/react-query'
import "./index.css";
import App from "./App.tsx";
import CharactersPage from "./pages/CharactersPage.tsx";

const queryClient = new QueryClient()

createRoot(document.getElementById("root")!).render(
<StrictMode>
<App />
<CharactersPage />
<QueryClientProvider client={queryClient}>
<App />
</QueryClientProvider>
</StrictMode>,
);
39 changes: 16 additions & 23 deletions src/pages/CharactersPage.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { useEffect, useState } from "react";
import CharacterCardSmall from "../components/CharacterCardSmall";
import CharacterDetailCard from "../components/CharacterDetailCard";
import SearchIcon from "../assets/search.svg";
import { useQuery } from '@tanstack/react-query';

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

Expand All @@ -15,8 +16,20 @@ const filters: { title: string; key: FilterCategory; options: string[] }[] = [
{ title: "Status", key: "status", options: ["Alive", "Dead", "Unknown"] },
];

// AI was used to create the fetchCharacters function
async function fetchCharacters(): Promise<Character[]> {
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();
return data.results;
}

export default function CharactersPage() {
const [characters, setCharacters] = useState<Character[]>([]);
const {data: characters = []} = useQuery({queryKey: ['characters'], queryFn: fetchCharacters});
const [selectedCharacter, setSelectedCharacter] = useState<Character | null>(
null,
);
Expand Down Expand Up @@ -59,26 +72,6 @@ export default function CharactersPage() {
);
}, [favoriteCharacterIds]);

useEffect(() => {
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) {
console.error(error);
}
}
fetchCharacters();
}, []);

const filteredCharacters = characters.filter((character) => {
const matchesSearch = character.name
.toLowerCase()
Expand Down Expand Up @@ -150,7 +143,7 @@ export default function CharactersPage() {
}

return (
<main className="content">
<section className="content">
<section className="search-area" aria-label="Characters">
<form
className="searchbar"
Expand Down Expand Up @@ -221,6 +214,6 @@ export default function CharactersPage() {
onToggleFavorite={() => handleToggleFavorite(selectedCharacter.id)}
/>
)}
</main>
</section>
);
}
6 changes: 1 addition & 5 deletions src/pages/HomePage.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,7 @@
import Hero from "../components/Hero";

function HomePage() {
return (
<main>
<Hero />
</main>
);
return <Hero />;
}

export default HomePage;