├── client
├── public
│ ├── robots.txt
│ ├── favicon.ico
│ ├── manifest.json
│ └── index.html
├── src
│ ├── assets
│ │ └── images
│ │ │ ├── logo.png
│ │ │ ├── search.png
│ │ │ └── share.svg
│ ├── index.js
│ ├── components
│ │ ├── Pages
│ │ │ ├── Watchlist.jsx
│ │ │ ├── Recent.jsx
│ │ │ ├── Movies.jsx
│ │ │ ├── SearchResults.jsx
│ │ │ ├── Filtered.jsx
│ │ │ ├── Home.jsx
│ │ │ ├── Login.css
│ │ │ ├── Signup.jsx
│ │ │ ├── Genres.css
│ │ │ ├── Home.css
│ │ │ ├── Login.jsx
│ │ │ ├── AnimePlayerPage.css
│ │ │ ├── Genres.jsx
│ │ │ └── AnimePlayerPage.jsx
│ │ ├── Players
│ │ │ ├── TrailerPlayer.jsx
│ │ │ └── AnimePlayer.jsx
│ │ ├── Others
│ │ │ └── ClockLoader.jsx
│ │ ├── Sections
│ │ │ ├── AnimeSection.jsx
│ │ │ ├── MoreSection.jsx
│ │ │ ├── InfiniteSection.css
│ │ │ ├── UpcomingSection.jsx
│ │ │ ├── Hero.jsx
│ │ │ ├── InfiniteSection.jsx
│ │ │ ├── Navbar.css
│ │ │ └── Navbar.jsx
│ │ ├── Layouts
│ │ │ ├── VerticalCarousel.jsx
│ │ │ ├── GridRenderer.jsx
│ │ │ └── CarouselRenderer.jsx
│ │ └── Cards
│ │ │ ├── UpcomingCard.jsx
│ │ │ ├── AnimeCard.jsx
│ │ │ ├── HeroCard.css
│ │ │ ├── HeroCard.jsx
│ │ │ ├── UpcomingCard.css
│ │ │ └── AnimeCard.css
│ ├── App.css
│ └── App.jsx
├── .env
└── package.json
└── .gitignore
/client/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/client/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kishorkrishnak/animebliss/HEAD/client/public/favicon.ico
--------------------------------------------------------------------------------
/client/src/assets/images/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kishorkrishnak/animebliss/HEAD/client/src/assets/images/logo.png
--------------------------------------------------------------------------------
/client/src/assets/images/search.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/kishorkrishnak/animebliss/HEAD/client/src/assets/images/search.png
--------------------------------------------------------------------------------
/client/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom/client";
3 | import App from "./App";
4 | const root = ReactDOM.createRoot(document.getElementById("root"));
5 | root.render();
6 |
--------------------------------------------------------------------------------
/client/.env:
--------------------------------------------------------------------------------
1 | REACT_APP_CONSUMET_API_URL=https://consumet-instance-alpha.vercel.app
2 | REACT_APP_CONSUMET_PROVIDER=gogoanime
3 | REACT_APP_ANILIST_API_URL=https://consumet-instance-alpha.vercel.app/meta/anilist
4 | REACT_APP_CLIENT_ID = 658977310896-knrl3gka66fldh83dao2rhgbblmd4un9.apps.googleusercontent.com
--------------------------------------------------------------------------------
/client/src/components/Pages/Watchlist.jsx:
--------------------------------------------------------------------------------
1 | import Navbar from "../Sections/Navbar";
2 | const Watchlist = () => {
3 | return (
4 | <>
5 |
6 |
setIsPlaying(false)}
11 | />
12 | );
13 | };
14 |
15 | export default TrailerPlayer;
16 |
--------------------------------------------------------------------------------
/client/src/components/Others/ClockLoader.jsx:
--------------------------------------------------------------------------------
1 | import Loader from "react-spinners/MoonLoader";
2 |
3 | const ClockLoader = ({ color, size }) => {
4 | return (
5 |
20 | );
21 | };
22 | export default ClockLoader;
23 |
--------------------------------------------------------------------------------
/client/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Animebliss",
3 | "name": "Watch anime, no ads",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "favicon.ico",
12 | "type": "image/x-icon",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "favicon.ico",
17 | "type": "image/x-icon",
18 | "sizes": "192x192"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#000000",
24 | "background_color": "#ffffff"
25 | }
26 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Recent.jsx:
--------------------------------------------------------------------------------
1 | import InfiniteSection from "../Sections/InfiniteSection";
2 | import Navbar from "../Sections/Navbar";
3 | import React from "react";
4 | const RecentPage = () => {
5 | const baseURL = process.env.REACT_APP_CONSUMET_API_URL
6 |
7 | return (
8 | <>
9 |
10 |
17 | >
18 | );
19 | };
20 | export default RecentPage;
21 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Movies.jsx:
--------------------------------------------------------------------------------
1 | import InfiniteSection from "../Sections/InfiniteSection";
2 | import Navbar from "../Sections/Navbar";
3 | import React from "react";
4 | const MoviesPage = () => {
5 | const baseURL = process.env.REACT_APP_CONSUMET_API_URL
6 |
7 | return (
8 | <>
9 |
10 |
19 | >
20 | );
21 | };
22 | export default MoviesPage;
23 |
--------------------------------------------------------------------------------
/client/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 | Animehub | Paradise of anime fans
12 |
13 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/client/src/App.css:
--------------------------------------------------------------------------------
1 | * {
2 | margin: 0;
3 | box-sizing: border-box;
4 | scroll-margin-top: 20px;
5 | }
6 |
7 | html {
8 | scroll-behavior: smooth;
9 | }
10 |
11 | body {
12 | font-family: "Inter", sans-serif;
13 | background-color: #0f0617;
14 | margin: 0;
15 | max-height: 100vh;
16 | max-width: 100vw;
17 | overflow-x: hidden;
18 | }
19 |
20 | body::-webkit-scrollbar-track {
21 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
22 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
23 | background-color: transparent;
24 | }
25 | body::-webkit-scrollbar {
26 | width: 5px;
27 | background-color: transparent;
28 | }
29 | body::-webkit-scrollbar-thumb {
30 | background-color: grey;
31 | border-radius: 4px;
32 | }
33 |
--------------------------------------------------------------------------------
/client/src/components/Sections/AnimeSection.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useEffect, useState } from "react";
3 | import CarouselRenderer from "../Layouts/CarouselRenderer";
4 |
5 | const AnimeSection = ({ sectiontitle, url, id }) => {
6 | const [fetchedData, setFetchedData] = useState([]);
7 | useEffect(() => {
8 | axios.get(url).then(({ data: { results } }) => {
9 | setFetchedData(results);
10 | });
11 | }, []);
12 | return (
13 |
14 | {fetchedData.length > 0 && (
15 |
21 | )}
22 |
23 | );
24 | };
25 | export default AnimeSection;
26 |
--------------------------------------------------------------------------------
/client/src/components/Sections/MoreSection.jsx:
--------------------------------------------------------------------------------
1 | import { useLocation } from "react-router-dom";
2 | import InfiniteSection from "./InfiniteSection";
3 | import Navbar from "./Navbar";
4 | const MoreSection = () => {
5 | const location = useLocation();
6 | return (
7 | <>
8 |
9 |
19 | {location.state.sectionTitle}
20 |
21 |
27 | >
28 | );
29 | };
30 | export default MoreSection;
31 |
--------------------------------------------------------------------------------
/client/src/components/Pages/SearchResults.jsx:
--------------------------------------------------------------------------------
1 | import GridRenderer from "../Layouts/GridRenderer";
2 | import { setConfiguration } from "react-grid-system";
3 | import { useLocation } from "react-router-dom";
4 | import Navbar from "../Sections/Navbar";
5 | setConfiguration({ breakpoints: [580, 924, 1434, 1767, 2000, 2400] });
6 | const SearchResults = () => {
7 | const location = useLocation();
8 | return (
9 | <>
10 |
11 |
19 | Search Results for{" "}
20 | {location.state.input}
21 |
22 |
23 | >
24 | );
25 | };
26 | export default SearchResults;
27 |
--------------------------------------------------------------------------------
/client/src/components/Layouts/VerticalCarousel.jsx:
--------------------------------------------------------------------------------
1 | import { v4 as uuidv4 } from "uuid";
2 | import AnimeCard from "../Cards/AnimeCard";
3 | const VerticalCarousel = ({ finalQuery, sectionTitle }) => {
4 | return (
5 |
6 |
7 | {sectionTitle}
8 |
9 |
10 | {finalQuery.map((query) => (
11 |
12 |
18 |
19 | {query.title.english ?? query.title.romaji}
20 |
21 |
22 | ))}
23 |
24 |
25 | );
26 | };
27 | export default VerticalCarousel;
28 |
--------------------------------------------------------------------------------
/client/src/components/Cards/UpcomingCard.jsx:
--------------------------------------------------------------------------------
1 | import TextTruncate from "react-text-truncate";
2 | import "./UpcomingCard.css";
3 | const UpcomingCard = ({
4 | title,
5 | image,
6 | trailerVideoId,
7 | setIsPlaying,
8 | setTrailerId,
9 | }) => {
10 | return (
11 | <>
12 |
37 | >
38 | );
39 | };
40 | export default UpcomingCard;
41 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_STORE
2 | node_modules
3 | scripts/flow/*/.flowconfig
4 | .flowconfig
5 | *~
6 | *.pyc
7 | .grunt
8 | _SpecRunner.html
9 | __benchmarks__
10 | build/
11 | remote-repo/
12 | coverage/
13 | .module-cache
14 | fixtures/dom/public/react-dom.js
15 | fixtures/dom/public/react.js
16 | test/the-files-to-test.generated.js
17 | *.log*
18 | chrome-user-data
19 | *.sublime-project
20 | *.sublime-workspace
21 | .idea
22 | *.iml
23 | .vscode
24 | *.swp
25 | *.swo
26 |
27 | packages/react-devtools-core/dist
28 | packages/react-devtools-extensions/chrome/build
29 | packages/react-devtools-extensions/chrome/*.crx
30 | packages/react-devtools-extensions/chrome/*.pem
31 | packages/react-devtools-extensions/firefox/build
32 | packages/react-devtools-extensions/firefox/*.xpi
33 | packages/react-devtools-extensions/firefox/*.pem
34 | packages/react-devtools-extensions/shared/build
35 | packages/react-devtools-extensions/.tempUserDataDir
36 | packages/react-devtools-inline/dist
37 | packages/react-devtools-shell/dist
38 | packages/react-devtools-timeline/dist
--------------------------------------------------------------------------------
/client/src/components/Sections/InfiniteSection.css:
--------------------------------------------------------------------------------
1 | .section-infinite {
2 | padding-bottom: 40px;
3 | }
4 | .section-title {
5 | font-size: 1.9rem;
6 | margin-left: 15px;
7 | color: white;
8 | }
9 | @media screen and (min-width: 768px) {
10 | .section-title {
11 | font-size: 2.5rem;
12 | margin-left: 23px;
13 | }
14 | }
15 | .pagination-wrapper {
16 | margin-top: 20px;
17 | display: flex;
18 | align-items: center;
19 | width: 100vw;
20 | justify-content: center;
21 | }
22 | .pagination {
23 | height: 60px;
24 | width: 96%;
25 | display: flex;
26 | align-items: center;
27 | margin-top: 20px;
28 | border-top: 1px solid dodgerblue;
29 | justify-content: space-between;
30 | }
31 | .previous-page-button,
32 | .next-page-button {
33 | font-size: 15px;
34 | outline: none;
35 | border: none;
36 | color: white;
37 | width: 150px;
38 | background-color: transparent;
39 | }
40 | .btn-pageindex {
41 | border: none;
42 | padding: 4px 8px;
43 | border-radius: 5px;
44 | color: white;
45 | background: none;
46 | font-size: 14px;
47 | }
48 |
--------------------------------------------------------------------------------
/client/src/components/Sections/UpcomingSection.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useEffect, useState } from "react";
3 | import CarouselRenderer from "../Layouts/CarouselRenderer";
4 | import TrailerPlayer from "../Players/TrailerPlayer";
5 |
6 | const UpcomingSection = () => {
7 | const [trailerId, setTrailerId] = useState("");
8 | const [isPlaying, setIsPlaying] = useState(false);
9 | const [upcoming, setUpComing] = useState([]);
10 |
11 | const url = "https://api.jikan.moe/v4/top/anime?filter=upcoming";
12 | useEffect(() => {
13 | axios.get(url).then(({ data: { data } }) => {
14 | setUpComing(data);
15 | });
16 | }, []);
17 | return (
18 |
19 | {upcoming.length > 0 && (
20 |
28 | )}
29 |
34 |
35 | );
36 | };
37 |
38 | export default UpcomingSection;
39 |
--------------------------------------------------------------------------------
/client/src/assets/images/share.svg:
--------------------------------------------------------------------------------
1 |
2 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Filtered.jsx:
--------------------------------------------------------------------------------
1 | import { setConfiguration } from "react-grid-system";
2 | import { useLocation } from "react-router-dom";
3 | import InfiniteSection from "../Sections/InfiniteSection";
4 | import "./Genres.css";
5 | import { useState } from "react";
6 | import Navbar from "../Sections/Navbar";
7 | setConfiguration({ breakpoints: [768, 1170, 1500, 1700, 1800, 1900] });
8 | const GenresPage = () => {
9 | const location = useLocation();
10 | const baseURL = process.env.REACT_APP_CONSUMET_API_URL;
11 | const [queryUrl, setQueryUrl] = useState(
12 | location.state.type === "genre"
13 | ? `${baseURL}/meta/anilist/advanced-search?genres=[` +
14 | '"' +
15 | location.state.value +
16 | '"' +
17 | "]"
18 | : `${baseURL}/meta/anilist/advanced-search?` +
19 | location.state.type +
20 | "=" +
21 | location.state.value
22 | );
23 | return (
24 | <>
25 |
26 | {queryUrl && queryUrl !== "" && (
27 |
35 | )}
36 | >
37 | );
38 | };
39 |
40 | export default GenresPage;
41 |
--------------------------------------------------------------------------------
/client/src/components/Cards/AnimeCard.jsx:
--------------------------------------------------------------------------------
1 | import { StarFilled } from "@ant-design/icons";
2 | import { useNavigate } from "react-router-dom";
3 | import TextTruncate from "react-text-truncate";
4 | import "./AnimeCard.css";
5 | const AnimeCard = ({ title, image, id, rating, year }) => {
6 | const navigate = useNavigate();
7 | async function fetchVideo(id) {
8 | navigate("/watch/" + id);
9 | }
10 | return (
11 | <>
12 | {
15 | fetchVideo(id);
16 | }}
17 | className="animecard-wrapper"
18 | >
19 |
25 | {rating && (
26 |
27 |
28 | {rating / 10}
29 |
30 | )}
31 |
32 |
33 | {title && (
34 |
35 |
39 |
40 | )}
41 | {year &&
{year}
}
42 |
43 | >
44 | );
45 | };
46 | export default AnimeCard;
47 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Home.jsx:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import ScrollToTop from "react-scroll-to-top";
3 | import AnimeSection from "../Sections/AnimeSection";
4 | import Hero from "../Sections/Hero";
5 | import InfiniteSection from "../Sections/InfiniteSection";
6 | import UpcomingSection from "../Sections/UpcomingSection";
7 |
8 | import "./Home.css";
9 | const Home = () => {
10 | const baseURL = process.env.REACT_APP_ANILIST_API_URL;
11 | const [heroSectionLoaded, setHeroSectionLoaded] = useState(true);
12 |
13 | return (
14 | <>
15 |
16 | {heroSectionLoaded && (
17 | <>
18 | *
19 |
24 |
29 |
36 | >
37 | )}
38 |
39 |
52 | >
53 | );
54 | };
55 | export default Home;
56 |
--------------------------------------------------------------------------------
/client/src/components/Cards/HeroCard.css:
--------------------------------------------------------------------------------
1 | .herocard-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | justify-content: space-between;
5 | background-position: center;
6 | background-size: cover;
7 | height: 470px;
8 | margin-top: 20px;
9 | align-items: flex-start;
10 | padding: 20px 40px;
11 | text-align: left;
12 | width: 98.7%;
13 | border-radius: 8px;
14 | }
15 |
16 | .herocard-wrapper img {
17 | height: 100%;
18 | width: 100%;
19 | }
20 |
21 | .herocard-animeinfo-wrapper {
22 | display: flex;
23 | flex-direction: column;
24 | gap: 15px;
25 | }
26 |
27 | .herocard-animeinfo {
28 | color: white;
29 | display: flex;
30 | gap: 20px;
31 | }
32 |
33 | .herocard-animeinfo-title {
34 | color: white;
35 | font-size: 2.7rem;
36 | }
37 | .herocard-animeinfo-description {
38 | text-align: justify;
39 | color: white;
40 | font-family: "Inter", sans-serif;
41 | line-height: 1.5;
42 | width: 50%;
43 | }
44 |
45 | .herocard-btn-watch {
46 | background-color: rgba(0, 0, 0, 0.4);
47 | border: 1px solid white;
48 | color: white;
49 | display: flex;
50 | justify-content: center;
51 | align-items: center;
52 | padding: 9px 15px;
53 | font-family: inherit;
54 | border-radius: 4px;
55 | font-size: 1.5rem;
56 | font-weight: bold;
57 | }
58 |
59 | .btn-watchnow:hover {
60 | opacity: 0.8;
61 | transition: 500ms ease;
62 | }
63 |
64 | @media screen and (max-width: 768px) {
65 | .herocard-wrapper {
66 | height: 320px;
67 | }
68 | .herocard-animeinfo-item {
69 | font-size: 1.4rem;
70 | }
71 | .herocard-animeinfo-title {
72 | font-size: 2.5rem;
73 | }
74 | .herocard-animeinfo-description {
75 | font-size: 1.5rem;
76 | }
77 | .herocard-animeinfo-description {
78 | width: 100%;
79 | font-size: 1.4rem;
80 | }
81 | .herocard-btn-watch {
82 | font-size: 1.2rem;
83 | }
84 | }
85 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Login.css:
--------------------------------------------------------------------------------
1 | @media screen and (max-width: 700px) {
2 | .main-wrapper {
3 | flex-direction: column;
4 | }
5 | .welcome-text {
6 | display: none;
7 | }
8 | }
9 | @media screen and (max-width: 375px) {
10 | .login-container {
11 | width: 95%;
12 | }
13 | }
14 |
15 | .main-wrapper {
16 | display: flex;
17 | min-height: 100vh;
18 | align-items: center;
19 | justify-content: space-evenly;
20 | }
21 |
22 | .welcome-text {
23 | color: white;
24 | font-size: 5.6rem;
25 | }
26 |
27 | .login-container {
28 | display: flex;
29 | flex-direction: column;
30 | justify-content: center;
31 | gap: 6px;
32 | width: 96%;
33 | max-width: 400px;
34 | padding: 50px;
35 | color: black;
36 | background-color: white;
37 | border-radius: 10px;
38 | }
39 | .extras div:first-of-type {
40 | display: flex;
41 | align-items: center;
42 | gap: 5px;
43 | }
44 | .extras {
45 | gap: 12px;
46 | display: flex;
47 | font-size: 1.2rem;
48 | justify-content: space-between;
49 | }
50 | .welcome-text-dodgerblue {
51 | color: dodgerblue;
52 | }
53 | .login-form {
54 | margin-top: 8px;
55 | display: flex;
56 | flex-direction: column;
57 | gap: 5px;
58 | }
59 |
60 | .login-container button {
61 | margin-top: 8px;
62 | background-color: #131418;
63 | outline: none;
64 | border: none;
65 | font-size: 1.4rem;
66 | color: white;
67 | border-radius: 8px;
68 | padding: 12px 0px;
69 | justify-self: center;
70 | }
71 | .login-form button {
72 | background-color: dodgerblue;
73 | }
74 |
75 | .signupprompt {
76 | margin-top: 4px;
77 |
78 | font-size: 1.2rem;
79 | display: flex;
80 | justify-content: center;
81 | gap: 10px;
82 | }
83 |
84 | .input::placeholder {
85 | }
86 | .input {
87 | color: white;
88 | font-size: 1.5rem;
89 | padding: 12px;
90 | outline: none;
91 | border: none;
92 | border-radius: 5px;
93 | background-color: #212121;
94 | }
95 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Signup.jsx:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Navbar from "../Sections/Navbar";
3 | import "./Login.css";
4 |
5 | const SignupPage = () => {
6 | return (
7 | <>
8 |
9 |
10 |
11 | Hola,
12 |
13 | Welcome to
Animehub
14 |
15 |
16 |
17 |
Sign Up
18 |
Please fill in your details.
19 |
20 |
48 |
49 |
50 |
Already have a account?
51 |
Sign In
52 |
53 |
54 |
55 | >
56 | );
57 | };
58 |
59 | export default SignupPage;
60 |
--------------------------------------------------------------------------------
/client/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "client",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@ant-design/icons": "^4.7.0",
7 | "@fortawesome/fontawesome-svg-core": "^6.2.0",
8 | "@fortawesome/free-solid-svg-icons": "^6.2.0",
9 | "@fortawesome/react-fontawesome": "^0.2.0",
10 | "@testing-library/jest-dom": "^5.16.5",
11 | "@testing-library/react": "^13.3.0",
12 | "@testing-library/user-event": "^13.5.0",
13 | "@types/jest": "^29.0.3",
14 | "@types/node": "^18.7.19",
15 | "@types/react": "^18.0.21",
16 | "@types/react-dom": "^18.0.6",
17 | "@vime/core": "^5.3.1",
18 | "@vime/react": "^5.3.1",
19 | "axios": "^0.27.2",
20 | "deps": "^1.0.0",
21 | "framer-motion": "^7.3.4",
22 | "gapi-script": "^1.2.0",
23 | "react": "^18.2.0",
24 | "react-dom": "^18.2.0",
25 | "react-elastic-carousel": "^0.11.5",
26 | "react-google-login": "^5.2.2",
27 | "react-grid-system": "^8.1.6",
28 | "react-hot-toast": "^2.3.0",
29 | "react-icons": "^4.4.0",
30 | "react-modal-video": "^1.2.10",
31 | "react-router-dom": "^6.3.0",
32 | "react-scripts": "5.0.1",
33 | "react-scroll-to-top": "^3.0.0",
34 | "react-spinners": "^0.13.4",
35 | "react-text-truncate": "^0.19.0",
36 | "react-web-share": "^2.0.1",
37 | "sass": "^1.54.9",
38 | "styled-components": "^5.3.5",
39 | "uuid": "^8.3.2",
40 | "web-vitals": "^2.1.4"
41 | },
42 | "scripts": {
43 | "start": "react-scripts start",
44 | "build": "react-scripts build",
45 | "test": "react-scripts test",
46 | "eject": "react-scripts eject"
47 | },
48 | "eslintConfig": {
49 | "extends": [
50 | "react-app",
51 | "react-app/jest"
52 | ]
53 | },
54 | "browserslist": {
55 | "production": [
56 | ">0.2%",
57 | "not dead",
58 | "not op_mini all"
59 | ],
60 | "development": [
61 | "last 1 chrome version",
62 | "last 1 firefox version",
63 | "last 1 safari version"
64 | ]
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/client/src/components/Layouts/GridRenderer.jsx:
--------------------------------------------------------------------------------
1 | import { motion } from "framer-motion";
2 | import { useEffect, useState } from "react";
3 | import { Col, Container, Row, setConfiguration } from "react-grid-system";
4 | import { v4 as uuidv4 } from "uuid";
5 | import CarouselCard from "../Cards/AnimeCard";
6 | setConfiguration({ breakpoints: [600, 924, 1434, 1785, 2000, 2400] });
7 | const GridRenderer = ({ finalQuery, setAnimeInfo, isAnimate }) => {
8 | useEffect(() => {
9 | setIsAnimated(isAnimate);
10 | }, [isAnimate]);
11 | const [isAnimated, setIsAnimated] = useState(true);
12 | return (
13 |
14 |
15 | {finalQuery.map((query, index) => (
16 |
25 |
39 |
50 |
51 |
52 | ))}
53 |
54 |
55 | );
56 | };
57 | export default GridRenderer;
58 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Genres.css:
--------------------------------------------------------------------------------
1 | .genresdiv,
2 | .formatsdiv,
3 | .seasonsdiv,
4 | .statusesdiv {
5 | transition: all 300ms ease;
6 | width: 95%;
7 | margin: 0 auto;
8 | display: grid;
9 | align-items: center;
10 | gap: 30px;
11 | justify-items: center;
12 | grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
13 | column-gap: 10;
14 | }
15 | .genre:hover,
16 | .format:hover,
17 | .status:hover,
18 | .season:hover {
19 | transform: scale(1.05);
20 | transition: 300ms ease;
21 | }
22 | .genre,
23 | .format,
24 | .status,
25 | .season {
26 | cursor: pointer;
27 | background-color: rgb(0, 0, 0, 0.5);
28 | background-size: cover;
29 | background-position: center;
30 | text-align: center;
31 | display: flex;
32 | justify-content: center;
33 | color: white;
34 | align-items: center;
35 | height: 155px;
36 | width: 270px;
37 | border-radius: 5px;
38 | }
39 | .genres-title,
40 | .formats-title,
41 | .seasons-title,
42 | .statuses-title {
43 | color: white;
44 | margin-bottom: 10px;
45 | font-size: 2.5rem;
46 | margin-top: 40px;
47 | margin-left: 2.6%;
48 | }
49 | .genres-title {
50 | margin-top: 80px;
51 | }
52 | .statusesdiv {
53 | padding-bottom: 50px;
54 | }
55 | @media screen and (max-width: 560px) {
56 | .genres-title,
57 | .formats-title,
58 | .seasons-title,
59 | .statuses-title {
60 | margin-left: 4.2%;
61 | }
62 | .genresdiv,
63 | .formatsdiv,
64 | .statusesdiv,
65 | .seasonsdiv {
66 | width: 90%;
67 | grid-template-columns: repeat(auto-fill, minmax(41vw, 1fr));
68 | }
69 | .genresdiv div,
70 | .formatsdiv div,
71 | .statusesdiv div,
72 | .seasonsdiv div {
73 | gap: 10px;
74 | row-gap: 0px;
75 | column-gap: 0px;
76 | width: 46vw;
77 | height: 120px;
78 | }
79 | }
80 | @media screen and (max-width: 375px) {
81 | .genresdiv,
82 | .formatsdiv,
83 | .statusesdiv .seasonsdiv {
84 | width: 90%;
85 | grid-template-columns: repeat(auto-fill, minmax(40vw, 1fr));
86 | }
87 | .genresdiv div,
88 | .formatsdiv div,
89 | .statusesdiv div,
90 | .seasonsdiv div {
91 | width: 46vw;
92 | }
93 | }
94 | @media screen and (max-width: 300px) {
95 | .genresdiv div,
96 | .formatsdiv div,
97 | .statusesdiv div,
98 | .seasonsdiv div {
99 | width: 90vw;
100 | }
101 | }
102 |
--------------------------------------------------------------------------------
/client/src/components/Sections/Hero.jsx:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import { useEffect, useRef, useState } from "react";
3 | import Carousel from "react-elastic-carousel";
4 | import { v4 as uuidv4 } from "uuid";
5 | import HeroCard from "../Cards/HeroCard";
6 | import Navbar from "./Navbar";
7 | const Hero = ({ setHeroSectionLoaded }) => {
8 | const carouselRef = useRef(null);
9 | let resetTimeout;
10 | const [fetchedAnime, setFetchedAnime] = useState([]);
11 | const baseURL = process.env.REACT_APP_ANILIST_API_URL;
12 |
13 | useEffect(() => {
14 | axios.get(`${baseURL}/popular`).then(({ data: { results } }) => {
15 | setFetchedAnime(results);
16 | setHeroSectionLoaded(true);
17 | }, []);
18 | }, []);
19 |
20 | return (
21 | <>
22 | {fetchedAnime.length > 0 && (
23 |
24 |
25 |
26 | return (
27 | {
33 | clearTimeout(resetTimeout);
34 | resetTimeout = setTimeout(() => {
35 | carouselRef?.current?.goTo(0);
36 | }, 4000);
37 | }}
38 | pagination={true}
39 | >
40 | {fetchedAnime.map((item) =>
41 | item.totalEpisodes &&
42 | item.id &&
43 | item.releaseDate &&
44 | item.duration &&
45 | item.title &&
46 | item.description &&
47 | item.cover ? (
48 |
58 | ) : null
59 | )}
60 |
61 | );
62 |
63 |
64 | )}
65 | >
66 | );
67 | };
68 | export default Hero;
69 |
--------------------------------------------------------------------------------
/client/src/components/Cards/HeroCard.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | CalendarOutlined,
3 | ClockCircleOutlined,
4 | PlayCircleOutlined,
5 | } from "@ant-design/icons";
6 | import { faListOl } from "@fortawesome/free-solid-svg-icons";
7 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
8 | import { useNavigate } from "react-router-dom";
9 | import TextTruncate from "react-text-truncate";
10 | import "./HeroCard.css";
11 | const HeaderCarouselCard = ({
12 | duration,
13 | cover,
14 | title,
15 | id,
16 | year,
17 | description,
18 | epcount,
19 | }) => {
20 | const navigate = useNavigate();
21 | async function fetchVideo(id) {
22 | navigate("/watch/" + id);
23 | }
24 |
25 | return (
26 | <>
27 |
33 |
34 |
{title}
35 |
36 |
37 |
38 | {" "}
39 | TV
40 |
41 |
42 | {epcount} Episodes
43 |
44 |
45 | {duration} Minutes
46 |
47 |
48 | {year}
49 |
50 |
51 |
52 | {" "}
53 | |<.+[\W]>/g, "")}
62 | line={4}
63 | >
64 |
65 |
66 |
76 |
77 | >
78 | );
79 | };
80 |
81 | export default HeaderCarouselCard;
82 |
--------------------------------------------------------------------------------
/client/src/App.jsx:
--------------------------------------------------------------------------------
1 | //component imports
2 | import React, { useState } from "react";
3 | import { BrowserRouter, Route, Routes } from "react-router-dom";
4 | import "./App.css";
5 | import ClockLoader from "./components/Others/ClockLoader";
6 | import AnimePlayerPage from "./components/Pages/AnimePlayerPage";
7 | import FilteredPage from "./components/Pages/Filtered";
8 | import GenresPage from "./components/Pages/Genres";
9 | import Home from "./components/Pages/Home";
10 | import LoginPage from "./components/Pages/Login";
11 | import MoviesPage from "./components/Pages/Movies";
12 | import RecentPage from "./components/Pages/Recent";
13 | import SearchResults from "./components/Pages/SearchResults";
14 | import SignupPage from "./components/Pages/Signup";
15 | import Watchlist from "./components/Pages/Watchlist";
16 | import MoreSection from "./components/Sections/MoreSection";
17 |
18 | // state for showing and hiding spinner
19 | export const GlobalContext = React.createContext();
20 | const App = () => {
21 | const [videoIsLoading, setVideoIsLoading] = useState(false);
22 | const [loggedIn, setIsLoggedIn] = useState(false);
23 |
24 | return (
25 |
33 | {videoIsLoading && }
34 |
35 | <>
36 |
37 | } />
38 | } />
39 | } />
40 | } />
41 | } />
42 | } />
43 | } />
44 | }
48 | />
49 | }
53 | />
54 | }
58 | />
59 | } />
60 |
61 | >
62 |
63 |
64 | );
65 | };
66 | export default App;
67 |
--------------------------------------------------------------------------------
/client/src/components/Layouts/CarouselRenderer.jsx:
--------------------------------------------------------------------------------
1 | import { RightOutlined } from "@ant-design/icons";
2 | import Carousel from "react-elastic-carousel";
3 | import { useNavigate } from "react-router-dom";
4 | import { v4 as uuidv4 } from "uuid";
5 | import AnimeCard from "../Cards/AnimeCard";
6 | import UpcomingCard from "../Cards/UpcomingCard";
7 | const CarouselRenderer = ({
8 | finalQuery,
9 | sectionTitle,
10 | isRecent,
11 | isAnimeCard,
12 | setIsPlaying,
13 | setTrailerId,
14 | url,
15 | }) => {
16 | const navigate = useNavigate();
17 | const breakPoints = [
18 | { width: 1, itemsToShow: !isAnimeCard ? 2 : 3 },
19 | { width: 580, itemsToShow: !isAnimeCard ? 2 : 4 },
20 | { width: 800, itemsToShow: !isAnimeCard ? 3 : 4 },
21 | { width: 900, itemsToShow: !isAnimeCard ? 3 : 5 },
22 | { width: 1100, itemsToShow: !isAnimeCard ? 3 : 5 },
23 | { width: 1270, itemsToShow: !isAnimeCard ? 4 : 6 },
24 | { width: 1760, itemsToShow: !isAnimeCard ? 4 : 7 },
25 | { width: 1920, itemsToShow: !isAnimeCard ? 4 : 8 },
26 | ];
27 |
28 | return (
29 |
30 |
48 |
56 | {finalQuery.map((query, index) =>
57 | isAnimeCard ? (
58 |
67 | ) : (
68 |
80 | )
81 | )}
82 |
83 |
84 | );
85 | };
86 |
87 | export default CarouselRenderer;
88 |
--------------------------------------------------------------------------------
/client/src/components/Cards/UpcomingCard.css:
--------------------------------------------------------------------------------
1 | .upcomingcard-wrapper {
2 | display: flex;
3 | flex-direction: column;
4 | align-items: center;
5 | height: fit-content;
6 | margin-top: 3px;
7 | justify-content: center;
8 | text-align: center;
9 | cursor: pointer;
10 | }
11 |
12 | .upcomingcard-title {
13 | font-size: 1.2rem;
14 | color: white;
15 | margin-top: 5px;
16 | }
17 |
18 | @media screen and (max-width: 359px) {
19 | .upcomingcard-card {
20 | height: 90px;
21 | width: 150px;
22 | }
23 | }
24 | @media screen and (min-width: 360px) {
25 | .upcomingcard-card {
26 | height: 100px;
27 | width: 167px;
28 | }
29 | }
30 | @media screen and (min-width: 380px) {
31 | .upcomingcard-card {
32 | height: 100px;
33 | width: 175px;
34 | }
35 | }
36 | @media screen and (min-width: 390px) {
37 | .upcomingcard-card {
38 | height: 110px;
39 | width: 180px;
40 | }
41 | }
42 | @media screen and (min-width: 440px) {
43 | .upcomingcard-card {
44 | height: 130px;
45 | width: 200px;
46 | }
47 | }
48 | @media screen and (min-width: 460px) {
49 | .upcomingcard-card {
50 | height: 130px;
51 | width: 220px;
52 | }
53 | }
54 | @media screen and (min-width: 480px) {
55 | .upcomingcard-card {
56 | height: 150px;
57 | width: 225px;
58 | }
59 | }
60 | @media screen and (min-width: 580px) {
61 | .upcomingcard-card {
62 | height: 150px;
63 | width: 290px;
64 | }
65 | }
66 | @media screen and (min-width: 650px) {
67 | .upcomingcard-card {
68 | height: 150px;
69 | width: 310px;
70 | }
71 | }
72 | @media screen and (min-width: 700px) {
73 | .upcomingcard-card {
74 | height: 150px;
75 | width: 330px;
76 | }
77 | }
78 | @media screen and (min-width: 760px) {
79 | .upcomingcard-card {
80 | height: 150px;
81 | width: 350px;
82 | }
83 | }
84 | @media screen and (min-width: 820px) {
85 | .upcomingcard-card {
86 | height: 180px;
87 | width: 270px;
88 | }
89 | .upcomingcard-title {
90 | font-size: 1.5rem;
91 | }
92 | }
93 | @media screen and (min-width: 990px) {
94 | .upcomingcard-card {
95 | height: 180px;
96 | width: 320px;
97 | }
98 | }
99 | @media screen and (min-width: 1100px) {
100 | .upcomingcard-card {
101 | height: 200px;
102 | width: 310px;
103 | }
104 | }
105 | @media screen and (min-width: 1190px) {
106 | .upcomingcard-card {
107 | height: 200px;
108 | width: 370px;
109 | }
110 | }
111 |
112 | @media screen and (min-width: 1270px) {
113 | .upcomingcard-card {
114 | height: 200px;
115 | width: 310px;
116 | }
117 | }
118 | @media screen and (min-width: 1400px) {
119 | .upcomingcard-card {
120 | height: 200px;
121 | width: 340px;
122 | }
123 | }
124 | @media screen and (min-width: 1600px) {
125 | .upcomingcard-card {
126 | height: 230px;
127 | width: 360px;
128 | }
129 | }
130 | @media screen and (min-width: 1710px) {
131 | .upcomingcard-card {
132 | height: 240px;
133 | width: 400px;
134 | }
135 | }
136 | @media screen and (min-width: 1750px) {
137 | .upcomingcard-card {
138 | height: 240px;
139 | width: 430px;
140 | }
141 | }
142 | .upcomingcard-card {
143 | border-radius: 4px;
144 | transition: all 0.5s;
145 | background-position: center;
146 | background-size: cover;
147 | }
148 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Home.css:
--------------------------------------------------------------------------------
1 | @import url("https://fonts.googleapis.com/css2?family=Inter:wght@500&display=swap");
2 | html {
3 | scroll-behavior: smooth;
4 | }
5 |
6 | .section-upcoming {
7 | margin-top: 10px;
8 | }
9 |
10 | .section-anime,
11 | .section-infinite {
12 | margin-top: 8px;
13 | }
14 |
15 | button.rec-dot {
16 | background-color: #3f1e26;
17 | }
18 |
19 | .animecard-wrapper:hover,
20 | .upcomingcard-wrapper:hover {
21 | transform: scale(1.02);
22 | transition: 300ms ease;
23 | }
24 |
25 | .contindex::-webkit-scrollbar-track {
26 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
27 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
28 | background-color: transparent;
29 | }
30 |
31 | .contindex::-webkit-scrollbar {
32 | width: 10px;
33 | background-color: transparent;
34 | }
35 |
36 | .contindex::-webkit-scrollbar-thumb {
37 | background-color: #366073;
38 | border-radius: 4px;
39 | }
40 |
41 | button.rec-dot {
42 | height: 8.5px;
43 | width: 8.5px;
44 | }
45 |
46 | .section-anime button.rec,
47 | .section-upcoming button.rec {
48 | display: none;
49 | }
50 |
51 | button.rec-dot_active,
52 | button.rec-dot_active:focus,
53 | button.rec-dot_active:hover button.rec-dot:focus {
54 | background-color: #b7b5ba;
55 | }
56 |
57 | button.rec-dot_active:hover {
58 | box-shadow: none;
59 | }
60 |
61 | button.rec-dot {
62 | box-shadow: none;
63 | }
64 |
65 | .row-title {
66 | color: white;
67 | font-size: 1.9rem;
68 | width: 60%;
69 | margin-left: 16px;
70 | margin-bottom: 3px;
71 | }
72 |
73 | .more-button {
74 | display: flex;
75 | align-items: center;
76 | justify-content: center;
77 | margin-right: 30px;
78 | font-size: 1.55rem;
79 | gap: 5px;
80 | margin-bottom: 3px;
81 | color: #8f72db;
82 | }
83 |
84 | @media screen and (min-width: 768px) {
85 | .row-title {
86 | font-size: 2.5rem;
87 | width: 60%;
88 | margin-left: 30px;
89 | margin-bottom: 10px;
90 | }
91 | }
92 |
93 | @media screen and (max-width: 768px) {
94 | .curranime {
95 | font-size: 1.4rem !important;
96 | }
97 | .additional-anime-info {
98 | font-size: 1.4rem !important;
99 | }
100 | }
101 |
102 | @media screen and (max-width: 768px) {
103 | .more-button {
104 | font-size: 1.4rem;
105 | }
106 | button.rec {
107 | display: none;
108 | }
109 | }
110 |
111 | .pageindex {
112 | display: flex;
113 | gap: 40px;
114 | justify-content: center;
115 | }
116 |
117 | @media screen and (max-width: 1000px) {
118 | .pageindex {
119 | gap: 1px;
120 | font-size: 2rem;
121 | width: fit-content;
122 | }
123 | .pagination {
124 | margin-top: 5px;
125 | }
126 | .previousPageButton * {
127 | order: 1;
128 | }
129 | .nextPageButton,
130 | .previousPageButton {
131 | display: flex;
132 | flex-direction: column;
133 | align-items: center;
134 | justify-content: center;
135 | }
136 | .pageindex button {
137 | font-size: 1.7rem;
138 | }
139 | }
140 |
141 | @media screen and (max-width: 360px) {
142 | .pageindex {
143 | display: none;
144 | }
145 | }
146 |
147 | .btn-index {
148 | cursor: pointer;
149 | }
150 |
151 | .btn-pageindex:hover {
152 | border: 1px solid dodgerblue !important;
153 | }
154 |
155 | .modal-video-body {
156 | max-width: 85vw !important;
157 | }
158 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Login.jsx:
--------------------------------------------------------------------------------
1 | import { GithubFilled, GoogleCircleFilled } from "@ant-design/icons";
2 | import "./Login.css";
3 | import { useEffect } from "react";
4 | import Navbar from "../Sections/Navbar";
5 | import { GoogleLogin } from "react-google-login";
6 | import { useNavigate } from "react-router-dom";
7 | import { gapi } from "gapi-script";
8 | import toast, { Toaster } from "react-hot-toast";
9 | const Login = ({ setIsLoggedIn }) => {
10 | const navigate = useNavigate();
11 | const clientId =
12 | "952500448273-nr07eb37fsoadpoktd855vrpmphsrg9e.apps.googleusercontent.com";
13 | const onSuccess = (res) => {
14 | toast.success("Login Successful!");
15 | setIsLoggedIn(true);
16 | navigate("/");
17 | };
18 | const onFailure = (err) => {
19 | toast.error("Login Failed");
20 | };
21 | useEffect(() => {
22 | gapi.load("client:auth2", () => {
23 | gapi.client.init({
24 | clientId: clientId,
25 | scope: "",
26 | });
27 | });
28 | });
29 | return (
30 | <>
31 |
32 |
33 |
34 | Hola,
35 |
36 | Welcome to
Animehub
37 |
38 |
39 |
Sign in
40 |
41 | Please sign in to your account.
42 |
43 |
69 |
70 |
(
72 |
78 | )}
79 | clientId={clientId}
80 | onSuccess={onSuccess}
81 | onFailure={onFailure}
82 | redirectUri="https://animehub.vercel.app/"
83 | cookiePolicy={"single_host_origin"}
84 | />
85 |
88 |
89 |
Dont't have a account?
90 |
Sign Up
91 |
92 |
93 |
94 |
100 | >
101 | );
102 | };
103 | export default Login;
104 |
--------------------------------------------------------------------------------
/client/src/components/Cards/AnimeCard.css:
--------------------------------------------------------------------------------
1 | .animecard-wrapper {
2 | display: flex;
3 | margin-top: 3px;
4 | flex-direction: column;
5 | align-items: center;
6 | height: fit-content;
7 | justify-content: center;
8 | text-align: center;
9 | cursor: pointer;
10 | }
11 |
12 | .animecard-card {
13 | border-radius: 4px;
14 | background-position: center;
15 | background-size: cover;
16 | transition: all 0.5s;
17 | }
18 |
19 | .animecard-starcontainer {
20 | color: yellow;
21 | background-color: rgba(0, 0, 0, 0.6);
22 | padding: 2px 7px;
23 | position: absolute;
24 | border-radius: 4px;
25 | font-size: 1.35rem;
26 | display: flex;
27 | gap: 4px;
28 | justify-content: center;
29 | align-items: center;
30 | }
31 |
32 | .animecard-epnumber {
33 | color: white;
34 | font-weight: lighter;
35 | margin-top: 5px;
36 | font-size: 1.35rem;
37 | }
38 |
39 | .animecard-year {
40 | color: white;
41 | font-size: 1.15rem;
42 | margin-top: 4px;
43 | }
44 |
45 | .animecard-title {
46 | font-size: 1.5rem;
47 | color: white;
48 | margin-top: 3px;
49 | }
50 |
51 | @media screen and (min-width: 800px) {
52 | .animecard-year {
53 | font-size: 1.35rem;
54 | }
55 | }
56 |
57 | @media screen and (max-width: 359px) {
58 | .animecard-card {
59 | height: 130px;
60 | width: 95px;
61 | }
62 | }
63 |
64 | @media screen and (min-width: 360px) {
65 | .animecard-card {
66 | height: 140px;
67 | width: 110px;
68 | }
69 | }
70 |
71 | @media screen and (min-width: 380px) {
72 | .animecard-card {
73 | height: 150px;
74 | width: 115px;
75 | }
76 | }
77 |
78 | @media screen and (min-width: 420px) {
79 | .animecard-card {
80 | height: 155px;
81 | width: 125px;
82 | }
83 | }
84 |
85 | @media screen and (min-width: 440px) {
86 | .animecard-card {
87 | height: 155px;
88 | width: 125px;
89 | }
90 | }
91 |
92 | @media screen and (min-width: 475px) {
93 | .animecard-card {
94 | height: 185px;
95 | width: 145px;
96 | }
97 | }
98 |
99 | @media screen and (min-width: 700px) {
100 | .animecard-card {
101 | height: 250px;
102 | width: 160px;
103 | }
104 | }
105 |
106 | @media screen and (min-width: 800px) {
107 | .animecard-card {
108 | height: 250px;
109 | width: 180px;
110 | }
111 | }
112 |
113 | @media screen and (max-width: 768px) {
114 | .epnumber {
115 | font-size: 1.15rem;
116 | }
117 | .animecard-title {
118 | font-size: 1.2rem;
119 | }
120 | }
121 |
122 | @media screen and (min-width: 830px) {
123 | .animecard-card {
124 | height: 250px;
125 | width: 180px;
126 | }
127 | }
128 |
129 | @media screen and (min-width: 1010px) {
130 | .animecard-card {
131 | height: 250px;
132 | width: 180px;
133 | }
134 | }
135 | @media screen and (min-width: 1125px) {
136 | .animecard-card {
137 | height: 250px;
138 | width: 180px;
139 | }
140 | }
141 |
142 | @media screen and (min-width: 1300px) {
143 | .animecard-card {
144 | height: 340px;
145 | width: 230px;
146 | }
147 | }
148 |
149 | @media screen and (min-width: 1400px) {
150 | .animecard-card {
151 | height: 340px;
152 | width: 230px;
153 | }
154 | }
155 |
156 | @media screen and (min-width: 1570px) {
157 | .animecard-card {
158 | height: 340px;
159 | width: 230px;
160 | }
161 | }
162 |
163 | @media screen and (min-width: 1600px) {
164 | .animecard-card {
165 | height: 340px;
166 | width: 230px;
167 | }
168 | }
169 |
170 | @media screen and (min-width: 1710px) {
171 | .animecard-card {
172 | height: 340px;
173 | width: 230px;
174 | }
175 | }
176 |
177 | @media screen and (min-width: 1800px) {
178 | .animecard-card {
179 | height: 340px;
180 | width: 230px;
181 | }
182 |
183 | .vertical-grid .animecard-card {
184 | height: 380px;
185 | width: 260px;
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/client/src/components/Pages/AnimePlayerPage.css:
--------------------------------------------------------------------------------
1 | .watchlist,
2 | .share {
3 | display: flex;
4 | flex-direction: column;
5 | justify-content: center;
6 | align-items: center;
7 | cursor: pointer;
8 | }
9 | .animeplayer-container {
10 | margin-top: 65px;
11 | }
12 | .sharewatchlistcontainer {
13 | background-color: #181020;
14 | height: 100px;
15 | margin-top: 25px;
16 | justify-content: space-evenly;
17 | align-items: center;
18 | display: flex;
19 | gap: 10px;
20 | width: 210px;
21 | border-radius: 3px;
22 | }
23 | .watchlist span,
24 | .share span {
25 | color: #d8d8d8;
26 | font-size: 1.2rem;
27 | }
28 | .epindex:hover {
29 | background-color: #823061;
30 | }
31 | .vime-container {
32 | width: 97%;
33 | margin: 0 auto;
34 | }
35 | .vertical-grid-container {
36 | display: block;
37 | position: absolute;
38 | right: 30px;
39 | top: 80px;
40 | width: 29vw;
41 | }
42 | .vertical-grid::-webkit-scrollbar-track {
43 | -webkit-box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
44 | box-shadow: inset 0 0 6px rgba(0, 0, 0, 0.3);
45 | background-color: transparent;
46 | }
47 | .vertical-grid::-webkit-scrollbar {
48 | width: 5px;
49 | background-color: transparent;
50 | }
51 | .vertical-grid::-webkit-scrollbar-thumb {
52 | background-color: #2f2f2f;
53 | border-radius: 4px;
54 | }
55 | .vertical-grid {
56 | grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
57 | max-height: 980px;
58 | overflow-x: hidden;
59 | display: grid;
60 | overflow-y: scroll;
61 | row-gap: 15px;
62 | }
63 |
64 | .recommend-div button.rec {
65 | display: none;
66 | }
67 | .top-narrowbar {
68 | background-color: rgb(38, 50, 56);
69 | height: 35px;
70 | border-radius: 3px 3px 0px 0px;
71 | display: flex;
72 | align-items: center;
73 | padding: 0px 15px;
74 | }
75 | .curranimeinfo {
76 | margin-top: 5px;
77 | display: flex;
78 | gap: 25px;
79 | }
80 | .contindex {
81 | overflow-y: scroll;
82 | max-height: 100px;
83 | height: fit-content;
84 | display: grid;
85 | gap: 4px 4px;
86 | justify-items: start;
87 | justify-content: start;
88 | align-items: start;
89 | grid-template-columns: repeat(auto-fill, minmax(50px, 1fr));
90 | }
91 | .recommendations-title {
92 | color: white;
93 | font-size: 1.7rem;
94 | }
95 | .anime-title {
96 | font-size: 1.8rem;
97 | color: white;
98 | }
99 | .curranime-status,
100 | .curranime-adaptation,
101 | .curranime-genres,
102 | .curranime-studios {
103 | font-size: 1.35rem;
104 | color: #d8d8d8;
105 | }
106 | .curranime-genres {
107 | color: #197ee0;
108 | }
109 | .summary-content {
110 | text-align: justify;
111 | color: #d8d8d8;
112 | font-size: 1.35rem;
113 | font-weight: lighter;
114 | }
115 | .summary-title {
116 | font-size: 1.7rem;
117 | color: white;
118 | margin-top: 20px;
119 | }
120 | .epindex {
121 | cursor: pointer;
122 | border-radius: 3px;
123 | height: 30px;
124 | width: 50px;
125 | color: white;
126 | display: flex;
127 | justify-content: center;
128 | align-items: center;
129 | font-size: 1.3rem;
130 | }
131 |
132 | @media screen and (max-width: 1700px) {
133 | .vertical-grid .animecard-card {
134 | height: 280px;
135 | width: 200px;
136 | }
137 | }
138 |
139 | .curranime-platform,
140 | .curranime-score,
141 | .curranime-epaired,
142 | .curranime-releaseyear {
143 | color: white;
144 | }
145 | .curranime {
146 | width: 99%;
147 | padding: 15px;
148 | background-color: #0f0617;
149 | line-height: 1.5;
150 | }
151 |
152 | @media screen and (max-width: 1500px) {
153 | .vertical-grid-container {
154 | display: none;
155 | }
156 | }
157 | @media screen and (min-width: 768px) {
158 | .curranimeinfo {
159 | display: flex !important;
160 | justify-content: "center" !important;
161 | align-items: "center" !important;
162 | }
163 | .curranime {
164 | padding-left: 22px;
165 | }
166 | .anime-title {
167 | font-size: 2.6rem;
168 | }
169 | }
170 | @media screen and (min-width: 1400px) {
171 | .vime-container {
172 | margin-left: 22px;
173 | }
174 | .animeplayer-container {
175 | margin-top: 90px;
176 | margin-left: 10px;
177 | }
178 | }
179 | @media screen and (min-width: 1500px) {
180 | .animeplayer-container {
181 | max-width: 68vw;
182 | }
183 | }
184 |
--------------------------------------------------------------------------------
/client/src/components/Sections/InfiniteSection.jsx:
--------------------------------------------------------------------------------
1 | import { useContext, useEffect, useState } from "react";
2 | import GridRenderer from "../Layouts/GridRenderer.jsx";
3 | import axios from "axios";
4 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
5 | import { v4 as uuidv4 } from "uuid";
6 | import toast, { Toaster } from "react-hot-toast";
7 | import "./InfiniteSection.css";
8 | import {
9 | faArrowLeftLong,
10 | faArrowRightLong,
11 | } from "@fortawesome/free-solid-svg-icons";
12 | import { GlobalContext } from "../../App.jsx";
13 | const InfiniteSection = ({ url, sectiontitle, itemlimit, id, querytype }) => {
14 | const SharedState = useContext(GlobalContext);
15 | const [fetchedData, setFetchedData] = useState([]);
16 | const [currpage, setCurrpage] = useState(1);
17 | const [isAnimate, setIsAnimate] = useState(false);
18 | const [hasNextPage, setHasNextPage] = useState(true);
19 | const [pageNumbers, setPageNumbers] = useState([1, 2, 3, 4, 5]);
20 |
21 | const updatePageNumberButtons = (e) => {
22 | if (e.target.classList.contains("next-page-button")) {
23 | if (currpage % 5 === 0) {
24 | let temp = [];
25 | for (let i = 1; i <= 5; i++) {
26 | temp.push(currpage + i);
27 | }
28 | setPageNumbers(temp);
29 | }
30 | }
31 | if (e.target.classList.contains("previous-page-button")) {
32 | if (currpage % 5 === 1) {
33 | let temp = [];
34 | for (let i = 5; i >= 1; i--) {
35 | temp.push(currpage - i);
36 | }
37 | setPageNumbers(temp);
38 | }
39 | }
40 | };
41 | useEffect(() => {
42 | setCurrpage(1);
43 | }, [url]);
44 | useEffect(() => {
45 | setIsAnimate(false);
46 | SharedState.setVideoIsLoading(true);
47 | if (currpage > 1) {
48 | document.querySelector("#" + id).scrollIntoView();
49 | }
50 | axios
51 | .get(url + querytype + "page=" + currpage + "&perPage=" + itemlimit)
52 | .then(({ data: { hasNextPage, results } }) => {
53 | if (hasNextPage) {
54 | setHasNextPage(true);
55 | } else {
56 | setHasNextPage(false);
57 | }
58 | setFetchedData(results);
59 | setIsAnimate(true);
60 | })
61 | .finally(() => SharedState.setVideoIsLoading(false));
62 | }, [currpage, url]);
63 | return (
64 | <>
65 |
72 | {fetchedData.length > 0 && (
73 | <>
74 | {sectiontitle}
75 |
79 |
80 |
81 |
95 |
96 | {pageNumbers.map((pageNumber) => (
97 |
110 | ))}
111 |
112 |
126 |
127 |
128 | >
129 | )}
130 |
146 |
147 | >
148 | );
149 | };
150 | export default InfiniteSection;
151 |
--------------------------------------------------------------------------------
/client/src/components/Sections/Navbar.css:
--------------------------------------------------------------------------------
1 | *,
2 | *::after,
3 | *::before {
4 | margin: 0;
5 | box-sizing: border-box;
6 | }
7 | html {
8 | font-size: 62.5%;
9 | }
10 | body {
11 | font-size: 1.6rem;
12 | }
13 | li {
14 | list-style: none;
15 | }
16 | a {
17 | text-decoration: none;
18 | color: black;
19 | }
20 | .nav {
21 | width: 100%;
22 | position: fixed;
23 | display: flex;
24 | font-size: 1.6rem;
25 | background-color: #0f0617;
26 | top: 0;
27 | left: 0;
28 | right: 0;
29 | align-items: center;
30 | justify-content: space-between;
31 | padding-right: 25px;
32 | height: 65px;
33 | z-index: 2000;
34 | padding-left: 40px;
35 | }
36 | .nav-side-div {
37 | align-items: center;
38 | justify-content: center;
39 | gap: 20px;
40 | display: flex;
41 | }
42 | .nav__link {
43 | color: white;
44 | cursor: pointer;
45 | font: inherit;
46 | }
47 | .nav__brand {
48 | display: flex;
49 | align-items: center;
50 | color: white;
51 | gap: 10px;
52 | text-transform: uppercase;
53 | }
54 | .nav__menu {
55 | display: flex;
56 | align-items: center;
57 | justify-content: flex-start;
58 | gap: 2.5rem;
59 | }
60 | .nav-brand {
61 | cursor: pointer;
62 | gap: 10px;
63 | display: flex;
64 | align-items: center;
65 | color: white;
66 | justify-content: center;
67 | }
68 | .nav-brand-logo {
69 | height: 34px;
70 | color: white;
71 | padding: 0px;
72 | }
73 | .nav__toggler {
74 | display: none;
75 | }
76 | .nav__toggler div {
77 | width: 2rem;
78 | height: 0.3rem;
79 | margin: 0.4rem;
80 | background: rgb(204, 204, 204);
81 | transition: 0.3s ease-in;
82 | }
83 | @media screen and (max-width: 1250px) {
84 | .auth li:first-child {
85 | padding: 5px 14px !important;
86 | }
87 | .auth li:last-child {
88 | background-color: #8230c6;
89 | border-radius: 4px;
90 | border: none;
91 | outline: none;
92 | }
93 | .auth li {
94 | border: 1px solid white;
95 | border-radius: 5px;
96 | display: flex;
97 | align-items: center;
98 | padding: 6px 14px !important;
99 | justify-content: center;
100 | }
101 |
102 | .auth {
103 | display: flex;
104 | order: -3;
105 | gap: 10px;
106 | }
107 | .magnify-icon {
108 | display: none;
109 | color: white;
110 | font-size: 25px;
111 | }
112 | .searchbar {
113 | background-image: url("../../assets/images/search.png");
114 | background-position: 8px 8.5px;
115 | background-repeat: no-repeat;
116 | background-size: 20px;
117 | padding: 10px 40px !important;
118 | }
119 | .nav {
120 | justify-content: space-between;
121 | padding-left: 25px;
122 | }
123 | .nav__toggler {
124 | display: block;
125 | cursor: pointer;
126 | border-radius: 5px;
127 | }
128 | .nav__item {
129 | color: white;
130 | }
131 | .searchbar {
132 | display: none;
133 | }
134 | .nav__menu {
135 | position: fixed;
136 | padding-top: 20px;
137 | z-index: 1;
138 | gap: 0.8rem;
139 | top: 60px;
140 | right: 0;
141 | width: 100vw;
142 | height: fit-content;
143 | display: flex;
144 | justify-content: center;
145 | align-items: center;
146 | padding-bottom: 40px;
147 | text-align: center;
148 | padding-right: 30px;
149 | border: 1px solid hsl(248, 24%, 10%);
150 | background-color: #10141e;
151 | border-radius: 5px;
152 | flex-direction: column;
153 | font-size: 7px;
154 | transform: translateX(100%);
155 | transition: 0.1s ease-in;
156 | }
157 | .nav__item {
158 | font-size: 1.6rem;
159 | width: 99%;
160 | padding-top: 10px;
161 | padding-bottom: 10px;
162 | color: #d1d5db;
163 | }
164 | .nav__item:hover {
165 | border-radius: 6px;
166 | background-color: #374151;
167 | }
168 | }
169 | .nav__active {
170 | padding-bottom: 20px;
171 | transform: translateX(0%);
172 | }
173 | .toggle .line1 {
174 | transform: rotate(-45deg) translate(-4px, 5px);
175 | }
176 | .toggle .line2 {
177 | opacity: 0;
178 | }
179 | .toggle .line3 {
180 | transform: rotate(45deg) translate(-4px, -5px);
181 | }
182 | .searchbar {
183 | outline: none;
184 | display: inline-block;
185 | padding: 10px 15px;
186 | border-radius: 0.7rem;
187 | border: none;
188 | font-size: 1.5rem;
189 | color: #9aa2ae;
190 | background-color: #374151;
191 | }
192 | .searchbar::placeholder {
193 | color: white;
194 | font-family: inherit;
195 | }
196 | .searchbar:hover,
197 | .searchbar:focus {
198 | border: 1px solid violet;
199 | color: white;
200 | }
201 | @media screen and (min-width: 1100px) {
202 | .auth li:first-child {
203 | padding: 7px 14px;
204 | }
205 | .auth li:last-child {
206 | background-color: #8230c6;
207 | border-radius: 4px;
208 | border: none;
209 | outline: none;
210 | }
211 | .nav__item-login {
212 | border: 1px solid white;
213 | }
214 | .auth li {
215 | border-radius: 5px;
216 | display: flex;
217 | align-items: center;
218 | padding: 8px 14px;
219 | justify-content: center;
220 | }
221 | .nav__item-logout {
222 | padding: 3px 5px !important;
223 | }
224 | .auth {
225 | position: absolute;
226 | display: flex;
227 | justify-content: center;
228 | align-items: center;
229 | gap: 5px;
230 | right: 0;
231 | margin-right: 4%;
232 | }
233 | }
234 | @media screen and (min-width: 380px) {
235 | .searchbar {
236 | width: 60vw;
237 | }
238 | .nav-side-div {
239 | gap: 17px;
240 | }
241 | }
242 | @media screen and (min-width: 420px) {
243 | .searchbar {
244 | width: 67vw;
245 | }
246 | }
247 | @media screen and (min-width: 500px) {
248 | .searchbar {
249 | width: 300px;
250 | }
251 | }
252 | @media screen and (max-width: 380px) {
253 | .searchbar {
254 | width: 60vw;
255 | }
256 | }
257 | @media screen and (max-width: 1000px) {
258 | .nav-brand-title {
259 | display: none;
260 | }
261 | }
262 |
--------------------------------------------------------------------------------
/client/src/components/Sections/Navbar.jsx:
--------------------------------------------------------------------------------
1 | import { faMagnifyingGlass } from "@fortawesome/free-solid-svg-icons";
2 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
3 | import React, { useContext, useEffect, useRef, useState } from "react";
4 | import { GoogleLogout } from "react-google-login";
5 | import toast, { Toaster } from "react-hot-toast";
6 | import { useLocation, useNavigate } from "react-router-dom";
7 | import { GlobalContext } from "../../App";
8 | import logo from "../../assets/images/logo.png";
9 | import "./Navbar.css";
10 | const Navbar = () => {
11 | const SharedState = useContext(GlobalContext);
12 | const [active, setActive] = useState("nav__menu");
13 | const [icon, setIcon] = useState("nav__toggler");
14 | const clientId = process.env.REACT_APP_CLIENT_ID
15 | const baseURL = process.env.REACT_APP_CONSUMET_API_URL
16 | const useOutsideAlerter = (ref) => {
17 | useEffect(() => {
18 | function handleClickOutside(event) {
19 | if (ref.current && !ref.current.contains(event.target)) {
20 | if (active === "nav__menu") {
21 | setIcon("nav__toggler");
22 | setActive("nav__menu");
23 | }
24 | }
25 | }
26 | document.addEventListener("mousedown", handleClickOutside);
27 | return () => {
28 | document.removeEventListener("mousedown", handleClickOutside);
29 | };
30 | }, [ref]);
31 | };
32 |
33 | const wrapperRef = useRef(null);
34 | useOutsideAlerter(wrapperRef);
35 | const location = useLocation();
36 | const navigate = useNavigate();
37 | const searchAnime = (input) => {
38 | return fetch(`${baseURL}/meta/anilist/${input}`)
39 | .then((response) => {
40 | return response.json();
41 | })
42 | .then((data) => {
43 | navigate("/search", {
44 | state: {
45 | finalResults: data.results,
46 | input: input,
47 | },
48 | });
49 | });
50 | };
51 | const [value, setValue] = useState("");
52 | const navToggle = () => {
53 | if (active === "nav__menu") {
54 | setActive("nav__menu nav__active");
55 | } else {
56 | setActive("nav__menu");
57 | }
58 | if (icon === "nav__toggler") {
59 | setIcon("nav__toggler toggle");
60 | } else setIcon("nav__toggler");
61 | };
62 |
63 | return (
64 |
201 | );
202 | };
203 | export default Navbar;
204 |
--------------------------------------------------------------------------------
/client/src/components/Players/AnimePlayer.jsx:
--------------------------------------------------------------------------------
1 | import "@vime/core/themes/default.css";
2 | import "@vime/core/themes/light.css";
3 | import {
4 | Control,
5 | Controls,
6 | DefaultUi,
7 | Hls,
8 | PlaybackControl,
9 | Player,
10 | Tooltip,
11 | } from "@vime/react";
12 | import { useEffect, useState } from "react";
13 | import toast, { Toaster } from "react-hot-toast";
14 | import { useNavigate } from "react-router-dom";
15 | const AnimePlayer = ({ src, animeInfoUrl, setVideoIsLoading }) => {
16 | const [url, setUrl] = useState(null);
17 |
18 | const navigate = useNavigate();
19 | const [savedTime, setSavedTime] = useState(null);
20 | useEffect(() => {
21 | if (localStorage.getItem(animeInfoUrl) !== null) {
22 | setSavedTime(Number(localStorage.getItem(animeInfoUrl)));
23 | }
24 | if (Object.keys(localStorage).length > 10) localStorage.clear();
25 | localStorage.setItem("recentlywatched", animeInfoUrl);
26 | }, []);
27 | const [time, setTime] = useState(0);
28 | useEffect(() => {
29 |
30 | if (!src) {
31 | toast.error("No servers available");
32 | navigate("/");
33 | return;
34 | }
35 | setUrl(src[0]);
36 | setVideoIsLoading(false);
37 | }, [src]);
38 | const hlsConfig = {
39 | crossOrigin: "anonymous",
40 | enableWorker: false,
41 | };
42 | return (
43 | <>
44 | {time !== null && url && (
45 |
46 |
{
48 | if (savedTime) setTime(savedTime);
49 | }}
50 | currentTime={time}
51 | onVmCurrentTimeChange={(e) => {
52 | setTime(e.detail);
53 | localStorage.setItem(animeInfoUrl, e.detail);
54 | }}
55 | onVmError={() => {
56 | if (url === src[1]) {
57 | toast.error(
58 | "Sorry we could'nt play that :( Going back to home "
59 | );
60 | navigate("/");
61 | } else {
62 | setUrl(src[1]);
63 | }
64 | }}
65 | hlsConfig={hlsConfig}
66 | theme="dark"
67 | style={{
68 | "--vm-player-theme": "rgba(255, 255, 255, .3)",
69 | "--vm-slider-track-height": "5px",
70 | "--vm-slider-thumb-height": "5px",
71 | "--vm-slider-track-focused-height": "6px",
72 | "--vm-slider-value-color": "#582fcb",
73 | "--vm-loading-screen-dot-color": "#8230c6",
74 | }}
75 | autoplay={true}
76 | >
77 |
78 |
85 |
86 |
87 |
94 |
95 |
117 | - 10s
118 |
119 |
120 |
121 |
141 | + 10s
142 |
143 |
144 |
145 |
146 |
147 |
148 | )}
149 | >
150 | );
151 | };
152 | export default AnimePlayer;
153 |
--------------------------------------------------------------------------------
/client/src/components/Pages/Genres.jsx:
--------------------------------------------------------------------------------
1 | import Navbar from "../Sections/Navbar";
2 | import { useNavigate } from "react-router-dom";
3 | import "./Genres.css";
4 | import { v4 as uuidv4 } from "uuid";
5 | import { useState } from "react";
6 | const Genres = () => {
7 | const [genresinfo, setGenresInfo] = useState([
8 | {
9 | title: "Action",
10 | image:
11 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/16498-8jpFCOcDmneX.jpg",
12 | },
13 | {
14 | title: "Adventure",
15 | image:
16 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/101922-YfZhKBUDDS6L.jpg",
17 | },
18 | {
19 | title: "Comedy",
20 | image:
21 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/21087-sHb9zUZFsHe1.jpg",
22 | },
23 | {
24 | title: "Drama",
25 | image:
26 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/113415-jQBSkxWAAk83.jpg",
27 | },
28 | {
29 | title: "Fantasy",
30 | image:
31 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/11757-TlEEV9weG4Ag.jpg",
32 | },
33 | {
34 | title: "Horror",
35 | image:
36 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20605-RCJ7M71zLmrh.jpg",
37 | },
38 | {
39 | title: "Mahou Shoujo",
40 | image:
41 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/9756-d5M8NffgJJHB.jpg",
42 | },
43 | {
44 | title: "Mecha",
45 | image:
46 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/99423-xu78CeWOO5FW.jpg",
47 | },
48 | {
49 | title: "Music",
50 | image:
51 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20665-j4kSsfhfkM24.jpg",
52 | },
53 | {
54 | title: "Mystery",
55 | image:
56 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/21234-7lfSSPoMmwr2.jpg",
57 | },
58 | {
59 | title: "Psychological",
60 | image:
61 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/1535.jpg",
62 | },
63 | {
64 | title: "Romance",
65 | image:
66 | "https://s4.anilist.co/file/anilistcdn/media/anime/cover/large/bx21519-XIr3PeczUjjF.png",
67 | },
68 | {
69 | title: "Sci-fi",
70 | image:
71 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/n9253-JIhmKgBKsWUN.jpg",
72 | },
73 | {
74 | title: "Slice of Life",
75 | image:
76 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20954-f30bHMXa5Qoe.jpg",
77 | },
78 | {
79 | title: "Sports",
80 | image:
81 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20464-PpYjO9cPN1gs.jpg",
82 | },
83 | {
84 | title: "Supernatural",
85 | image:
86 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20447-nlgQQzcgWbgw.jpg",
87 | },
88 | {
89 | title: "Thriller",
90 | image:
91 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/108632-yeLbrgPN4Oni.jpg",
92 | },
93 | ]);
94 | const [formatsInfo, setFormatsInfo] = useState([
95 | {
96 | title: "TV",
97 | image:
98 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/16498-8jpFCOcDmneX.jpg",
99 | },
100 | {
101 | title: "OVA",
102 | image:
103 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/21087-sHb9zUZFsHe1.jpg",
104 | },
105 | {
106 | title: "TV SHORT",
107 | image:
108 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/101922-YfZhKBUDDS6L.jpg",
109 | },
110 | {
111 | title: "ONA",
112 | image:
113 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/11757-TlEEV9weG4Ag.jpg",
114 | },
115 | {
116 | title: "MOVIE",
117 | image:
118 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/113415-jQBSkxWAAk83.jpg",
119 | },
120 | {
121 | title: "SPECIAL",
122 | image:
123 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/11757-TlEEV9weG4Ag.jpg",
124 | },
125 | {
126 | title: "MUSIC",
127 | image:
128 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/11757-TlEEV9weG4Ag.jpg",
129 | },
130 | ]);
131 | const [seasonsInfo, setSeasonsInfo] = useState([
132 | {
133 | title: "WINTER",
134 | image:
135 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/101759-MhlCoeqnODso.jpg",
136 | },
137 | {
138 | title: "SPRING",
139 | image:
140 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/5114-q0V5URebphSG.jpg",
141 | },
142 | {
143 | title: "SUMMER",
144 | image:
145 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20997-nE9VyUa08vS0.jpg",
146 | },
147 | {
148 | title: "FALL",
149 | image:
150 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/97940-1URQdQ4U1a0b.jpg",
151 | },
152 | ]);
153 | const [statusesInfo, setStatusesInfo] = useState([
154 | {
155 | title: "RELEASING",
156 | image:
157 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/21-wf37VakJmZqs.jpg",
158 | },
159 | {
160 | title: "FINISHED",
161 | image:
162 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/20-HHxhPj5JD13a.jpg",
163 | },
164 | {
165 | title: "CANCELLED",
166 | image:
167 | "https://s4.anilist.co/file/anilistcdn/media/anime/banner/11757-TlEEV9weG4Ag.jpg",
168 | },
169 | ]);
170 | const navigate = useNavigate();
171 | return (
172 | <>
173 |
174 | Genres
175 |
176 | {genresinfo.map((genreinfo) => {
177 | return (
178 |
{
181 | navigate(
182 | "/filtered/genre/" + e.target.innerText.toLowerCase(),
183 | {
184 | state: { type: "genre", value: e.target.innerText },
185 | }
186 | );
187 | }}
188 | className="genre"
189 | style={{
190 | backgroundImage: `linear-gradient( rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4) ),url(${genreinfo.image})`,
191 | }}
192 | >
193 | {genreinfo.title}
194 |
195 | );
196 | })}
197 |
198 | Formats
199 |
200 | {formatsInfo.map((formatInfo) => {
201 | return (
202 |
{
204 | navigate(
205 | "/filtered/format/" +
206 | e.target.innerText.replaceAll(" ", "_").toLowerCase(),
207 | {
208 | state: {
209 | type: "format",
210 | value: e.target.innerText.replaceAll(" ", "_"),
211 | },
212 | }
213 | );
214 | }}
215 | key={uuidv4()}
216 | className="format"
217 | style={{
218 | backgroundImage: `linear-gradient( rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4) ),url(${formatInfo.image})`,
219 | }}
220 | >
221 | {formatInfo.title}
222 |
223 | );
224 | })}
225 |
226 | Seasons
227 |
228 | {seasonsInfo.map((seasonInfo) => {
229 | return (
230 |
{
233 | navigate(
234 | "/filtered/season/" + e.target.innerText.toLowerCase(),
235 | {
236 | state: { type: "season", value: e.target.innerText },
237 | }
238 | );
239 | }}
240 | style={{
241 | backgroundImage: `linear-gradient( rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4) ), url(${seasonInfo.image})`,
242 | }}
243 | className="season"
244 | >
245 | {seasonInfo.title}
246 |
247 | );
248 | })}
249 |
250 | Status
251 |
252 | {statusesInfo.map((statusInfo) => {
253 | return (
254 |
{
257 | navigate(
258 | "/filtered/status/" +
259 | e.target.innerText.replaceAll(" ", "_").toLowerCase(),
260 | {
261 | state: {
262 | type: "status",
263 | value: e.target.innerText.replaceAll(" ", "_"),
264 | },
265 | }
266 | );
267 | }}
268 | style={{
269 | backgroundImage: `linear-gradient( rgba(0, 0, 0, 0.4), rgba(0, 0, 0, 0.4) ), url(${statusInfo.image})`,
270 | }}
271 | className="status"
272 | >
273 | {statusInfo.title}
274 |
275 | );
276 | })}
277 |
278 | >
279 | );
280 | };
281 | export default Genres;
282 |
--------------------------------------------------------------------------------
/client/src/components/Pages/AnimePlayerPage.jsx:
--------------------------------------------------------------------------------
1 | import {
2 | CalendarOutlined,
3 | OrderedListOutlined,
4 | PlayCircleOutlined,
5 | StarFilled,
6 | } from "@ant-design/icons";
7 | import { faFileCirclePlus } from "@fortawesome/free-solid-svg-icons";
8 | import { FontAwesomeIcon } from "@fortawesome/react-fontawesome";
9 | import axios from "axios";
10 | import React, { useContext, useEffect, useState } from "react";
11 | import { useParams } from "react-router-dom";
12 | import ScrollToTop from "react-scroll-to-top";
13 | import TextTruncate from "react-text-truncate";
14 | import { RWebShare } from "react-web-share";
15 | import { v4 as uuidv4 } from "uuid";
16 | import { GlobalContext } from "../../App";
17 | import CarouselRenderer from "../Layouts/CarouselRenderer";
18 | import VerticalCarousel from "../Layouts/VerticalCarousel";
19 | import AnimePlayer from "../Players/AnimePlayer";
20 | import AnimeSection from "../Sections/AnimeSection";
21 | import Navbar from "../Sections/Navbar";
22 | import "./AnimePlayerPage.css";
23 | const AnimePlayerPage = () => {
24 | const SharedState = useContext(GlobalContext);
25 |
26 | const { id } = useParams();
27 | const [adaptation, setAdaptation] = useState(null);
28 | const [description, setDescription] = useState(null);
29 | const [anime, setAnime] = useState(null);
30 | const [selectedOption, setSelectedOption] = useState(1);
31 | const [currentStreamUrl, setCurrentStreamUrl] = useState(null);
32 | const [currentId, setCurrentId] = useState("");
33 | const epArray = [];
34 | const [ep, setEp] = useState(null);
35 | const baseURL = process.env.REACT_APP_CONSUMET_API_URL;
36 | const animeProvider = process.env.REACT_APP_CONSUMET_PROVIDER;
37 | async function fetchVideoById(url) {
38 | return await axios.get(url).then(({ data }) => {
39 |
40 | setCurrentStreamUrl([data.sources[0].url, data.sources[1].url]);
41 | });
42 | }
43 |
44 | const cleanDescription = (description) => {
45 | setDescription(
46 | description
47 | .substring(
48 | 0,
49 | description.indexOf("(") === -1
50 | ? description.length
51 | : description.indexOf("(")
52 | )
53 | .replaceAll(/<\/?[\w\s]*>|<.+[\W]>/g, "")
54 | );
55 | };
56 | const initialFetch = async () => {
57 | SharedState.setVideoIsLoading(true);
58 |
59 | return await axios
60 | .get(`${baseURL}/meta/anilist/info/${id}?provider=${animeProvider}`)
61 | .then(({ data }) => {
62 | console.log(data);
63 | setAnime(data);
64 | setCurrentId(data.episodes[selectedOption - 1].id);
65 | for (let i = 1; i <= data.episodes.length; i++) {
66 | epArray.push(i);
67 | }
68 | setEp(epArray);
69 | let adaptation = "";
70 | for (let i = 0; i < data.relations.length; i++) {
71 | if (data.relations[i].relationType === "ADAPTATION") {
72 | adaptation =
73 | data.relations[i].title.english || data.relations[i].title.romaji;
74 | }
75 | }
76 | setAdaptation(adaptation);
77 | cleanDescription(data.description);
78 | })
79 | .finally(() => SharedState.setVideoIsLoading(false));
80 | };
81 |
82 | useEffect(() => {
83 | console.log("hi");
84 | initialFetch();
85 | }, [id]);
86 |
87 | useEffect(() => {
88 | if (currentId !== "")
89 | fetchVideoById(
90 | `${baseURL}/meta/anilist/watch/${currentId}?provider=${animeProvider}`
91 | );
92 | }, [currentId]);
93 | useEffect(() => {
94 | if (anime) setCurrentId(anime.episodes[selectedOption - 1].id);
95 | }, [selectedOption, anime]);
96 | return (
97 | <>
98 |
99 | {currentStreamUrl !== null && (
100 | <>
101 |
102 |
109 |
110 |
{anime.title.english}
111 |
112 |
113 | {anime.genres.join(" • ")}
114 |
115 |
116 |
117 |
118 | TV Show
119 |
120 |
121 | Rating: {" "}
122 | {anime.rating / 10}
123 |
124 |
125 | Total Ep: {anime.episodes.length}
126 |
127 |
128 | {anime.releaseDate}
129 |
130 |
131 |
152 |
153 |
154 |
161 |
162 |
170 |
Share
171 |
172 |
173 |
174 |
175 |
180 | Watchlist
181 |
182 |
183 |
Synopsis
184 |
185 |
189 |
190 |
191 |
192 |
193 | Studios:
194 |
195 | {anime.studios.join(", ")}
196 |
197 |
198 | {adaptation && (
199 |
200 | Adapation:
201 | {adaptation}
202 |
203 | )}
204 |
205 | Status:
206 | {anime.status}
207 |
208 |
209 |
210 |
211 |
212 | {anime.recommendations.length > 0 && (
213 |
217 | )}
218 |
219 |
220 | {anime.recommendations && (
221 |
226 | )}
227 |
232 | >
233 | )}
234 |
246 | >
247 | );
248 | };
249 | export default AnimePlayerPage;
250 |
--------------------------------------------------------------------------------