Skip to content

Commit

Permalink
add: Implement comprehensive detail pages
Browse files Browse the repository at this point in the history
  • Loading branch information
lyudmil-mitev committed Oct 10, 2024
1 parent 114ae4e commit 122cc79
Show file tree
Hide file tree
Showing 14 changed files with 460 additions and 12 deletions.
387 changes: 386 additions & 1 deletion package-lock.json

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"tailwindcss": "^3.4.13",
"typescript": "^5.5.3",
"typescript-eslint": "^8.7.0",
"vite": "^5.4.8"
"vite": "^5.4.8",
"vitest": "^2.1.2"
}
}
Binary file added public/mr_pbh.webp
Binary file not shown.
7 changes: 7 additions & 0 deletions src/components/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function LoadingSpinner() {
return (
<div className="inline-block">
<div className="animate-spin rounded-full h-4 w-4 border-t-2 border-b-2 border-gray-900 dark:border-white"></div>
</div>
);
}
14 changes: 10 additions & 4 deletions src/components/Pagination.tsx
Original file line number Diff line number Diff line change
@@ -1,17 +1,23 @@
import { Link } from "react-router-dom";
import { Link, useNavigation } from "react-router-dom";
import LoadingSpinner from "./LoadingSpinner";

export default function Pagination({ page, totalPages }: { page: number, totalPages: number }) {
const { state } = useNavigation();
if (isNaN(page) || isNaN(totalPages) || page < 1 || page > totalPages) {
throw new Error('Page not found');
}

return (
<nav className="flex justify-center gap-4 p-4">
<Link to={`?page=${page - 1}`} className={`p-2 rounded-lg ${page === 1 ? 'invisible' : 'bg-gray-100 dark:bg-gray-800'}`}>
&lt;
<span className="sr-only">Previous</span>
<span> Previous</span>
</Link>
<span className="flex items-center gap-2 text-gray-600 dark:text-gray-400">
Page {page} of {totalPages}
Page {state === "loading" ? <LoadingSpinner /> : page } of {totalPages}
</span>
<Link to={`?page=${page + 1}`} className={`p-2 rounded-lg ${page === totalPages ? 'invisible' : 'bg-gray-100 dark:bg-gray-800'}`}>
<span className="sr-only">Next</span>
<span>Next </span>
&gt;
</Link>
</nav>
Expand Down
10 changes: 9 additions & 1 deletion src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,46 +11,54 @@ import './index.css'
import CharacterDetails from './routes/CharacterDetails.tsx'
import EpisodeDetails from './routes/EpisodeDetails.tsx'
import LocationDetails from './routes/LocationDetails.tsx'
import ErrorPage from './routes/ErrorPage.tsx'

const router = createBrowserRouter([
{
path: '/',
element: <Root />,
// errorElement: <h1>404 Not Found</h1>, // TODO
errorElement: <ErrorPage />,
children: [
{
index: true,
element: <Characters />,
errorElement: <ErrorPage />,
loader: charactersLoader,
},
{
path: '/characters',
element: <Characters />,
errorElement: <ErrorPage />,
loader: charactersLoader,
},
{
path: '/characters/:characterId',
element: <CharacterDetails />,
errorElement: <ErrorPage />,
loader: characterDetailLoader
},
{
path: '/locations',
element: <Locations />,
errorElement: <ErrorPage />,
loader: locationsLoader,
},
{
path: '/locations/:locationId',
element: <LocationDetails />,
errorElement: <ErrorPage />,
loader: locationDetailLoader
},
{
path: '/episodes',
element: <Episodes />,
errorElement: <ErrorPage />,
loader: episodesLoader,
},
{
path: '/episodes/:episodeId',
element: <EpisodeDetails />,
errorElement: <ErrorPage />,
loader: episodeDetailLoader
},
]
Expand Down
3 changes: 2 additions & 1 deletion src/routes/CharacterDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import { Character, Episode, getEpisode } from "rickmortyapi";
import { parseAPIId } from "../loaders";
import EpisodeCard from "../components/EpisodeCard";
import DetailsLayout, { DetailFacts } from "../components/DetailsLayout";
import LoadingSpinner from "../components/LoadingSpinner";

export default function CharacterDetails() {
const char = useLoaderData() as Character;
Expand Down Expand Up @@ -40,7 +41,7 @@ export default function CharacterDetails() {
<Link to={`/episodes/${episode.id}`} key={index}>
<EpisodeCard episode={episode} />
</Link>
)) : "Loading..."}
)) : <><LoadingSpinner/> Loading...</>}
</DetailsLayout>
);
}
1 change: 1 addition & 0 deletions src/routes/Characters.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function Characters() {
</Link>
))}
</div>
<Pagination page={page} totalPages={pages} />
</section>
);
};
1 change: 1 addition & 0 deletions src/routes/Episodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function Episodes() {
</Link>
))}
</div>
<Pagination page={page} totalPages={pages} />
</section>
);
}
14 changes: 14 additions & 0 deletions src/routes/ErrorPage.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Link } from 'react-router-dom';
import MrPBH from '/mr_pbh.webp'

