├── src ├── Pages │ ├── Search │ │ ├── Search.css │ │ └── Search.js │ ├── Trending │ │ ├── Trending.css │ │ └── Trending.js │ ├── Series │ │ └── Series.js │ └── Movies │ │ └── Movies.js ├── hooks │ └── useGenre.js ├── components │ ├── Header │ │ ├── Header.js │ │ └── Header.css │ ├── Carousel │ │ ├── Carousel.css │ │ └── Carousel.js │ ├── SingleContent │ │ ├── SingleContent.css │ │ └── SingleContent.js │ ├── Pagination │ │ └── CustomPagination.js │ ├── Genres │ │ └── Genres.js │ ├── ContentModal │ │ ├── ContentModal.css │ │ └── ContentModal.js │ └── MainNav.js ├── index.js ├── config │ └── config.js ├── App.css ├── index.css ├── App.js ├── service-worker.js └── serviceWorkerRegistration.js ├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo256.png ├── logo384.png ├── logo512.png ├── maskable.png ├── favicon-16x16.png ├── favicon-32x32.png ├── apple-touch-icon.png ├── site.webmanifest ├── index.html └── manifest.json ├── README.md ├── .gitignore ├── package.json └── .eslintcache /src/Pages/Search/Search.css: -------------------------------------------------------------------------------- 1 | .search { 2 | display: flex; 3 | margin: 15px 0; 4 | } 5 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo256.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/logo256.png -------------------------------------------------------------------------------- /public/logo384.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/logo384.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/maskable.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/maskable.png -------------------------------------------------------------------------------- /public/favicon-16x16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/favicon-16x16.png -------------------------------------------------------------------------------- /public/favicon-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/favicon-32x32.png -------------------------------------------------------------------------------- /public/apple-touch-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/piyush-eon/react-entertainment-hub/HEAD/public/apple-touch-icon.png -------------------------------------------------------------------------------- /src/Pages/Trending/Trending.css: -------------------------------------------------------------------------------- 1 | .trending { 2 | display: flex; 3 | flex-wrap: wrap; 4 | justify-content: space-around; 5 | } 6 | -------------------------------------------------------------------------------- /src/hooks/useGenre.js: -------------------------------------------------------------------------------- 1 | const useGenre = (selectedGenres) => { 2 | if (selectedGenres.length < 1) return ""; 3 | 4 | const GenreIds = selectedGenres.map((g) => g.id); 5 | return GenreIds.reduce((acc, curr) => acc + "," + curr); 6 | }; 7 | 8 | export default useGenre; 9 | -------------------------------------------------------------------------------- /public/site.webmanifest: -------------------------------------------------------------------------------- 1 | {"name":"","short_name":"","icons":[{"src":"/android-chrome-192x192.png","sizes":"192x192","type":"image/png"},{"src":"/android-chrome-512x512.png","sizes":"512x512","type":"image/png"}],"theme_color":"#ffffff","background_color":"#ffffff","display":"standalone"} -------------------------------------------------------------------------------- /src/components/Header/Header.js: -------------------------------------------------------------------------------- 1 | import "./Header.css"; 2 | 3 | const Header = () => { 4 | return ( 5 | window.scroll(0, 0)} className="header"> 6 | 🎬 Entertainment Hub 🎥 7 | 8 | ); 9 | }; 10 | 11 | export default Header; 12 | -------------------------------------------------------------------------------- /src/components/Carousel/Carousel.css: -------------------------------------------------------------------------------- 1 | .carouselItem { 2 | display: flex; 3 | flex-direction: column; 4 | object-fit: contain; 5 | padding: 10px; 6 | } 7 | .carouselItem__img { 8 | border-radius: 10px; 9 | margin-bottom: 5px; 10 | box-shadow: 0px 0px 5px black; 11 | } 12 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import * as serviceWorkerRegistration from "./serviceWorkerRegistration"; 6 | 7 | ReactDOM.render(, document.getElementById("root")); 8 | 9 | serviceWorkerRegistration.register(); 10 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Entertainment Hub 2 | 3 | 4 | 7 | 8 |
5 | Movie App built in React JS and Material UI. 6 |
9 | 10 | ## [Click Here to Watch Full tutorial on Youtube](https://www.youtube.com/watch?v=IQXjO0t4XRM&list=PLKhlp2qtUcSYC7EffnHzD-Ws2xG-j3aYo) 11 | 12 | ![MOVIE APP](https://user-images.githubusercontent.com/51760520/124705920-1172ac80-df14-11eb-9568-1e91968b1273.png) 13 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/components/Header/Header.css: -------------------------------------------------------------------------------- 1 | .header { 2 | width: 100%; 3 | cursor: pointer; 4 | position: fixed; 5 | display: flex; 6 | justify-content: center; 7 | text-transform: uppercase; 8 | background-color: #39445a; 9 | font-family: "Montserrat", sans-serif; 10 | font-size: 5vw; 11 | padding-bottom: 15px; 12 | box-shadow: 0px 1px 5px black; 13 | color: white; 14 | z-index: 100; 15 | } 16 | 17 | @media (max-width: 1000px) { 18 | .header { 19 | padding-top: 15px; 20 | font-size: 6.4vw; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/config/config.js: -------------------------------------------------------------------------------- 1 | //image sizes for tmdb 2 | export const img_300 = "https://image.tmdb.org/t/p/w300"; 3 | export const img_500 = "https://image.tmdb.org/t/p/w500"; 4 | 5 | // contentModal and singleContent 6 | export const unavailable = 7 | "https://www.movienewz.com/img/films/poster-holder.jpg"; 8 | 9 | // contentModal 10 | export const unavailableLandscape = 11 | "https://user-images.githubusercontent.com/10515204/56117400-9a911800-5f85-11e9-878b-3f998609a6c8.jpg"; 12 | 13 | // For Carousel 14 | export const noPicture = 15 | "https://upload.wikimedia.org/wikipedia/en/6/60/No_Picture.jpg"; 16 | -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | * { 2 | scroll-behavior: smooth; 3 | } 4 | 5 | .app { 6 | min-height: 100vh; 7 | background-color: #39445a; 8 | color: white; 9 | padding-top: 130px; 10 | padding-bottom: 70px; 11 | } 12 | 13 | @media (max-width: 700px) { 14 | .app { 15 | padding-top: 70px; 16 | } 17 | } 18 | 19 | .pageTitle { 20 | text-transform: uppercase; 21 | display: flex; 22 | justify-content: center; 23 | font-family: "Montserrat", sans-serif; 24 | font-size: 2vw; 25 | padding: 4px; 26 | border-radius: 50px; 27 | color: white; 28 | } 29 | 30 | @media (max-width: 1000px) { 31 | .pageTitle { 32 | font-size: 6.4vw; 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Montserrat:wght@100&display=swap"); 2 | @import "react-alice-carousel/lib/alice-carousel.css"; 3 | 4 | a { 5 | text-decoration: none; 6 | color: inherit; 7 | } 8 | 9 | body { 10 | margin: 0; 11 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 12 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 13 | sans-serif; 14 | -webkit-font-smoothing: antialiased; 15 | -moz-osx-font-smoothing: grayscale; 16 | } 17 | 18 | code { 19 | font-family: source-code-pro, Menlo, Monaco, Consolas, "Courier New", 20 | monospace; 21 | } 22 | -------------------------------------------------------------------------------- /src/components/SingleContent/SingleContent.css: -------------------------------------------------------------------------------- 1 | .media { 2 | display: flex; 3 | flex-direction: column; 4 | width: 200px; 5 | padding: 5px; 6 | margin: 5px 0; 7 | background-color: #282c34; 8 | border-radius: 10px; 9 | position: relative; 10 | font-family: "Montserrat", sans-serif; 11 | } 12 | 13 | .media:hover { 14 | background-color: white; 15 | color: black; 16 | } 17 | 18 | @media (max-width: 550px) { 19 | .media { 20 | width: 46%; 21 | } 22 | } 23 | 24 | .poster { 25 | border-radius: 10px; 26 | } 27 | 28 | .title { 29 | width: 100%; 30 | text-align: center; 31 | font-size: 17px; 32 | padding: 8px 0; 33 | } 34 | 35 | .subTitle { 36 | display: flex; 37 | justify-content: space-between; 38 | padding-bottom: 3px; 39 | padding: 0 2px; 40 | padding-bottom: 3px; 41 | } 42 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 14 | 18 | 19 | Entertainment Hub 20 | 21 | 22 | 23 |
24 | 25 | 26 | -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "Entertainment Hub", 3 | "short_name": "Entertainment Hub", 4 | "theme_color": "#000000", 5 | "background_color": "#000000", 6 | "display": "standalone", 7 | "scope": "/", 8 | "start_url": "/", 9 | "icons": [ 10 | { 11 | "src": "favicon.ico", 12 | "sizes": "64x64 32x32 24x24 16x16", 13 | "type": "image/x-icon" 14 | }, 15 | { 16 | "src": "maskable.png", 17 | "sizes": "196x196", 18 | "type": "image/png", 19 | "purpose": "any maskable" 20 | }, 21 | { 22 | "src": "logo192.png", 23 | "sizes": "192x192", 24 | "type": "image/png" 25 | }, 26 | { 27 | "src": "logo256.png", 28 | "sizes": "256x256", 29 | "type": "image/png" 30 | }, 31 | { 32 | "src": "logo384.png", 33 | "sizes": "384x384", 34 | "type": "image/png" 35 | }, 36 | { 37 | "src": "logo512.png", 38 | "sizes": "512x512", 39 | "type": "image/png" 40 | } 41 | ] 42 | } 43 | -------------------------------------------------------------------------------- /src/components/SingleContent/SingleContent.js: -------------------------------------------------------------------------------- 1 | import { Badge } from "@material-ui/core"; 2 | import { img_300, unavailable } from "../../config/config"; 3 | import "./SingleContent.css"; 4 | import ContentModal from "../ContentModal/ContentModal"; 5 | 6 | const SingleContent = ({ 7 | id, 8 | poster, 9 | title, 10 | date, 11 | media_type, 12 | vote_average, 13 | }) => { 14 | return ( 15 | 16 | 6 ? "primary" : "secondary"} 19 | /> 20 | {title} 25 | {title} 26 | 27 | {media_type === "tv" ? "TV Series" : "Movie"} 28 | {date} 29 | 30 | 31 | ); 32 | }; 33 | 34 | export default SingleContent; 35 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { BrowserRouter, Route, Switch } from "react-router-dom"; 2 | import "./App.css"; 3 | import Header from "./components/Header/Header"; 4 | import SimpleBottomNavigation from "./components/MainNav"; 5 | import Movies from "./Pages/Movies/Movies"; 6 | import Series from "./Pages/Series/Series"; 7 | import Trending from "./Pages/Trending/Trending"; 8 | import Search from "./Pages/Search/Search"; 9 | import { Container } from "@material-ui/core"; 10 | 11 | function App() { 12 | return ( 13 | 14 |
15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 |
25 | 26 | 27 | ); 28 | } 29 | 30 | export default App; 31 | -------------------------------------------------------------------------------- /src/components/Pagination/CustomPagination.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import Pagination from "@material-ui/lab/Pagination"; 3 | import { createMuiTheme, ThemeProvider } from "@material-ui/core"; 4 | 5 | const darkTheme = createMuiTheme({ 6 | palette: { 7 | type: "dark", 8 | }, 9 | }); 10 | 11 | export default function CustomPagination({ setPage, numOfPages = 10 }) { 12 | // Scroll to top when page changes 13 | const handlePageChange = (page) => { 14 | setPage(page); 15 | window.scroll(0, 0); 16 | }; 17 | 18 | return ( 19 |
27 | 28 | handlePageChange(e.target.textContent)} 30 | count={numOfPages} 31 | color="primary" 32 | hideNextButton 33 | hidePrevButton 34 | /> 35 | 36 |
37 | ); 38 | } 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movie-series-app", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.2", 7 | "@material-ui/icons": "^4.11.2", 8 | "@material-ui/lab": "^4.0.0-alpha.57", 9 | "@testing-library/jest-dom": "^5.11.4", 10 | "@testing-library/react": "^11.1.0", 11 | "@testing-library/user-event": "^12.1.10", 12 | "axios": "^0.21.1", 13 | "react": "^17.0.1", 14 | "react-alice-carousel": "^2.2.2", 15 | "react-dom": "^17.0.1", 16 | "react-router-dom": "^5.2.0", 17 | "react-scripts": "4.0.1", 18 | "web-vitals": "^0.2.4" 19 | }, 20 | "scripts": { 21 | "start": "react-scripts start", 22 | "build": "react-scripts build", 23 | "test": "react-scripts test", 24 | "eject": "react-scripts eject" 25 | }, 26 | "eslintConfig": { 27 | "extends": [ 28 | "react-app", 29 | "react-app/jest" 30 | ] 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.2%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 1 chrome version", 40 | "last 1 firefox version", 41 | "last 1 safari version" 42 | ] 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/Pages/Trending/Trending.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import "./Trending.css"; 3 | import { useEffect, useState } from "react"; 4 | import SingleContent from "../../components/SingleContent/SingleContent"; 5 | import CustomPagination from "../../components/Pagination/CustomPagination"; 6 | 7 | const Trending = () => { 8 | const [page, setPage] = useState(1); 9 | const [content, setContent] = useState([]); 10 | 11 | const fetchTrending = async () => { 12 | const { data } = await axios.get( 13 | `https://api.themoviedb.org/3/trending/all/day?api_key=${process.env.REACT_APP_API_KEY}&page=${page}` 14 | ); 15 | 16 | setContent(data.results); 17 | }; 18 | 19 | useEffect(() => { 20 | window.scroll(0, 0); 21 | fetchTrending(); 22 | // eslint-disable-next-line 23 | }, [page]); 24 | 25 | return ( 26 |
27 | Trending Today 28 |
29 | {content && 30 | content.map((c) => ( 31 | 40 | ))} 41 |
42 | 43 |
44 | ); 45 | }; 46 | 47 | export default Trending; 48 | -------------------------------------------------------------------------------- /src/components/Carousel/Carousel.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import React, { useEffect, useState } from "react"; 3 | import AliceCarousel from "react-alice-carousel"; 4 | import "react-alice-carousel/lib/alice-carousel.css"; 5 | import { img_300, noPicture } from "../../config/config"; 6 | import "./Carousel.css"; 7 | 8 | const handleDragStart = (e) => e.preventDefault(); 9 | 10 | const Gallery = ({ id, media_type }) => { 11 | const [credits, setCredits] = useState([]); 12 | 13 | const items = credits.map((c) => ( 14 |
15 | {c?.name} 21 | {c?.name} 22 |
23 | )); 24 | 25 | const responsive = { 26 | 0: { 27 | items: 3, 28 | }, 29 | 512: { 30 | items: 5, 31 | }, 32 | 1024: { 33 | items: 7, 34 | }, 35 | }; 36 | 37 | const fetchCredits = async () => { 38 | const { data } = await axios.get( 39 | `https://api.themoviedb.org/3/${media_type}/${id}/credits?api_key=${process.env.REACT_APP_API_KEY}&language=en-US` 40 | ); 41 | setCredits(data.cast); 42 | }; 43 | 44 | useEffect(() => { 45 | fetchCredits(); 46 | // eslint-disable-next-line 47 | }, []); 48 | 49 | return ( 50 | 59 | ); 60 | }; 61 | 62 | export default Gallery; 63 | -------------------------------------------------------------------------------- /src/components/Genres/Genres.js: -------------------------------------------------------------------------------- 1 | import { Chip } from "@material-ui/core"; 2 | import axios from "axios"; 3 | import { useEffect } from "react"; 4 | 5 | const Genres = ({ 6 | selectedGenres, 7 | setSelectedGenres, 8 | genres, 9 | setGenres, 10 | type, 11 | setPage, 12 | }) => { 13 | const handleAdd = (genre) => { 14 | setSelectedGenres([...selectedGenres, genre]); 15 | setGenres(genres.filter((g) => g.id !== genre.id)); 16 | setPage(1); 17 | }; 18 | 19 | const handleRemove = (genre) => { 20 | setSelectedGenres( 21 | selectedGenres.filter((selected) => selected.id !== genre.id) 22 | ); 23 | setGenres([...genres, genre]); 24 | setPage(1); 25 | }; 26 | 27 | const fetchGenres = async () => { 28 | const { data } = await axios.get( 29 | `https://api.themoviedb.org/3/genre/${type}/list?api_key=${process.env.REACT_APP_API_KEY}&language=en-US` 30 | ); 31 | setGenres(data.genres); 32 | }; 33 | 34 | useEffect(() => { 35 | fetchGenres(); 36 | 37 | return () => { 38 | setGenres({}); // unmounting 39 | }; 40 | // eslint-disable-next-line 41 | }, []); 42 | 43 | return ( 44 |
45 | {selectedGenres.map((genre) => ( 46 | handleRemove(genre)} 54 | /> 55 | ))} 56 | {genres.map((genre) => ( 57 | handleAdd(genre)} 64 | /> 65 | ))} 66 |
67 | ); 68 | }; 69 | 70 | export default Genres; 71 | -------------------------------------------------------------------------------- /src/components/ContentModal/ContentModal.css: -------------------------------------------------------------------------------- 1 | @import url("https://fonts.googleapis.com/css2?family=Roboto:wght@100&display=swap"); 2 | 3 | .ContentModal__landscape { 4 | object-fit: contain; 5 | border-radius: 10px; 6 | } 7 | 8 | .ContentModal__portrait { 9 | display: none; 10 | object-fit: contain; 11 | border-radius: 10px; 12 | } 13 | 14 | .tagline { 15 | padding-bottom: 10px; 16 | align-self: center; 17 | } 18 | 19 | .ContentModal { 20 | display: flex; 21 | flex-direction: column; 22 | justify-content: space-between; 23 | height: 100%; 24 | width: 100%; 25 | overflow-y: scroll; 26 | scrollbar-width: none; 27 | } 28 | 29 | .ContentModal::-webkit-scrollbar { 30 | display: none; 31 | } 32 | 33 | .ContentModal__about { 34 | padding: 10px; 35 | width: 95%; 36 | height: 90%; 37 | display: flex; 38 | flex-direction: column; 39 | font-family: "Roboto", sans-serif; 40 | justify-content: space-evenly; 41 | font-weight: 300; 42 | } 43 | 44 | .ContentModal__title { 45 | height: 12%; 46 | font-size: 5vw; 47 | display: flex; 48 | align-items: center; 49 | justify-content: center; 50 | } 51 | 52 | .ContentModal__description { 53 | display: flex; 54 | height: 40%; 55 | overflow-y: scroll; 56 | padding: 15px; 57 | border-radius: 20px; 58 | scrollbar-width: thin; /* Firefox */ 59 | box-shadow: inset 0 0 5px #000000; 60 | text-align: justify; 61 | } 62 | 63 | .ContentModal__description::-webkit-scrollbar { 64 | display: none; 65 | } 66 | 67 | @media (min-width: 835px) { 68 | .ContentModal__landscape { 69 | display: none; 70 | } 71 | .ContentModal__portrait { 72 | display: flex; 73 | width: 38%; 74 | } 75 | .ContentModal { 76 | flex-direction: row; 77 | justify-content: space-around; 78 | padding: 10px 0; 79 | } 80 | .ContentModal__about { 81 | width: 58%; 82 | padding: 0; 83 | height: 100%; 84 | } 85 | .ContentModal__title { 86 | font-size: 3.5vw; 87 | } 88 | .ContentModal__description { 89 | font-size: 22px; 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /src/components/MainNav.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import BottomNavigation from "@material-ui/core/BottomNavigation"; 4 | import BottomNavigationAction from "@material-ui/core/BottomNavigationAction"; 5 | import TvIcon from "@material-ui/icons/Tv"; 6 | import MovieIcon from "@material-ui/icons/Movie"; 7 | import SearchIcon from "@material-ui/icons/Search"; 8 | import WhatshotIcon from "@material-ui/icons/Whatshot"; 9 | import { useHistory } from "react-router-dom"; 10 | 11 | const useStyles = makeStyles({ 12 | root: { 13 | width: "100%", 14 | position: "fixed", 15 | bottom: 0, 16 | backgroundColor: "#2d313a", 17 | zIndex: 100, 18 | }, 19 | }); 20 | 21 | export default function SimpleBottomNavigation() { 22 | const classes = useStyles(); 23 | const [value, setValue] = React.useState(0); 24 | const history = useHistory(); 25 | 26 | useEffect(() => { 27 | if (value === 0) { 28 | history.push("/"); 29 | } else if (value === 1) { 30 | history.push("/movies"); 31 | } else if (value === 2) { 32 | history.push("/series"); 33 | } else if (value === 3) { 34 | history.push("/search"); 35 | } 36 | }, [value, history]); 37 | 38 | return ( 39 | { 42 | setValue(newValue); 43 | }} 44 | showLabels 45 | className={classes.root} 46 | > 47 | } 51 | /> 52 | } 56 | /> 57 | } 61 | /> 62 | } 66 | /> 67 | 68 | ); 69 | } 70 | -------------------------------------------------------------------------------- /src/Pages/Series/Series.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import Genres from "../../components/Genres/Genres"; 4 | import CustomPagination from "../../components/Pagination/CustomPagination"; 5 | import SingleContent from "../../components/SingleContent/SingleContent"; 6 | import useGenre from "../../hooks/useGenre"; 7 | 8 | const Series = () => { 9 | const [genres, setGenres] = useState([]); 10 | const [selectedGenres, setSelectedGenres] = useState([]); 11 | const [page, setPage] = useState(1); 12 | const [content, setContent] = useState([]); 13 | const [numOfPages, setNumOfPages] = useState(); 14 | const genreforURL = useGenre(selectedGenres); 15 | 16 | const fetchSeries = async () => { 17 | const { data } = await axios.get( 18 | `https://api.themoviedb.org/3/discover/tv?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}&with_genres=${genreforURL}` 19 | ); 20 | setContent(data.results); 21 | setNumOfPages(data.total_pages); 22 | // console.log(data); 23 | }; 24 | 25 | useEffect(() => { 26 | window.scroll(0, 0); 27 | fetchSeries(); 28 | // eslint-disable-next-line 29 | }, [genreforURL, page]); 30 | 31 | return ( 32 |
33 | Discover Series 34 | 42 |
43 | {content && 44 | content.map((c) => ( 45 | 54 | ))} 55 |
56 | {numOfPages > 1 && ( 57 | 58 | )} 59 |
60 | ); 61 | }; 62 | 63 | export default Series; 64 | -------------------------------------------------------------------------------- /src/Pages/Movies/Movies.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { useEffect, useState } from "react"; 3 | import Genres from "../../components/Genres/Genres"; 4 | import SingleContent from "../../components/SingleContent/SingleContent"; 5 | import useGenre from "../../hooks/useGenre"; 6 | import CustomPagination from "../../components/Pagination/CustomPagination"; 7 | 8 | const Movies = () => { 9 | const [genres, setGenres] = useState([]); 10 | const [selectedGenres, setSelectedGenres] = useState([]); 11 | const [page, setPage] = useState(1); 12 | const [content, setContent] = useState([]); 13 | const [numOfPages, setNumOfPages] = useState(); 14 | const genreforURL = useGenre(selectedGenres); 15 | // console.log(selectedGenres); 16 | 17 | const fetchMovies = async () => { 18 | const { data } = await axios.get( 19 | `https://api.themoviedb.org/3/discover/movie?api_key=${process.env.REACT_APP_API_KEY}&language=en-US&sort_by=popularity.desc&include_adult=false&include_video=false&page=${page}&with_genres=${genreforURL}` 20 | ); 21 | setContent(data.results); 22 | setNumOfPages(data.total_pages); 23 | }; 24 | 25 | useEffect(() => { 26 | window.scroll(0, 0); 27 | fetchMovies(); 28 | // eslint-disable-next-line 29 | }, [genreforURL, page]); 30 | 31 | return ( 32 |
33 | Discover Movies 34 | 42 |
43 | {content && 44 | content.map((c) => ( 45 | 54 | ))} 55 |
56 | {numOfPages > 1 && ( 57 | 58 | )} 59 |
60 | ); 61 | }; 62 | 63 | export default Movies; 64 | -------------------------------------------------------------------------------- /src/service-worker.js: -------------------------------------------------------------------------------- 1 | /* eslint-disable no-restricted-globals */ 2 | 3 | // This service worker can be customized! 4 | // See https://developers.google.com/web/tools/workbox/modules 5 | // for the list of available Workbox modules, or add any other 6 | // code you'd like. 7 | // You can also remove this file if you'd prefer not to use a 8 | // service worker, and the Workbox build step will be skipped. 9 | 10 | import { clientsClaim } from "workbox-core"; 11 | import { ExpirationPlugin } from "workbox-expiration"; 12 | import { precacheAndRoute, createHandlerBoundToURL } from "workbox-precaching"; 13 | import { registerRoute } from "workbox-routing"; 14 | import { StaleWhileRevalidate } from "workbox-strategies"; 15 | 16 | clientsClaim(); 17 | 18 | // Precache all of the assets generated by your build process. 19 | // Their URLs are injected into the manifest variable below. 20 | // This variable must be present somewhere in your service worker file, 21 | // even if you decide not to use precaching. See https://cra.link/PWA 22 | precacheAndRoute(self.__WB_MANIFEST); 23 | 24 | // Set up App Shell-style routing, so that all navigation requests 25 | // are fulfilled with your index.html shell. Learn more at 26 | // https://developers.google.com/web/fundamentals/architecture/app-shell 27 | const fileExtensionRegexp = new RegExp("/[^/?]+\\.[^/]+$"); 28 | registerRoute( 29 | // Return false to exempt requests from being fulfilled by index.html. 30 | ({ request, url }) => { 31 | // If this isn't a navigation, skip. 32 | if (request.mode !== "navigate") { 33 | return false; 34 | } // If this is a URL that starts with /_, skip. 35 | 36 | if (url.pathname.startsWith("/_")) { 37 | return false; 38 | } // If this looks like a URL for a resource, because it contains // a file extension, skip. 39 | 40 | if (url.pathname.match(fileExtensionRegexp)) { 41 | return false; 42 | } // Return true to signal that we want to use the handler. 43 | 44 | return true; 45 | }, 46 | createHandlerBoundToURL(process.env.PUBLIC_URL + "/index.html") 47 | ); 48 | 49 | // An example runtime caching route for requests that aren't handled by the 50 | // precache, in this case same-origin .png requests like those from in public/ 51 | registerRoute( 52 | // Add in any other file extensions or routing criteria as needed. 53 | ({ url }) => 54 | url.origin === self.location.origin && url.pathname.endsWith(".png"), // Customize this strategy as needed, e.g., by changing to CacheFirst. 55 | new StaleWhileRevalidate({ 56 | cacheName: "images", 57 | plugins: [ 58 | // Ensure that once this runtime cache reaches a maximum size the 59 | // least-recently used images are removed. 60 | new ExpirationPlugin({ maxEntries: 50 }), 61 | ], 62 | }) 63 | ); 64 | 65 | // This allows the web app to trigger skipWaiting via 66 | // registration.waiting.postMessage({type: 'SKIP_WAITING'}) 67 | self.addEventListener("message", (event) => { 68 | if (event.data && event.data.type === "SKIP_WAITING") { 69 | self.skipWaiting(); 70 | } 71 | }); 72 | 73 | // Any other custom service worker logic can go here. 74 | -------------------------------------------------------------------------------- /src/Pages/Search/Search.js: -------------------------------------------------------------------------------- 1 | import { 2 | Button, 3 | createMuiTheme, 4 | Tab, 5 | Tabs, 6 | TextField, 7 | ThemeProvider, 8 | } from "@material-ui/core"; 9 | import "./Search.css"; 10 | import SearchIcon from "@material-ui/icons/Search"; 11 | import { useEffect, useState } from "react"; 12 | import axios from "axios"; 13 | import CustomPagination from "../../components/Pagination/CustomPagination"; 14 | import SingleContent from "../../components/SingleContent/SingleContent"; 15 | 16 | const Search = () => { 17 | const [type, setType] = useState(0); 18 | const [searchText, setSearchText] = useState(""); 19 | const [page, setPage] = useState(1); 20 | const [content, setContent] = useState([]); 21 | const [numOfPages, setNumOfPages] = useState(); 22 | 23 | const darkTheme = createMuiTheme({ 24 | palette: { 25 | type: "dark", 26 | primary: { 27 | main: "#fff", 28 | }, 29 | }, 30 | }); 31 | 32 | const fetchSearch = async () => { 33 | try { 34 | const { data } = await axios.get( 35 | `https://api.themoviedb.org/3/search/${type ? "tv" : "movie"}?api_key=${ 36 | process.env.REACT_APP_API_KEY 37 | }&language=en-US&query=${searchText}&page=${page}&include_adult=false` 38 | ); 39 | setContent(data.results); 40 | setNumOfPages(data.total_pages); 41 | // console.log(data); 42 | } catch (error) { 43 | console.error(error); 44 | } 45 | }; 46 | 47 | useEffect(() => { 48 | window.scroll(0, 0); 49 | fetchSearch(); 50 | // eslint-disable-next-line 51 | }, [type, page]); 52 | 53 | return ( 54 |
55 | 56 |
57 | setSearchText(e.target.value)} 63 | /> 64 | 71 |
72 | { 77 | setType(newValue); 78 | setPage(1); 79 | }} 80 | style={{ paddingBottom: 5 }} 81 | aria-label="disabled tabs example" 82 | > 83 | 84 | 85 | 86 |
87 |
88 | {content && 89 | content.map((c) => ( 90 | 99 | ))} 100 | {searchText && 101 | !content && 102 | (type ?

No Series Found

:

No Movies Found

)} 103 |
104 | {numOfPages > 1 && ( 105 | 106 | )} 107 |
108 | ); 109 | }; 110 | 111 | export default Search; 112 | -------------------------------------------------------------------------------- /.eslintcache: -------------------------------------------------------------------------------- 1 | [{"E:\\Coding\\React\\movie-series-app\\src\\index.js":"1","E:\\Coding\\React\\movie-series-app\\src\\App.js":"2","E:\\Coding\\React\\movie-series-app\\src\\components\\MainNav.js":"3","E:\\Coding\\React\\movie-series-app\\src\\components\\Header\\Header.js":"4","E:\\Coding\\React\\movie-series-app\\src\\Pages\\Movies\\Movies.js":"5","E:\\Coding\\React\\movie-series-app\\src\\Pages\\Trending\\Trending.js":"6","E:\\Coding\\React\\movie-series-app\\src\\Pages\\Series\\Series.js":"7","E:\\Coding\\React\\movie-series-app\\src\\Pages\\Search\\Search.js":"8","E:\\Coding\\React\\movie-series-app\\src\\components\\SingleContent\\SingleContent.js":"9","E:\\Coding\\React\\movie-series-app\\src\\components\\Genres\\Genres.js":"10","E:\\Coding\\React\\movie-series-app\\src\\config\\config.js":"11","E:\\Coding\\React\\movie-series-app\\src\\hooks\\useGenre.js":"12","E:\\Coding\\React\\movie-series-app\\src\\components\\Pagination\\CustomPagination.js":"13","E:\\Coding\\React\\movie-series-app\\src\\components\\ContentModal\\ContentModal.js":"14","E:\\Coding\\React\\movie-series-app\\src\\components\\Carousel\\Carousel.js":"15","E:\\Coding\\React\\movie-series-app\\src\\serviceWorkerRegistration.js":"16"},{"size":281,"mtime":1611921081417,"results":"17","hashOfConfig":"18"},{"size":917,"mtime":1611921184693,"results":"19","hashOfConfig":"18"},{"size":1887,"mtime":1611476354015,"results":"20","hashOfConfig":"18"},{"size":213,"mtime":1611919014871,"results":"21","hashOfConfig":"18"},{"size":2070,"mtime":1612849643293,"results":"22","hashOfConfig":"18"},{"size":1334,"mtime":1612758101307,"results":"23","hashOfConfig":"18"},{"size":2053,"mtime":1612758097059,"results":"24","hashOfConfig":"18"},{"size":3175,"mtime":1612849308927,"results":"25","hashOfConfig":"18"},{"size":875,"mtime":1611918061947,"results":"26","hashOfConfig":"18"},{"size":1663,"mtime":1611918589254,"results":"27","hashOfConfig":"18"},{"size":543,"mtime":1612876479736,"results":"28","hashOfConfig":"18"},{"size":232,"mtime":1611569176034,"results":"29","hashOfConfig":"18"},{"size":901,"mtime":1612444744991,"results":"30","hashOfConfig":"18"},{"size":4557,"mtime":1612875542038,"results":"31","hashOfConfig":"18"},{"size":1479,"mtime":1611918579659,"results":"32","hashOfConfig":"18"},{"size":5092,"mtime":1611921187595,"results":"33","hashOfConfig":"18"},{"filePath":"34","messages":"35","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"43nqr1",{"filePath":"36","messages":"37","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"38","messages":"39","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"40","messages":"41","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"42","messages":"43","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"44","messages":"45","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"46","messages":"47","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"48","messages":"49","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"50","messages":"51","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"52","messages":"53","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"54","messages":"55","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"56","messages":"57","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"58","messages":"59","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"60","messages":"61","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"62","messages":"63","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},{"filePath":"64","messages":"65","errorCount":0,"warningCount":0,"fixableErrorCount":0,"fixableWarningCount":0},"E:\\Coding\\React\\movie-series-app\\src\\index.js",[],"E:\\Coding\\React\\movie-series-app\\src\\App.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\MainNav.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\Header\\Header.js",[],"E:\\Coding\\React\\movie-series-app\\src\\Pages\\Movies\\Movies.js",[],"E:\\Coding\\React\\movie-series-app\\src\\Pages\\Trending\\Trending.js",[],"E:\\Coding\\React\\movie-series-app\\src\\Pages\\Series\\Series.js",[],"E:\\Coding\\React\\movie-series-app\\src\\Pages\\Search\\Search.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\SingleContent\\SingleContent.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\Genres\\Genres.js",[],"E:\\Coding\\React\\movie-series-app\\src\\config\\config.js",[],"E:\\Coding\\React\\movie-series-app\\src\\hooks\\useGenre.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\Pagination\\CustomPagination.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\ContentModal\\ContentModal.js",[],"E:\\Coding\\React\\movie-series-app\\src\\components\\Carousel\\Carousel.js",[],"E:\\Coding\\React\\movie-series-app\\src\\serviceWorkerRegistration.js",[]] -------------------------------------------------------------------------------- /src/components/ContentModal/ContentModal.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import { makeStyles } from "@material-ui/core/styles"; 3 | import Modal from "@material-ui/core/Modal"; 4 | import Backdrop from "@material-ui/core/Backdrop"; 5 | import Fade from "@material-ui/core/Fade"; 6 | import axios from "axios"; 7 | import { 8 | img_500, 9 | unavailable, 10 | unavailableLandscape, 11 | } from "../../config/config"; 12 | import "./ContentModal.css"; 13 | import { Button } from "@material-ui/core"; 14 | import YouTubeIcon from "@material-ui/icons/YouTube"; 15 | import Carousel from "../Carousel/Carousel"; 16 | 17 | const useStyles = makeStyles((theme) => ({ 18 | modal: { 19 | display: "flex", 20 | alignItems: "center", 21 | justifyContent: "center", 22 | }, 23 | paper: { 24 | width: "90%", 25 | height: "80%", 26 | backgroundColor: "#39445a", 27 | border: "1px solid #282c34", 28 | borderRadius: 10, 29 | color: "white", 30 | boxShadow: theme.shadows[5], 31 | padding: theme.spacing(1, 1, 3), 32 | }, 33 | })); 34 | 35 | export default function TransitionsModal({ children, media_type, id }) { 36 | const classes = useStyles(); 37 | const [open, setOpen] = useState(false); 38 | const [content, setContent] = useState(); 39 | const [video, setVideo] = useState(); 40 | 41 | const handleOpen = () => { 42 | setOpen(true); 43 | }; 44 | 45 | const handleClose = () => { 46 | setOpen(false); 47 | }; 48 | 49 | const fetchData = async () => { 50 | const { data } = await axios.get( 51 | `https://api.themoviedb.org/3/${media_type}/${id}?api_key=${process.env.REACT_APP_API_KEY}&language=en-US` 52 | ); 53 | 54 | setContent(data); 55 | // console.log(data); 56 | }; 57 | 58 | const fetchVideo = async () => { 59 | const { data } = await axios.get( 60 | `https://api.themoviedb.org/3/${media_type}/${id}/videos?api_key=${process.env.REACT_APP_API_KEY}&language=en-US` 61 | ); 62 | 63 | setVideo(data.results[0]?.key); 64 | }; 65 | 66 | useEffect(() => { 67 | fetchData(); 68 | fetchVideo(); 69 | // eslint-disable-next-line 70 | }, []); 71 | 72 | return ( 73 | <> 74 |
80 | {children} 81 |
82 | 94 | 95 | {content && ( 96 |
97 |
98 | {content.name 107 | {content.name 116 |
117 | 118 | {content.name || content.title} ( 119 | {( 120 | content.first_air_date || 121 | content.release_date || 122 | "-----" 123 | ).substring(0, 4)} 124 | ) 125 | 126 | {content.tagline && ( 127 | {content.tagline} 128 | )} 129 | 130 | 131 | {content.overview} 132 | 133 | 134 |
135 | 136 |
137 | 138 | 147 |
148 |
149 |
150 | )} 151 |
152 |
153 | 154 | ); 155 | } 156 | -------------------------------------------------------------------------------- /src/serviceWorkerRegistration.js: -------------------------------------------------------------------------------- 1 | // This optional code is used to register a service worker. 2 | // register() is not called by default. 3 | 4 | // This lets the app load faster on subsequent visits in production, and gives 5 | // it offline capabilities. However, it also means that developers (and users) 6 | // will only see deployed updates on subsequent visits to a page, after all the 7 | // existing tabs open on the page have been closed, since previously cached 8 | // resources are updated in the background. 9 | 10 | // To learn more about the benefits of this model and instructions on how to 11 | // opt-in, read https://cra.link/PWA 12 | 13 | const isLocalhost = Boolean( 14 | window.location.hostname === "localhost" || 15 | // [::1] is the IPv6 localhost address. 16 | window.location.hostname === "[::1]" || 17 | // 127.0.0.0/8 are considered localhost for IPv4. 18 | window.location.hostname.match( 19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/ 20 | ) 21 | ); 22 | 23 | export function register(config) { 24 | if (process.env.NODE_ENV === "production" && "serviceWorker" in navigator) { 25 | // The URL constructor is available in all browsers that support SW. 26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href); 27 | if (publicUrl.origin !== window.location.origin) { 28 | // Our service worker won't work if PUBLIC_URL is on a different origin 29 | // from what our page is served on. This might happen if a CDN is used to 30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374 31 | return; 32 | } 33 | 34 | window.addEventListener("load", () => { 35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`; 36 | 37 | if (isLocalhost) { 38 | // This is running on localhost. Let's check if a service worker still exists or not. 39 | checkValidServiceWorker(swUrl, config); 40 | 41 | // Add some additional logging to localhost, pointing developers to the 42 | // service worker/PWA documentation. 43 | navigator.serviceWorker.ready.then(() => { 44 | console.log( 45 | "This web app is being served cache-first by a service " + 46 | "worker. To learn more, visit https://cra.link/PWA" 47 | ); 48 | }); 49 | } else { 50 | // Is not localhost. Just register service worker 51 | registerValidSW(swUrl, config); 52 | } 53 | }); 54 | } 55 | } 56 | 57 | function registerValidSW(swUrl, config) { 58 | navigator.serviceWorker 59 | .register(swUrl) 60 | .then((registration) => { 61 | registration.onupdatefound = () => { 62 | const installingWorker = registration.installing; 63 | if (installingWorker == null) { 64 | return; 65 | } 66 | installingWorker.onstatechange = () => { 67 | if (installingWorker.state === "installed") { 68 | if (navigator.serviceWorker.controller) { 69 | // At this point, the updated precached content has been fetched, 70 | // but the previous service worker will still serve the older 71 | // content until all client tabs are closed. 72 | console.log( 73 | "New content is available and will be used when all " + 74 | "tabs for this page are closed. See https://cra.link/PWA." 75 | ); 76 | 77 | // Execute callback 78 | if (config && config.onUpdate) { 79 | config.onUpdate(registration); 80 | } 81 | } else { 82 | // At this point, everything has been precached. 83 | // It's the perfect time to display a 84 | // "Content is cached for offline use." message. 85 | console.log("Content is cached for offline use."); 86 | 87 | // Execute callback 88 | if (config && config.onSuccess) { 89 | config.onSuccess(registration); 90 | } 91 | } 92 | } 93 | }; 94 | }; 95 | }) 96 | .catch((error) => { 97 | console.error("Error during service worker registration:", error); 98 | }); 99 | } 100 | 101 | function checkValidServiceWorker(swUrl, config) { 102 | // Check if the service worker can be found. If it can't reload the page. 103 | fetch(swUrl, { 104 | headers: { "Service-Worker": "script" }, 105 | }) 106 | .then((response) => { 107 | // Ensure service worker exists, and that we really are getting a JS file. 108 | const contentType = response.headers.get("content-type"); 109 | if ( 110 | response.status === 404 || 111 | (contentType != null && contentType.indexOf("javascript") === -1) 112 | ) { 113 | // No service worker found. Probably a different app. Reload the page. 114 | navigator.serviceWorker.ready.then((registration) => { 115 | registration.unregister().then(() => { 116 | window.location.reload(); 117 | }); 118 | }); 119 | } else { 120 | // Service worker found. Proceed as normal. 121 | registerValidSW(swUrl, config); 122 | } 123 | }) 124 | .catch(() => { 125 | console.log( 126 | "No internet connection found. App is running in offline mode." 127 | ); 128 | }); 129 | } 130 | 131 | export function unregister() { 132 | if ("serviceWorker" in navigator) { 133 | navigator.serviceWorker.ready 134 | .then((registration) => { 135 | registration.unregister(); 136 | }) 137 | .catch((error) => { 138 | console.error(error.message); 139 | }); 140 | } 141 | } 142 | --------------------------------------------------------------------------------