diff --git a/assets/otaku.ico b/assets/otaku.ico new file mode 100644 index 0000000..194aa28 Binary files /dev/null and b/assets/otaku.ico differ diff --git a/index.html b/index.html index b9e2d5b..4600a9d 100644 --- a/index.html +++ b/index.html @@ -3,7 +3,8 @@ - Anime Website + + Anime, Manga Information Page
diff --git a/src/components/anime-details/DetailListItem.jsx b/src/components/anime-details/DetailListItem.jsx index df5846d..d0998f0 100644 --- a/src/components/anime-details/DetailListItem.jsx +++ b/src/components/anime-details/DetailListItem.jsx @@ -1,4 +1,4 @@ -import React from "react"; +import { memo } from "react"; import { NavLink } from "react-router-dom"; const DetailListItem = ({ items, title }) => { @@ -18,4 +18,4 @@ const DetailListItem = ({ items, title }) => { ); }; -export default DetailListItem; +export default memo(DetailListItem); diff --git a/src/components/anime-details/DetailStatus.jsx b/src/components/anime-details/DetailStatus.jsx index 3a7f168..27e21b4 100644 --- a/src/components/anime-details/DetailStatus.jsx +++ b/src/components/anime-details/DetailStatus.jsx @@ -1,3 +1,5 @@ +import { memo } from "react"; + const DetailStatus = ({ type, content, className }) => { return ( @@ -7,4 +9,4 @@ const DetailStatus = ({ type, content, className }) => { ); }; -export default DetailStatus; +export default memo(DetailStatus); diff --git a/src/components/anime/AnimeItem.jsx b/src/components/anime/AnimeItem.jsx index f55630d..2f3d695 100644 --- a/src/components/anime/AnimeItem.jsx +++ b/src/components/anime/AnimeItem.jsx @@ -2,6 +2,7 @@ import { useNavigate } from "react-router-dom"; import IconStar from "../icons/IconStar"; import IconFavorite from "../icons/IconFavorite"; import Button from "../buttons/Button"; +import { memo } from "react"; const AnimeItem = ({ anime }) => { const navigate = useNavigate(); @@ -39,4 +40,4 @@ const AnimeItem = ({ anime }) => { ); }; -export default AnimeItem; +export default memo(AnimeItem); diff --git a/src/components/anime/AnimeItemSkeleton.jsx b/src/components/anime/AnimeItemSkeleton.jsx index ea45120..b932642 100644 --- a/src/components/anime/AnimeItemSkeleton.jsx +++ b/src/components/anime/AnimeItemSkeleton.jsx @@ -1,6 +1,7 @@ import IconStar from "../icons/IconStar"; import IconFavorite from "../icons/IconFavorite"; import LoadingSkeleton from "../loading/LoadingSkeleton"; +import { memo } from "react"; const AnimeItemSkeleton = () => { return ( @@ -31,4 +32,4 @@ const AnimeItemSkeleton = () => { ); }; -export default AnimeItemSkeleton; +export default memo(AnimeItemSkeleton); diff --git a/src/components/anime/AnimeList.jsx b/src/components/anime/AnimeList.jsx index 8ea0fed..9193e93 100644 --- a/src/components/anime/AnimeList.jsx +++ b/src/components/anime/AnimeList.jsx @@ -5,6 +5,7 @@ import { getListAnime } from "../../apis/apis"; import { useQuery } from "@tanstack/react-query"; import { toast } from "react-toastify"; import { useNavigate } from "react-router-dom"; +import { memo } from "react"; const AnimeList = ({ type }) => { const { data, isError, isLoading } = useQuery(["list-anime", type], () => @@ -39,4 +40,4 @@ const AnimeList = ({ type }) => { ); }; -export default AnimeList; +export default memo(AnimeList); diff --git a/src/components/buttons/Button.jsx b/src/components/buttons/Button.jsx index 521b18f..542ca8b 100644 --- a/src/components/buttons/Button.jsx +++ b/src/components/buttons/Button.jsx @@ -1,3 +1,5 @@ +import { memo } from "react"; + const Button = ({ className = "", onClick = () => {}, children }) => { return ( - - -
- {isLoading && ( -
- {new Array(4).fill(0).map(() => ( - - ))} -
- )} - -
- {!isLoading && - data.pages.map((pageData) => - pageData.data.map((item) => ( - <> - {type === "anime" ? ( - - ) : ( - - )} - - )) - )} -
-
-
- - - ); -}; - -export default SearchContainer; diff --git a/src/main.jsx b/src/main.jsx index 49b4372..a5a0c05 100644 --- a/src/main.jsx +++ b/src/main.jsx @@ -1,7 +1,7 @@ import { render } from "react-dom"; import { lazy, Suspense } from "react"; +import { ToastContainer } from "react-toastify"; import { BrowserRouter, Route, Routes } from "react-router-dom"; - import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; import LoadingComponent from "./components/loading/LoadingComponent"; @@ -11,14 +11,12 @@ const HomePage = lazy(() => import("./page/HomePage")); const AnimePage = lazy(() => import("./page/AnimePage")); const SearchPage = lazy(() => import("./page/SearchPage")); const AnimeDetailPage = lazy(() => import("./page/AnimeDetailPage")); -const CharacterPage = lazy(() => import("./page/CharacterPage")); const CharacterDetailPage = lazy(() => import("./page/CharacterDetailPage")); const Error404Page = lazy(() => import("./page/Error404Page")); import "swiper/css"; import "react-toastify/dist/ReactToastify.css"; import "./index.scss"; -import { ToastContainer } from "react-toastify"; const queryClient = new QueryClient(); @@ -31,12 +29,15 @@ render( } /> } /> } /> - } /> + } + /> } /> - } /> + } /> } /> diff --git a/src/page/AnimeDetailPage.jsx b/src/page/AnimeDetailPage.jsx index d36d5bb..3fb749b 100644 --- a/src/page/AnimeDetailPage.jsx +++ b/src/page/AnimeDetailPage.jsx @@ -1,17 +1,18 @@ import { useNavigate, useParams } from "react-router-dom"; -import DetailStatus from "../components/anime-details/DetailStatus"; -import IconStar from "../components/icons/IconStar"; -import IconUserGroup from "../components/icons/IconUserGroup"; -import IconFavorite from "../components/icons/IconFavorite"; -import IconRank from "../components/icons/IconRank"; - import { getRating } from "../utils/getRating"; -import DetailListItem from "../components/anime-details/DetailListItem"; -import LoadingComponent from "../components/loading/LoadingComponent"; import { useQuery } from "@tanstack/react-query"; import { getAnimeDetail } from "../apis/apis"; import { toast } from "react-toastify"; +import { + DetailListItem, + DetailStatus, + IconFavorite, + IconRank, + IconStar, + IconUserGroup, + LoadingComponent, +} from "../components"; const AnimeDetailPage = () => { const { animeID } = useParams(); diff --git a/src/page/AnimePage.jsx b/src/page/AnimePage.jsx index 3a1f268..9882118 100644 --- a/src/page/AnimePage.jsx +++ b/src/page/AnimePage.jsx @@ -1,4 +1,4 @@ -import AnimeList from "../components/anime/AnimeList"; +import { AnimeList } from "../components"; const AnimePage = () => { return ( diff --git a/src/page/CharacterDetailPage.jsx b/src/page/CharacterDetailPage.jsx index 36c81e5..71d782c 100644 --- a/src/page/CharacterDetailPage.jsx +++ b/src/page/CharacterDetailPage.jsx @@ -1,11 +1,9 @@ import { useNavigate, useParams } from "react-router-dom"; import { useQuery } from "@tanstack/react-query"; +import { toast } from "react-toastify"; -import IconFavorite from "../components/icons/IconFavorite"; -import IconEmail from "../components/icons/IconEmail"; -import LoadingComponent from "../components/loading/LoadingComponent"; import { getCharacterDetail } from "../apis/apis"; -import { toast } from "react-toastify"; +import { IconEmail, IconFavorite, LoadingComponent } from "../components"; const CharacterDetailPage = () => { const navigate = useNavigate(); diff --git a/src/page/CharacterPage.jsx b/src/page/CharacterPage.jsx deleted file mode 100644 index 595751e..0000000 --- a/src/page/CharacterPage.jsx +++ /dev/null @@ -1,7 +0,0 @@ -import SearchContainer from "../components/search/SearchContainer"; - -const CharacterPage = () => { - return ; -}; - -export default CharacterPage; diff --git a/src/page/Error404Page.jsx b/src/page/Error404Page.jsx index 5fac6bb..fb99ea5 100644 --- a/src/page/Error404Page.jsx +++ b/src/page/Error404Page.jsx @@ -1,6 +1,7 @@ import { useNavigate } from "react-router-dom"; -import Button from "../components/buttons/Button"; + import errorImg from "../images/404.png"; +import { Button } from "../components"; const Error404Page = () => { const navigate = useNavigate(); diff --git a/src/page/SearchPage.jsx b/src/page/SearchPage.jsx index d602476..57d6053 100644 --- a/src/page/SearchPage.jsx +++ b/src/page/SearchPage.jsx @@ -1,7 +1,113 @@ -import SearchContainer from "../components/search/SearchContainer"; +import { v4 } from "uuid"; +import { toast } from "react-toastify"; +import { useNavigate } from "react-router-dom"; +import { useEffect, useRef, useState } from "react"; +import InfiniteScroll from "react-infinite-scroller"; +import { useInfiniteQuery } from "@tanstack/react-query"; -const SearchPage = () => { - return ; +import { search } from "../apis/apis"; +import { AnimeItem, AnimeItemSkeleton, CharacterItem } from "../components"; + +const SearchPage = ({ type }) => { + const navigate = useNavigate(); + const [query, setQuery] = useState("naruto"); + const inputRef = useRef(null); + const searchBtnRef = useRef(null); + + const url = `https://api.jikan.moe/v4/${type}?q=${query}`; + + const { + data, + hasNextPage, + fetchNextPage, + isError, + isLoading, + isFetchingNextPage, + } = useInfiniteQuery( + [`search-${type}`, query], + ({ pageParam = url }) => search(pageParam), + { + getNextPageParam: (lastPage, _) => + lastPage.pagination.has_next_page + ? `${url}&page=${lastPage.pagination.current_page + 1}` + : undefined, + } + ); + + if (isError) { + toast.error("Something went wrong! Please try again!"); + return navigate("/"); + } + + useEffect(() => { + const handlerEnterKeyPress = (e) => { + if (e.code === "Enter") { + setQuery(inputRef.current.value); + } + }; + + document.addEventListener("keyup", handlerEnterKeyPress); + + return () => { + document.removeEventListener("keyup", handlerEnterKeyPress); + }; + }, []); + + return ( +
+
+
+ + +
+ +
+ {isLoading && ( +
+ {new Array(4).fill(0).map(() => ( + + ))} +
+ )} + +
+ {!isLoading && + data.pages.map((pageData) => + pageData.data.map((item) => ( + <> + {type === "anime" ? ( + + ) : ( + + )} + + )) + )} + {isFetchingNextPage && ( + <> + {new Array(4).fill(0).map(() => ( + + ))} + + )} +
+
+
+
+
+ ); }; export default SearchPage; diff --git a/src/utils/fetcher.jsx b/src/utils/fetcher.jsx deleted file mode 100644 index 3c44991..0000000 --- a/src/utils/fetcher.jsx +++ /dev/null @@ -1 +0,0 @@ -export const fetcher = (...args) => fetch(...args).then((res) => res.json()); diff --git a/src/utils/request.jsx b/src/utils/request.jsx deleted file mode 100644 index e69de29..0000000