├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── _redirects
├── assets
│ ├── 404.png
│ └── logo.svg
├── favicon.ico
├── index.html
├── logo192.png
├── logo512.png
├── manifest.json
└── robots.txt
└── src
├── App.js
├── components
├── Home
│ ├── AnimeCards.js
│ ├── Carousel.js
│ └── WatchingEpisodes.js
├── Navigation
│ ├── Nav.js
│ └── Search.js
├── VideoPlayer
│ └── VideoPlayer.js
├── WatchAnime
│ └── ServersList.js
└── skeletons
│ ├── AnimeCardsSkeleton.js
│ ├── AnimeDetailsSkeleton.js
│ ├── CarouselSkeleton.js
│ ├── SearchResultsSkeleton.js
│ └── WatchAnimeSkeleton.js
├── hooks
├── searchQueryStrings.js
└── useWindowDimensions.js
├── index.js
├── pages
├── AnimeDetails.js
├── FavouriteAnime.js
├── Home.js
├── MalAnimeDetails.js
├── PopularAnime.js
├── PopularMovies.js
├── SearchResults.js
├── Top100Anime.js
├── TrendingAnime.js
├── WatchAnime.js
└── WatchAnimeV2.js
└── styles
└── globalStyles.js
/.gitignore:
--------------------------------------------------------------------------------
1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2 |
3 | # dependencies
4 | /node_modules
5 | /.pnp
6 | .pnp.js
7 |
8 | # testing
9 | /coverage
10 |
11 | # production
12 | /build
13 |
14 | # misc
15 | .DS_Store
16 | .env
17 | .env.local
18 | .env.development.local
19 | .env.test.local
20 | .env.production.local
21 |
22 | npm-debug.log*
23 | yarn-debug.log*
24 | yarn-error.log*
25 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 |
3 | # Welcome to Miyou Github Repository
4 |
5 | 
6 |
7 | ## Miyou is an online anime streaming site built using React.js. [Visit Here](https://www.miyou.me/).
8 |
9 | ### Next.js implementation of this web-app can be found [here.](https://github.com/reyangurjar/Miyou.me) Hosted [URL](https://miyou-topaz.vercel.app/)
10 |
11 | ### `npm i` (Install the dependencies)
12 |
13 | ### `npm start` (Run locally)
14 |
15 | ### Set up your .env file
16 |
17 | First add a .env file in your root directory. Then add
18 | `REACT_APP_BACKEND_URL='https://miyou-api.cyclic.app/' REACT_APP_BASE_URL='https://graphql.anilist.co'`
19 |
20 | ## Todo
21 |
22 | ### Changing the search page
23 |
24 | Add the ability to search genres or search from multiple genres. And sort by Trending or Popularity or User Preference. Something like this.
25 | 
26 | If anybody wants to implement just the design part in CSS, you are more than welcome to contribute. It would be a big help.
27 |
28 | ## Contributing
29 |
30 | Contributions are always welcome.
31 |
32 | You can contribute to this project by forking the project, adding or making changes, and submitting a pull request.
33 |
34 | ## Disclaimer
35 |
36 | I'm privating the backend repo because a lot of people are copying the project without giving any credits and pretending to be the real one. You can only test this on your localhost and cannot host it. It will give you CORS error.
37 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "miyou-frontend",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "@testing-library/jest-dom": "^5.16.4",
7 | "@testing-library/react": "^13.1.1",
8 | "@testing-library/user-event": "^13.5.0",
9 | "axios": "^0.26.1",
10 | "hls.js": "^1.1.5",
11 | "plyr": "^3.7.2",
12 | "react": "^18.0.0",
13 | "react-dom": "^18.0.0",
14 | "react-hot-toast": "^2.4.0",
15 | "react-icons": "^4.3.1",
16 | "react-loading-skeleton": "^3.1.0",
17 | "react-router-dom": "^6.3.0",
18 | "react-scripts": "5.0.1",
19 | "styled-components": "^5.3.5",
20 | "swiper": "^8.1.3",
21 | "web-vitals": "^2.1.4"
22 | },
23 | "scripts": {
24 | "start": "react-scripts start",
25 | "build": "react-scripts build",
26 | "test": "react-scripts test",
27 | "eject": "react-scripts eject"
28 | },
29 | "eslintConfig": {
30 | "extends": [
31 | "react-app",
32 | "react-app/jest"
33 | ]
34 | },
35 | "browserslist": {
36 | "production": [
37 | ">0.2%",
38 | "not dead",
39 | "not op_mini all"
40 | ],
41 | "development": [
42 | "last 1 chrome version",
43 | "last 1 firefox version",
44 | "last 1 safari version"
45 | ]
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/public/_redirects:
--------------------------------------------------------------------------------
1 | /* /index.html 200
--------------------------------------------------------------------------------
/public/assets/404.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/debsishu/Miyou/f88fba1375d1c6782c21bc40c9f9f6b2f7bd86a3/public/assets/404.png
--------------------------------------------------------------------------------
/public/assets/logo.svg:
--------------------------------------------------------------------------------
1 |
4 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/debsishu/Miyou/f88fba1375d1c6782c21bc40c9f9f6b2f7bd86a3/public/favicon.ico
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
12 |
16 |
20 |
24 |
25 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
42 |
43 |
44 |
45 |
49 |
50 |
59 | Miyou - Watch Anime Free Online With English Sub and Dub
60 |
61 |
62 |
63 |
64 |
65 |
75 |
76 |
77 |
--------------------------------------------------------------------------------
/public/logo192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/debsishu/Miyou/f88fba1375d1c6782c21bc40c9f9f6b2f7bd86a3/public/logo192.png
--------------------------------------------------------------------------------
/public/logo512.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/debsishu/Miyou/f88fba1375d1c6782c21bc40c9f9f6b2f7bd86a3/public/logo512.png
--------------------------------------------------------------------------------
/public/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "short_name": "Miyou",
3 | "name": "Let's Watch Anime",
4 | "icons": [
5 | {
6 | "src": "favicon.ico",
7 | "sizes": "64x64 32x32 24x24 16x16",
8 | "type": "image/x-icon"
9 | },
10 | {
11 | "src": "logo192.png",
12 | "type": "image/png",
13 | "sizes": "192x192"
14 | },
15 | {
16 | "src": "logo512.png",
17 | "type": "image/png",
18 | "sizes": "512x512"
19 | }
20 | ],
21 | "start_url": ".",
22 | "display": "standalone",
23 | "theme_color": "#ffffff",
24 | "background_color": "#1A1927"
25 | }
26 |
--------------------------------------------------------------------------------
/public/robots.txt:
--------------------------------------------------------------------------------
1 | # https://www.robotstxt.org/robotstxt.html
2 | User-agent: *
3 | Disallow:
4 |
--------------------------------------------------------------------------------
/src/App.js:
--------------------------------------------------------------------------------
1 | import { BrowserRouter as Router, Routes, Route } from "react-router-dom";
2 | import { Toaster } from "react-hot-toast";
3 | import Nav from "./components/Navigation/Nav";
4 | import AnimeDetails from "./pages/AnimeDetails";
5 | import FavouriteAnime from "./pages/FavouriteAnime";
6 | import Home from "./pages/Home";
7 | import MalAnimeDetails from "./pages/MalAnimeDetails";
8 | import PopularAnime from "./pages/PopularAnime";
9 | import PopularMovies from "./pages/PopularMovies";
10 | import SearchResults from "./pages/SearchResults";
11 | import Top100Anime from "./pages/Top100Anime";
12 | import TrendingAnime from "./pages/TrendingAnime";
13 | import WatchAnime from "./pages/WatchAnime";
14 | import WatchAnimeV2 from "./pages/WatchAnimeV2";
15 | import GlobalStyle from "./styles/globalStyles";
16 |
17 | function App() {
18 | return (
19 |
20 |
21 |
22 |
23 | } />
24 | } />
25 | } />
26 | } />
27 | } />
28 | } />
29 | } />
30 | } />
31 | } />
32 | } />
33 | } />
34 |
35 |
45 |
46 | );
47 | }
48 |
49 | export default App;
50 |
--------------------------------------------------------------------------------
/src/components/Home/AnimeCards.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import { Swiper, SwiperSlide } from "swiper/react";
6 | import { Scrollbar } from "swiper";
7 | import AnimeCardsSkeleton from "../../components/skeletons/AnimeCardsSkeleton";
8 |
9 | import "swiper/css";
10 | import "swiper/css/scrollbar";
11 |
12 | function AnimeCards(props) {
13 | const [data, setData] = useState([]);
14 | const [loading, setLoading] = useState(true);
15 | useEffect(() => {
16 | getData();
17 | }, []);
18 |
19 | async function getData() {
20 | let res = await axios.get(
21 | `${process.env.REACT_APP_BACKEND_URL}api/getmalinfo?criteria=${props.criteria}&count=${props.count}`
22 | );
23 |
24 | setLoading(false);
25 | setData(res.data.data);
26 | }
27 | return (
28 |
29 | {loading &&
}
30 | {!loading && (
31 |
62 | {data.map((item, i) => (
63 |
64 |
65 |
66 |
67 |
68 | {item.node.title}
69 |
70 |
71 | ))}
72 |
73 | )}
74 |
75 | );
76 | }
77 |
78 | const Wrapper = styled.div`
79 | img {
80 | width: 160px;
81 | height: 235px;
82 | border-radius: 0.5rem;
83 | margin-bottom: 0.3rem;
84 | object-fit: cover;
85 | @media screen and (max-width: 600px) {
86 | width: 120px;
87 | height: 180px;
88 | }
89 | @media screen and (max-width: 400px) {
90 | width: 100px;
91 | height: 160px;
92 | }
93 | }
94 |
95 | p {
96 | color: white;
97 | font-size: 1rem;
98 | font-weight: 400;
99 | }
100 | `;
101 |
102 | export default AnimeCards;
103 |
--------------------------------------------------------------------------------
/src/components/Home/Carousel.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | import { Link } from "react-router-dom";
5 | import { Swiper, SwiperSlide } from "swiper/react";
6 | import { Navigation, Pagination, Scrollbar, A11y, Autoplay } from "swiper";
7 | import { BsFillPlayFill } from "react-icons/bs";
8 | import { IconContext } from "react-icons";
9 | import useWindowDimensions from "../../hooks/useWindowDimensions";
10 |
11 | import "swiper/css";
12 | import "swiper/css/navigation";
13 | import "swiper/css/pagination";
14 | import "swiper/css/scrollbar";
15 |
16 | function Carousel({ images }) {
17 | const { height, width } = useWindowDimensions();
18 |
19 | return (
20 |
21 |
33 | {images.map(
34 | (item, index) =>
35 | item.bannerImage !== null && (
36 |
37 |
38 | {width <= 600 && (
39 |
44 | )}
45 | {width > 600 && (
46 |
47 | )}
48 |
49 |
50 | {width <= 600 && (
51 |
52 | {item.title.english !== null
53 | ? item.title.english.length > 35
54 | ? item.title.english.substring(0, 35) + "..."
55 | : item.title.english
56 | : item.title.romaji.length > 35
57 | ? item.title.romaji.substring(0, 35) + "..."
58 | : item.title.romaji}
59 |
60 | )}
61 | {width > 600 && (
62 |
63 | {item.title.english !== null
64 | ? item.title.english
65 | : item.title.romaji}
66 |
67 | )}
68 |
69 | {width <= 600 && (
70 |
79 |
82 |
83 | )}
84 | {width > 600 && (
85 |
95 |
99 |
100 | )}
101 |
102 |
103 |
104 |
105 | )
106 | )}
107 |
108 |
109 | );
110 | }
111 |
112 | const bannerImgStyle = {
113 | width: "100%",
114 | height: "330px",
115 | objectFit: "cover",
116 | borderRadius: "0.7rem",
117 | };
118 |
119 | const bannerImageStyleMobile = {
120 | width: "100%",
121 | height: "250px",
122 | objectFit: "cover",
123 | borderRadius: "0.5rem",
124 | };
125 |
126 | const Container = styled.div`
127 | position: relative;
128 | `;
129 |
130 | const Wrapper = styled.div`
131 | position: absolute;
132 | width: 100%;
133 | height: 50%;
134 | bottom: 0;
135 | left: 0;
136 | margin-bottom: 0.2rem;
137 | background: linear-gradient(
138 | 180deg,
139 | rgba(27, 26, 39, 0) 0%,
140 | rgba(38, 36, 65, 0.3) 30%,
141 | rgba(0, 0, 0, 1) 100%
142 | );
143 | background-blend-mode: multiply;
144 | border-radius: 0.7rem;
145 |
146 | @media screen and (max-width: 600px) {
147 | border-radius: 0.5rem;
148 | background: linear-gradient(
149 | 180deg,
150 | rgba(27, 26, 39, 0) 0%,
151 | rgba(38, 36, 65, 0.3) 30%,
152 | rgba(0, 0, 0, 1) 100%
153 | );
154 | }
155 | `;
156 |
157 | const Content = styled.div`
158 | display: flex;
159 | justify-content: space-between;
160 | align-items: center;
161 | color: white;
162 | margin: 6rem 2.3rem 0 2.3rem;
163 |
164 | p {
165 | font-weight: 600;
166 | font-size: 1.6rem;
167 | }
168 | @media screen and (max-width: 600px) {
169 | align-items: flex-start;
170 | margin: 3rem 1.3rem 0 1.3rem;
171 | p {
172 | margin-top: 0.5rem;
173 | font-size: 1.4rem;
174 | }
175 | }
176 | `;
177 |
178 | const Button = styled(Link)`
179 | color: white;
180 | font-weight: 500;
181 | text-decoration: none;
182 | background-color: #7676ff;
183 | outline: none;
184 | border: none;
185 | padding: 0.75rem 1.3rem 0.75rem 1.3rem;
186 | border-radius: 0.4rem;
187 | cursor: pointer;
188 | font-size: 0.9rem;
189 |
190 | @media screen and (max-width: 600px) {
191 | border-radius: 50%;
192 | padding: 1.1rem;
193 | margin-top: 2.8rem;
194 | }
195 | `;
196 |
197 | export default Carousel;
198 |
--------------------------------------------------------------------------------
/src/components/Home/WatchingEpisodes.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import { Swiper, SwiperSlide } from "swiper/react";
6 | import { Scrollbar } from "swiper";
7 | import AnimeCardsSkeleton from "../skeletons/AnimeCardsSkeleton";
8 | import { IoClose } from "react-icons/io5";
9 | import { IconContext } from "react-icons";
10 | import { searchWatchedId } from "../../hooks/searchQueryStrings";
11 |
12 | import "swiper/css";
13 | import "swiper/css/scrollbar";
14 |
15 | function WatchingEpisodes() {
16 | const [data, setData] = useState([]);
17 | const [localData, setLocalData] = useState([]);
18 | const [loading, setLoading] = useState(true);
19 | const [change, setChange] = useState(false);
20 |
21 | useEffect(() => {
22 | getAnimeData();
23 | }, []);
24 |
25 | async function getAnimeData() {
26 | setLoading(true);
27 | let data = localStorage.getItem("Watching");
28 | data = JSON.parse(data);
29 | setLocalData(data);
30 | let ids = [];
31 | for (let i = 0; i < data.length; i++) {
32 | ids.push(data[i].malId);
33 | }
34 | let result = await axios({
35 | url: process.env.REACT_APP_BASE_URL,
36 | method: "POST",
37 | headers: {
38 | "Content-Type": "application/json",
39 | Accept: "application/json",
40 | },
41 | data: {
42 | query: searchWatchedId,
43 | variables: {
44 | ids,
45 | },
46 | },
47 | }).catch((err) => {
48 | console.log(err);
49 | });
50 | let output = [];
51 | for (let i = 0; i < data.length; i++) {
52 | for (let j = 0; j < result.data.data.Page.media.length; j++) {
53 | if (
54 | parseInt(result.data.data.Page.media[j].idMal) ===
55 | parseInt(data[i].malId)
56 | ) {
57 | output.push(result.data.data.Page.media[j]);
58 | }
59 | }
60 | }
61 | setData(output);
62 | setLoading(false);
63 | }
64 |
65 | function removeAnime(index) {
66 | let lsData = localStorage.getItem("Watching");
67 | lsData = JSON.parse(lsData);
68 | lsData.splice(index, 1);
69 | setLocalData(lsData);
70 | lsData = JSON.stringify(lsData);
71 | localStorage.setItem("Watching", lsData);
72 | data.splice(index, 1);
73 | setChange(!change);
74 | }
75 |
76 | return (
77 |
78 | {loading &&
}
79 | {!loading && (
80 |
111 | {data.map((item, i) => (
112 |
113 |
114 |
123 |
131 |
132 |
133 |
136 |
137 |
138 |
139 | {item.title.english !== null
140 | ? item.title.english
141 | : item.title.userPreferred}
142 | {localData[i].isDub ? " (Dub)" : " (Sub)"}
143 |
144 |
145 | {"Episode - " + localData[i].episode}
146 |
147 |
148 |
149 | ))}
150 |
151 | )}
152 |
153 | );
154 | }
155 |
156 | const Wrapper = styled.div`
157 | position: relative;
158 |
159 | .closeButton {
160 | position: absolute;
161 | cursor: pointer;
162 | outline: none;
163 | border: none;
164 | padding: 0.5rem;
165 | background-color: rgba(0, 0, 0, 0.7);
166 | border-radius: 0.5rem 0 0.2rem 0;
167 | }
168 | img {
169 | width: 160px;
170 | height: 235px;
171 | border-radius: 0.5rem;
172 | margin-bottom: 0.3rem;
173 | object-fit: cover;
174 | @media screen and (max-width: 600px) {
175 | width: 120px;
176 | height: 180px;
177 | }
178 | @media screen and (max-width: 400px) {
179 | width: 100px;
180 | height: 160px;
181 | }
182 | }
183 |
184 | p {
185 | color: white;
186 | font-size: 1rem;
187 | font-weight: 400;
188 | @media screen and (max-width: 600px) {
189 | max-width: 120px;
190 | }
191 | @media screen and (max-width: 400px) {
192 | max-width: 100px;
193 | }
194 | }
195 |
196 | .episodeNumber {
197 | font-size: 0.9rem;
198 | font-weight: 300;
199 | color: #b5c3de;
200 | }
201 | `;
202 |
203 | export default WatchingEpisodes;
204 |
--------------------------------------------------------------------------------
/src/components/Navigation/Nav.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import styled from "styled-components";
4 | import { FiSearch } from "react-icons/fi";
5 | import { IconContext } from "react-icons";
6 | import Search from "./Search";
7 | import useWindowDimensions from "../../hooks/useWindowDimensions";
8 |
9 | function Nav() {
10 | const [isActive, setIsActive] = useState(false);
11 | const { height, width } = useWindowDimensions();
12 |
13 | return (
14 |
15 |
16 |
17 |
18 |
19 |
20 | Trending
21 | Popular
22 | Favourites
23 | Top Movies
24 |
25 |
26 | {width <= 600 && (
27 |
37 |
40 |
41 | )}
42 | {width > 600 && (
43 |
53 |
57 |
58 | )}
59 |
60 | {isActive &&
}
61 | {isActive &&
}
62 |
63 | );
64 | }
65 |
66 | const Shadow = styled.div`
67 | z-index: 9;
68 | position: absolute;
69 | top: 0;
70 | height: 100vh;
71 | width: 98.6vw;
72 | background-color: rgba(0, 0, 0, 0.6);
73 | overflow: hidden;
74 | `;
75 |
76 | const Button = styled.button`
77 | color: white;
78 | font-family: "Lexend", sans-serif;
79 | font-weight: 500;
80 | background-color: #7676ff;
81 | outline: none;
82 | border: none;
83 | padding: 0.7rem 1.6rem 0.7rem 1.6rem;
84 | border-radius: 0.4rem;
85 | cursor: pointer;
86 | font-size: 0.9rem;
87 | FiSearch {
88 | font-size: 1rem;
89 | }
90 | white-space: nowrap;
91 | @media screen and (max-width: 600px) {
92 | padding: 0.5rem;
93 | padding-right: 0;
94 | background-color: transparent;
95 | }
96 | `;
97 |
98 | const Links = styled(Link)`
99 | color: white;
100 | font-weight: 400;
101 | text-decoration: none;
102 | margin: 0rem 1.3rem 0 1.3rem;
103 | `;
104 |
105 | const NavBar = styled.nav`
106 | display: flex;
107 | justify-content: space-between;
108 | align-items: center;
109 | margin: 1.8rem 5rem 0 5rem;
110 | @media screen and (max-width: 600px) {
111 | margin: 1rem 2rem;
112 | margin-top: 1rem;
113 | img {
114 | height: 1.7rem;
115 | }
116 | .nav-links {
117 | display: none;
118 | }
119 | }
120 | `;
121 |
122 | export default Nav;
123 |
--------------------------------------------------------------------------------
/src/components/Navigation/Search.js:
--------------------------------------------------------------------------------
1 | import React, { useState } from "react";
2 | import { Link, useNavigate } from "react-router-dom";
3 | import styled from "styled-components";
4 | import { FiSearch } from "react-icons/fi";
5 | import { CgClose } from "react-icons/cg";
6 | import { IconContext } from "react-icons";
7 | import useWindowDimensions from "../../hooks/useWindowDimensions";
8 |
9 | function Search({ isActive, setIsActive }) {
10 | const [title, setTitle] = useState("");
11 | const { width, height } = useWindowDimensions();
12 | const navigate = useNavigate();
13 |
14 | function searchEnter() {
15 | if (title !== "") {
16 | setIsActive(false);
17 | navigate("/search/" + title);
18 | }
19 | }
20 |
21 | return (
22 |
23 |
24 |
35 |
38 |
39 |
40 |
41 |
42 |
43 | {width <= 600 && (
44 |
55 |
56 |
57 | )}
58 | {width > 600 && (
59 |
70 |
71 |
72 | )}
73 |
74 | {
81 | setTitle(e.target.value);
82 | }}
83 | onKeyPress={(event) => {
84 | if (event.key === "Enter") {
85 | searchEnter();
86 | }
87 | }}
88 | />
89 |
90 | {title !== "" && (
91 |
{
94 | setIsActive(false);
95 | }}
96 | >
97 | Search
98 |
99 | )}
100 | {title === "" &&
}
101 |
102 |
103 |
104 | );
105 | }
106 |
107 | const Content = styled.div`
108 | background-color: #1a1927;
109 | padding: 0rem 4rem 3.8rem 4rem;
110 | border-radius: 0.5rem;
111 |
112 | .main {
113 | background-color: white;
114 | padding: 0.5rem;
115 | padding-left: 1.2rem;
116 | padding-right: 0.8rem;
117 | border-radius: 0.4rem;
118 | display: flex;
119 | justify-content: space-between;
120 | align-items: center;
121 | }
122 | div {
123 | display: flex;
124 | align-items: center;
125 | width: 100%;
126 | }
127 | input {
128 | outline: none;
129 | border: none;
130 | padding: 1rem 2rem 1rem 0.5rem;
131 | font-size: 1rem;
132 | font-family: "Lexend", sans-serif;
133 | font-weight: 400;
134 | width: 100%;
135 | background-color: white;
136 | color: black;
137 | }
138 | ::placeholder {
139 | color: #c5c5c5;
140 | }
141 |
142 | @media screen and (max-width: 600px) {
143 | padding: 1rem;
144 |
145 | .main {
146 | flex-direction: column;
147 | background-color: transparent;
148 | padding: 0;
149 | padding-left: 0;
150 | padding-right: 0;
151 | }
152 |
153 | div {
154 | background-color: white;
155 | padding: 0.3rem 1rem;
156 | border-radius: 0.3rem;
157 | width: 100%;
158 | margin-bottom: 1rem;
159 | }
160 | }
161 |
162 | button {
163 | outline: none;
164 | border: none;
165 | background-color: #7676ff;
166 | color: white;
167 | font-size: 1rem;
168 | padding: 0.9rem 2rem;
169 | text-decoration: none;
170 | border-radius: 0.3rem;
171 | text-align: center;
172 | font-family: "Lexend", sans-serif;
173 | font-weight: 500;
174 | cursor: pointer;
175 |
176 | @media screen and (max-width: 600px) {
177 | display: block;
178 | width: 100%;
179 | font-size: 1.2rem;
180 | }
181 | }
182 | `;
183 |
184 | const CloseButton = styled.div`
185 | display: flex;
186 | justify-content: flex-end;
187 | button {
188 | background-color: transparent;
189 | outline: none;
190 | border: none;
191 | padding: 1rem;
192 | cursor: pointer;
193 | }
194 | `;
195 |
196 | const SearchButton = styled(Link)`
197 | background-color: #7676ff;
198 | color: white;
199 | padding: 0.9rem 2rem;
200 | text-decoration: none;
201 | border-radius: 0.3rem;
202 | text-align: center;
203 | font-weight: 500;
204 |
205 | @media screen and (max-width: 600px) {
206 | display: block;
207 | width: 100%;
208 | font-size: 1.2rem;
209 | }
210 | `;
211 |
212 | const Wrapper = styled.div`
213 | background-color: #1a1927;
214 | position: absolute;
215 | z-index: 10;
216 | top: 30%;
217 | left: 50%;
218 | transform: translate(-50%, -50%);
219 | width: 80%;
220 | border: 1px solid #35334e;
221 | border-radius: 0.5rem;
222 |
223 | @media screen and (max-width: 600px) {
224 | width: 93%;
225 | }
226 | `;
227 |
228 | export default Search;
229 |
--------------------------------------------------------------------------------
/src/components/VideoPlayer/VideoPlayer.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { HiOutlineSwitchHorizontal } from "react-icons/hi";
3 | import { BsSkipEnd } from "react-icons/bs";
4 | import { MdPlayDisabled, MdPlayArrow } from "react-icons/md";
5 | import { IconContext } from "react-icons";
6 | import styled from "styled-components";
7 | import useWindowDimensions from "../../hooks/useWindowDimensions";
8 | import { useNavigate, useParams } from "react-router-dom";
9 | import Hls from "hls.js";
10 | import plyr from "plyr";
11 | import "plyr/dist/plyr.css";
12 | import toast from "react-hot-toast";
13 |
14 | function VideoPlayer({
15 | sources,
16 | internalPlayer,
17 | setInternalPlayer,
18 | title,
19 | type,
20 | banner,
21 | totalEpisodes,
22 | currentEpisode,
23 | malId
24 | }) {
25 | const { width } = useWindowDimensions();
26 | const navigate = useNavigate();
27 | const slug = useParams().slug;
28 | const episode = useParams().episode;
29 |
30 | let src = sources;
31 | const [player, setPlayer] = useState(null);
32 | const [autoPlay, setAutoplay] = useState(false);
33 |
34 | function skipIntro() {
35 | player.forward(85);
36 | }
37 |
38 | function updateAutoplay(data) {
39 | toast.success(`Autoplay ${data ? "Enabled" : "Disabled"}`, {
40 | position: "top-center",
41 | });
42 | localStorage.setItem("autoplay", data);
43 | setAutoplay(data);
44 | }
45 |
46 | useEffect(() => {
47 | if (!localStorage.getItem("autoplay")) {
48 | localStorage.setItem("autoplay", false);
49 | } else {
50 | setAutoplay(localStorage.getItem("autoplay") === "true");
51 | }
52 | const video = document.getElementById("player");
53 | let flag = true;
54 |
55 | const defaultOptions = {
56 | captions: { active: true, update: true, language: "en" },
57 | controls:
58 | width > 600
59 | ? [
60 | "play-large",
61 | "rewind",
62 | "play",
63 | "fast-forward",
64 | "progress",
65 | "current-time",
66 | "duration",
67 | "mute",
68 | "volume",
69 | "settings",
70 | "fullscreen",
71 | ]
72 | : [
73 | "play-large",
74 | "rewind",
75 | "play",
76 | "fast-forward",
77 | "progress",
78 | "current-time",
79 | "duration",
80 | "settings",
81 | "fullscreen",
82 | ],
83 | };
84 |
85 | if (type === "mp4") {
86 | video.removeAttribute("crossorigin");
87 | const player = new plyr(video, defaultOptions);
88 | player.source = {
89 | type: "video",
90 | title: "Example title",
91 | poster: banner,
92 | sources: [
93 | {
94 | src: src,
95 | type: "video/mp4",
96 | },
97 | ],
98 | };
99 | }
100 | if (Hls.isSupported()) {
101 | const hls = new Hls();
102 | hls.loadSource(src);
103 | hls.attachMedia(video);
104 |
105 | hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
106 | const availableQualities = hls.levels.map((l) => l.height);
107 | availableQualities.unshift(0);
108 | defaultOptions.quality = {
109 | default: 0,
110 | options: availableQualities,
111 | forced: true,
112 | onChange: (e) => updateQuality(e),
113 | };
114 | hls.on(Hls.Events.LEVEL_SWITCHED, function(event, data) {
115 | var span = document.querySelector(
116 | ".plyr__menu__container [data-plyr='quality'][value='0'] span"
117 | );
118 | if (hls.autoLevelEnabled) {
119 | span.innerHTML = `Auto (${hls.levels[data.level].height}p)`;
120 | } else {
121 | span.innerHTML = `Auto`;
122 | }
123 | });
124 | let player = new plyr(video, defaultOptions);
125 | setPlayer(new plyr(video, defaultOptions));
126 | let plyer;
127 | var button = document.createElement("button");
128 | button.classList.add("skip-button");
129 | button.innerHTML = "Skip Intro";
130 | button.addEventListener("click", function() {
131 | player.forward(85);
132 | });
133 | player.on("ready", () => {
134 | plyer = document.querySelector(".plyr__controls");
135 | document
136 | .querySelector(".plyr__video-wrapper")
137 | .addEventListener("click", () => {
138 | let regexp = /android|iphone|kindle|ipad/i;
139 | if (
140 | regexp.test(navigator.userAgent) &&
141 | getComputedStyle(player.elements.controls).opacity === "1"
142 | ) {
143 | player.togglePlay();
144 | }
145 | });
146 |
147 | var tapedTwice = false;
148 | function tapHandler(event) {
149 | if (!tapedTwice) {
150 | tapedTwice = true;
151 | setTimeout(function() {
152 | tapedTwice = false;
153 | }, 300);
154 | return false;
155 | }
156 | event.preventDefault();
157 | //action on double tap goes below
158 | player.fullscreen.toggle();
159 | }
160 | document
161 | .querySelector(".plyr__video-wrapper")
162 | .addEventListener("touchstart", tapHandler);
163 | });
164 |
165 | player.on("enterfullscreen", (event) => {
166 | plyer.appendChild(button);
167 | window.screen.orientation.lock("landscape");
168 | });
169 |
170 | player.on("exitfullscreen", (event) => {
171 | document.querySelector(".skip-button").remove();
172 | window.screen.orientation.lock("portrait");
173 | });
174 |
175 | player.on("timeupdate", function(e) {
176 | var time = player.currentTime,
177 | lastTime = localStorage.getItem(title);
178 | if (time > lastTime) {
179 | localStorage.setItem(title, Math.round(player.currentTime));
180 | }
181 | });
182 |
183 | player.on("ended", function() {
184 | localStorage.removeItem(title);
185 | console.log(currentEpisode + " _ " + totalEpisodes);
186 | console.log(episode + " _ " + slug);
187 |
188 | if (
189 | localStorage.getItem("autoplay") === "true" &&
190 | parseInt(currentEpisode) !== parseInt(totalEpisodes)
191 | ) {
192 | navigate(`/play/${malId}/${slug}/${parseInt(episode) + 1}`);
193 | }
194 | });
195 |
196 | player.on("play", function(e) {
197 | if (flag) {
198 | var lastTime = localStorage.getItem(title);
199 | if (lastTime !== null && lastTime > player.currentTime) {
200 | player.forward(parseInt(lastTime));
201 | }
202 | flag = false;
203 | }
204 | });
205 |
206 | player.on("seeking", (event) => {
207 | localStorage.setItem(title, Math.round(player.currentTime));
208 | });
209 | });
210 | hls.attachMedia(video);
211 | window.hls = hls;
212 |
213 | function updateQuality(newQuality) {
214 | if (newQuality === 0) {
215 | window.hls.currentLevel = -1;
216 | console.log("Auto quality selection");
217 | } else {
218 | window.hls.levels.forEach((level, levelIndex) => {
219 | if (level.height === newQuality) {
220 | console.log("Found quality match with " + newQuality);
221 | window.hls.currentLevel = levelIndex;
222 | }
223 | });
224 | }
225 | }
226 | } else if (video.canPlayType("application/vnd.apple.mpegurl")) {
227 | video.src = src;
228 | const defaultOptions = {
229 | captions: { active: true, update: true, language: "en" },
230 | controls: [
231 | "play-large",
232 | "rewind",
233 | "play",
234 | "fast-forward",
235 | "progress",
236 | "current-time",
237 | "duration",
238 | "mute",
239 | "volume",
240 | "settings",
241 | "fullscreen",
242 | ],
243 | };
244 | let player = new plyr(video, defaultOptions);
245 | setPlayer(new plyr(video, defaultOptions));
246 | let plyer;
247 | var button = document.createElement("button");
248 | button.classList.add("skip-button");
249 | button.innerHTML = "Skip Intro";
250 | button.addEventListener("click", function() {
251 | player.forward(85);
252 | });
253 | player.on("ready", () => {
254 | plyer = document.querySelector(".plyr__controls");
255 | });
256 |
257 | player.on("enterfullscreen", (event) => {
258 | plyer.appendChild(button);
259 | window.screen.orientation.lock("landscape");
260 | });
261 |
262 | player.on("exitfullscreen", (event) => {
263 | document.querySelector(".skip-button").remove();
264 | window.screen.orientation.lock("portrait");
265 | });
266 |
267 | player.on("timeupdate", function(e) {
268 | var time = player.currentTime,
269 | lastTime = localStorage.getItem(title);
270 | if (time > lastTime) {
271 | localStorage.setItem(title, Math.round(player.currentTime));
272 | }
273 | if (player.ended) {
274 | localStorage.removeItem(title);
275 | }
276 | });
277 |
278 | player.on("play", function(e) {
279 | if (flag) {
280 | var lastTime = localStorage.getItem(title);
281 | if (lastTime !== null && lastTime > player.currentTime) {
282 | player.forward(parseInt(lastTime));
283 | }
284 | flag = false;
285 | }
286 | });
287 |
288 | player.on("seeking", (event) => {
289 | localStorage.setItem(title, Math.round(player.currentTime));
290 | });
291 | } else {
292 | const player = new plyr(video, defaultOptions);
293 | player.source = {
294 | type: "video",
295 | title: "Example title",
296 | sources: [
297 | {
298 | src: src,
299 | type: "video/mp4",
300 | },
301 | ],
302 | };
303 | }
304 | }, []);
305 |
306 | return (
307 |
313 |
314 |
323 | {internalPlayer && Internal Player
}
324 |
325 | {autoPlay && (
326 |
327 |
333 |
334 | )}
335 | {!autoPlay && (
336 |
337 |
343 |
344 | )}
345 |
346 |
357 |
358 |
359 |
362 |
363 |
364 |
365 |
366 |
375 |
376 | );
377 | }
378 |
379 | const Conttainer = styled.div`
380 | display: flex;
381 | justify-content: space-between;
382 | align-items: center;
383 | background-color: #242235;
384 | padding: 0.5rem 1rem;
385 | border-radius: 0.5rem 0.5rem 0 0;
386 | border: 1px solid #393653;
387 | margin-top: 1rem;
388 | border-bottom: none;
389 | font-weight: 400;
390 | p {
391 | color: white;
392 | }
393 |
394 | button {
395 | outline: none;
396 | border: none;
397 | background: transparent;
398 | margin-left: 1rem;
399 | cursor: pointer;
400 | }
401 |
402 | .tooltip {
403 | position: relative;
404 | display: inline-block;
405 | border-bottom: 1px dotted black;
406 | }
407 |
408 | .tooltip .tooltiptext {
409 | visibility: hidden;
410 | width: 120px;
411 | background-color: rgba(0, 0, 0, 0.8);
412 | color: #fff;
413 | text-align: center;
414 | border-radius: 6px;
415 | padding: 5px 5px;
416 | position: absolute;
417 | z-index: 1;
418 | bottom: 150%;
419 | left: 50%;
420 | margin-left: -60px;
421 | opacity: 0;
422 | transition: opacity 0.2s;
423 | }
424 |
425 | .tooltip .tooltiptext::after {
426 | content: "";
427 | position: absolute;
428 | top: 100%;
429 | left: 50%;
430 | margin-left: -5px;
431 | border-width: 5px;
432 | border-style: solid;
433 | border-color: black transparent transparent transparent;
434 | }
435 |
436 | .tooltip:hover .tooltiptext {
437 | visibility: visible;
438 | opacity: 1;
439 | }
440 | `;
441 |
442 | export default VideoPlayer;
443 |
--------------------------------------------------------------------------------
/src/components/WatchAnime/ServersList.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 |
4 | function ServersList({ episodeLinks, currentServer, setCurrentServer }) {
5 | return (
6 |
7 |
8 |
9 |
Servers List
10 |
11 | {episodeLinks[0].vidstreaming !== undefined && (
12 |
26 | )}
27 | {episodeLinks[0].streamsb !== undefined && (
28 |
42 | )}
43 | {episodeLinks[0].gogoserver !== undefined && (
44 |
58 | )}
59 | {episodeLinks[0].xstreamcdn !== undefined && (
60 |
74 | )}
75 | {episodeLinks[0].mixdrop !== undefined && (
76 |
90 | )}
91 | {episodeLinks[0].mp4upload !== undefined && (
92 |
106 | )}
107 | {episodeLinks[0].doodstream !== undefined && (
108 |
122 | )}
123 |
124 |
125 |
126 |
127 | );
128 | }
129 |
130 | const ServerWrapper = styled.div`
131 | p {
132 | color: white;
133 | font-size: 1.4rem;
134 | font-family: "Gilroy-Medium", sans-serif;
135 | text-decoration: underline;
136 | }
137 |
138 | .server-wrapper {
139 | padding: 1rem;
140 | background-color: #1a1927;
141 | border: 1px solid #272639;
142 | border-radius: 0.4rem;
143 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
144 | }
145 |
146 | .serverlinks {
147 | display: grid;
148 | grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
149 | grid-gap: 1rem;
150 | grid-row-gap: 1rem;
151 | justify-content: space-between;
152 | margin-top: 1rem;
153 | }
154 |
155 | button {
156 | cursor: pointer;
157 | outline: none;
158 | color: white;
159 | background-color: #242235;
160 | border: 1px solid #393653;
161 | padding: 0.7rem 1.5rem;
162 | border-radius: 0.4rem;
163 | font-family: "Gilroy-Medium", sans-serif;
164 | font-size: 0.9rem;
165 | }
166 |
167 | @media screen and (max-width: 600px) {
168 | p {
169 | font-size: 1.2rem;
170 | }
171 | }
172 | `;
173 |
174 | export default ServersList;
175 |
--------------------------------------------------------------------------------
/src/components/skeletons/AnimeCardsSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Swiper, SwiperSlide } from "swiper/react";
3 | import { Scrollbar } from "swiper";
4 | import Skeleton from "react-loading-skeleton";
5 | import useWindowDimensions from "../../hooks/useWindowDimensions";
6 |
7 | import "swiper/css";
8 | import "swiper/css/scrollbar";
9 |
10 | function AnimeCardsSkeleton() {
11 | const { height, width } = useWindowDimensions();
12 |
13 | return (
14 |
19 |
50 | {[...Array(8)].map((x, i) => (
51 |
52 |
63 |
72 |
73 | ))}
74 |
75 |
76 | );
77 | }
78 |
79 | export default AnimeCardsSkeleton;
80 |
--------------------------------------------------------------------------------
/src/components/skeletons/AnimeDetailsSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Skeleton from "react-loading-skeleton";
4 | import useWindowDimensions from "../../hooks/useWindowDimensions";
5 |
6 | function AnimeDetailsSkeleton() {
7 | const { width, height } = useWindowDimensions();
8 |
9 | return (
10 |
11 |
20 |
21 |
29 |
30 |
31 | );
32 | }
33 |
34 | const ContentWrapper = styled.div`
35 | padding: 0 3rem 0 3rem;
36 | @media screen and (max-width: 600px) {
37 | padding: 1rem;
38 | }
39 | `;
40 |
41 | const Content = styled.div`
42 | margin: 2rem 5rem 2rem 5rem;
43 | position: relative;
44 | @media screen and (max-width: 600px) {
45 | margin: 1rem;
46 | }
47 | `;
48 |
49 | export default AnimeDetailsSkeleton;
50 |
--------------------------------------------------------------------------------
/src/components/skeletons/CarouselSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Skeleton from "react-loading-skeleton";
3 | import "react-loading-skeleton/dist/skeleton.css";
4 | import useWindowDimensions from "../../hooks/useWindowDimensions";
5 |
6 | function CarouselSkeleton() {
7 | const { height, width } = useWindowDimensions();
8 |
9 | return (
10 |
15 |
21 |
22 | );
23 | }
24 |
25 | export default CarouselSkeleton;
26 |
--------------------------------------------------------------------------------
/src/components/skeletons/SearchResultsSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Skeleton from "react-loading-skeleton";
4 | import useWindowDimensions from "../../hooks/useWindowDimensions";
5 |
6 | function SearchResultsSkeleton({ name }) {
7 | const { height, width } = useWindowDimensions();
8 |
9 | return (
10 |
11 |
12 | {name === undefined ? "Search" : name} Results
13 |
14 |
15 | {[...Array(40)].map((x, i) => (
16 |
17 |
24 |
33 |
34 | ))}
35 |
36 |
37 | );
38 | }
39 |
40 | const Heading = styled.p`
41 | font-size: 1.8rem;
42 | color: white;
43 | font-weight: 200;
44 | margin-bottom: 2rem;
45 | span {
46 | font-weight: 600;
47 | }
48 |
49 | @media screen and (max-width: 600px) {
50 | font-size: 1.6rem;
51 | margin-bottom: 1rem;
52 | }
53 | `;
54 |
55 | const Parent = styled.div`
56 | margin: 2rem 5rem 2rem 5rem;
57 | @media screen and (max-width: 600px) {
58 | margin: 1rem;
59 | }
60 | `;
61 |
62 | const CardWrapper = styled.div`
63 | display: grid;
64 | grid-template-columns: repeat(auto-fill, 160px);
65 | grid-gap: 1rem;
66 | grid-row-gap: 1.5rem;
67 | justify-content: space-between;
68 |
69 | @media screen and (max-width: 600px) {
70 | grid-template-columns: repeat(auto-fill, 120px);
71 | grid-gap: 0rem;
72 | grid-row-gap: 1.5rem;
73 | }
74 |
75 | @media screen and (max-width: 400px) {
76 | grid-template-columns: repeat(auto-fill, 110px);
77 | grid-gap: 0rem;
78 | grid-row-gap: 1.5rem;
79 | }
80 |
81 | @media screen and (max-width: 380px) {
82 | grid-template-columns: repeat(auto-fill, 100px);
83 | grid-gap: 0rem;
84 | grid-row-gap: 1.5rem;
85 | }
86 | `;
87 |
88 | export default SearchResultsSkeleton;
89 |
--------------------------------------------------------------------------------
/src/components/skeletons/WatchAnimeSkeleton.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import styled from "styled-components";
3 | import Skeleton from "react-loading-skeleton";
4 | import useWindowDimensions from "../../hooks/useWindowDimensions";
5 |
6 | function WatchAnimeSkeleton() {
7 | const { width, height } = useWindowDimensions();
8 |
9 | return (
10 |
11 |
12 |
13 |
21 |
29 |
37 |
38 |
39 | Episodes
40 |
41 | {[...Array(20)].map((x, i) => (
42 |
43 |
50 |
51 | ))}
52 |
53 |
54 |
55 |
56 | );
57 | }
58 |
59 | const Episodes = styled.div`
60 | display: grid;
61 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
62 | grid-gap: 0.8rem;
63 | grid-row-gap: 1rem;
64 | justify-content: space-between;
65 |
66 | @media screen and (max-width: 600px) {
67 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
68 | }
69 | `;
70 |
71 | const EpisodesWrapper = styled.div`
72 | margin-top: 1rem;
73 | border: 1px solid #272639;
74 | border-radius: 0.4rem;
75 | padding: 1rem;
76 |
77 | p {
78 | font-size: 1.3rem;
79 | color: white;
80 | font-weight: 400;
81 | margin-bottom: 1rem;
82 | }
83 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
84 | `;
85 |
86 | const Wrapper = styled.div`
87 | margin: 0.5rem 5rem 2rem 5rem;
88 | display: grid;
89 | grid-template-columns: 70% calc(30% - 1rem);
90 | gap: 1rem;
91 | align-items: flex-start;
92 | @media screen and (max-width: 900px) {
93 | grid-template-columns: auto;
94 | }
95 | @media screen and (max-width: 600px) {
96 | margin: 1rem;
97 | }
98 | `;
99 |
100 | export default WatchAnimeSkeleton;
101 |
--------------------------------------------------------------------------------
/src/hooks/searchQueryStrings.js:
--------------------------------------------------------------------------------
1 | export let PopularAnimeQuery = `
2 | query($perPage: Int, $page: Int) {
3 | Page(page: $page, perPage: $perPage) {
4 | pageInfo {
5 | total
6 | perPage
7 | currentPage
8 | lastPage
9 | hasNextPage
10 | }
11 | media(sort : POPULARITY_DESC, type: ANIME) {
12 | idMal
13 | title {
14 | romaji
15 | english
16 | userPreferred
17 | }
18 | bannerImage
19 | coverImage {
20 | medium
21 | large
22 | extraLarge
23 | }
24 | description
25 | episodes
26 |
27 | }
28 | }
29 | }
30 | `;
31 |
32 | export let TrendingAnimeQuery = `
33 | query($perPage: Int, $page: Int) {
34 | Page(page: $page, perPage: $perPage) {
35 | pageInfo {
36 | total
37 | perPage
38 | currentPage
39 | lastPage
40 | hasNextPage
41 | }
42 | media (sort :TRENDING_DESC, type : ANIME){
43 | idMal
44 | title {
45 | romaji
46 | english
47 | userPreferred
48 | }
49 | bannerImage
50 | coverImage {
51 | medium
52 | large
53 | extraLarge
54 | }
55 | description
56 | episodes
57 |
58 | }
59 | }
60 | }
61 | `;
62 |
63 | export let top100AnimeQuery = `
64 | query($perPage: Int, $page: Int) {
65 | Page(page: $page, perPage: $perPage) {
66 | pageInfo {
67 | total
68 | perPage
69 | currentPage
70 | lastPage
71 | hasNextPage
72 | }
73 | media (sort :SCORE_DESC, type : ANIME){
74 | idMal
75 | title {
76 | romaji
77 | english
78 | userPreferred
79 | }
80 | bannerImage
81 | coverImage {
82 | medium
83 | large
84 | extraLarge
85 | }
86 | description
87 | episodes
88 |
89 | }
90 | }
91 | }
92 | `;
93 |
94 | export let favouritesAnimeQuery = `
95 | query($perPage: Int, $page: Int) {
96 | Page(page: $page, perPage: $perPage) {
97 | pageInfo {
98 | total
99 | perPage
100 | currentPage
101 | lastPage
102 | hasNextPage
103 | }
104 | media(sort: FAVOURITES_DESC, type: ANIME) {
105 | idMal
106 | title {
107 | romaji
108 | english
109 | userPreferred
110 | }
111 | bannerImage
112 | coverImage {
113 | medium
114 | large
115 | extraLarge
116 | }
117 | description
118 | episodes
119 | }
120 | }
121 | }
122 | `;
123 |
124 | export let searchAnimeQuery = `
125 | query($search: String) {
126 | Page(page: 1, perPage: 100) {
127 | media(search: $search, type: ANIME, sort: POPULARITY_DESC) {
128 | idMal
129 | title {
130 | romaji
131 | english
132 | native
133 | userPreferred
134 | }
135 | genres
136 | bannerImage
137 | coverImage {
138 | extraLarge
139 | large
140 | }
141 | }
142 | }
143 | }
144 | `;
145 |
146 | export let searchByIdQuery = `
147 | query($id: Int) {
148 | Media(idMal: $id, type: ANIME){
149 | title {
150 | romaji
151 | english
152 | native
153 | userPreferred
154 | }
155 | type
156 | status
157 | genres
158 | description
159 | startDate {
160 | year
161 | month
162 | day
163 | }
164 | endDate {
165 | year
166 | month
167 | day
168 | }
169 | averageScore
170 | bannerImage
171 | coverImage {
172 | extraLarge
173 | large
174 | }
175 | }
176 | }
177 | `;
178 |
179 | export let searchWatchedId = `
180 | query($ids: [Int]) {
181 | Page(page: 1, perPage: 100) {
182 | media(idMal_in: $ids, type: ANIME, sort: SEARCH_MATCH){
183 | title {
184 | romaji
185 | userPreferred
186 | english
187 | }
188 | coverImage {
189 | large
190 | extraLarge
191 | }
192 | idMal
193 | }
194 | }
195 | }
196 | `;
197 |
198 | export let searchByAniIdQuery = `
199 | query($id: Int) {
200 | Media(id: $id){
201 | title {
202 | romaji
203 | english
204 | native
205 | userPreferred
206 | }
207 | type
208 | status
209 | genres
210 | description
211 | startDate {
212 | year
213 | month
214 | day
215 | }
216 | endDate {
217 | year
218 | month
219 | day
220 | }
221 | averageScore
222 | bannerImage
223 | coverImage {
224 | extraLarge
225 | large
226 | }
227 | }
228 | }
229 | `;
230 |
--------------------------------------------------------------------------------
/src/hooks/useWindowDimensions.js:
--------------------------------------------------------------------------------
1 | import { useState, useEffect } from "react";
2 |
3 | function getWindowDimensions() {
4 | const { innerWidth: width, innerHeight: height } = window;
5 | return {
6 | width,
7 | height,
8 | };
9 | }
10 |
11 | export default function useWindowDimensions() {
12 | const [windowDimensions, setWindowDimensions] = useState(
13 | getWindowDimensions()
14 | );
15 |
16 | useEffect(() => {
17 | function handleResize() {
18 | setWindowDimensions(getWindowDimensions());
19 | }
20 |
21 | window.addEventListener("resize", handleResize);
22 | return () => window.removeEventListener("resize", handleResize);
23 | }, []);
24 |
25 | return windowDimensions;
26 | }
27 |
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom/client';
3 | import App from './App';
4 |
5 | const root = ReactDOM.createRoot(document.getElementById('root'));
6 | root.render(
7 | //
8 |
9 | //
10 | );
11 |
12 | // If you want to start measuring performance in your app, pass a function
13 | // to log results (for example: reportWebVitals(console.log))
14 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals
15 |
--------------------------------------------------------------------------------
/src/pages/AnimeDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { useParams, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import AnimeDetailsSkeleton from "../components/skeletons/AnimeDetailsSkeleton";
6 | import useWindowDimensions from "../hooks/useWindowDimensions";
7 |
8 | function AnimeDetails() {
9 | let slug = useParams().slug;
10 | const [animeDetails, setAnimeDetails] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 | const [expanded, setExpanded] = useState(false);
13 | const { height, width } = useWindowDimensions();
14 | const [localStorageDetails, setLocalStorageDetails] = useState(0);
15 |
16 | useEffect(() => {
17 | getAnimeDetails();
18 | }, []);
19 |
20 | async function getAnimeDetails() {
21 | setLoading(true);
22 | setExpanded(false);
23 | window.scrollTo(0, 0);
24 | let res = await axios.get(
25 | `${process.env.REACT_APP_BACKEND_URL}api/getanime?link=/category/${slug}`
26 | );
27 | setLoading(false);
28 | setAnimeDetails(res.data);
29 | getLocalStorage(res.data);
30 | document.title = res.data[0].gogoResponse.title + " - Miyou"
31 | }
32 |
33 | function readMoreHandler() {
34 | setExpanded(!expanded);
35 | }
36 |
37 | function getLocalStorage(animeDetails) {
38 | if (localStorage.getItem("Animes")) {
39 | let lsData = localStorage.getItem("Animes");
40 | lsData = JSON.parse(lsData);
41 |
42 | let index = lsData.Names.findIndex(
43 | (i) => i.name === animeDetails[0].gogoResponse.title
44 | );
45 |
46 | if (index !== -1) {
47 | setLocalStorageDetails(lsData.Names[index].currentEpisode);
48 | }
49 | }
50 | }
51 |
52 | return (
53 |
54 | {loading &&
}
55 | {!loading && (
56 |
57 | {animeDetails.length > 0 && (
58 |
59 |
68 |
69 |
70 |
71 | {localStorageDetails === 0 && (
72 |
77 | )}
78 | {localStorageDetails !== 0 && (
79 |
89 | )}
90 |
91 |
92 |
{animeDetails[0].gogoResponse.title}
93 |
94 | Type:
95 | {animeDetails[0].gogoResponse.type.replace("Type:", "")}
96 |
97 | {width <= 600 && expanded && (
98 |
99 | Plot Summery:
100 | {animeDetails[0].gogoResponse.description.replace(
101 | "Plot Summary:",
102 | ""
103 | )}
104 |
107 |
108 | )}
109 | {width <= 600 && !expanded && (
110 |
111 | Plot Summery:
112 | {animeDetails[0].gogoResponse.description
113 | .replace("Plot Summary:", "")
114 | .substring(0, 200) + "... "}
115 |
118 |
119 | )}
120 | {width > 600 && (
121 |
122 | Plot Summery:
123 | {animeDetails[0].gogoResponse.description.replace(
124 | "Plot Summary:",
125 | ""
126 | )}
127 |
128 | )}
129 |
130 |
131 | Genre:
132 | {animeDetails[0].gogoResponse.genre.replace("Genre:", "")}
133 |
134 |
135 | Released:
136 | {animeDetails[0].gogoResponse.released.replace(
137 | "Released:",
138 | ""
139 | )}
140 |
141 |
142 | Status:
143 | {animeDetails[0].gogoResponse.status.replace("Status:", "")}
144 |
145 |
146 | Number of Episodes:
147 | {animeDetails[0].gogoResponse.numOfEpisodes}
148 |
149 |
150 |
151 |
152 | Episodes
153 | {width <= 600 && (
154 |
155 | {animeDetails[0].gogoResponse.episodes.map((item, i) => (
156 |
164 | {i + 1}
165 |
166 | ))}
167 |
168 | )}
169 | {width > 600 && (
170 |
171 | {animeDetails[0].gogoResponse.episodes.map((item, i) => (
172 |
180 | Episode {i + 1}
181 |
182 | ))}
183 |
184 | )}
185 |
186 |
187 | )}
188 |
189 | )}
190 |
191 | );
192 | }
193 |
194 | const Episode = styled.div`
195 | margin: 0 4rem 0 4rem;
196 | padding: 2rem;
197 | outline: 2px solid #272639;
198 | border-radius: 0.5rem;
199 | color: white;
200 |
201 | h2 {
202 | font-size: 1.4rem;
203 | text-decoration: underline;
204 | margin-bottom: 1rem;
205 | }
206 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
207 |
208 | @media screen and (max-width: 600px) {
209 | padding: 1rem;
210 | margin: 1rem;
211 | }
212 | `;
213 |
214 | const Episodes = styled.div`
215 | display: grid;
216 | grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
217 | grid-gap: 1rem;
218 | grid-row-gap: 1rem;
219 | justify-content: space-between;
220 |
221 | @media screen and (max-width: 600px) {
222 | grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr));
223 | }
224 | `;
225 |
226 | const EpisodeLink = styled(Link)`
227 | text-align: center;
228 | color: white;
229 | text-decoration: none;
230 | background-color: #242235;
231 | padding: 0.9rem 2rem;
232 | font-family: "Gilroy-Medium", sans-serif;
233 | border-radius: 0.5rem;
234 | border: 1px solid #393653;
235 | transition: 0.2s;
236 |
237 | :hover {
238 | background-color: #7676ff;
239 | }
240 |
241 | @media screen and (max-width: 600px) {
242 | padding: 1rem;
243 | border-radius: 0.3rem;
244 | font-family: "Gilroy-Bold", sans-serif;
245 | }
246 | `;
247 |
248 | const Content = styled.div`
249 | margin: 2rem 5rem 2rem 5rem;
250 | position: relative;
251 |
252 | @media screen and (max-width: 600px) {
253 | margin: 1rem;
254 | }
255 | `;
256 |
257 | const ContentWrapper = styled.div`
258 | padding: 0 3rem 0 3rem;
259 | display: flex;
260 |
261 | div > * {
262 | margin-bottom: 0.6rem;
263 | }
264 |
265 | div {
266 | margin: 1rem;
267 | font-size: 1rem;
268 | color: #b5c3de;
269 | font-family: "Gilroy-Regular", sans-serif;
270 | span {
271 | font-family: "Gilroy-Bold", sans-serif;
272 | color: white;
273 | }
274 | p {
275 | text-align: justify;
276 | }
277 | h1 {
278 | font-family: "Gilroy-Bold", sans-serif;
279 | font-weight: normal;
280 | color: white;
281 | }
282 | button {
283 | display: none;
284 | }
285 | }
286 |
287 | @media screen and (max-width: 600px) {
288 | display: flex;
289 | flex-direction: column-reverse;
290 | padding: 0;
291 | div {
292 | margin: 1rem;
293 | margin-bottom: 0.2rem;
294 | h1 {
295 | font-size: 1.6rem;
296 | }
297 | p {
298 | font-size: 1rem;
299 | }
300 | button {
301 | display: inline;
302 | border: none;
303 | outline: none;
304 | background-color: transparent;
305 | text-decoration: underline;
306 | font-family: "Gilroy-Bold", sans-serif;
307 | font-size: 1rem;
308 | color: white;
309 | }
310 | }
311 | }
312 | `;
313 |
314 | const Poster = styled.div`
315 | display: flex;
316 | flex-direction: column;
317 | img {
318 | width: 220px;
319 | height: 300px;
320 | border-radius: 0.5rem;
321 | margin-bottom: 2rem;
322 | position: relative;
323 | top: -20%;
324 | filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
325 | }
326 | @media screen and (max-width: 600px) {
327 | img {
328 | display: none;
329 | }
330 | }
331 | `;
332 |
333 | const Button = styled(Link)`
334 | font-size: 1.3rem;
335 | padding: 1rem 3.4rem;
336 | text-align: center;
337 | text-decoration: none;
338 | color: white;
339 | background-color: #7676ff;
340 | font-family: "Gilroy-Bold", sans-serif;
341 | border-radius: 0.4rem;
342 | position: relative;
343 | top: -25%;
344 | white-space: nowrap;
345 |
346 | @media screen and (max-width: 600px) {
347 | display: block;
348 | width: 100%;
349 | }
350 | `;
351 |
352 | const Banner = styled.img`
353 | width: 100%;
354 | height: 20rem;
355 | object-fit: cover;
356 | border-radius: 0.7rem;
357 |
358 | @media screen and (max-width: 600px) {
359 | height: 13rem;
360 | border-radius: 0.5rem;
361 | }
362 | `;
363 |
364 | export default AnimeDetails;
365 |
--------------------------------------------------------------------------------
/src/pages/FavouriteAnime.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useParams } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 | import { favouritesAnimeQuery } from "../hooks/searchQueryStrings";
7 |
8 | function FavouriteAnime() {
9 | let page = useParams().page;
10 | const [animeDetails, setAnimeDetails] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | getAnime();
15 | }, [page]);
16 |
17 | async function getAnime() {
18 | setLoading(true);
19 | window.scrollTo(0, 0);
20 | const res = await axios({
21 | url: process.env.REACT_APP_BASE_URL,
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | Accept: "application/json",
26 | },
27 | data: {
28 | query: favouritesAnimeQuery,
29 | variables: {
30 | page: page,
31 | perPage: 50,
32 | },
33 | },
34 | }).catch((err) => {
35 | console.log(err);
36 | });
37 | setLoading(false);
38 | setAnimeDetails(res.data.data.Page.media);
39 | document.title = "Favorite Anime - Miyou";
40 | }
41 | return (
42 |
43 | {loading &&
}
44 | {!loading && (
45 |
46 |
47 | Favourite Anime Results
48 |
49 |
50 | {animeDetails.map((item, i) => (
51 |
52 |
53 |
54 | {item.title.english !== null
55 | ? item.title.english
56 | : item.title.userPreferred}
57 |
58 |
59 | ))}
60 |
61 |
62 | {page > 1 && (
63 |
64 | Previous
65 |
66 | )}
67 |
68 | Next
69 |
70 |
71 |
72 | )}
73 |
74 | );
75 | }
76 |
77 | const NavButtons = styled.div`
78 | margin-top: 2.5rem;
79 | margin-bottom: 2rem;
80 | display: flex;
81 | align-items: center;
82 | justify-content: center;
83 | gap: 1rem;
84 | `;
85 |
86 | const NavButton = styled(Link)`
87 | padding: 0.8rem 2rem;
88 | text-decoration: none;
89 | color: white;
90 | background-color: none;
91 | border: 2px solid #53507a;
92 | border-radius: 0.5rem;
93 | `;
94 |
95 | const Parent = styled.div`
96 | margin: 2rem 5rem 2rem 5rem;
97 | @media screen and (max-width: 600px) {
98 | margin: 1rem;
99 | }
100 | `;
101 |
102 | const CardWrapper = styled.div`
103 | display: grid;
104 | grid-template-columns: repeat(auto-fill, 160px);
105 | grid-gap: 1rem;
106 | grid-row-gap: 1.5rem;
107 | justify-content: space-between;
108 |
109 | @media screen and (max-width: 600px) {
110 | grid-template-columns: repeat(auto-fill, 120px);
111 | grid-gap: 0rem;
112 | grid-row-gap: 1.5rem;
113 | }
114 |
115 | @media screen and (max-width: 400px) {
116 | grid-template-columns: repeat(auto-fill, 110px);
117 | grid-gap: 0rem;
118 | grid-row-gap: 1.5rem;
119 | }
120 |
121 | @media screen and (max-width: 380px) {
122 | grid-template-columns: repeat(auto-fill, 100px);
123 | grid-gap: 0rem;
124 | grid-row-gap: 1.5rem;
125 | }
126 | `;
127 |
128 | const Links = styled(Link)`
129 | text-decoration: none;
130 | img {
131 | width: 160px;
132 | height: 235px;
133 | border-radius: 0.5rem;
134 | object-fit: cover;
135 | @media screen and (max-width: 600px) {
136 | width: 120px;
137 | height: 180px;
138 | border-radius: 0.3rem;
139 | }
140 | @media screen and (max-width: 400px) {
141 | width: 110px;
142 | height: 170px;
143 | }
144 | @media screen and (max-width: 380px) {
145 | width: 100px;
146 | height: 160px;
147 | }
148 | }
149 |
150 | p {
151 | color: white;
152 | font-size: 1rem;
153 | font-weight: 400;
154 | text-decoration: none;
155 | max-width: 160px;
156 | @media screen and (max-width: 380px) {
157 | width: 100px;
158 | font-size: 0.9rem;
159 | }
160 | }
161 | `;
162 |
163 | const Heading = styled.p`
164 | font-size: 1.8rem;
165 | color: white;
166 | font-weight: 200;
167 | margin-bottom: 2rem;
168 | span {
169 | font-weight: 600;
170 | }
171 |
172 | @media screen and (max-width: 600px) {
173 | font-size: 1.6rem;
174 | margin-bottom: 1rem;
175 | }
176 | `;
177 |
178 | export default FavouriteAnime;
179 |
--------------------------------------------------------------------------------
/src/pages/Home.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import { Link } from "react-router-dom";
3 | import styled from "styled-components";
4 | import Carousel from "../components/Home/Carousel";
5 | import axios from "axios";
6 | import AnimeCards from "../components/Home/AnimeCards";
7 | import HomeSkeleton from "../components/skeletons/CarouselSkeleton";
8 | import useWindowDimensions from "../hooks/useWindowDimensions";
9 | import WatchingEpisodes from "../components/Home/WatchingEpisodes";
10 | import { TrendingAnimeQuery } from "../hooks/searchQueryStrings";
11 |
12 | function Home() {
13 | const [images, setImages] = useState([]);
14 | const [loading, setLoading] = useState(true);
15 | const { height, width } = useWindowDimensions();
16 |
17 | useEffect(() => {
18 | getImages();
19 | }, []);
20 |
21 | async function getImages() {
22 | window.scrollTo(0, 0);
23 | let result = await axios({
24 | url: process.env.REACT_APP_BASE_URL,
25 | method: "POST",
26 | headers: {
27 | "Content-Type": "application/json",
28 | Accept: "application/json",
29 | },
30 | data: {
31 | query: TrendingAnimeQuery,
32 | variables: {
33 | page: 1,
34 | perPage: 15,
35 | },
36 | },
37 | }).catch((err) => {
38 | console.log(err);
39 | });
40 | setImages(result.data.data.Page.media);
41 | setLoading(false);
42 | document.title = "Miyou - Watch Anime Free Online With English Sub and Dub";
43 | }
44 |
45 | function checkSize() {
46 | let lsData = localStorage.getItem("Watching");
47 | lsData = JSON.parse(lsData);
48 | if (lsData.length === 0) {
49 | return false;
50 | }
51 | return true;
52 | }
53 | return (
54 |
55 |
56 |
57 | Recommended to you
58 |
59 | {loading && }
60 | {!loading && }
61 | {localStorage.getItem("Watching") && checkSize() && (
62 |
63 |
64 |
65 | Continue Watching
66 |
67 |
68 |
69 |
70 | )}
71 |
72 |
73 |
74 | Trending Now
75 |
76 | View All
77 |
78 |
79 |
80 |
81 |
82 |
83 | All Time Popular
84 |
85 | View All
86 |
87 |
88 |
89 |
90 |
91 |
92 | Top 100 Anime
93 |
94 | View All
95 |
96 |
97 |
98 |
99 |
100 |
101 | All Time Favourite
102 |
103 | View All
104 |
105 |
106 |
107 |
108 |
109 |
110 | Popular Movies
111 |
112 | View All
113 |
114 |
115 |
116 |
117 |
118 | );
119 | }
120 |
121 | const Links = styled(Link)`
122 | color: white;
123 | font-size: 1.1rem;
124 | font-family: "Gilroy-Medium", sans-serif;
125 | @media screen and (max-width: 600px) {
126 | color: white;
127 | font-size: 1rem;
128 | font-family: "Gilroy-Medium", sans-serif;
129 | }
130 | `;
131 |
132 | const HomeDiv = styled.div`
133 | margin: 1.5rem 5rem 1rem 5rem;
134 | @media screen and (max-width: 600px) {
135 | margin: 1rem 1rem 0rem 1rem;
136 | }
137 | `;
138 |
139 | const HomeHeading = styled.p`
140 | font-size: 2.3rem;
141 | color: white;
142 | font-weight: 200;
143 |
144 | span {
145 | font-weight: 600;
146 | }
147 | margin-bottom: 1rem;
148 |
149 | @media screen and (max-width: 600px) {
150 | font-size: 1.7rem;
151 | }
152 | `;
153 |
154 | const Heading = styled.p`
155 | font-size: 1.8rem;
156 | color: white;
157 | font-weight: 200;
158 | margin-top: 1rem;
159 | span {
160 | font-weight: 600;
161 | }
162 |
163 | @media screen and (max-width: 600px) {
164 | font-size: 1.5rem;
165 | }
166 | `;
167 |
168 | const HeadingWrapper = styled.div`
169 | display: flex;
170 | justify-content: space-between;
171 | align-items: center;
172 | margin-bottom: 1rem;
173 | @media screen and (max-width: 600px) {
174 | margin-top: 1rem;
175 | }
176 | `;
177 |
178 | export default Home;
179 |
--------------------------------------------------------------------------------
/src/pages/MalAnimeDetails.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { useParams, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import AnimeDetailsSkeleton from "../components/skeletons/AnimeDetailsSkeleton";
6 | import useWindowDimensions from "../hooks/useWindowDimensions";
7 | import { searchByIdQuery } from "../hooks/searchQueryStrings";
8 |
9 | function MalAnimeDetails() {
10 | let id = useParams().id;
11 |
12 | const [loading, setLoading] = useState(true);
13 | const { width } = useWindowDimensions();
14 | const [anilistResponse, setAnilistResponse] = useState();
15 | const [malResponse, setMalResponse] = useState();
16 | const [expanded, setExpanded] = useState(false);
17 | const [dub, setDub] = useState(false);
18 | const [notAvailable, setNotAvailable] = useState(false);
19 |
20 | useEffect(() => {
21 | getInfo();
22 | }, []);
23 |
24 | function readMoreHandler() {
25 | setExpanded(!expanded);
26 | }
27 |
28 | async function getInfo() {
29 | if (id === "null") {
30 | setNotAvailable(true);
31 | return;
32 | }
33 | let aniRes = await axios({
34 | url: process.env.REACT_APP_BASE_URL,
35 | method: "POST",
36 | headers: {
37 | "Content-Type": "application/json",
38 | Accept: "application/json",
39 | },
40 | data: {
41 | query: searchByIdQuery,
42 | variables: {
43 | id,
44 | },
45 | },
46 | }).catch((err) => {
47 | console.log(err);
48 | });
49 | setAnilistResponse(aniRes.data.data.Media);
50 | let malRes = await axios
51 | .get(`${process.env.REACT_APP_BACKEND_URL}api/getidinfo?malId=${id}`)
52 | .catch((err) => {
53 | setNotAvailable(true);
54 | });
55 | setMalResponse(malRes.data);
56 | setLoading(false);
57 | }
58 |
59 | return (
60 |
61 | {notAvailable && (
62 |
63 |
64 | Oops! This Anime Is Not Available
65 |
66 | )}
67 | {loading && !notAvailable &&
}
68 | {!loading && !notAvailable && (
69 |
70 | {anilistResponse !== undefined && (
71 |
72 |
80 |
81 |
82 |
83 |
86 | {malResponse.isDub && (
87 |
93 | )}
94 |
95 |
96 |
{anilistResponse.title.userPreferred}
97 | {anilistResponse.title.english != null && (
98 |
{"English - " + anilistResponse.title.english}
99 | )}
100 |
101 | Type:
102 | {anilistResponse.type}
103 |
104 | {width <= 600 && expanded && (
105 |
106 | Plot Summery: ${anilistResponse.description}`,
109 | }}
110 | >
111 |
114 |
115 | )}
116 |
117 | {width <= 600 && !expanded && (
118 |
119 | Plot Summery:
120 | {anilistResponse.description.substring(0, 200) + "... "}
121 |
124 |
125 | )}
126 | {width > 600 && (
127 |
Plot Summery: " +
131 | anilistResponse.description,
132 | }}
133 | >
134 | )}
135 |
136 |
137 | Genre:
138 | {anilistResponse.genres.toString()}
139 |
140 |
141 | Released:
142 | {anilistResponse.startDate.year}
143 |
144 |
145 | Status:
146 | {anilistResponse.status}
147 |
148 |
149 | Number of Sub Episodes:
150 | {malResponse.subTotalEpisodes}
151 |
152 | {malResponse.isDub && (
153 |
154 | Number of Dub Episodes:
155 | {malResponse.dubTotalEpisodes}
156 |
157 | )}
158 |
159 |
160 |
161 |
162 | Episodes
163 | {malResponse.isDub && (
164 |
165 |
174 |
175 | )}
176 |
177 | {width > 600 && (
178 |
179 | {malResponse.isDub &&
180 | dub &&
181 | [...Array(malResponse.dubTotalEpisodes)].map((x, i) => (
182 |
185 | Episode {i + 1}
186 |
187 | ))}
188 |
189 | {!dub &&
190 | [...Array(malResponse.subTotalEpisodes)].map((x, i) => (
191 |
194 | Episode {i + 1}
195 |
196 | ))}
197 |
198 | )}
199 | {width <= 600 && (
200 |
201 | {malResponse.isDub &&
202 | dub &&
203 | [...Array(malResponse.dubTotalEpisodes)].map((x, i) => (
204 |
207 | {i + 1}
208 |
209 | ))}
210 |
211 | {!dub &&
212 | [...Array(malResponse.subTotalEpisodes)].map((x, i) => (
213 |
216 | {i + 1}
217 |
218 | ))}
219 |
220 | )}
221 |
222 |
223 | )}
224 |
225 | )}
226 |
227 | );
228 | }
229 |
230 | const NotAvailable = styled.div`
231 | display: flex;
232 | flex-direction: column;
233 | align-items: center;
234 | justify-content: center;
235 | margin-top: 5rem;
236 | img {
237 | width: 30rem;
238 | }
239 |
240 | h1 {
241 | margin-top: -2rem;
242 | font-weight: normal;
243 | font-weight: 600;
244 | }
245 |
246 | @media screen and (max-width: 600px) {
247 | img {
248 | width: 18rem;
249 | }
250 |
251 | h1 {
252 | font-size: 1.3rem;
253 | }
254 | }
255 | `;
256 |
257 | const DubContainer = styled.div`
258 | display: flex;
259 | gap: 1rem;
260 | align-items: center;
261 | margin-bottom: 0.5rem;
262 |
263 | .switch {
264 | position: relative;
265 |
266 | label {
267 | display: flex;
268 | align-items: center;
269 | font-family: "Lexend", sans-serif;
270 | font-weight: 400;
271 | cursor: pointer;
272 | margin-bottom: 0.3rem;
273 | }
274 |
275 | .label {
276 | margin-bottom: 0.7rem;
277 | font-weight: 500;
278 | }
279 |
280 | .indicator {
281 | position: relative;
282 | width: 60px;
283 | height: 30px;
284 | background: #242235;
285 | border: 2px solid #393653;
286 | display: block;
287 | border-radius: 30px;
288 | margin-right: 10px;
289 | margin-bottom: 10px;
290 |
291 | &:before {
292 | width: 22px;
293 | height: 22px;
294 | content: "";
295 | display: block;
296 | background: #7676ff;
297 | border-radius: 26px;
298 | transform: translate(2px, 2px);
299 | position: relative;
300 | z-index: 2;
301 | transition: all 0.5s;
302 | }
303 | }
304 | input {
305 | visibility: hidden;
306 | position: absolute;
307 |
308 | &:checked {
309 | & + .indicator {
310 | &:before {
311 | transform: translate(32px, 2px);
312 | }
313 | &:after {
314 | width: 54px;
315 | }
316 | }
317 | }
318 | }
319 | }
320 | `;
321 |
322 | const Episode = styled.div`
323 | margin: 0 4rem 0 4rem;
324 | padding: 2rem;
325 | outline: 2px solid #272639;
326 | border-radius: 0.5rem;
327 | color: white;
328 |
329 | h2 {
330 | font-size: 1.2rem;
331 | font-weight: 500;
332 | margin-bottom: 1rem;
333 | }
334 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
335 |
336 | @media screen and (max-width: 600px) {
337 | padding: 1rem;
338 | margin: 1rem;
339 | }
340 | `;
341 |
342 | const Episodes = styled.div`
343 | display: grid;
344 | grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
345 | grid-gap: 1rem;
346 | grid-row-gap: 1rem;
347 | justify-content: space-between;
348 |
349 | @media screen and (max-width: 600px) {
350 | grid-template-columns: repeat(auto-fit, minmax(4rem, 1fr));
351 | }
352 | `;
353 |
354 | const EpisodeLink = styled(Link)`
355 | text-align: center;
356 | color: white;
357 | text-decoration: none;
358 | background-color: #242235;
359 | padding: 0.9rem 2rem;
360 | font-weight: 500;
361 | border-radius: 0.5rem;
362 | border: 1px solid #393653;
363 | transition: 0.2s;
364 |
365 | :hover {
366 | background-color: #7676ff;
367 | }
368 |
369 | @media screen and (max-width: 600px) {
370 | padding: 1rem;
371 | border-radius: 0.3rem;
372 | font-weight: 500;
373 | }
374 | `;
375 |
376 | const Content = styled.div`
377 | margin: 2rem 5rem 2rem 5rem;
378 | position: relative;
379 |
380 | @media screen and (max-width: 600px) {
381 | margin: 1rem;
382 | }
383 | `;
384 |
385 | const ContentWrapper = styled.div`
386 | padding: 0 3rem 0 3rem;
387 | display: flex;
388 |
389 | div > * {
390 | margin-bottom: 0.6rem;
391 | }
392 |
393 | div {
394 | margin: 1rem;
395 | font-size: 1rem;
396 | color: #b5c3de;
397 | span {
398 | font-weight: 700;
399 | color: white;
400 | }
401 | p {
402 | font-weight: 300;
403 | text-align: justify;
404 | }
405 | h1 {
406 | font-weight: 700;
407 | color: white;
408 | }
409 | h3 {
410 | font-weight: 500;
411 | }
412 | button {
413 | display: none;
414 | }
415 | }
416 |
417 | @media screen and (max-width: 600px) {
418 | display: flex;
419 | flex-direction: column-reverse;
420 | padding: 0;
421 | div {
422 | margin: 1rem;
423 | margin-bottom: 0.2rem;
424 | h1 {
425 | font-size: 1.6rem;
426 | }
427 | p {
428 | font-size: 1rem;
429 | }
430 | button {
431 | display: inline;
432 | border: none;
433 | outline: none;
434 | background-color: transparent;
435 | text-decoration: underline;
436 | font-weight: 700;
437 | font-size: 1rem;
438 | color: white;
439 | }
440 | }
441 | }
442 | `;
443 |
444 | const Poster = styled.div`
445 | display: flex;
446 | flex-direction: column;
447 | img {
448 | width: 220px;
449 | height: 300px;
450 | border-radius: 0.5rem;
451 | margin-bottom: 2.3rem;
452 | position: relative;
453 | top: -20%;
454 | filter: drop-shadow(0px 0px 10px rgba(0, 0, 0, 0.5));
455 | }
456 | @media screen and (max-width: 600px) {
457 | img {
458 | display: none;
459 | }
460 | }
461 |
462 | .outline {
463 | background-color: transparent;
464 | border: 2px solid #9792cf;
465 | }
466 | `;
467 |
468 | const Button = styled(Link)`
469 | font-size: 1.2rem;
470 | padding: 1rem 3.4rem;
471 | text-align: center;
472 | text-decoration: none;
473 | color: white;
474 | background-color: #7676ff;
475 | font-weight: 700;
476 | border-radius: 0.4rem;
477 | position: relative;
478 | top: -25%;
479 | white-space: nowrap;
480 |
481 | @media screen and (max-width: 600px) {
482 | display: block;
483 | width: 100%;
484 | }
485 | `;
486 |
487 | const Banner = styled.img`
488 | width: 100%;
489 | height: 20rem;
490 | object-fit: cover;
491 | border-radius: 0.7rem;
492 |
493 | @media screen and (max-width: 600px) {
494 | height: 13rem;
495 | border-radius: 0.5rem;
496 | }
497 | `;
498 |
499 | export default MalAnimeDetails;
500 |
--------------------------------------------------------------------------------
/src/pages/PopularAnime.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useParams } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 | import { PopularAnimeQuery } from "../hooks/searchQueryStrings";
7 |
8 | function PopularAnime() {
9 | let page = useParams().page;
10 | const [animeDetails, setAnimeDetails] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | getAnime();
15 | }, [page]);
16 |
17 | async function getAnime() {
18 | setLoading(true);
19 | window.scrollTo(0, 0);
20 | const res = await axios({
21 | url: process.env.REACT_APP_BASE_URL,
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | Accept: "application/json",
26 | },
27 | data: {
28 | query: PopularAnimeQuery,
29 | variables: {
30 | page: page,
31 | perPage: 50,
32 | },
33 | },
34 | }).catch((err) => {
35 | console.log(err);
36 | });
37 | setLoading(false);
38 | console.log(res.data.data.Page.media);
39 | setAnimeDetails(res.data.data.Page.media);
40 | document.title = "Popular Anime - Miyou";
41 | }
42 | return (
43 |
44 | {loading &&
}
45 | {!loading && (
46 |
47 |
48 | Popular Anime Results
49 |
50 |
51 | {animeDetails.map((item, i) => (
52 |
53 |
54 |
55 | {item.title.english !== null
56 | ? item.title.english
57 | : item.title.userPreferred}
58 |
59 |
60 | ))}
61 |
62 |
63 | {page > 1 && (
64 |
65 | Previous
66 |
67 | )}
68 | Next
69 |
70 |
71 | )}
72 |
73 | );
74 | }
75 |
76 | const NavButtons = styled.div`
77 | margin-top: 2.5rem;
78 | margin-bottom: 2rem;
79 | display: flex;
80 | align-items: center;
81 | justify-content: center;
82 | gap: 1rem;
83 | `;
84 |
85 | const NavButton = styled(Link)`
86 | padding: 0.8rem 2rem;
87 | text-decoration: none;
88 | color: white;
89 | background-color: none;
90 | border: 2px solid #53507a;
91 | border-radius: 0.5rem;
92 | `;
93 |
94 | const Parent = styled.div`
95 | margin: 2rem 5rem 2rem 5rem;
96 | @media screen and (max-width: 600px) {
97 | margin: 1rem;
98 | }
99 | `;
100 |
101 | const CardWrapper = styled.div`
102 | display: grid;
103 | grid-template-columns: repeat(auto-fill, 160px);
104 | grid-gap: 1rem;
105 | grid-row-gap: 1.5rem;
106 | justify-content: space-between;
107 |
108 | @media screen and (max-width: 600px) {
109 | grid-template-columns: repeat(auto-fill, 120px);
110 | grid-gap: 0rem;
111 | grid-row-gap: 1.5rem;
112 | }
113 |
114 | @media screen and (max-width: 400px) {
115 | grid-template-columns: repeat(auto-fill, 110px);
116 | grid-gap: 0rem;
117 | grid-row-gap: 1.5rem;
118 | }
119 |
120 | @media screen and (max-width: 380px) {
121 | grid-template-columns: repeat(auto-fill, 100px);
122 | grid-gap: 0rem;
123 | grid-row-gap: 1.5rem;
124 | }
125 | `;
126 |
127 | const Links = styled(Link)`
128 | text-decoration: none;
129 | img {
130 | width: 160px;
131 | height: 235px;
132 | border-radius: 0.5rem;
133 | object-fit: cover;
134 | @media screen and (max-width: 600px) {
135 | width: 120px;
136 | height: 180px;
137 | border-radius: 0.3rem;
138 | }
139 | @media screen and (max-width: 400px) {
140 | width: 110px;
141 | height: 170px;
142 | }
143 | @media screen and (max-width: 380px) {
144 | width: 100px;
145 | height: 160px;
146 | }
147 | }
148 |
149 | p {
150 | color: white;
151 | font-size: 1rem;
152 | font-weight: 400;
153 | text-decoration: none;
154 | max-width: 160px;
155 | @media screen and (max-width: 380px) {
156 | width: 100px;
157 | font-size: 0.9rem;
158 | }
159 | }
160 | `;
161 |
162 | const Heading = styled.p`
163 | font-size: 1.8rem;
164 | color: white;
165 | font-weight: 200;
166 | margin-bottom: 2rem;
167 | span {
168 | font-weight: 600;
169 | }
170 |
171 | @media screen and (max-width: 600px) {
172 | font-size: 1.6rem;
173 | margin-bottom: 1rem;
174 | }
175 | `;
176 |
177 | export default PopularAnime;
178 |
--------------------------------------------------------------------------------
/src/pages/PopularMovies.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 |
7 | function PopularMovies() {
8 | const [animeDetails, setAnimeDetails] = useState([]);
9 | const [loading, setLoading] = useState(true);
10 |
11 | useEffect(() => {
12 | getAnime();
13 | }, []);
14 |
15 | async function getAnime() {
16 | window.scrollTo(0, 0);
17 | let res = await axios.get(
18 | `${process.env.REACT_APP_BACKEND_URL}api/getmalinfo?criteria=movie&count=100`
19 | );
20 |
21 | setLoading(false);
22 | console.log(res.data.data);
23 | setAnimeDetails(res.data.data);
24 | document.title = "Popular Anime - Miyou";
25 | }
26 |
27 | return (
28 |
29 | {loading &&
}
30 | {!loading && (
31 |
32 |
33 | Popular Movie Results
34 |
35 |
36 | {animeDetails.map((item, i) => (
37 |
38 |
39 | {item.node.title}
40 |
41 | ))}
42 |
43 |
44 | )}
45 |
46 | );
47 | }
48 | const Parent = styled.div`
49 | margin: 2rem 5rem 2rem 5rem;
50 | @media screen and (max-width: 600px) {
51 | margin: 1rem;
52 | }
53 | `;
54 |
55 | const CardWrapper = styled.div`
56 | display: grid;
57 | grid-template-columns: repeat(auto-fill, 160px);
58 | grid-gap: 1rem;
59 | grid-row-gap: 1.5rem;
60 | justify-content: space-between;
61 |
62 | @media screen and (max-width: 600px) {
63 | grid-template-columns: repeat(auto-fill, 120px);
64 | grid-gap: 0rem;
65 | grid-row-gap: 1.5rem;
66 | }
67 |
68 | @media screen and (max-width: 400px) {
69 | grid-template-columns: repeat(auto-fill, 110px);
70 | grid-gap: 0rem;
71 | grid-row-gap: 1.5rem;
72 | }
73 |
74 | @media screen and (max-width: 380px) {
75 | grid-template-columns: repeat(auto-fill, 100px);
76 | grid-gap: 0rem;
77 | grid-row-gap: 1.5rem;
78 | }
79 | `;
80 |
81 | const Links = styled(Link)`
82 | text-decoration: none;
83 | img {
84 | width: 160px;
85 | height: 235px;
86 | border-radius: 0.5rem;
87 | object-fit: cover;
88 | @media screen and (max-width: 600px) {
89 | width: 120px;
90 | height: 180px;
91 | border-radius: 0.3rem;
92 | }
93 | @media screen and (max-width: 400px) {
94 | width: 110px;
95 | height: 170px;
96 | }
97 | @media screen and (max-width: 380px) {
98 | width: 100px;
99 | height: 160px;
100 | }
101 | }
102 |
103 | p {
104 | color: white;
105 | font-size: 1rem;
106 | font-weight: 400;
107 | text-decoration: none;
108 | max-width: 160px;
109 | @media screen and (max-width: 380px) {
110 | width: 100px;
111 | font-size: 0.9rem;
112 | }
113 | }
114 | `;
115 |
116 | const Heading = styled.p`
117 | font-size: 1.8rem;
118 | color: white;
119 | font-weight: 200;
120 | margin-bottom: 2rem;
121 | span {
122 | font-weight: 600;
123 | }
124 |
125 | @media screen and (max-width: 600px) {
126 | font-size: 1.6rem;
127 | margin-bottom: 1rem;
128 | }
129 | `;
130 | export default PopularMovies;
131 |
--------------------------------------------------------------------------------
/src/pages/SearchResults.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { useParams, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 | import { searchAnimeQuery } from "../hooks/searchQueryStrings";
7 |
8 | function SearchResults() {
9 | let urlParams = useParams().name;
10 | const [results, setResults] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | getResults();
15 | }, [urlParams]);
16 |
17 | async function getResults() {
18 | setLoading(true);
19 | window.scrollTo(0, 0);
20 | let res = await axios({
21 | url: process.env.REACT_APP_BASE_URL,
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | Accept: "application/json",
26 | },
27 | data: {
28 | query: searchAnimeQuery,
29 | variables: {
30 | search: urlParams,
31 | },
32 | },
33 | }).catch((err) => {
34 | console.log(err);
35 | });
36 | setLoading(false);
37 | setResults(res.data.data.Page.media);
38 | document.title = urlParams + " - Miyou";
39 | }
40 | return (
41 |
42 | {loading &&
}
43 | {!loading && (
44 |
45 |
46 | Search Results
47 |
48 |
49 | {results.map((item, i) => (
50 |
51 |
52 |
53 | {item.title.english !== null
54 | ? item.title.english
55 | : item.title.userPreferred}
56 |
57 |
58 | ))}
59 |
60 | {results.length === 0 && No Search Results Found
}
61 |
62 | )}
63 |
64 | );
65 | }
66 |
67 | const Parent = styled.div`
68 | margin: 2rem 5rem 2rem 5rem;
69 | h2 {
70 | color: white;
71 | }
72 | @media screen and (max-width: 600px) {
73 | margin: 1rem;
74 | }
75 | `;
76 |
77 | const CardWrapper = styled.div`
78 | display: grid;
79 | grid-template-columns: repeat(auto-fill, 160px);
80 | grid-gap: 1rem;
81 | grid-row-gap: 1.5rem;
82 | justify-content: space-between;
83 |
84 | @media screen and (max-width: 600px) {
85 | grid-template-columns: repeat(auto-fill, 120px);
86 | grid-gap: 0rem;
87 | grid-row-gap: 1.5rem;
88 | }
89 |
90 | @media screen and (max-width: 400px) {
91 | grid-template-columns: repeat(auto-fill, 110px);
92 | grid-gap: 0rem;
93 | grid-row-gap: 1.5rem;
94 | }
95 |
96 | @media screen and (max-width: 380px) {
97 | grid-template-columns: repeat(auto-fill, 100px);
98 | grid-gap: 0rem;
99 | grid-row-gap: 1.5rem;
100 | }
101 | `;
102 |
103 | const Wrapper = styled(Link)`
104 | text-decoration: none;
105 | img {
106 | width: 160px;
107 | height: 235px;
108 | border-radius: 0.5rem;
109 | object-fit: cover;
110 | @media screen and (max-width: 600px) {
111 | width: 120px;
112 | height: 180px;
113 | border-radius: 0.3rem;
114 | }
115 | @media screen and (max-width: 400px) {
116 | width: 110px;
117 | height: 170px;
118 | }
119 | @media screen and (max-width: 380px) {
120 | width: 100px;
121 | height: 160px;
122 | }
123 | }
124 |
125 | p {
126 | color: white;
127 | font-size: 1rem;
128 | font-weight: 400;
129 | text-decoration: none;
130 | max-width: 160px;
131 | @media screen and (max-width: 380px) {
132 | width: 100px;
133 | font-size: 0.9rem;
134 | }
135 | }
136 | `;
137 |
138 | const Heading = styled.p`
139 | font-size: 1.8rem;
140 | color: white;
141 | font-weight: 200;
142 | margin-bottom: 2rem;
143 | span {
144 | font-weight: 600;
145 | }
146 |
147 | @media screen and (max-width: 600px) {
148 | font-size: 1.6rem;
149 | margin-bottom: 1rem;
150 | }
151 | `;
152 |
153 | export default SearchResults;
154 |
--------------------------------------------------------------------------------
/src/pages/Top100Anime.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useParams } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 | import { top100AnimeQuery } from "../hooks/searchQueryStrings";
7 |
8 | function Top100Anime() {
9 | let page = useParams().page;
10 | const [animeDetails, setAnimeDetails] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | getAnime();
15 | }, [page]);
16 |
17 | async function getAnime() {
18 | setLoading(true);
19 | window.scrollTo(0, 0);
20 | const res = await axios({
21 | url: process.env.REACT_APP_BASE_URL,
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | Accept: "application/json",
26 | },
27 | data: {
28 | query: top100AnimeQuery,
29 | variables: {
30 | page: page,
31 | perPage: 50,
32 | },
33 | },
34 | }).catch((err) => {
35 | console.log(err);
36 | });
37 | setLoading(false);
38 | setAnimeDetails(res.data.data.Page.media);
39 | document.title = "Top 100 Anime - Miyou";
40 | }
41 | return (
42 |
43 | {loading &&
}
44 | {!loading && (
45 |
46 |
47 | Top 100 Anime Results
48 |
49 |
50 | {animeDetails.map((item, i) => (
51 |
52 |
53 |
54 | {item.title.english !== null
55 | ? item.title.english
56 | : item.title.userPreferred}
57 |
58 |
59 | ))}
60 |
61 |
62 | {page > 1 && (
63 |
64 | Previous
65 |
66 | )}
67 | Next
68 |
69 |
70 | )}
71 |
72 | );
73 | }
74 |
75 | const NavButtons = styled.div`
76 | margin-top: 2.5rem;
77 | margin-bottom: 2rem;
78 | display: flex;
79 | align-items: center;
80 | justify-content: center;
81 | gap: 1rem;
82 | `;
83 |
84 | const NavButton = styled(Link)`
85 | padding: 0.8rem 2rem;
86 | text-decoration: none;
87 | color: white;
88 | background-color: none;
89 | border: 2px solid #53507a;
90 | border-radius: 0.5rem;
91 | `;
92 |
93 | const Parent = styled.div`
94 | margin: 2rem 5rem 2rem 5rem;
95 | @media screen and (max-width: 600px) {
96 | margin: 1rem;
97 | }
98 | `;
99 |
100 | const CardWrapper = styled.div`
101 | display: grid;
102 | grid-template-columns: repeat(auto-fill, 160px);
103 | grid-gap: 1rem;
104 | grid-row-gap: 1.5rem;
105 | justify-content: space-between;
106 |
107 | @media screen and (max-width: 600px) {
108 | grid-template-columns: repeat(auto-fill, 120px);
109 | grid-gap: 0rem;
110 | grid-row-gap: 1.5rem;
111 | }
112 |
113 | @media screen and (max-width: 400px) {
114 | grid-template-columns: repeat(auto-fill, 110px);
115 | grid-gap: 0rem;
116 | grid-row-gap: 1.5rem;
117 | }
118 |
119 | @media screen and (max-width: 380px) {
120 | grid-template-columns: repeat(auto-fill, 100px);
121 | grid-gap: 0rem;
122 | grid-row-gap: 1.5rem;
123 | }
124 | `;
125 |
126 | const Links = styled(Link)`
127 | text-decoration: none;
128 | img {
129 | width: 160px;
130 | height: 235px;
131 | border-radius: 0.5rem;
132 | object-fit: cover;
133 | @media screen and (max-width: 600px) {
134 | width: 120px;
135 | height: 180px;
136 | border-radius: 0.3rem;
137 | }
138 | @media screen and (max-width: 400px) {
139 | width: 110px;
140 | height: 170px;
141 | }
142 | @media screen and (max-width: 380px) {
143 | width: 100px;
144 | height: 160px;
145 | }
146 | }
147 |
148 | p {
149 | color: white;
150 | font-size: 1rem;
151 | font-weight: 400;
152 | text-decoration: none;
153 | max-width: 160px;
154 | @media screen and (max-width: 380px) {
155 | width: 100px;
156 | font-size: 0.9rem;
157 | }
158 | }
159 | `;
160 |
161 | const Heading = styled.p`
162 | font-size: 1.8rem;
163 | color: white;
164 | font-weight: 200;
165 | margin-bottom: 2rem;
166 | span {
167 | font-weight: 600;
168 | }
169 |
170 | @media screen and (max-width: 600px) {
171 | font-size: 1.6rem;
172 | margin-bottom: 1rem;
173 | }
174 | `;
175 |
176 | export default Top100Anime;
177 |
--------------------------------------------------------------------------------
/src/pages/TrendingAnime.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { Link, useParams } from "react-router-dom";
4 | import styled from "styled-components";
5 | import SearchResultsSkeleton from "../components/skeletons/SearchResultsSkeleton";
6 | import { TrendingAnimeQuery } from "../hooks/searchQueryStrings";
7 |
8 | function TrendingAnime() {
9 | let page = useParams().page;
10 | const [animeDetails, setAnimeDetails] = useState([]);
11 | const [loading, setLoading] = useState(true);
12 |
13 | useEffect(() => {
14 | getAnime();
15 | }, [page]);
16 |
17 | async function getAnime() {
18 | setLoading(true);
19 | window.scrollTo(0, 0);
20 | const res = await axios({
21 | url: process.env.REACT_APP_BASE_URL,
22 | method: "POST",
23 | headers: {
24 | "Content-Type": "application/json",
25 | Accept: "application/json",
26 | },
27 | data: {
28 | query: TrendingAnimeQuery,
29 | variables: {
30 | page: page,
31 | perPage: 50,
32 | },
33 | },
34 | }).catch((err) => {
35 | console.log(err);
36 | });
37 | setLoading(false);
38 | setAnimeDetails(res.data.data.Page.media);
39 | document.title = "Trending Anime - Miyou";
40 | }
41 | return (
42 |
43 | {loading &&
}
44 | {!loading && (
45 |
46 |
47 | Trending Anime Results
48 |
49 |
50 | {animeDetails.map((item, i) => (
51 |
52 |
53 |
54 | {item.title.english !== null
55 | ? item.title.english
56 | : item.title.userPreferred}
57 |
58 |
59 | ))}
60 |
61 |
62 | {page > 1 && (
63 |
64 | Previous
65 |
66 | )}
67 | Next
68 |
69 |
70 | )}
71 |
72 | );
73 | }
74 |
75 | const NavButtons = styled.div`
76 | margin-top: 2.5rem;
77 | margin-bottom: 2rem;
78 | display: flex;
79 | align-items: center;
80 | justify-content: center;
81 | gap: 1rem;
82 | `;
83 |
84 | const NavButton = styled(Link)`
85 | padding: 0.8rem 2rem;
86 | text-decoration: none;
87 | color: white;
88 | background-color: none;
89 | border: 2px solid #53507a;
90 | border-radius: 0.5rem;
91 | `;
92 |
93 | const Parent = styled.div`
94 | margin: 2rem 5rem 2rem 5rem;
95 | @media screen and (max-width: 600px) {
96 | margin: 1rem;
97 | }
98 | `;
99 |
100 | const CardWrapper = styled.div`
101 | display: grid;
102 | grid-template-columns: repeat(auto-fill, 160px);
103 | grid-gap: 1rem;
104 | grid-row-gap: 1.5rem;
105 | justify-content: space-between;
106 |
107 | @media screen and (max-width: 600px) {
108 | grid-template-columns: repeat(auto-fill, 120px);
109 | grid-gap: 0rem;
110 | grid-row-gap: 1.5rem;
111 | }
112 |
113 | @media screen and (max-width: 400px) {
114 | grid-template-columns: repeat(auto-fill, 110px);
115 | grid-gap: 0rem;
116 | grid-row-gap: 1.5rem;
117 | }
118 |
119 | @media screen and (max-width: 380px) {
120 | grid-template-columns: repeat(auto-fill, 100px);
121 | grid-gap: 0rem;
122 | grid-row-gap: 1.5rem;
123 | }
124 | `;
125 |
126 | const Links = styled(Link)`
127 | text-decoration: none;
128 | img {
129 | width: 160px;
130 | height: 235px;
131 | border-radius: 0.5rem;
132 | object-fit: cover;
133 | @media screen and (max-width: 600px) {
134 | width: 120px;
135 | height: 180px;
136 | border-radius: 0.3rem;
137 | }
138 | @media screen and (max-width: 400px) {
139 | width: 110px;
140 | height: 170px;
141 | }
142 | @media screen and (max-width: 380px) {
143 | width: 100px;
144 | height: 160px;
145 | }
146 | }
147 |
148 | p {
149 | color: white;
150 | font-size: 1rem;
151 | font-weight: 400;
152 | text-decoration: none;
153 | max-width: 160px;
154 | @media screen and (max-width: 380px) {
155 | width: 100px;
156 | font-size: 0.9rem;
157 | }
158 | }
159 | `;
160 |
161 | const Heading = styled.p`
162 | font-size: 1.8rem;
163 | color: white;
164 | font-weight: 200;
165 | margin-bottom: 2rem;
166 | span {
167 | font-weight: 600;
168 | }
169 |
170 | @media screen and (max-width: 600px) {
171 | font-size: 1.6rem;
172 | margin-bottom: 1rem;
173 | }
174 | `;
175 |
176 | export default TrendingAnime;
177 |
--------------------------------------------------------------------------------
/src/pages/WatchAnime.js:
--------------------------------------------------------------------------------
1 | import axios from "axios";
2 | import React, { useEffect, useState } from "react";
3 | import { useParams, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import { BiArrowToBottom, BiFullscreen } from "react-icons/bi";
6 | import { HiArrowSmLeft, HiArrowSmRight } from "react-icons/hi";
7 | import { HiOutlineSwitchHorizontal } from "react-icons/hi";
8 | import { IconContext } from "react-icons";
9 | import WatchAnimeSkeleton from "../components/skeletons/WatchAnimeSkeleton";
10 | import useWindowDimensions from "../hooks/useWindowDimensions";
11 | import VideoPlayer from "../components/VideoPlayer/VideoPlayer";
12 | import ServersList from "../components/WatchAnime/ServersList";
13 |
14 | function WatchAnime() {
15 | let episodeSlug = useParams().episode;
16 |
17 | const [episodeLinks, setEpisodeLinks] = useState([]);
18 | const [currentServer, setCurrentServer] = useState("");
19 | const [loading, setLoading] = useState(true);
20 | const { width, height } = useWindowDimensions();
21 | const [fullScreen, setFullScreen] = useState(false);
22 | const [internalPlayer, setInternalPlayer] = useState(true);
23 | const [localStorageDetails, setLocalStorageDetails] = useState(0);
24 |
25 | useEffect(() => {
26 | getEpisodeLinks();
27 | }, [episodeSlug]);
28 |
29 | async function getEpisodeLinks() {
30 | setLoading(true);
31 | window.scrollTo(0, 0);
32 | let res = await axios.get(
33 | `${process.env.REACT_APP_BACKEND_URL}api/getlinks?link=/${episodeSlug}`
34 | );
35 | setLoading(false);
36 | setEpisodeLinks(res.data);
37 | setCurrentServer(res.data[0].vidstreaming);
38 | if (
39 | res.data[0].sources.sources !== null ||
40 | res.data[0].sources.sources !== undefined
41 | ) {
42 | setInternalPlayer(true);
43 | }
44 | updateLocalStorage(episodeSlug, res.data);
45 | getLocalStorage(
46 | res.data[0].titleName.substring(
47 | 0,
48 | res.data[0].titleName.indexOf("Episode")
49 | )
50 | );
51 | document.title = `${res.data[0].titleName
52 | .substring(res.data[0].titleName.indexOf("Episode"))
53 | .replace("Episode", "EP")} - ${res.data[0].titleName.substring(
54 | 0,
55 | res.data[0].titleName.indexOf("Episode")
56 | )} - Miyou`;
57 | }
58 |
59 | function getLocalStorage(animeDetails) {
60 | animeDetails = animeDetails.substring(0, animeDetails.length - 1);
61 |
62 | if (localStorage.getItem("Animes")) {
63 | let lsData = localStorage.getItem("Animes");
64 | lsData = JSON.parse(lsData);
65 |
66 | let index = lsData.Names.findIndex((i) => i.name === animeDetails);
67 |
68 | if (index !== -1) {
69 | setLocalStorageDetails(lsData.Names[index].currentEpisode);
70 | }
71 | }
72 | }
73 |
74 | function fullScreenHandler(e) {
75 | setFullScreen(!fullScreen);
76 | let video = document.getElementById("video");
77 |
78 | if (!document.fullscreenElement) {
79 | video.requestFullscreen();
80 | window.screen.orientation.lock("landscape-primary");
81 | } else {
82 | document.exitFullscreen();
83 | }
84 | }
85 |
86 | function updateLocalStorage(episode, episodeLinks) {
87 | let episodeNum = episode.replace(/.*?(\d+)[^\d]*$/, "$1");
88 | let animeName = episodeLinks[0].titleName.substring(
89 | 0,
90 | episodeLinks[0].titleName.indexOf("Episode")
91 | );
92 | animeName = animeName.substring(0, animeName.length - 1);
93 | if (localStorage.getItem("Animes")) {
94 | let lsData = localStorage.getItem("Animes");
95 | lsData = JSON.parse(lsData);
96 |
97 | let index = lsData.Names.findIndex((i) => i.name === animeName);
98 | if (index !== -1) {
99 | lsData.Names.splice(index, 1);
100 | lsData.Names.unshift({
101 | name: animeName,
102 | currentEpisode: episodeNum,
103 | episodeLink: episodeSlug,
104 | });
105 | } else {
106 | lsData.Names.unshift({
107 | name: animeName,
108 | currentEpisode: episodeNum,
109 | episodeLink: episodeSlug,
110 | });
111 | }
112 | lsData = JSON.stringify(lsData);
113 | localStorage.setItem("Animes", lsData);
114 | } else {
115 | let data = {
116 | Names: [],
117 | };
118 | data.Names.push({
119 | name: animeName,
120 | currentEpisode: episodeNum,
121 | episodeLink: episodeSlug,
122 | });
123 | data = JSON.stringify(data);
124 | localStorage.setItem("Animes", data);
125 | }
126 | }
127 |
128 | return (
129 |
130 | {loading &&
}
131 | {!loading && (
132 |
133 | {episodeLinks.length > 0 && currentServer !== "" && (
134 |
135 |
136 |
137 |
138 |
139 | {episodeLinks[0].titleName.substring(
140 | 0,
141 | episodeLinks[0].titleName.indexOf("Episode")
142 | )}
143 | {" "}
144 | -
145 | {" " +
146 | episodeLinks[0].titleName.substring(
147 | episodeLinks[0].titleName.indexOf("Episode")
148 | )}
149 |
150 | {width <= 600 && (
151 |
159 |
164 |
165 |
166 |
167 | )}
168 | {width > 600 && (
169 |
179 |
184 | Download
185 |
186 |
187 |
188 | )}
189 |
190 |
191 |
192 |
193 |
194 | {internalPlayer && (
195 |
201 | )}
202 | {!internalPlayer && (
203 |
204 |
205 |
214 | External Player (Contain Ads)
215 |
216 |
217 |
224 | Change Server
225 |
226 |
227 |
228 |
229 |
230 |
240 | {width <= 600 && (
241 |
242 |
252 | fullScreenHandler(e)}
254 | />
255 |
256 |
257 | )}
258 |
259 |
260 | )}
261 |
262 | {width <= 600 && (
263 |
271 |
289 |
290 |
291 |
292 | )}
293 | {width > 600 && (
294 |
304 |
322 |
323 | Previous
324 |
325 |
326 | )}
327 | {width <= 600 && (
328 |
336 |
355 |
356 |
357 |
358 | )}
359 | {width > 600 && (
360 |
370 |
389 | Next
390 |
391 |
392 |
393 | )}
394 |
395 | {!internalPlayer && (
396 |
401 | )}
402 |
403 |
404 | Episodes
405 |
406 | {episodeLinks[0].episodes.map((item, i) => (
407 |
418 | {i + 1}
419 |
420 | ))}
421 |
422 |
423 |
424 |
425 | )}
426 |
427 | )}
428 |
429 | );
430 | }
431 |
432 | const VideoPlayerWrapper = styled.div`
433 | display: grid;
434 | grid-template-columns: 70% calc(30% - 1rem);
435 | gap: 1rem;
436 | align-items: flex-start;
437 | @media screen and (max-width: 900px) {
438 | grid-template-columns: auto;
439 | }
440 | `;
441 |
442 | const Conttainer = styled.div`
443 | display: flex;
444 | justify-content: space-between;
445 | align-items: center;
446 | background-color: #242235;
447 | padding: 0.5rem 1rem;
448 | border-radius: 0.5rem 0.5rem 0 0;
449 | border: 1px solid #393653;
450 | border-bottom: none;
451 | margin-top: 1rem;
452 | font-weight: 400;
453 | p {
454 | color: white;
455 | }
456 |
457 | button {
458 | outline: none;
459 | border: none;
460 | background: transparent;
461 | margin-left: 1rem;
462 | cursor: pointer;
463 | }
464 |
465 | .tooltip {
466 | position: relative;
467 | display: inline-block;
468 | border-bottom: 1px dotted black;
469 | }
470 |
471 | .tooltip .tooltiptext {
472 | visibility: hidden;
473 | width: 120px;
474 | background-color: rgba(0, 0, 0, 0.8);
475 | color: #fff;
476 | text-align: center;
477 | border-radius: 6px;
478 | padding: 5px 5px;
479 | position: absolute;
480 | z-index: 1;
481 | bottom: 150%;
482 | left: 50%;
483 | margin-left: -60px;
484 | opacity: 0;
485 | transition: opacity 0.2s;
486 | }
487 |
488 | .tooltip .tooltiptext::after {
489 | content: "";
490 | position: absolute;
491 | top: 100%;
492 | left: 50%;
493 | margin-left: -5px;
494 | border-width: 5px;
495 | border-style: solid;
496 | border-color: black transparent transparent transparent;
497 | }
498 |
499 | .tooltip:hover .tooltiptext {
500 | visibility: visible;
501 | opacity: 1;
502 | }
503 | `;
504 |
505 | const IframeWrapper = styled.div`
506 | position: relative;
507 | padding-bottom: 56.25%; /* proportion value to aspect ratio 16:9 (9 / 16 = 0.5625 or 56.25%) */
508 | height: 0;
509 | overflow: hidden;
510 | margin-bottom: 1rem;
511 | border-radius: 0 0 0.5rem 0.5rem;
512 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.6);
513 | background-image: url("https://i.ibb.co/28yS92Z/If-the-video-does-not-load-please-refresh-the-page.png");
514 | background-size: 23rem;
515 | background-repeat: no-repeat;
516 | background-position: center;
517 |
518 | iframe {
519 | position: absolute;
520 | top: 0;
521 | left: 0;
522 | width: 100%;
523 | height: 100%;
524 | }
525 |
526 | div {
527 | position: absolute;
528 | z-index: 10;
529 | padding: 1rem;
530 | }
531 |
532 | @media screen and (max-width: 600px) {
533 | padding-bottom: 66.3%;
534 | background-size: 13rem;
535 | }
536 | `;
537 |
538 | const EpisodesWrapper = styled.div`
539 | margin-top: 1rem;
540 | border: 1px solid #272639;
541 | border-radius: 0.4rem;
542 | padding: 1rem;
543 |
544 | p {
545 | font-size: 1.3rem;
546 | text-decoration: underline;
547 | color: white;
548 | font-weight: 400;
549 | margin-bottom: 1rem;
550 | }
551 | /* box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81); */
552 | `;
553 |
554 | const Episodes = styled.div`
555 | display: grid;
556 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
557 | grid-gap: 0.8rem;
558 | grid-row-gap: 1rem;
559 | justify-content: space-between;
560 |
561 | @media screen and (max-width: 600px) {
562 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
563 | }
564 | `;
565 |
566 | const EpisodeLink = styled(Link)`
567 | text-align: center;
568 | color: white;
569 | text-decoration: none;
570 | background-color: #242235;
571 | padding: 0.6rem 0.8rem;
572 | font-weight: 400;
573 | border-radius: 0.3rem;
574 | border: 1px solid #393653;
575 | transition: 0.2s;
576 |
577 | :hover {
578 | background-color: #7676ff;
579 | }
580 | `;
581 |
582 | const ServerWrapper = styled.div`
583 | p {
584 | color: white;
585 | font-size: 1.4rem;
586 | font-weight: 400;
587 | text-decoration: underline;
588 | }
589 |
590 | .server-wrapper {
591 | padding: 1rem;
592 | background-color: #1a1927;
593 | border: 1px solid #272639;
594 | border-radius: 0.4rem;
595 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
596 | }
597 |
598 | .serverlinks {
599 | display: grid;
600 | grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
601 | grid-gap: 1rem;
602 | grid-row-gap: 1rem;
603 | justify-content: space-between;
604 | margin-top: 1rem;
605 | }
606 |
607 | button {
608 | cursor: pointer;
609 | outline: none;
610 | color: white;
611 | background-color: #242235;
612 | border: 1px solid #393653;
613 | padding: 0.7rem 1.5rem;
614 | border-radius: 0.4rem;
615 | font-weight: 400;
616 | font-size: 0.9rem;
617 | }
618 |
619 | @media screen and (max-width: 600px) {
620 | p {
621 | font-size: 1.2rem;
622 | }
623 | }
624 | `;
625 |
626 | const Wrapper = styled.div`
627 | margin: 2rem 5rem 2rem 5rem;
628 | @media screen and (max-width: 600px) {
629 | margin: 1rem;
630 | }
631 | `;
632 |
633 | const EpisodeButtons = styled.div`
634 | display: flex;
635 | justify-content: space-between;
636 | align-items: center;
637 | margin-bottom: 1rem;
638 | `;
639 |
640 | const EpisodeLinks = styled(Link)`
641 | color: white;
642 | padding: 0.6rem 1rem;
643 | background-color: #242235;
644 | border: 1px solid #393653;
645 | text-decoration: none;
646 | font-weight: 400;
647 | border-radius: 0.4rem;
648 |
649 | @media screen and (max-width: 600px) {
650 | padding: 1rem;
651 | border-radius: 50%;
652 | }
653 | `;
654 |
655 | const Titles = styled.div`
656 | display: flex;
657 | justify-content: space-between;
658 | align-items: center;
659 | color: white;
660 | margin-bottom: 0.5rem;
661 | p {
662 | font-size: 1.7rem;
663 | font-weight: 200;
664 | }
665 |
666 | span {
667 | font-weight: 600;
668 | }
669 |
670 | a {
671 | font-weight: 400;
672 | background-color: #242235;
673 | border: 1px solid #393653;
674 | text-decoration: none;
675 | color: white;
676 | padding: 0.7rem 1.1rem 0.7rem 1.5rem;
677 | border-radius: 0.4rem;
678 | }
679 | @media screen and (max-width: 600px) {
680 | margin-bottom: 1rem;
681 | p {
682 | font-size: 1.3rem;
683 | }
684 | a {
685 | padding: 0.7rem;
686 | border-radius: 50%;
687 | margin-left: 1rem;
688 | }
689 | }
690 | `;
691 |
692 | export default WatchAnime;
693 |
--------------------------------------------------------------------------------
/src/pages/WatchAnimeV2.js:
--------------------------------------------------------------------------------
1 | import React, { useEffect, useState } from "react";
2 | import axios from "axios";
3 | import { useParams, Link } from "react-router-dom";
4 | import styled from "styled-components";
5 | import { BiArrowToBottom, BiFullscreen } from "react-icons/bi";
6 | import {
7 | HiArrowSmLeft,
8 | HiArrowSmRight,
9 | HiOutlineSwitchHorizontal,
10 | } from "react-icons/hi";
11 | import { IconContext } from "react-icons";
12 | import WatchAnimeSkeleton from "../components/skeletons/WatchAnimeSkeleton";
13 | import useWindowDimensions from "../hooks/useWindowDimensions";
14 | import VideoPlayer from "../components/VideoPlayer/VideoPlayer";
15 | import { searchByIdQuery } from "../hooks/searchQueryStrings";
16 | import toast from "react-hot-toast";
17 |
18 | function WatchAnimeV2() {
19 | const mal_id = useParams().id;
20 | const slug = useParams().slug;
21 | const episode = useParams().episode;
22 |
23 | const [episodeLinks, setEpisodeLinks] = useState([]);
24 | const [currentServer, setCurrentServer] = useState("");
25 | const [animeDetails, setAnimeDetails] = useState();
26 | const [loading, setLoading] = useState(true);
27 | const { width } = useWindowDimensions();
28 | const [fullScreen, setFullScreen] = useState(false);
29 | const [internalPlayer, setInternalPlayer] = useState(true);
30 |
31 | useEffect(() => {
32 | getEpisodeLinks();
33 | }, [episode]);
34 |
35 | async function getEpisodeLinks() {
36 | setLoading(true);
37 | window.scrollTo(0, 0);
38 | let res = await axios.get(
39 | `${process.env.REACT_APP_BACKEND_URL}api/getmixlinks?id=${slug}&ep=${episode}`
40 | );
41 | setEpisodeLinks(res.data);
42 | setCurrentServer(res.data.gogoLink);
43 | if (!res.data.sources) {
44 | setInternalPlayer(true);
45 | }
46 | updateLocalStorage(
47 | res.data.animeId,
48 | res.data.episodeNum,
49 | mal_id,
50 | res.data.isDub
51 | );
52 | let aniRes = await axios({
53 | url: process.env.REACT_APP_BASE_URL,
54 | method: "POST",
55 | headers: {
56 | "Content-Type": "application/json",
57 | Accept: "application/json",
58 | },
59 | data: {
60 | query: searchByIdQuery,
61 | variables: {
62 | id: mal_id,
63 | },
64 | },
65 | }).catch((err) => {
66 | console.log(err);
67 | });
68 | setAnimeDetails(aniRes.data.data.Media);
69 | document.title = `${aniRes.data.data.Media.title.userPreferred} ${res.data.isDub ? "(Dub)" : "(Sub)"
70 | } EP-${episode} - Miyou`;
71 | setLoading(false);
72 | }
73 |
74 | function fullScreenHandler(e) {
75 | setFullScreen(!fullScreen);
76 | let video = document.getElementById("video");
77 |
78 | if (!document.fullscreenElement) {
79 | video.requestFullscreen();
80 | window.screen.orientation.lock("landscape-primary");
81 | } else {
82 | document.exitFullscreen();
83 | }
84 | }
85 |
86 | function updateLocalStorage(animeId, episode, malId, isDub) {
87 | if (localStorage.getItem("Watching")) {
88 | let data = localStorage.getItem("Watching");
89 | data = JSON.parse(data);
90 | let index = data.findIndex((i) => i.animeId === animeId);
91 | if (index !== -1) {
92 | data.splice(index, 1);
93 | }
94 | data.unshift({
95 | animeId,
96 | episode,
97 | malId,
98 | isDub,
99 | });
100 | data = JSON.stringify(data);
101 | localStorage.setItem("Watching", data);
102 | } else {
103 | let data = [];
104 | data.push({
105 | animeId,
106 | episode,
107 | malId,
108 | isDub,
109 | });
110 | data = JSON.stringify(data);
111 | localStorage.setItem("Watching", data);
112 | }
113 | }
114 |
115 | return (
116 |
117 | {loading &&
}
118 | {!loading && (
119 |
120 | {episodeLinks && animeDetails && currentServer !== "" && (
121 |
122 |
123 |
124 |
125 | {`${animeDetails.title.english !== null
126 | ? animeDetails.title.english
127 | : animeDetails.title.userPreferred
128 | } ${episodeLinks.isDub ? "(Dub)" : "(Sub)"}`}
129 | {` Episode - ${episode}`}
130 |
131 | {width <= 600 && (
132 |
140 |
145 |
146 |
147 |
148 | )}
149 | {width > 600 && (
150 |
160 |
165 | Download
166 |
167 |
168 |
169 | )}
170 |
171 |
178 | If the video player doesn't load or if blank refresh the page
179 | or use the external server
180 |
181 |
182 |
183 |
184 |
185 | {internalPlayer && (
186 |
197 | )}
198 | {!internalPlayer && (
199 |
200 |
201 |
210 | External Player (Contain Ads)
211 |
212 |
213 |
226 | Change Server
227 |
228 |
229 |
230 |
231 |
232 |
242 | {width <= 600 && (
243 |
244 |
254 | fullScreenHandler(e)}
256 | />
257 |
258 |
259 | )}
260 |
261 |
262 | )}
263 |
264 | {width <= 600 && (
265 |
273 |
285 |
286 |
287 |
288 | )}
289 | {width > 600 && (
290 |
300 |
312 |
313 | Previous
314 |
315 |
316 | )}
317 | {width <= 600 && (
318 |
326 |
339 |
340 |
341 |
342 | )}
343 | {width > 600 && (
344 |
354 |
367 | Next
368 |
369 |
370 |
371 | )}
372 |
373 |
374 |
375 | Episodes
376 |
377 | {[...Array(parseInt(episodeLinks.totalEpisodes))].map(
378 | (x, i) => (
379 |
388 | {i + 1}
389 |
390 | )
391 | )}
392 |
393 |
394 |
395 |
396 | )}
397 |
398 | )}
399 |
400 | );
401 | }
402 |
403 | const VideoPlayerWrapper = styled.div`
404 | display: grid;
405 | grid-template-columns: 70% calc(30% - 1rem);
406 | gap: 1rem;
407 | align-items: flex-start;
408 | @media screen and (max-width: 900px) {
409 | grid-template-columns: auto;
410 | }
411 | `;
412 |
413 | const Conttainer = styled.div`
414 | display: flex;
415 | justify-content: space-between;
416 | align-items: center;
417 | background-color: #242235;
418 | padding: 0.5rem 1rem;
419 | border-radius: 0.5rem 0.5rem 0 0;
420 | border: 1px solid #393653;
421 | border-bottom: none;
422 | margin-top: 1rem;
423 | font-weight: 400;
424 | p {
425 | color: white;
426 | }
427 |
428 | button {
429 | outline: none;
430 | border: none;
431 | background: transparent;
432 | margin-left: 1rem;
433 | cursor: pointer;
434 | }
435 |
436 | .tooltip {
437 | position: relative;
438 | display: inline-block;
439 | border-bottom: 1px dotted black;
440 | }
441 |
442 | .tooltip .tooltiptext {
443 | visibility: hidden;
444 | width: 120px;
445 | background-color: rgba(0, 0, 0, 0.8);
446 | color: #fff;
447 | text-align: center;
448 | border-radius: 6px;
449 | padding: 5px 5px;
450 | position: absolute;
451 | z-index: 1;
452 | bottom: 150%;
453 | left: 50%;
454 | margin-left: -60px;
455 | opacity: 0;
456 | transition: opacity 0.2s;
457 | }
458 |
459 | .tooltip .tooltiptext::after {
460 | content: "";
461 | position: absolute;
462 | top: 100%;
463 | left: 50%;
464 | margin-left: -5px;
465 | border-width: 5px;
466 | border-style: solid;
467 | border-color: black transparent transparent transparent;
468 | }
469 |
470 | .tooltip:hover .tooltiptext {
471 | visibility: visible;
472 | opacity: 1;
473 | }
474 | `;
475 |
476 | const IframeWrapper = styled.div`
477 | position: relative;
478 | padding-bottom: 56.25%; /* proportion value to aspect ratio 16:9 (9 / 16 = 0.5625 or 56.25%) */
479 | height: 0;
480 | overflow: hidden;
481 | margin-bottom: 1rem;
482 | border-radius: 0 0 0.5rem 0.5rem;
483 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.6);
484 | background-image: url("https://i.ibb.co/28yS92Z/If-the-video-does-not-load-please-refresh-the-page.png");
485 | background-size: 23rem;
486 | background-repeat: no-repeat;
487 | background-position: center;
488 |
489 | iframe {
490 | position: absolute;
491 | top: 0;
492 | left: 0;
493 | width: 100%;
494 | height: 100%;
495 | }
496 |
497 | div {
498 | position: absolute;
499 | z-index: 10;
500 | padding: 1rem;
501 | }
502 |
503 | @media screen and (max-width: 600px) {
504 | padding-bottom: 66.3%;
505 | background-size: 13rem;
506 | }
507 | `;
508 |
509 | const EpisodesWrapper = styled.div`
510 | margin-top: 1rem;
511 | border: 1px solid #272639;
512 | border-radius: 0.4rem;
513 | padding: 1rem;
514 |
515 | p {
516 | font-size: 1.3rem;
517 | text-decoration: underline;
518 | color: white;
519 | font-weight: 400;
520 | margin-bottom: 1rem;
521 | }
522 | /* box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81); */
523 | `;
524 |
525 | const Episodes = styled.div`
526 | display: grid;
527 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
528 | grid-gap: 0.8rem;
529 | grid-row-gap: 1rem;
530 | justify-content: space-between;
531 |
532 | @media screen and (max-width: 600px) {
533 | grid-template-columns: repeat(auto-fit, minmax(3rem, 1fr));
534 | }
535 | `;
536 |
537 | const EpisodeLink = styled(Link)`
538 | text-align: center;
539 | color: white;
540 | text-decoration: none;
541 | background-color: #242235;
542 | padding: 0.6rem 0.8rem;
543 | font-weight: 400;
544 | border-radius: 0.3rem;
545 | border: 1px solid #393653;
546 | transition: 0.2s;
547 |
548 | :hover {
549 | background-color: #7676ff;
550 | }
551 | `;
552 |
553 | const ServerWrapper = styled.div`
554 | p {
555 | color: white;
556 | font-size: 1.4rem;
557 | font-weight: 400;
558 | text-decoration: underline;
559 | }
560 |
561 | .server-wrapper {
562 | padding: 1rem;
563 | background-color: #1a1927;
564 | border: 1px solid #272639;
565 | border-radius: 0.4rem;
566 | box-shadow: 0px 4.41109px 20.291px rgba(16, 16, 24, 0.81);
567 | }
568 |
569 | .serverlinks {
570 | display: grid;
571 | grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr));
572 | grid-gap: 1rem;
573 | grid-row-gap: 1rem;
574 | justify-content: space-between;
575 | margin-top: 1rem;
576 | }
577 |
578 | button {
579 | cursor: pointer;
580 | outline: none;
581 | color: white;
582 | background-color: #242235;
583 | border: 1px solid #393653;
584 | padding: 0.7rem 1.5rem;
585 | border-radius: 0.4rem;
586 | font-weight: 400;
587 | font-size: 0.9rem;
588 | }
589 |
590 | @media screen and (max-width: 600px) {
591 | p {
592 | font-size: 1.2rem;
593 | }
594 | }
595 | `;
596 |
597 | const Wrapper = styled.div`
598 | margin: 2rem 5rem 2rem 5rem;
599 | @media screen and (max-width: 600px) {
600 | margin: 1rem;
601 | }
602 | `;
603 |
604 | const EpisodeButtons = styled.div`
605 | display: flex;
606 | justify-content: space-between;
607 | align-items: center;
608 | margin-bottom: 1rem;
609 | `;
610 |
611 | const EpisodeLinks = styled(Link)`
612 | color: white;
613 | padding: 0.6rem 1rem;
614 | background-color: #242235;
615 | border: 1px solid #393653;
616 | text-decoration: none;
617 | font-weight: 400;
618 | border-radius: 0.4rem;
619 |
620 | @media screen and (max-width: 600px) {
621 | padding: 1rem;
622 | border-radius: 50%;
623 | }
624 | `;
625 |
626 | const Titles = styled.div`
627 | display: flex;
628 | justify-content: space-between;
629 | align-items: center;
630 | color: white;
631 | margin-bottom: 0.5rem;
632 | p {
633 | font-size: 1.7rem;
634 | font-weight: 200;
635 | }
636 |
637 | span {
638 | font-weight: 600;
639 | }
640 |
641 | a {
642 | font-weight: 400;
643 | background-color: #242235;
644 | border: 1px solid #393653;
645 | text-decoration: none;
646 | color: white;
647 | padding: 0.7rem 1.1rem 0.7rem 1.5rem;
648 | border-radius: 0.4rem;
649 | }
650 | @media screen and (max-width: 600px) {
651 | margin-bottom: 1rem;
652 | p {
653 | font-size: 1.3rem;
654 | }
655 | a {
656 | padding: 0.7rem;
657 | border-radius: 50%;
658 | margin-left: 1rem;
659 | }
660 | }
661 | `;
662 |
663 | export default WatchAnimeV2;
664 |
--------------------------------------------------------------------------------
/src/styles/globalStyles.js:
--------------------------------------------------------------------------------
1 | import { createGlobalStyle } from "styled-components";
2 |
3 | // font-family: 'Gilroy-Bold', sans-serif;
4 | // font-family: 'Gilroy-Heavy', sans-serif;
5 | // font-family: 'Gilroy-Light', sans-serif;
6 | // font-family: 'Gilroy-Medium', sans-serif;
7 | // font-family: 'Gilroy-Regular', sans-serif;
8 |
9 | const GlobalStyle = createGlobalStyle`
10 | * {
11 | margin: 0;
12 | padding: 0;
13 | box-sizing: border-box;
14 | }
15 | body {
16 | background-color: #1A1927;
17 | font-family: 'Lexend', sans-serif;
18 | }
19 |
20 | :root {
21 | color-scheme: dark;
22 | }
23 |
24 | ::placeholder {
25 | color: black;
26 | opacity: 0.3;
27 | }
28 |
29 | .swiper-pagination-bullet-active {
30 | background-color: #6969FF !important;
31 | }
32 | .swiper-pagination-bullet {
33 | background-color: #C8C8FF;
34 | @media screen and (max-width: 600px) {
35 | width: 0.4rem;
36 | height: 0.4rem;
37 | }
38 | }
39 |
40 | .swiper-button-next,
41 | .swiper-button-prev {
42 | color: white !important;
43 | padding-bottom: 20px;
44 | }
45 |
46 | .swiper-wrapper{
47 | padding-bottom: 30px;
48 | @media screen and (max-width: 600px) {
49 | padding-bottom: 40px;
50 | }
51 | }
52 |
53 | .swiper-scrollbar {
54 | background-color: #28273A;
55 | }
56 |
57 | .swiper-scrollbar-drag {
58 | background-color: #3D3B5B;
59 | }
60 |
61 | .swiper-container-horizontal>.swiper-pagination-bullets, .swiper-pagination-custom, .swiper-pagination-fraction{
62 | bottom: 0px !important;
63 | }
64 |
65 | @media only screen and (max-width: 600px) {
66 | .plyr button {
67 | font-size: 0.8rem;
68 | }
69 | }
70 |
71 | .skip-button {
72 | padding: 0.3rem 1rem;
73 | position: absolute;
74 | top: -1.5rem;
75 | right: 3rem;
76 | border-radius: 0.3rem;
77 | border: 1px solid rgba(255, 255, 255, 0.4);
78 | outline: none;
79 | cursor: pointer;
80 | background-color: rgba(0, 0, 0, 0.6);
81 | color: white;
82 | }
83 |
84 | .plyr__poster {
85 | background-size: cover;
86 | }
87 |
88 | .plyr {
89 | aspect-ratio: 16 / 9;
90 | }
91 | `;
92 |
93 | export default GlobalStyle;
94 |
--------------------------------------------------------------------------------