├── 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 | 
48 |
49 | LOGIN_PAGE:
50 |
51 | 
52 |
53 | MAIN PAGE:
54 |
55 | 
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 |
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 |
navigate("/player")}
48 | />
49 |
50 | {isHovered && (
51 |
52 |
53 |

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 |

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

52 |
53 |
54 |

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 |
16 |
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 |
--------------------------------------------------------------------------------