├── LOGIN PAGE.png ├── MAIN PAGE.png ├── README.md ├── REGISTER PAGE.png ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── index.html ├── logo192.png ├── logo512.png ├── manifest.json └── robots.txt ├── src ├── App.js ├── assets │ ├── Sample(This is not my own video).mp4 │ ├── card.jpg │ ├── home.jpg │ ├── homeTitle.webp │ ├── login.jpg │ ├── logo-logomark.png │ └── logo.png ├── components │ ├── BackgroundImage.jsx │ ├── Card.jsx │ ├── CardSlider.jsx │ ├── Header.jsx │ ├── Navbar.jsx │ ├── NotAvailable.jsx │ ├── SelectGenre.jsx │ └── Slider.jsx ├── index.css ├── index.js ├── pages │ ├── Login.jsx │ ├── Movies.jsx │ ├── Netflix.jsx │ ├── Player.jsx │ ├── Signup.jsx │ ├── TVShows.jsx │ └── UserListedMovies.jsx ├── store │ └── index.js └── utils │ ├── constants.js │ └── firebase-config.js └── yarn.lock /LOGIN PAGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/LOGIN PAGE.png -------------------------------------------------------------------------------- /MAIN PAGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/MAIN PAGE.png -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 📺 Netflix UI Clone using React JS 🚀 2 | 3 | Dive into the React JS universe 🌐 and harness the power of modern web development to replicate Netflix's sleek user interface. Craft pixel-perfect components, fetch dynamic content 🎬, and employ responsive design techniques for an authentic streaming experience. Showcase your tech prowess and create the future of online entertainment! 🔥 4 | ## Features 5 | 6 | - Register Page 7 | - Login Page 8 | - Landing Page 9 | 10 | ## Installation 11 | 12 | 13 | ```bash 14 | - Go to the Project Directory 15 | - Open Terminal 16 | - "npm install" 17 | (Will install all the necessary packages for the document) 18 | ``` 19 | ## Tech Stack 20 | 21 | - HTML 22 | - CSS(Bootstrap) 23 | - React JS 24 | - Redux Toolkit 25 | 26 | 27 | ## Deployment 28 | 29 | To deploy this project run 30 | 31 | ```bash 32 | npm run start 33 | ``` 34 | 35 | ```bash 36 | Runs the app in the development mode.\ 37 | Open [http://localhost:3000](http://localhost:3000) to view it in your browser. 38 | 39 | The page will reload when you make changes.\ 40 | You may also see any lint errors in the console. 41 | ``` 42 | 43 | 44 | ## Screenshots 45 | REGISTER_PAGE: 46 | 47 | ![App Screenshot](/REGISTER%20PAGE.png) 48 | 49 | LOGIN_PAGE: 50 | 51 | ![App Screenshot](/LOGIN%20PAGE.png) 52 | 53 | MAIN PAGE: 54 | 55 | ![App Screenshot](/MAIN%20PAGE.png) 56 | 57 | ## Authors 58 | 59 | - [@WebRevo](https://github.com/WebRevo) 60 | - [@VarshiRevo](https://github.com/VarshiRevo) 61 | 62 | 63 | ## Support 64 | 65 | For support, 66 | Email : 67 | sarathi2021ai@gmail.com, 68 | varshigaps04.ai@gmail.com or 69 | 70 | Join our Slack channel: 71 | https://join.slack.com/t/team-revoshared_invitezt-23bhnca2e-VEbQiDF8c~1hUYVjsNa06Q 72 | 73 | 74 | 75 | -------------------------------------------------------------------------------- /REGISTER PAGE.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/REGISTER PAGE.png -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@reduxjs/toolkit": "^1.8.2", 7 | "@testing-library/jest-dom": "^5.16.4", 8 | "@testing-library/react": "^13.0.1", 9 | "@testing-library/user-event": "^13.5.0", 10 | "axios": "^0.27.2", 11 | "firebase": "^9.8.2", 12 | "react": "^18.0.0", 13 | "react-dom": "^18.0.0", 14 | "react-icons": "^4.3.1", 15 | "react-redux": "^8.0.2", 16 | "react-router-dom": "^6.3.0", 17 | "react-scripts": "5.0.1", 18 | "styled-components": "^5.3.5", 19 | "web-vitals": "^2.1.4" 20 | }, 21 | "scripts": { 22 | "start": "react-scripts start", 23 | "build": "react-scripts build", 24 | "test": "react-scripts test", 25 | "eject": "react-scripts eject" 26 | }, 27 | "eslintConfig": { 28 | "extends": [ 29 | "react-app", 30 | "react-app/jest" 31 | ] 32 | }, 33 | "browserslist": { 34 | "production": [ 35 | ">0.2%", 36 | "not dead", 37 | "not op_mini all" 38 | ], 39 | "development": [ 40 | "last 1 chrome version", 41 | "last 1 firefox version", 42 | "last 1 safari version" 43 | ] 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/public/logo512.png -------------------------------------------------------------------------------- /public/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "short_name": "React App", 3 | "name": "Create React App Sample", 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": "#000000", 24 | "background_color": "#ffffff" 25 | } 26 | -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { BrowserRouter, Route, Routes } from "react-router-dom"; 3 | import Login from "./pages/Login"; 4 | import MoviePage from "./pages/Movies"; 5 | import Netflix from "./pages/Netflix"; 6 | import Player from "./pages/Player"; 7 | import Signup from "./pages/Signup"; 8 | import TVShows from "./pages/TVShows"; 9 | import UserListedMovies from "./pages/UserListedMovies"; 10 | 11 | export default function App() { 12 | return ( 13 | 14 | 15 | } /> 16 | } /> 17 | } /> 18 | } /> 19 | } /> 20 | } /> 21 | } /> 22 | } /> 23 | 24 | 25 | ); 26 | } 27 | -------------------------------------------------------------------------------- /src/assets/Sample(This is not my own video).mp4: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/Sample(This is not my own video).mp4 -------------------------------------------------------------------------------- /src/assets/card.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/card.jpg -------------------------------------------------------------------------------- /src/assets/home.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/home.jpg -------------------------------------------------------------------------------- /src/assets/homeTitle.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/homeTitle.webp -------------------------------------------------------------------------------- /src/assets/login.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/login.jpg -------------------------------------------------------------------------------- /src/assets/logo-logomark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/logo-logomark.png -------------------------------------------------------------------------------- /src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WebRevo/NETFLIX_UI_CLONE/a9ebb5f4b9552d6b0de77cf4687e62a34fd0b02f/src/assets/logo.png -------------------------------------------------------------------------------- /src/components/BackgroundImage.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import background from "../assets/login.jpg"; 4 | 5 | export default function BackgroundImage() { 6 | return ( 7 | 8 | background 9 | 10 | ); 11 | } 12 | 13 | const Container = styled.div` 14 | height: 100vh; 15 | width: 100vw; 16 | img { 17 | height: 100vh; 18 | width: 100vw; 19 | } 20 | `; 21 | -------------------------------------------------------------------------------- /src/components/Card.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import styled from "styled-components"; 4 | import { IoPlayCircleSharp } from "react-icons/io5"; 5 | import { AiOutlinePlus } from "react-icons/ai"; 6 | import { RiThumbUpFill, RiThumbDownFill } from "react-icons/ri"; 7 | import { BiChevronDown } from "react-icons/bi"; 8 | import { BsCheck } from "react-icons/bs"; 9 | import axios from "axios"; 10 | import { onAuthStateChanged } from "firebase/auth"; 11 | import { firebaseAuth } from "../utils/firebase-config"; 12 | import { useDispatch } from "react-redux"; 13 | import { removeMovieFromLiked } from "../store"; 14 | import video from "../assets/Sample(This is not my own video).mp4"; 15 | 16 | export default React.memo(function Card({ index, movieData, isLiked = false }) { 17 | const navigate = useNavigate(); 18 | const dispatch = useDispatch(); 19 | const [isHovered, setIsHovered] = useState(false); 20 | const [email, setEmail] = useState(undefined); 21 | 22 | onAuthStateChanged(firebaseAuth, (currentUser) => { 23 | if (currentUser) { 24 | setEmail(currentUser.email); 25 | } else navigate("/login"); 26 | }); 27 | 28 | const addToList = async () => { 29 | try { 30 | await axios.post("http://localhost:5000/api/user/add", { 31 | email, 32 | data: movieData, 33 | }); 34 | } catch (error) { 35 | console.log(error); 36 | } 37 | }; 38 | 39 | return ( 40 | setIsHovered(true)} 42 | onMouseLeave={() => setIsHovered(false)} 43 | > 44 | card navigate("/player")} 48 | /> 49 | 50 | {isHovered && ( 51 |
52 |
53 | card navigate("/player")} 57 | /> 58 |
66 |
67 |

