├── 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 |

Watchlist

7 |

8 | Please login to use watchlist(but login not yet implemented xD) 9 |

10 | 11 | ); 12 | }; 13 | export default Watchlist; 14 | -------------------------------------------------------------------------------- /client/src/components/Players/TrailerPlayer.jsx: -------------------------------------------------------------------------------- 1 | import ModalVideo from "react-modal-video"; 2 | import "react-modal-video/scss/modal-video.scss"; 3 | const TrailerPlayer = ({ trailerId, isPlaying, setIsPlaying }) => { 4 | return ( 5 | 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 |
{ 14 | e.preventDefault(); 15 | setTrailerId(trailerVideoId); 16 | setIsPlaying(true); 17 | }} 18 | className="upcomingcard-wrapper" 19 | > 20 |
26 | 27 | { 29 | e.preventDefault(); 30 | }} 31 | href="/" 32 | className="upcomingcard-title" 33 | > 34 | 35 | 36 |
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 |
21 | 22 | 27 | 28 | 33 | 34 | 39 | 40 | 45 | 46 | 47 |
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 |
59 | 66 |
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 |
44 | 45 | 50 | 51 | 56 |
57 |
58 | 63 | Remember me 64 |
65 | Forgot your password? 66 |
67 | 68 |
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 | { 97 | setTime((prevTime) => prevTime - 15); 98 | }} 99 | x="0" 100 | y="0" 101 | height={37} 102 | width={37} 103 | style={{ padding: 3 }} 104 | fill={"white"} 105 | version="1" 106 | viewBox="0 0 512 512" 107 | > 108 | 112 | 116 | 117 | - 10s 118 | 119 | 120 | 121 | { 123 | setTime((prevTime) => prevTime + 15); 124 | }} 125 | height={37} 126 | width={37} 127 | fill={"white"} 128 | style={{ padding: 3 }} 129 | version="1" 130 | viewBox="0 0 512 512" 131 | > 132 | 136 | 140 | 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 |
103 | 108 |
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 |
132 |
133 | {ep.map((ep) => { 134 | return ( 135 |
{ 143 | setSelectedOption(Number(e.target.innerText)); 144 | }} 145 | > 146 | {ep} 147 |
148 | ); 149 | })} 150 |
151 |
152 |
153 |
154 | 161 |
162 | 168 | 169 | 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 | --------------------------------------------------------------------------------