+
-
+
{title}
-
{score}
-
+
{score || "0"}
+
+
-
{favorites}
-
+
{favorites || "0"}
+
-
-
-
+
);
diff --git a/src/components/buttons/Button.jsx b/src/components/buttons/Button.jsx
new file mode 100644
index 0000000..2dbd7f0
--- /dev/null
+++ b/src/components/buttons/Button.jsx
@@ -0,0 +1,14 @@
+import React from "react";
+
+const Button = ({ className = "", onClick = () => {}, children }) => {
+ return (
+
+ );
+};
+
+export default Button;
diff --git a/src/components/icons/IconFavorite.jsx b/src/components/icons/IconFavorite.jsx
new file mode 100644
index 0000000..ef7da54
--- /dev/null
+++ b/src/components/icons/IconFavorite.jsx
@@ -0,0 +1,20 @@
+import React from "react";
+
+const IconFavorite = ({ className }) => {
+ return (
+
+ );
+};
+
+export default IconFavorite;
diff --git a/src/components/icons/IconRank.jsx b/src/components/icons/IconRank.jsx
new file mode 100644
index 0000000..0560f9e
--- /dev/null
+++ b/src/components/icons/IconRank.jsx
@@ -0,0 +1,20 @@
+import React from "react";
+
+const IconRank = ({ className }) => {
+ return (
+
+ );
+};
+
+export default IconRank;
diff --git a/src/components/icons/IconStar.jsx b/src/components/icons/IconStar.jsx
new file mode 100644
index 0000000..bf34a4b
--- /dev/null
+++ b/src/components/icons/IconStar.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+const IconStar = ({ className }) => {
+ return (
+
+ );
+};
+
+export default IconStar;
diff --git a/src/components/icons/IconUserGroup.jsx b/src/components/icons/IconUserGroup.jsx
new file mode 100644
index 0000000..798fa8e
--- /dev/null
+++ b/src/components/icons/IconUserGroup.jsx
@@ -0,0 +1,16 @@
+import React from "react";
+
+const IconUserGroup = ({ className }) => {
+ return (
+
+ );
+};
+
+export default IconUserGroup;
diff --git a/src/components/loading/Loading.jsx b/src/components/loading/Loading.jsx
deleted file mode 100644
index 0af1b60..0000000
--- a/src/components/loading/Loading.jsx
+++ /dev/null
@@ -1,26 +0,0 @@
-import { Fragment } from "react";
-
-const Loading = () => {
- return (
-
-
-
- );
-};
-
-export default Loading;
diff --git a/src/context/mangaContext.jsx b/src/context/mangaContext.jsx
deleted file mode 100644
index 6e0f0b3..0000000
--- a/src/context/mangaContext.jsx
+++ /dev/null
@@ -1,16 +0,0 @@
-import { createContext, useContext } from "react";
-
-const MangaContext = createContext(undefined);
-
-const MangaProvider = (props) => {
- return
;
-};
-
-const useManga = () => {
- const context = useContext(MangaContext);
- if (typeof context === "undefined")
- throw new Error("useManga must be used within a MangaProvider");
- return context;
-};
-
-export { MangaProvider, useManga };
diff --git a/src/hooks/useDebounce.jsx b/src/hooks/useDebounce.jsx
deleted file mode 100644
index 0c3a3a8..0000000
--- a/src/hooks/useDebounce.jsx
+++ /dev/null
@@ -1,22 +0,0 @@
-import { useEffect, useState } from "react";
-
-export default function useDebounce(value, delay) {
- // State and setters for debounced value
- const [debouncedValue, setDebouncedValue] = useState(value);
- useEffect(
- () => {
- // Update debounced value after delay
- const handler = setTimeout(() => {
- setDebouncedValue(value);
- }, delay);
- // Cancel the timeout if value changes (also on delay change or unmount)
- // This is how we prevent debounced value from updating if value is changed ...
- // .. within the delay period. Timeout gets cleared and restarted.
- return () => {
- clearTimeout(handler);
- };
- },
- [value, delay] // Only re-call effect if value or delay changes
- );
- return debouncedValue;
-}
diff --git a/src/index.scss b/src/index.scss
index 3ee1313..14e42eb 100644
--- a/src/index.scss
+++ b/src/index.scss
@@ -7,6 +7,7 @@
@layer base {
html {
font-family: 'Poppins', sans-serif;
+ scroll-behavior: smooth;
}
body {
diff --git a/src/main.jsx b/src/main.jsx
index 9fa5a36..dc23f0f 100644
--- a/src/main.jsx
+++ b/src/main.jsx
@@ -1,17 +1,28 @@
import React from "react";
import ReactDOM from "react-dom";
-import "./index.scss";
-import App from "./App";
import { BrowserRouter } from "react-router-dom";
-import { MangaProvider } from "./context/mangaContext";
+import { ToastContainer } from "react-toastify";
+
+import App from "./App";
+import "./index.scss";
+import "react-toastify/dist/ReactToastify.css";
ReactDOM.render(
-
-
-
-
-
+
+
+
+
,
document.getElementById("root")
);
diff --git a/src/page/AnimeDetailPage.jsx b/src/page/AnimeDetailPage.jsx
index f8e4a68..6c1588a 100644
--- a/src/page/AnimeDetailPage.jsx
+++ b/src/page/AnimeDetailPage.jsx
@@ -1,7 +1,13 @@
-import React from "react";
import { useParams } from "react-router-dom";
import useSWR from "swr";
import { fetcher } from "../utils/fetcher";
+import { toast } from "react-toastify";
+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";
const AnimeDetailPage = () => {
const { animeID } = useParams();
@@ -11,29 +17,141 @@ const AnimeDetailPage = () => {
fetcher
);
+ if (error) {
+ toast.error("Error! Please try again!");
+ return null;
+ }
+
if (!data) return null;
- const { images, title, title_japanese, synopsis } = data.data;
+ const {
+ images,
+ title,
+ title_japanese,
+ synopsis,
+ url,
+ trailer,
+ type,
+ source,
+ episodes,
+ status,
+ duration,
+ rating,
+ year,
+ score,
+ favorites,
+ members,
+ rank,
+ } = data.data;
return (
-
-
-
-
-
-
-
{title}
-
{title_japanese}
+
+
+
+
+
+
+
+ {title} ({year || "Empty year"})
+
+
+ {title_japanese} - {`Rank: ${rank}`}
+
+
+
+
+
+
+
+ {rank || "null"}
+
+
+
+ {score || "0"}
+
+
+
+
+ {favorites || "0"}
+
+
-
-
{synopsis}
+
+ {members || "0"}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Description:
+
+ {synopsis || "Description's empty"}
+
+
+
+
+ {trailer.embed_url && (
+
+
+
+ )}
);
};
diff --git a/src/page/HomePage.jsx b/src/page/HomePage.jsx
index f839017..15e72b7 100644
--- a/src/page/HomePage.jsx
+++ b/src/page/HomePage.jsx
@@ -1,17 +1,19 @@
import React from "react";
-import AnimeList from "../components/anime/AnimeList";
const HomePage = () => {
return (
-
-
-
-
- Top Manga
-
+
+
+
+ Project: Anime page with reactJS + Tailwindcss by SoSmoothy
+
+
+
+
);
diff --git a/src/page/SearchPage.jsx b/src/page/SearchPage.jsx
index 0e09c7b..86941e4 100644
--- a/src/page/SearchPage.jsx
+++ b/src/page/SearchPage.jsx
@@ -1,11 +1,12 @@
-import React from "react";
+import { useState, useRef } from "react";
import useSWR from "swr";
import { fetcher } from "../utils/fetcher";
import AnimeItem from "../components/anime/AnimeItem";
+import { toast } from "react-toastify";
const SearchPage = () => {
- const [query, setQuery] = React.useState("naruto");
- const inputRef = React.useRef(null);
+ const [query, setQuery] = useState("naruto");
+ const inputRef = useRef(null);
const handleSearchBtn = (e) => {
const searchVal = inputRef.current.value;
@@ -17,6 +18,8 @@ const SearchPage = () => {
fetcher
);
+ if (error) toast.error("Error! Please try again!");
+
return (
@@ -30,13 +33,13 @@ const SearchPage = () => {
/>
-
+
{data &&
data.data.length > 0 &&
data.data.map((anime) => (
diff --git a/src/utils/getRating.jsx b/src/utils/getRating.jsx
new file mode 100644
index 0000000..9eba760
--- /dev/null
+++ b/src/utils/getRating.jsx
@@ -0,0 +1,4 @@
+export const getRating = (rating = "") => {
+ if (!rating) return "";
+ return rating.split(" ")[0];
+};