navigate("/player")}> 68 | {movieData.name} 69 |

70 |
71 |
72 | navigate("/player")} 75 | /> 76 | 77 | 78 | {isLiked ? ( 79 | 82 | dispatch( 83 | removeMovieFromLiked({ movieId: movieData.id, email }) 84 | ) 85 | } 86 | /> 87 | ) : ( 88 | 89 | )} 90 |
91 |
92 | 93 |
94 |
95 |
96 |
    97 | {movieData.genres.map((genre) => ( 98 |
  • {genre}
  • 99 | ))} 100 |
101 |
102 |
103 |
104 | )} 105 |
106 | ); 107 | }); 108 | 109 | const Container = styled.div` 110 | max-width: 230px; 111 | width: 230px; 112 | height: 100%; 113 | cursor: pointer; 114 | position: relative; 115 | img { 116 | border-radius: 0.2rem; 117 | width: 100%; 118 | height: 100%; 119 | z-index: 10; 120 | } 121 | .hover { 122 | z-index: 99; 123 | height: max-content; 124 | width: 20rem; 125 | position: absolute; 126 | top: -18vh; 127 | left: 0; 128 | border-radius: 0.3rem; 129 | box-shadow: rgba(0, 0, 0, 0.75) 0px 3px 10px; 130 | background-color: #181818; 131 | transition: 0.3s ease-in-out; 132 | .image-video-container { 133 | position: relative; 134 | height: 140px; 135 | img { 136 | width: 100%; 137 | height: 140px; 138 | object-fit: cover; 139 | border-radius: 0.3rem; 140 | top: 0; 141 | z-index: 4; 142 | position: absolute; 143 | } 144 | video { 145 | width: 100%; 146 | height: 140px; 147 | object-fit: cover; 148 | border-radius: 0.3rem; 149 | top: 0; 150 | z-index: 5; 151 | position: absolute; 152 | } 153 | } 154 | .info-container { 155 | padding: 1rem; 156 | gap: 0.5rem; 157 | } 158 | .icons { 159 | .controls { 160 | display: flex; 161 | gap: 1rem; 162 | } 163 | svg { 164 | font-size: 2rem; 165 | cursor: pointer; 166 | transition: 0.3s ease-in-out; 167 | &:hover { 168 | color: #b8b8b8; 169 | } 170 | } 171 | } 172 | .genres { 173 | ul { 174 | gap: 1rem; 175 | li { 176 | padding-right: 0.7rem; 177 | &:first-of-type { 178 | list-style-type: none; 179 | } 180 | } 181 | } 182 | } 183 | } 184 | `; 185 | -------------------------------------------------------------------------------- /src/components/CardSlider.jsx: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import styled from "styled-components"; 3 | import { AiOutlineLeft, AiOutlineRight } from "react-icons/ai"; 4 | import Card from "./Card"; 5 | export default React.memo(function CardSlider({ data, title }) { 6 | const listRef = useRef(); 7 | const [sliderPosition, setSliderPosition] = useState(0); 8 | const [showControls, setShowControls] = useState(false); 9 | const handleDirection = (direction) => { 10 | let distance = listRef.current.getBoundingClientRect().x - 70; 11 | if (direction === "left" && sliderPosition > 0) { 12 | listRef.current.style.transform = `translateX(${230 + distance}px)`; 13 | setSliderPosition(sliderPosition - 1); 14 | } 15 | if (direction === "right" && sliderPosition < 4) { 16 | listRef.current.style.transform = `translateX(${-230 + distance}px)`; 17 | setSliderPosition(sliderPosition + 1); 18 | } 19 | }; 20 | 21 | return ( 22 | setShowControls(true)} 26 | onMouseLeave={() => setShowControls(false)} 27 | > 28 |

{title}

29 |
30 |
35 | handleDirection("left")} /> 36 |
37 |
38 | {data.map((movie, index) => { 39 | return ; 40 | })} 41 |
42 |
47 | handleDirection("right")} /> 48 |
49 |
50 |
51 | ); 52 | }); 53 | const Container = styled.div` 54 | gap: 1rem; 55 | position: relative; 56 | padding: 2rem 0; 57 | h1 { 58 | margin-left: 50px; 59 | } 60 | .wrapper { 61 | .slider { 62 | width: max-content; 63 | gap: 1rem; 64 | transform: translateX(0px); 65 | transition: 0.3s ease-in-out; 66 | margin-left: 50px; 67 | } 68 | .slider-action { 69 | position: absolute; 70 | z-index: 99; 71 | height: 100%; 72 | top: 0; 73 | bottom: 0; 74 | width: 50px; 75 | transition: 0.3s ease-in-out; 76 | svg { 77 | font-size: 2rem; 78 | } 79 | } 80 | .none { 81 | display: none; 82 | } 83 | .left { 84 | left: 0; 85 | } 86 | .right { 87 | right: 0; 88 | } 89 | } 90 | `; 91 | -------------------------------------------------------------------------------- /src/components/Header.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useNavigate } from "react-router-dom"; 3 | import styled from "styled-components"; 4 | import logo from "../assets/logo.png"; 5 | 6 | export default function Header(props) { 7 | const navigate = useNavigate(); 8 | return ( 9 | 10 |
11 | logo 12 |
13 | 16 |
17 | ); 18 | } 19 | const StyledHeader = styled.header` 20 | padding: 0 4rem; 21 | .logo { 22 | img { 23 | height: 5rem; 24 | } 25 | } 26 | button { 27 | padding: 0.5rem 1rem; 28 | background-color: #e50914; 29 | border: none; 30 | cursor: pointer; 31 | color: white; 32 | border-radius: 0.2rem; 33 | font-weight: bolder; 34 | font-size: 1.05rem; 35 | } 36 | `; 37 | -------------------------------------------------------------------------------- /src/components/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import { signOut } from "firebase/auth"; 2 | import React, { useState } from "react"; 3 | import { Link } from "react-router-dom"; 4 | import styled from "styled-components"; 5 | import logo from "../assets/logo.png"; 6 | import { firebaseAuth } from "../utils/firebase-config"; 7 | import { FaPowerOff, FaSearch } from "react-icons/fa"; 8 | export default function Navbar({ isScrolled }) { 9 | const [showSearch, setShowSearch] = useState(false); 10 | const [inputHover, setInputHover] = useState(false); 11 | const links = [ 12 | { name: "Home", link: "/" }, 13 | { name: "TV Shows", link: "/tv" }, 14 | { name: "Movies", link: "/movies" }, 15 | { name: "My List", link: "/mylist" }, 16 | ]; 17 | 18 | return ( 19 | 20 | 63 | 64 | ); 65 | } 66 | 67 | const Container = styled.div` 68 | .scrolled { 69 | background-color: black; 70 | } 71 | nav { 72 | position: sticky; 73 | top: 0; 74 | height: 6.5rem; 75 | width: 100%; 76 | justify-content: space-between; 77 | position: fixed; 78 | top: 0; 79 | z-index: 2; 80 | padding: 0 4rem; 81 | align-items: center; 82 | transition: 0.3s ease-in-out; 83 | .left { 84 | gap: 2rem; 85 | .brand { 86 | img { 87 | height: 4rem; 88 | } 89 | } 90 | .links { 91 | list-style-type: none; 92 | gap: 2rem; 93 | li { 94 | a { 95 | color: white; 96 | text-decoration: none; 97 | } 98 | } 99 | } 100 | } 101 | .right { 102 | gap: 1rem; 103 | button { 104 | background-color: transparent; 105 | border: none; 106 | cursor: pointer; 107 | &:focus { 108 | outline: none; 109 | } 110 | svg { 111 | color: #f34242; 112 | font-size: 1.2rem; 113 | } 114 | } 115 | .search { 116 | display: flex; 117 | gap: 0.4rem; 118 | align-items: center; 119 | justify-content: center; 120 | padding: 0.2rem; 121 | padding-left: 0.5rem; 122 | button { 123 | background-color: transparent; 124 | border: none; 125 | &:focus { 126 | outline: none; 127 | } 128 | svg { 129 | color: white; 130 | font-size: 1.2rem; 131 | } 132 | } 133 | input { 134 | width: 0; 135 | opacity: 0; 136 | visibility: hidden; 137 | transition: 0.3s ease-in-out; 138 | background-color: transparent; 139 | border: none; 140 | color: white; 141 | &:focus { 142 | outline: none; 143 | } 144 | } 145 | } 146 | .show-search { 147 | border: 1px solid white; 148 | background-color: rgba(0, 0, 0, 0.6); 149 | input { 150 | width: 100%; 151 | opacity: 1; 152 | visibility: visible; 153 | padding: 0.3rem; 154 | } 155 | } 156 | } 157 | } 158 | `; 159 | -------------------------------------------------------------------------------- /src/components/NotAvailable.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | export default function NotAvailable() { 4 | return ( 5 |

6 | No Movies avaialble for the selected genre. Please select a different 7 | genre. 8 |

9 | ); 10 | } 11 | -------------------------------------------------------------------------------- /src/components/SelectGenre.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useDispatch } from "react-redux"; 3 | import styled from "styled-components"; 4 | import { fetchDataByGenre } from "../store"; 5 | export default function SelectGenre({ genres, type }) { 6 | const dispatch = useDispatch(); 7 | return ( 8 | 28 | ); 29 | } 30 | 31 | const Select = styled.select` 32 | margin-left: 5rem; 33 | cursor: pointer; 34 | font-size: 1.4rem; 35 | background-color: rgba(0, 0, 0, 0.4); 36 | color: white; 37 | `; 38 | -------------------------------------------------------------------------------- /src/components/Slider.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import CardSlider from "./CardSlider"; 4 | export default function Slider({ movies }) { 5 | const getMoviesFromRange = (from, to) => { 6 | return movies.slice(from, to); 7 | }; 8 | return ( 9 | 10 | 11 | 12 | 16 | 20 | 21 | 22 | 23 | ); 24 | } 25 | 26 | const Container = styled.div``; 27 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | body { 2 | margin: 0; 3 | font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", 4 | "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", 5 | sans-serif; 6 | -webkit-font-smoothing: antialiased; 7 | -moz-osx-font-smoothing: grayscale; 8 | background-color: black; 9 | color: white; 10 | } 11 | 12 | * { 13 | margin: 0; 14 | padding: 0; 15 | box-sizing: border-box; 16 | } 17 | 18 | html, 19 | body { 20 | overflow-x: hidden; 21 | } 22 | 23 | body::-webkit-scrollbar { 24 | display: none; 25 | } 26 | 27 | .flex { 28 | display: flex; 29 | } 30 | 31 | .column { 32 | flex-direction: column; 33 | } 34 | 35 | .j-between { 36 | justify-content: space-between; 37 | } 38 | 39 | .j-center { 40 | justify-content: center; 41 | } 42 | 43 | .a-center { 44 | align-items: center; 45 | } 46 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { Provider } from "react-redux"; 4 | import "./index.css"; 5 | import App from "./App"; 6 | import { store } from "./store"; 7 | 8 | const root = ReactDOM.createRoot(document.getElementById("root")); 9 | root.render( 10 | 11 | 12 | 13 | 14 | 15 | ); 16 | -------------------------------------------------------------------------------- /src/pages/Login.jsx: -------------------------------------------------------------------------------- 1 | import React, { useState } from "react"; 2 | import styled from "styled-components"; 3 | import logo from "../assets/logo.png"; 4 | import background from "../assets/login.jpg"; 5 | import { useNavigate } from "react-router-dom"; 6 | import BackgroundImage from "../components/BackgroundImage"; 7 | import Header from "../components/Header"; 8 | import { onAuthStateChanged, signInWithEmailAndPassword } from "firebase/auth"; 9 | import { firebaseAuth } from "../utils/firebase-config"; 10 | 11 | function Login() { 12 | const [email, setEmail] = useState(""); 13 | const [password, setPassword] = useState(""); 14 | const navigate = useNavigate(); 15 | 16 | const handleLogin = async () => { 17 | try { 18 | await signInWithEmailAndPassword(firebaseAuth, email, password); 19 | } catch (error) { 20 | console.log(error.code); 21 | } 22 | }; 23 | 24 | onAuthStateChanged(firebaseAuth, (currentUser) => { 25 | if (currentUser) navigate("/"); 26 | }); 27 | 28 | return ( 29 | 30 | 31 |
32 |
33 |
34 |
35 |
36 |

Login

37 |
38 |
39 | setEmail(e.target.value)} 43 | value={email} 44 | /> 45 | setPassword(e.target.value)} 49 | value={password} 50 | /> 51 | 52 |
53 |
54 |
55 |
56 |
57 | ); 58 | } 59 | 60 | const Container = styled.div` 61 | position: relative; 62 | .content { 63 | position: absolute; 64 | top: 0; 65 | left: 0; 66 | height: 100vh; 67 | width: 100vw; 68 | background-color: rgba(0, 0, 0, 0.5); 69 | grid-template-rows: 15vh 85vh; 70 | .form-container { 71 | gap: 2rem; 72 | height: 85vh; 73 | .form { 74 | padding: 2rem; 75 | background-color: #000000b0; 76 | width: 25vw; 77 | gap: 2rem; 78 | color: white; 79 | .container { 80 | gap: 2rem; 81 | input { 82 | padding: 0.5rem 1rem; 83 | width: 15rem; 84 | } 85 | button { 86 | padding: 0.5rem 1rem; 87 | background-color: #e50914; 88 | border: none; 89 | cursor: pointer; 90 | color: white; 91 | border-radius: 0.2rem; 92 | font-weight: bolder; 93 | font-size: 1.05rem; 94 | } 95 | } 96 | } 97 | } 98 | } 99 | `; 100 | 101 | export default Login; 102 | -------------------------------------------------------------------------------- /src/pages/Movies.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styled from "styled-components"; 3 | import Navbar from "../components/Navbar"; 4 | import CardSlider from "../components/CardSlider"; 5 | import { onAuthStateChanged } from "firebase/auth"; 6 | import { firebaseAuth } from "../utils/firebase-config"; 7 | import { useNavigate } from "react-router-dom"; 8 | import { useSelector, useDispatch } from "react-redux"; 9 | import { fetchMovies, getGenres } from "../store"; 10 | import SelectGenre from "../components/SelectGenre"; 11 | import Slider from "../components/Slider"; 12 | import NotAvailable from "../components/NotAvailable"; 13 | 14 | function MoviePage() { 15 | const [isScrolled, setIsScrolled] = useState(false); 16 | const movies = useSelector((state) => state.netflix.movies); 17 | const genres = useSelector((state) => state.netflix.genres); 18 | const genresLoaded = useSelector((state) => state.netflix.genresLoaded); 19 | 20 | const navigate = useNavigate(); 21 | const dispatch = useDispatch(); 22 | 23 | useEffect(() => { 24 | dispatch(getGenres()); 25 | }, []); 26 | 27 | useEffect(() => { 28 | if (genresLoaded) { 29 | dispatch(fetchMovies({ genres, type: "movie" })); 30 | } 31 | }, [genresLoaded]); 32 | 33 | const [user, setUser] = useState(undefined); 34 | 35 | onAuthStateChanged(firebaseAuth, (currentUser) => { 36 | if (currentUser) setUser(currentUser.uid); 37 | else navigate("/login"); 38 | }); 39 | 40 | window.onscroll = () => { 41 | setIsScrolled(window.pageYOffset === 0 ? false : true); 42 | return () => (window.onscroll = null); 43 | }; 44 | 45 | return ( 46 | 47 |
48 | 49 |
50 |
51 | 52 | {movies.length ? : } 53 |
54 |
55 | ); 56 | } 57 | 58 | const Container = styled.div` 59 | .data { 60 | margin-top: 8rem; 61 | .not-available { 62 | text-align: center; 63 | color: white; 64 | margin-top: 4rem; 65 | } 66 | } 67 | `; 68 | export default MoviePage; 69 | -------------------------------------------------------------------------------- /src/pages/Netflix.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styled from "styled-components"; 3 | import Navbar from "../components/Navbar"; 4 | import backgroundImage from "../assets/home.jpg"; 5 | import MovieLogo from "../assets/homeTitle.webp"; 6 | 7 | import { onAuthStateChanged } from "firebase/auth"; 8 | import { firebaseAuth } from "../utils/firebase-config"; 9 | import { useNavigate } from "react-router-dom"; 10 | import { useSelector, useDispatch } from "react-redux"; 11 | import { fetchMovies, getGenres } from "../store"; 12 | import { FaPlay } from "react-icons/fa"; 13 | import { AiOutlineInfoCircle } from "react-icons/ai"; 14 | import Slider from "../components/Slider"; 15 | function Netflix() { 16 | const [isScrolled, setIsScrolled] = useState(false); 17 | const movies = useSelector((state) => state.netflix.movies); 18 | const genres = useSelector((state) => state.netflix.genres); 19 | const genresLoaded = useSelector((state) => state.netflix.genresLoaded); 20 | 21 | const navigate = useNavigate(); 22 | const dispatch = useDispatch(); 23 | 24 | useEffect(() => { 25 | dispatch(getGenres()); 26 | }, []); 27 | 28 | useEffect(() => { 29 | if (genresLoaded) { 30 | dispatch(fetchMovies({ genres, type: "all" })); 31 | } 32 | }, [genresLoaded]); 33 | 34 | onAuthStateChanged(firebaseAuth, (currentUser) => { 35 | if (!currentUser) navigate("/login"); 36 | }); 37 | 38 | window.onscroll = () => { 39 | setIsScrolled(window.pageYOffset === 0 ? false : true); 40 | return () => (window.onscroll = null); 41 | }; 42 | 43 | return ( 44 | 45 | 46 |
47 | background 52 |
53 |
54 | Movie Logo 55 |
56 |
57 | 64 | 68 |
69 |
70 |
71 | 72 |
73 | ); 74 | } 75 | 76 | const Container = styled.div` 77 | background-color: black; 78 | .hero { 79 | position: relative; 80 | .background-image { 81 | filter: brightness(60%); 82 | } 83 | img { 84 | height: 100vh; 85 | width: 100vw; 86 | } 87 | .container { 88 | position: absolute; 89 | bottom: 5rem; 90 | .logo { 91 | img { 92 | width: 100%; 93 | height: 100%; 94 | margin-left: 5rem; 95 | } 96 | } 97 | .buttons { 98 | margin: 5rem; 99 | gap: 2rem; 100 | button { 101 | font-size: 1.4rem; 102 | gap: 1rem; 103 | border-radius: 0.2rem; 104 | padding: 0.5rem; 105 | padding-left: 2rem; 106 | padding-right: 2.4rem; 107 | border: none; 108 | cursor: pointer; 109 | transition: 0.2s ease-in-out; 110 | &:hover { 111 | opacity: 0.8; 112 | } 113 | &:nth-of-type(2) { 114 | background-color: rgba(109, 109, 110, 0.7); 115 | color: white; 116 | svg { 117 | font-size: 1.8rem; 118 | } 119 | } 120 | } 121 | } 122 | } 123 | } 124 | `; 125 | export default Netflix; 126 | -------------------------------------------------------------------------------- /src/pages/Player.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | import { BsArrowLeft } from "react-icons/bs"; 4 | import { useNavigate } from "react-router-dom"; 5 | import video from "../assets/video.mp4"; 6 | export default function Player() { 7 | const navigate = useNavigate(); 8 | 9 | return ( 10 | 11 |
12 |
13 | navigate(-1)} /> 14 |
15 |
17 |
18 | ); 19 | } 20 | 21 | const Container = styled.div` 22 | .player { 23 | width: 100vw; 24 | height: 100vh; 25 | .back { 26 | position: absolute; 27 | padding: 2rem; 28 | z-index: 1; 29 | svg { 30 | font-size: 3rem; 31 | cursor: pointer; 32 | } 33 | } 34 | video { 35 | height: 100%; 36 | width: 100%; 37 | object-fit: cover; 38 | } 39 | } 40 | `; 41 | -------------------------------------------------------------------------------- /src/pages/Signup.jsx: -------------------------------------------------------------------------------- 1 | import { 2 | createUserWithEmailAndPassword, 3 | onAuthStateChanged, 4 | } from "firebase/auth"; 5 | import React, { useState } from "react"; 6 | import { useNavigate } from "react-router-dom"; 7 | import styled from "styled-components"; 8 | import BackgroundImage from "../components/BackgroundImage"; 9 | import Header from "../components/Header"; 10 | import { firebaseAuth } from "../utils/firebase-config"; 11 | function Signup() { 12 | const [showPassword, setShowPassword] = useState(false); 13 | const [formValues, setFormValues] = useState({ 14 | email: "", 15 | password: "", 16 | }); 17 | const navigate = useNavigate(); 18 | 19 | const handleSignIn = async () => { 20 | try { 21 | const { email, password } = formValues; 22 | await createUserWithEmailAndPassword(firebaseAuth, email, password); 23 | } catch (error) { 24 | console.log(error); 25 | } 26 | }; 27 | 28 | onAuthStateChanged(firebaseAuth, (currentUser) => { 29 | if (currentUser) navigate("/"); 30 | }); 31 | 32 | return ( 33 | 34 | 35 |
36 |
37 |
38 |
39 |

Unlimited movies, TV shows and more.

40 |

Watch anywhere. Cancel anytime.

41 |
42 | Ready to watch? Enter your email to create or restart membership. 43 |
44 |
45 |
46 | 50 | setFormValues({ 51 | ...formValues, 52 | [e.target.name]: e.target.value, 53 | }) 54 | } 55 | name="email" 56 | value={formValues.email} 57 | /> 58 | {showPassword && ( 59 | 63 | setFormValues({ 64 | ...formValues, 65 | [e.target.name]: e.target.value, 66 | }) 67 | } 68 | name="password" 69 | value={formValues.password} 70 | /> 71 | )} 72 | {!showPassword && ( 73 | 74 | )} 75 |
76 | {showPassword && } 77 |
78 |
79 |
80 | ); 81 | } 82 | 83 | const Container = styled.div` 84 | position: relative; 85 | .content { 86 | position: absolute; 87 | top: 0; 88 | left: 0; 89 | background-color: rgba(0, 0, 0, 0.5); 90 | height: 100vh; 91 | width: 100vw; 92 | display: grid; 93 | grid-template-rows: 15vh 85vh; 94 | .body { 95 | gap: 1rem; 96 | .text { 97 | gap: 1rem; 98 | text-align: center; 99 | font-size: 2rem; 100 | h1 { 101 | padding: 0 25rem; 102 | } 103 | } 104 | .form { 105 | display: grid; 106 | grid-template-columns: ${({ showPassword }) => 107 | showPassword ? "1fr 1fr" : "2fr 1fr"}; 108 | width: 60%; 109 | input { 110 | color: black; 111 | border: none; 112 | padding: 1.5rem; 113 | font-size: 1.2rem; 114 | border: 1px solid black; 115 | &:focus { 116 | outline: none; 117 | } 118 | } 119 | button { 120 | padding: 0.5rem 1rem; 121 | background-color: #e50914; 122 | border: none; 123 | cursor: pointer; 124 | color: white; 125 | font-weight: bolder; 126 | font-size: 1.05rem; 127 | } 128 | } 129 | button { 130 | padding: 0.5rem 1rem; 131 | background-color: #e50914; 132 | border: none; 133 | cursor: pointer; 134 | color: white; 135 | border-radius: 0.2rem; 136 | font-weight: bolder; 137 | font-size: 1.05rem; 138 | } 139 | } 140 | } 141 | `; 142 | 143 | export default Signup; 144 | -------------------------------------------------------------------------------- /src/pages/TVShows.jsx: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useState } from "react"; 2 | import styled from "styled-components"; 3 | import Navbar from "../components/Navbar"; 4 | import CardSlider from "../components/CardSlider"; 5 | import { onAuthStateChanged } from "firebase/auth"; 6 | import { firebaseAuth } from "../utils/firebase-config"; 7 | import { useNavigate } from "react-router-dom"; 8 | import { useSelector, useDispatch } from "react-redux"; 9 | import { fetchMovies, getGenres } from "../store"; 10 | import SelectGenre from "../components/SelectGenre"; 11 | import Slider from "../components/Slider"; 12 | 13 | function TVShows() { 14 | const [isScrolled, setIsScrolled] = useState(false); 15 | const movies = useSelector((state) => state.netflix.movies); 16 | const genres = useSelector((state) => state.netflix.genres); 17 | const genresLoaded = useSelector((state) => state.netflix.genresLoaded); 18 | const dataLoading = useSelector((state) => state.netflix.dataLoading); 19 | 20 | const navigate = useNavigate(); 21 | const dispatch = useDispatch(); 22 | 23 | useEffect(() => { 24 | if (!genres.length) dispatch(getGenres()); 25 | }, []); 26 | 27 | useEffect(() => { 28 | if (genresLoaded) { 29 | dispatch(fetchMovies({ genres, type: "tv" })); 30 | } 31 | }, [genresLoaded]); 32 | 33 | const [user, setUser] = useState(undefined); 34 | 35 | onAuthStateChanged(firebaseAuth, (currentUser) => { 36 | if (currentUser) setUser(currentUser.uid); 37 | else navigate("/login"); 38 | }); 39 | 40 | window.onscroll = () => { 41 | setIsScrolled(window.pageYOffset === 0 ? false : true); 42 | return () => (window.onscroll = null); 43 | }; 44 | 45 | return ( 46 | 47 | 48 |
49 | 50 | {movies.length ? ( 51 | <> 52 | 53 | 54 | ) : ( 55 |

56 | No TV Shows avaialble for the selected genre. Please select a 57 | different genre. 58 |

59 | )} 60 |
61 |
62 | ); 63 | } 64 | 65 | const Container = styled.div` 66 | .data { 67 | margin-top: 8rem; 68 | .not-available { 69 | text-align: center; 70 | margin-top: 4rem; 71 | } 72 | } 73 | `; 74 | export default TVShows; 75 | -------------------------------------------------------------------------------- /src/pages/UserListedMovies.jsx: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | import { onAuthStateChanged } from "firebase/auth"; 3 | import React, { useEffect, useState } from "react"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { firebaseAuth } from "../utils/firebase-config"; 6 | import Card from "../components/Card"; 7 | import styled from "styled-components"; 8 | import Navbar from "../components/Navbar"; 9 | import { getUsersLikedMovies } from "../store"; 10 | import { useDispatch, useSelector } from "react-redux"; 11 | 12 | export default function UserListedMovies() { 13 | const movies = useSelector((state) => state.netflix.movies); 14 | const dispatch = useDispatch(); 15 | const navigate = useNavigate(); 16 | const [isScrolled, setIsScrolled] = useState(false); 17 | const [email, setEmail] = useState(undefined); 18 | 19 | onAuthStateChanged(firebaseAuth, (currentUser) => { 20 | if (currentUser) setEmail(currentUser.email); 21 | else navigate("/login"); 22 | }); 23 | 24 | useEffect(() => { 25 | if (email) { 26 | dispatch(getUsersLikedMovies(email)); 27 | } 28 | }, [email]); 29 | 30 | window.onscroll = () => { 31 | setIsScrolled(window.pageYOffset === 0 ? false : true); 32 | return () => (window.onscroll = null); 33 | }; 34 | 35 | return ( 36 | 37 | 38 |
39 |

My List

40 |
41 | {movies.map((movie, index) => { 42 | return ( 43 | 49 | ); 50 | })} 51 |
52 |
53 |
54 | ); 55 | } 56 | 57 | const Container = styled.div` 58 | .content { 59 | margin: 2.3rem; 60 | margin-top: 8rem; 61 | gap: 3rem; 62 | h1 { 63 | margin-left: 3rem; 64 | } 65 | .grid { 66 | flex-wrap: wrap; 67 | gap: 1rem; 68 | } 69 | } 70 | `; 71 | -------------------------------------------------------------------------------- /src/store/index.js: -------------------------------------------------------------------------------- 1 | import { 2 | configureStore, 3 | createAsyncThunk, 4 | createSlice, 5 | } from "@reduxjs/toolkit"; 6 | import axios from "axios"; 7 | import { API_KEY, TMDB_BASE_URL } from "../utils/constants"; 8 | 9 | const initialState = { 10 | movies: [], 11 | genresLoaded: false, 12 | genres: [], 13 | }; 14 | 15 | export const getGenres = createAsyncThunk("netflix/genres", async () => { 16 | const { 17 | data: { genres }, 18 | } = await axios.get( 19 | "https://api.themoviedb.org/3/genre/movie/list?api_key=3d39d6bfe362592e6aa293f01fbcf9b9" 20 | ); 21 | return genres; 22 | }); 23 | 24 | const createArrayFromRawData = (array, moviesArray, genres) => { 25 | array.forEach((movie) => { 26 | const movieGenres = []; 27 | movie.genre_ids.forEach((genre) => { 28 | const name = genres.find(({ id }) => id === genre); 29 | if (name) movieGenres.push(name.name); 30 | }); 31 | if (movie.backdrop_path) 32 | moviesArray.push({ 33 | id: movie.id, 34 | name: movie?.original_name ? movie.original_name : movie.original_title, 35 | image: movie.backdrop_path, 36 | genres: movieGenres.slice(0, 3), 37 | }); 38 | }); 39 | }; 40 | 41 | const getRawData = async (api, genres, paging = false) => { 42 | const moviesArray = []; 43 | for (let i = 1; moviesArray.length < 60 && i < 10; i++) { 44 | const { 45 | data: { results }, 46 | } = await axios.get(`${api}${paging ? `&page=${i}` : ""}`); 47 | createArrayFromRawData(results, moviesArray, genres); 48 | } 49 | return moviesArray; 50 | }; 51 | 52 | export const fetchDataByGenre = createAsyncThunk( 53 | "netflix/genre", 54 | async ({ genre, type }, thunkAPI) => { 55 | const { 56 | netflix: { genres }, 57 | } = thunkAPI.getState(); 58 | return getRawData( 59 | `https://api.themoviedb.org/3/discover/${type}?api_key=3d39d6bfe362592e6aa293f01fbcf9b9&with_genres=${genre}`, 60 | genres 61 | ); 62 | } 63 | ); 64 | 65 | export const fetchMovies = createAsyncThunk( 66 | "netflix/trending", 67 | async ({ type }, thunkAPI) => { 68 | const { 69 | netflix: { genres }, 70 | } = thunkAPI.getState(); 71 | return getRawData( 72 | `${TMDB_BASE_URL}/trending/${type}/week?api_key=${API_KEY}`, 73 | genres, 74 | true 75 | ); 76 | } 77 | ); 78 | 79 | export const getUsersLikedMovies = createAsyncThunk( 80 | "netflix/getLiked", 81 | async (email) => { 82 | const { 83 | data: { movies }, 84 | } = await axios.get(`http://localhost:5000/api/user/liked/${email}`); 85 | return movies; 86 | } 87 | ); 88 | 89 | export const removeMovieFromLiked = createAsyncThunk( 90 | "netflix/deleteLiked", 91 | async ({ movieId, email }) => { 92 | const { 93 | data: { movies }, 94 | } = await axios.put("http://localhost:5000/api/user/remove", { 95 | email, 96 | movieId, 97 | }); 98 | return movies; 99 | } 100 | ); 101 | 102 | const NetflixSlice = createSlice({ 103 | name: "Netflix", 104 | initialState, 105 | extraReducers: (builder) => { 106 | builder.addCase(getGenres.fulfilled, (state, action) => { 107 | state.genres = action.payload; 108 | state.genresLoaded = true; 109 | }); 110 | builder.addCase(fetchMovies.fulfilled, (state, action) => { 111 | state.movies = action.payload; 112 | }); 113 | builder.addCase(fetchDataByGenre.fulfilled, (state, action) => { 114 | state.movies = action.payload; 115 | }); 116 | builder.addCase(getUsersLikedMovies.fulfilled, (state, action) => { 117 | state.movies = action.payload; 118 | }); 119 | builder.addCase(removeMovieFromLiked.fulfilled, (state, action) => { 120 | state.movies = action.payload; 121 | }); 122 | }, 123 | }); 124 | 125 | export const store = configureStore({ 126 | reducer: { 127 | netflix: NetflixSlice.reducer, 128 | }, 129 | }); 130 | 131 | export const { setGenres, setMovies } = NetflixSlice.actions; 132 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const API_KEY = "3d39d6bfe362592e6aa293f01fbcf9b9"; 2 | export const TMDB_BASE_URL = "https://api.themoviedb.org/3"; 3 | -------------------------------------------------------------------------------- /src/utils/firebase-config.js: -------------------------------------------------------------------------------- 1 | import { getAuth } from "firebase/auth"; 2 | import { initializeApp } from "firebase/app"; 3 | 4 | const firebaseConfig = { 5 | apiKey: "AIzaSyC1Hf0_rdWLBzDPJPcO9CJN4y6M6-EgKH4", 6 | authDomain: "react-auth-6788d.firebaseapp.com", 7 | projectId: "react-auth-6788d", 8 | storageBucket: "react-auth-6788d.appspot.com", 9 | messagingSenderId: "131797845021", 10 | appId: "1:131797845021:web:3f7ff4766e2b89ca5d32f4", 11 | measurementId: "G-VWPBR1NSLL", 12 | }; 13 | 14 | const app = initializeApp(firebaseConfig); 15 | export const firebaseAuth = getAuth(app); 16 | --------------------------------------------------------------------------------