export default function ErrorPage() {
return (
<div className="flex flex-col items-center h-screen space-y-4 pt-8">
<h1 className="text-6xl font-bold text-center dark:text-white">Oooh weee!</h1>
<h2 className="text-xl font-semibold text-center dark:text-gray">Looks like something went wrong!</h2>
<img src={MrPBH} alt="Mr. Poopybutthole" className="w-18 h-auto" />

<Link to="/" className="p-2 rounded-lg bg-gray-100 dark:bg-gray-800">Go Back</Link>
</div>
);
}
3 changes: 2 additions & 1 deletion src/routes/LocationDetails.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import PlanetImage from "/planet.png"
import { useEffect, useState } from "react";
import { parseAPIId } from "../loaders";
import CharacterCard from "../components/CharacterCard";
import LoadingSpinner from "../components/LoadingSpinner";

export default function LocationDetails() {
const location = useLoaderData() as Location;
Expand Down Expand Up @@ -38,7 +39,7 @@ export default function LocationDetails() {
<Link to={`/characters/${character.id}`} key={index}>
<CharacterCard character={character} />
</Link>
)) : location.residents.length > 0 ? "Loading..." : "No residents"}
)) : location.residents.length > 0 ? <><LoadingSpinner/> Loading...</> : "No residents"}
</DetailsLayout>
);
}
1 change: 1 addition & 0 deletions src/routes/Locations.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ export default function Locations() {
</Link>
))}
</div>
<Pagination page={page} totalPages={pages} />
</section>
);
}
26 changes: 24 additions & 2 deletions src/routes/Root.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,30 @@ export default function Root() {
<main className='bg-gray-100 dark:bg-gray-700'>
<Outlet />
</main>
<footer>
2024 - Implemented with Rick and Morty API
<footer className='bg-gray-300 dark:bg-gray-800 p-4 text-xs text-gray-700 dark:text-gray-400'>
<div className="flex flex-wrap justify-center">
<div className="flex flex-col space-y-2 mx-4">
<p>Thank you for visiting Rick and Morty Explorer</p>
<p>This project was created by <a href="https://www.linkedin.com/in/lyudmil-mitev-97556318/" target="_blank">Lyudmil Mitev</a></p>
<p>using Vite, React, TypeScript and React Router.</p>
<p>Check out the source code on <a href="https://github.com/lyudmil-mitev/rick-and-morty-explorer" target="_blank">GitHub</a></p>
<p>And feel free to comment or contribute!</p>
</div>
<div className="flex flex-col space-y-2 mx-4">
<p>Implemented with <a href="https://rickandmortyapi.com/" target="_blank">Rick and Morty API</a></p>
<p>Rick and Morty is a copyright and trademark</p>
<p>of The Cartoon Network, Inc, Adult Swim,</p>
<p>Justin Roiland, Dan Harmon and others.</p>
<p>This is a fan website and is in no way affiliated to them.</p>
</div>
<div className="flex flex-col space-y-2 mx-4">
<p>The data and images are used without claim </p>
<p>of ownership and belong to their respective owners.</p>
<p>The title font is made and <a href="https://www.deviantart.com/jonizaak/art/Get-Schwifty-A-Rick-and-Morty-font-638073728" target='_blank'>generously provided by John Izaak</a></p>
<p>The deep space background svg image</p>
<p>is adapted from <a href="https://codepen.io/finnhvman/pen/bGOYpbO" target="_blank">work by Bence Szabo</a></p>
</div>
</div>
</footer>
</>)
}
2 changes: 1 addition & 1 deletion tsconfig.app.tsbuildinfo
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"root":["./src/loaders.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/Banner.tsx","./src/components/CharacterDetail.tsx","./src/components/Characters.tsx","./src/components/EpisodeDetail.tsx","./src/components/Episodes.tsx","./src/components/LocationDetail.tsx","./src/components/Locations.tsx","./src/components/MiniCard.tsx","./src/components/Pagination.tsx","./src/components/TabBar.tsx","./src/routes/Root.tsx"],"version":"5.6.2"}
{"root":["./src/loaders.ts","./src/main.tsx","./src/vite-env.d.ts","./src/components/Banner.tsx","./src/components/CharacterCard.tsx","./src/components/DetailsLayout.tsx","./src/components/EpisodeCard.tsx","./src/components/LocationCard.tsx","./src/components/MiniCard.tsx","./src/components/Pagination.tsx","./src/components/TabBar.tsx","./src/routes/CharacterDetails.tsx","./src/routes/Characters.tsx","./src/routes/EpisodeDetails.tsx","./src/routes/Episodes.tsx","./src/routes/LocationDetails.tsx","./src/routes/Locations.tsx","./src/routes/Root.tsx"],"version":"5.6.2"}

0 comments on commit 122cc79

Please sign in to comment.