├── .gitignore ├── postcss.config.js ├── tailwind.config.js ├── src ├── index.html ├── utils │ ├── gptConfig.js │ ├── getValidatedInputs.js │ ├── constants.js │ ├── Firebase.js │ └── Router.js ├── reduxStore │ ├── index.js │ ├── GptSlice │ │ └── index.js │ ├── TrendingMovie │ │ └── index.js │ └── authSlice │ │ └── index.js ├── components │ ├── MovieCard.js │ ├── GptMovieSuggestions.js │ ├── MovieLists.js │ ├── TrailerCard.js │ ├── Navbar.js │ ├── TrailerBackround.js │ └── SearchBar.js ├── index.css ├── HOC │ └── PrivateRoute.js ├── index.js ├── api │ └── auth.api.js └── views │ ├── GptPage.js │ ├── LandingPage.js │ └── Authentication.js ├── webpack.config.js ├── package.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | yarn.lock 3 | .parcel-cache 4 | dist 5 | yarn-error.log 6 | .env 7 | .env.production -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('autoprefixer'), 5 | // Add any other PostCSS plugins if needed 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./src/**/*.{html,js,ts,jsx,tsx}", 5 | ], 6 | theme: { 7 | extend: {}, 8 | }, 9 | plugins: [], 10 | } -------------------------------------------------------------------------------- /src/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Netflix 7 | 8 | 9 |
10 | 11 | 12 | -------------------------------------------------------------------------------- /src/utils/gptConfig.js: -------------------------------------------------------------------------------- 1 | 2 | 3 | // example 4 | // async function main() { 5 | // const chatCompletion = await openai.chat.completions.create({ 6 | // messages: [{ role: 'user', content: 'Say this is a test' }], 7 | // model: 'gpt-3.5-turbo', 8 | // }); 9 | // } 10 | 11 | // main(); 12 | -------------------------------------------------------------------------------- /src/reduxStore/index.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import authenticationSlice from "./authSlice"; 3 | import movieSlice from "./TrendingMovie"; 4 | import GptSlice from "./GptSlice" 5 | export const reduxStore = configureStore({ 6 | reducer: { 7 | // todo 8 | auth: authenticationSlice, 9 | trending: movieSlice, 10 | gpt : GptSlice 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /src/utils/getValidatedInputs.js: -------------------------------------------------------------------------------- 1 | export const getValidatedInputs = (email, password) => { 2 | const emailTest = /^[\w\-\.]+@([\w-]+\.)+[\w-]{2,}$/gm.test(email); 3 | const passwordTest = 4 | /^((?=\S*?[A-Z])(?=\S*?[a-z])(?=\S*?[0-9]).{6,})\S$/.test(password); 5 | 6 | if (!emailTest) return "Please enter valid email address"; 7 | if (!passwordTest) return "Please enter valid password"; 8 | 9 | return ""; 10 | }; 11 | -------------------------------------------------------------------------------- /src/components/MovieCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { IMG_CDN_URL } from "../utils/constants"; 3 | 4 | const MovieCard = ({ item }) => { 5 | return item?.poster_path ? ( 6 |
7 | 8 |
9 | ) : ( 10 | <> 11 | ); 12 | }; 13 | 14 | export default MovieCard; 15 | -------------------------------------------------------------------------------- /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | #root { 5 | height: 100vh; 6 | margin: 0px; 7 | padding: 0px; 8 | width: 100vw; 9 | } 10 | *::-webkit-scrollbar { 11 | width: 0; 12 | height: 0; 13 | background-color: transparent; 14 | } 15 | 16 | &::-moz-scrollbar { 17 | visibility: hidden; 18 | } 19 | /*.ytp-chrome-top, 20 | .ytp-show-cards-title { 21 | display: none !important; 22 | } */ 23 | 24 | -------------------------------------------------------------------------------- /src/HOC/PrivateRoute.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | import { Navigate, useLocation } from "react-router-dom"; 4 | 5 | const PrivateRoute = ({ children }) => { 6 | const { isAuthenticated } = useSelector((state) => state.auth); 7 | const loc = useLocation(); 8 | 9 | if (isAuthenticated) return children; 10 | return ; 11 | }; 12 | 13 | export default PrivateRoute; 14 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import { RouterProvider } from "react-router-dom"; 4 | import { AppRoute } from "./utils/Router"; 5 | import { Provider } from "react-redux"; 6 | import { reduxStore } from "./reduxStore"; 7 | import "./index.css"; 8 | 9 | const root = ReactDOM.createRoot(document.getElementById("root")); 10 | root.render( 11 | 12 | 13 | 14 | ); -------------------------------------------------------------------------------- /src/components/GptMovieSuggestions.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MovieCard from "./MovieCard"; 3 | 4 | const GptMovieSuggestions = ({ suggestedMovie }) => { 5 | return ( 6 |
7 |

11 | {suggestedMovie?.[0]?.original_title} 12 |

13 |
14 | {suggestedMovie?.map((item) => { 15 | return ; 16 | })} 17 |
18 |
19 | ); 20 | }; 21 | 22 | export default GptMovieSuggestions; 23 | -------------------------------------------------------------------------------- /src/components/MovieLists.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import MovieCard from "./MovieCard"; 3 | 4 | const MovieLists = ({ title, movies, isgptlist }) => { 5 | return ( 6 |
7 |

12 | {title} 13 |

14 |
21 | {movies?.map((item) => { 22 | return ; 23 | })} 24 |
25 |
26 | ); 27 | }; 28 | 29 | export default MovieLists; 30 | -------------------------------------------------------------------------------- /src/reduxStore/GptSlice/index.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const GptSlice = createSlice({ 4 | name: "Gpt Slice", 5 | initialState: { 6 | isGptPage: false, 7 | searchResults: [], 8 | searching: false, 9 | }, 10 | reducers: { 11 | handleGptPage: (state, action) => { 12 | state.isGptPage = !state.isGptPage; 13 | }, 14 | setSearch: (state, action) => { 15 | state.searching = true; 16 | }, 17 | setMoviesSearchResults: (state, action) => { 18 | state.searchResults = action.payload; 19 | state.searching = false; 20 | }, 21 | }, 22 | }); 23 | 24 | export const { handleGptPage, setSearch, setMoviesSearchResults } = 25 | GptSlice.actions; 26 | export default GptSlice.reducer; 27 | -------------------------------------------------------------------------------- /src/utils/constants.js: -------------------------------------------------------------------------------- 1 | export const LOGO = 2 | "https://cdn.cookielaw.org/logos/dd6b162f-1a32-456a-9cfe-897231c7763c/4345ea78-053c-46d2-b11e-09adaef973dc/Netflix_Logo_PMS.png"; 3 | export const backgroundImage = 4 | "https://assets.nflxext.com/ffe/siteui/vlv3/77d35039-751f-4c3e-9c8d-1240c1ca6188/cf244808-d722-428f-80a9-052acdf158ec/IN-en-20231106-popsignuptwoweeks-perspective_alpha_website_large.jpg"; 5 | 6 | export const TMDB_OPTIONS = { 7 | method: "GET", 8 | headers: { 9 | accept: "application/json", 10 | Authorization: `Bearer ${process.env.REACT_APP_TMDB_AUTHORIZATION}`, 11 | }, 12 | }; 13 | export const IMG_CDN_URL = 14 | "https://image.tmdb.org/t/p/w250https://image.tmdb.org/t/p/w500"; 15 | export const NETFLIX_SERVER_BASE_URL ="https://api-netflix-online.onrender.com" -------------------------------------------------------------------------------- /src/reduxStore/TrendingMovie/index.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | loading: false, 5 | error: "", 6 | trendingMovie: [], 7 | currentTrailer: {}, 8 | popularMovie: [], 9 | nowPlayingMovie: [], 10 | }; 11 | const movieSlice = createSlice({ 12 | initialState, 13 | name: "TrendingMovie", 14 | reducers: { 15 | addTrendingMovie: (state, actions) => { 16 | state.trendingMovie = actions.payload; 17 | }, 18 | addCurrentTrailer: (state, actions) => { 19 | state.currentTrailer = actions.payload; 20 | }, 21 | addPopularMovie: (state, actions) => { 22 | state.popularMovie = actions.payload; 23 | }, 24 | addNowPlayingMovie: (state, actions) => { 25 | state.nowPlayingMovie = actions.payload; 26 | }, 27 | }, 28 | }); 29 | export const { 30 | addTrendingMovie, 31 | addCurrentTrailer, 32 | addNowPlayingMovie, 33 | addPopularMovie, 34 | } = movieSlice.actions; 35 | export default movieSlice.reducer; 36 | -------------------------------------------------------------------------------- /webpack.config.js: -------------------------------------------------------------------------------- 1 | const Dotenv = require('dotenv-webpack'); 2 | const path = require("path"); 3 | const HtmlWebpack = require("html-webpack-plugin"); 4 | module.exports = { 5 | resolve: { 6 | extensions: [".js", ".jsx", ".css"], 7 | }, 8 | entry: "./src/index.js", 9 | output: { 10 | path: path.resolve(__dirname, "dist"), 11 | }, 12 | devServer :{ 13 | historyApiFallback: true, 14 | }, 15 | plugins: [ 16 | new HtmlWebpack({ 17 | template: "./src/index.html", 18 | title : "index.html", 19 | }), 20 | 21 | new Dotenv(), 22 | ], 23 | module: { 24 | rules: [ 25 | { 26 | test: /.(js|jsx)$/, 27 | exclude: /node_modules/, 28 | use: { 29 | loader: "babel-loader", 30 | options: { 31 | presets: ["@babel/preset-env", "@babel/preset-react"], 32 | }, 33 | }, 34 | }, 35 | { 36 | test: /.css$/, 37 | use: ['style-loader', 'css-loader', 'postcss-loader'], 38 | }, 39 | ], 40 | }, 41 | }; 42 | -------------------------------------------------------------------------------- /src/utils/Firebase.js: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp } from "firebase/app"; 3 | import { getAnalytics } from "firebase/analytics"; 4 | import { getAuth } from "firebase/auth"; 5 | 6 | // TODO: Add SDKs for Firebase products that you want to use 7 | // https://firebase.google.com/docs/web/setup#available-libraries 8 | 9 | // Your web app's Firebase configuration 10 | // For Firebase JS SDK v7.20.0 and later, measurementId is optional 11 | const firebaseConfig = { 12 | apiKey: process.env.REACT_APP_FIREBASE_apiKey, 13 | authDomain: process.env.REACT_APP_FIREBASE_authDomain, 14 | projectId: process.env.REACT_APP_FIREBASE_projectId, 15 | storageBucket: process.env.REACT_APP_FIREBASE_storageBucket, 16 | messagingSenderId: process.env.REACT_APP_FIREBASE_messagingSenderId, 17 | appId: process.env.REACT_APP_FIREBASE_appId, 18 | measurementId: process.env.REACT_APP_FIREBASE_measurementId, 19 | }; 20 | // Initialize Firebase 21 | const app = initializeApp(firebaseConfig); 22 | const analytics = getAnalytics(app); 23 | 24 | ////////////////////// 25 | export const auth = getAuth(); 26 | -------------------------------------------------------------------------------- /src/utils/Router.js: -------------------------------------------------------------------------------- 1 | import React, { Suspense } from "react"; 2 | import { Outlet, createHashRouter } from "react-router-dom"; 3 | const Login = React.lazy(() => import("../views/Authentication")); 4 | const LandingPage = React.lazy(() => import("../views/LandingPage")); 5 | const Navbar = React.lazy(() => import("../components/Navbar")); 6 | const PrivateRoute = React.lazy(() => import("../HOC/PrivateRoute")); 7 | 8 | function AppLayout() { 9 | return ( 10 | <> 11 | 12 | 13 | 14 | ); 15 | } 16 | 17 | export const AppRoute = createHashRouter([ 18 | { 19 | path: "/", 20 | element: ( 21 | 22 | 23 | 24 | ), 25 | children: [ 26 | { 27 | path: "/", 28 | element: ( 29 | 30 | {" "} 31 | 32 | 33 | ), 34 | }, 35 | ], 36 | }, 37 | { 38 | path: "/authentication", 39 | element: ( 40 | 41 | {" "} 42 | 43 | 44 | ), 45 | }, 46 | ]); 47 | -------------------------------------------------------------------------------- /src/reduxStore/authSlice/index.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const initialState = { 4 | loading: false, 5 | errorMessage: "", 6 | user: {}, 7 | isAuthenticated: false, 8 | }; 9 | const authenticationSlice = createSlice({ 10 | name: "authenticationSlice", 11 | initialState, 12 | reducers: { 13 | authLoading: (state, actions) => { 14 | state.loading = true; 15 | state.errorMessage = ""; 16 | }, 17 | authError: (state, actions) => { 18 | state.errorMessage = actions.payload; 19 | state.loading = false; 20 | }, 21 | UserLoginSignIn: (state, actions) => { 22 | state.user = actions.payload; 23 | state.isAuthenticated = true; 24 | state.errorMessage = ""; 25 | state.loading = false; 26 | }, 27 | logOutUser: (state, actions) => { 28 | state.user = {}; 29 | state.isAuthenticated = false; 30 | state.errorMessage = ""; 31 | state.loading = false; 32 | }, 33 | }, 34 | }); 35 | 36 | export const { authLoading, authError, UserLoginSignIn, logOutUser } = 37 | authenticationSlice.actions; 38 | export default authenticationSlice.reducer; 39 | -------------------------------------------------------------------------------- /src/api/auth.api.js: -------------------------------------------------------------------------------- 1 | import { 2 | createUserWithEmailAndPassword, 3 | signInWithEmailAndPassword, 4 | signOut, 5 | } from "firebase/auth"; 6 | import { auth } from "../utils/Firebase"; 7 | import axios from "axios"; 8 | import { TMDB_OPTIONS } from "../utils/constants"; 9 | 10 | export const CreateNewUserApi = async ({ email, password }) => { 11 | return await createUserWithEmailAndPassword(auth, email, password); 12 | }; 13 | 14 | export const signInUserApi = async ({ email, password }) => { 15 | return await signInWithEmailAndPassword(auth, email, password); 16 | }; 17 | 18 | export const logoutUserApi = async () => { 19 | return await signOut(auth); 20 | }; 21 | 22 | export const NowPlayingMovies = (signal) => { 23 | return axios.get("https://api.themoviedb.org/3/movie/now_playing", { 24 | ...TMDB_OPTIONS, 25 | signal, 26 | }); 27 | }; 28 | 29 | export const TrendingMovies = (signal) => { 30 | return axios.get("https://api.themoviedb.org/3/trending/movie/day?language=en-hi", { 31 | ...TMDB_OPTIONS, 32 | signal, 33 | }); 34 | }; 35 | export const PopularMovies = (signal) => { 36 | return axios.get("https://api.themoviedb.org/3/movie/popular", { 37 | ...TMDB_OPTIONS, 38 | signal, 39 | }); 40 | }; 41 | 42 | export const getVideoApi = (movie_id) => { 43 | return axios.get( 44 | `https://api.themoviedb.org/3/movie/${movie_id}/videos`, 45 | TMDB_OPTIONS 46 | ); 47 | }; 48 | 49 | 50 | -------------------------------------------------------------------------------- /src/components/TrailerCard.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { useSelector } from "react-redux"; 3 | 4 | const TrailerCard = () => { 5 | const currentTrailer = useSelector((store) => store.trending?.currentTrailer); 6 | if (!currentTrailer?.movieDetail) return; 7 | const { original_title, overview } = currentTrailer?.movieDetail; 8 | 9 | return ( 10 |
19 |
20 |

21 | {original_title} 22 |

23 |

{overview}

24 |
25 | 28 | 31 |
32 |
33 |
34 | ); 35 | }; 36 | 37 | export default TrailerCard; 38 | -------------------------------------------------------------------------------- /src/views/GptPage.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { backgroundImage } from "../utils/constants"; 3 | import SearchBar from "../components/SearchBar"; 4 | import { useSelector } from "react-redux"; 5 | import GptMovieSuggestions from "../components/GptMovieSuggestions"; 6 | 7 | const GptPage = () => { 8 | const { searchResults } = useSelector((store) => store.gpt); 9 | return ( 10 |
11 |
12 | 16 |
17 |
18 | 19 |
20 | 21 |
22 | 23 |
24 | {searchResults.length && ( 25 |
26 | {searchResults?.map((suggestedMovie) => { 27 | return ( 28 | 32 | ); 33 | })} 34 |
35 | )} 36 |
37 |
38 | ); 39 | }; 40 | 41 | export default GptPage; 42 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "homepage": "https://deepumandal.github.io/netflix/", 3 | "name": "namaste_netflix_gpt", 4 | "version": "1.0.0", 5 | "description": "", 6 | "scripts": { 7 | "dev": "webpack-dev-server --mode development --open --hot", 8 | "build": "webpack --mode production", 9 | "predeploy": "npm run build", 10 | "deploy": "gh-pages -d dist" 11 | }, 12 | "keywords": [], 13 | "author": "", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@babel/core": "^7.23.3", 17 | "@babel/preset-env": "^7.23.3", 18 | "@babel/preset-react": "^7.23.3", 19 | "@reduxjs/toolkit": "^1.9.7", 20 | "autoprefixer": "^10.4.16", 21 | "axios": "^1.6.2", 22 | "babel-loader": "^9.1.3", 23 | "css-loader": "^6.8.1", 24 | "dotenv-webpack": "^8.0.1", 25 | "env": "^0.0.2", 26 | "firebase": "^10.6.0", 27 | "gh-pages": "^6.1.0", 28 | "html-webpack-plugin": "^5.5.3", 29 | "lodash": "^4.17.21", 30 | "openai": "^4.19.0", 31 | "postcss-loader": "^7.3.3", 32 | "react": "^18.2.0", 33 | "react-dom": "^18.2.0", 34 | "react-player": "^2.13.0", 35 | "react-redux": "^8.1.3", 36 | "react-router-dom": "^6.18.0", 37 | "react-youtube": "^10.1.0", 38 | "style-loader": "^3.3.3" 39 | }, 40 | "devDependencies": { 41 | "buffer": "^6.0.3", 42 | "postcss": "^8.4.31", 43 | "process": "^0.11.10", 44 | "tailwindcss": "^3.3.5", 45 | "webpack": "^5.89.0", 46 | "webpack-cli": "^5.1.4", 47 | "webpack-dev-server": "^4.15.1" 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/components/Navbar.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | 3 | import { useDispatch, useSelector } from "react-redux"; 4 | 5 | import { LOGO } from "../utils/constants"; 6 | import { logoutUserApi } from "../api/auth.api"; 7 | import { logOutUser } from "../reduxStore/authSlice"; 8 | import { handleGptPage } from "../reduxStore/GptSlice"; 9 | 10 | const Navbar = () => { 11 | const dispatch = useDispatch(); 12 | 13 | const { loading } = useSelector((store) => store.auth); 14 | const { isGptPage } = useSelector((store) => store.gpt); 15 | const handleSignout = (e) => { 16 | e.preventDefault(); 17 | logoutUserApi().then(() => { 18 | dispatch(logOutUser()); 19 | }); 20 | }; 21 | const onClickGptPage = (e) => { 22 | e.preventDefault(); 23 | dispatch(handleGptPage()); 24 | }; 25 | return ( 26 | // bg-gradient-to-b from-[#000000] 27 |
28 |
29 | 30 |
31 |
32 | 38 | 44 |
45 |
46 | ); 47 | }; 48 | 49 | export default Navbar; 50 | -------------------------------------------------------------------------------- /src/components/TrailerBackround.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect, useRef } from "react"; 2 | import { useSelector } from "react-redux"; 3 | import YouTube from "react-youtube"; 4 | 5 | const TrailerBackround = () => { 6 | const currentTrailer = useSelector((store) => store.trending?.currentTrailer); 7 | 8 | const handleOnload = (event) => { 9 | // const childDocument = event?.target?.contentWindow?.top?.document; 10 | // const childHtml = childDocument?.childNodes[1]; 11 | // const childHead = childHtml?.childNodes[0]; 12 | // const jsInjections = document.createElement("script"); 13 | // jsInjections.setAttribute("defer", ""); 14 | // jsInjections.innerHTML = ` 15 | // const yt_title = document.getElementsByClassName("ytp-show-cards-title") 16 | // // yt_title.style.display = "none"; 17 | // console.log("console from childHtml: " + yt_title.length) 18 | // `; 19 | // childHead.appendChild(jsInjections); 20 | // console.log("childDocument", event?.target?.contentWindow.postMessage()); 21 | }; 22 | 23 | if (!currentTrailer?.mediaDetail) return; 24 | const key = currentTrailer?.mediaDetail.key; 25 | return ( 26 |
37 | 51 |
52 | ); 53 | }; 54 | 55 | export default TrailerBackround; 56 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netflix Clone Project 2 | 3 | ## Features 4 | 5 | ### Authentication 6 | Users can create accounts, log in, and access content based on their authentication status. Secure user authentication mechanisms are implemented to ensure data privacy. 7 | 8 | ### Movie Search Feature 9 | The platform includes a robust search functionality allowing users to explore a vast library of movies. They can search for specific titles, genres, or keywords to discover content. 10 | 11 | ### Separate Server Source Code 12 | The server-side of this project is maintained in a separate repository. You can find the server source code at [Server Repository URL](https://github.com/deepumandal/netflix-server). 13 | 14 | ### GPT Search Support 15 | The Netflix clone integrates GPT (Generative Pre-trained Transformer) for enhanced search capabilities. GPT facilitates a more intuitive and sophisticated search experience, providing users with accurate and relevant results based on their queries. 16 | 17 | ## Repository Information 18 | - **Frontend Repository:** [Frontend Repository URL](https://github.com/deepumandal/Netflix) 19 | - **Server Repository:** [Server Repository URL](https://github.com/deepumandal/netflix-server) 20 | 21 | ## Getting Started 22 | To run this Netflix clone project locally, follow these steps: 23 | 1. Clone the frontend repository [Source](https://github.com/deepumandal/Netflix). 24 | 2. Clone the server repository [Source](https://github.com/deepumandal/netflix-server). 25 | 3. Follow the setup instructions provided in each repository's README file to set up the frontend and server environments. 26 | 4. Connect the frontend with the server by following the integration guidelines mentioned in the respective repositories. 27 | 28 | ## Usage 29 | Once the project is set up, users can navigate the Netflix clone, authenticate, explore movies using the search feature, and benefit from the GPT-powered search capabilities. 30 | 31 | ## Contributors 32 | This project was developed and maintained by [Deepak Mandal](https://github.com/deepumandal). 33 | 34 | ## License 35 | This repository is created solely for educational purposes and learning experience. It is not intended for any illegal or unauthorized activities. 36 | -------------------------------------------------------------------------------- /src/components/SearchBar.js: -------------------------------------------------------------------------------- 1 | import React, { memo, useEffect, useRef } from "react"; 2 | import { NETFLIX_SERVER_BASE_URL, TMDB_OPTIONS } from "../utils/constants"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { setMoviesSearchResults, setSearch } from "../reduxStore/GptSlice"; 5 | import axios from "axios"; 6 | const SearchBar = () => { 7 | const userGptQuerry = useRef(); // current.value 8 | const dispatch = useDispatch(); 9 | const { searching } = useSelector((store) => store.gpt); 10 | const searchMovieTMDB = async (movie) => { 11 | const data = await fetch( 12 | "https://api.themoviedb.org/3/search/movie?query=" + 13 | movie + 14 | "&include_adult=false&language=en-US&page=1", 15 | TMDB_OPTIONS 16 | ); 17 | const json = await data.json(); 18 | 19 | return json.results; 20 | }; 21 | 22 | const handleClickOnSearchbtn = async (event) => { 23 | event.preventDefault(); 24 | dispatch(setSearch()); 25 | 26 | axios 27 | .post(NETFLIX_SERVER_BASE_URL + "/openai/connections", { 28 | question: userGptQuerry.current.value, 29 | }) 30 | .then((res) => { 31 | const data = res.data; 32 | axios 33 | .post(NETFLIX_SERVER_BASE_URL + "/search/movie", data) 34 | .then((res) => { 35 | const { data } = res; 36 | console.log(data) 37 | dispatch(setMoviesSearchResults(data)); 38 | }); 39 | }); 40 | }; 41 | 42 | useEffect(() => { 43 | return () => { 44 | searching && dispatch(setSearch()); 45 | }; 46 | }, []); 47 | return ( 48 |
52 | 58 | 64 |
65 | ); 66 | }; 67 | 68 | export default memo(SearchBar); 69 | -------------------------------------------------------------------------------- /src/views/LandingPage.js: -------------------------------------------------------------------------------- 1 | import React, { useEffect } from "react"; 2 | import { 3 | NowPlayingMovies, 4 | PopularMovies, 5 | TrendingMovies, 6 | getVideoApi, 7 | } from "../api/auth.api"; 8 | import { useDispatch, useSelector } from "react-redux"; 9 | import { 10 | addCurrentTrailer, 11 | addNowPlayingMovie, 12 | addPopularMovie, 13 | addTrendingMovie, 14 | } from "../reduxStore/TrendingMovie"; 15 | import TrailerBackround from "../components/TrailerBackround"; 16 | import TrailerCard from "../components/TrailerCard"; 17 | import MovieLists from "../components/MovieLists"; 18 | import GptPage from "./GptPage"; 19 | 20 | const LandingPage = () => { 21 | const dispatch = useDispatch(); 22 | 23 | const { trendingMovie, popularMovie, nowPlayingMovie } = 24 | useSelector((store) => store.trending) || []; 25 | const { isGptPage } = useSelector((store) => store.gpt); 26 | 27 | const parseTrendingMovie = (movies) => { 28 | if (!movies.length) return; 29 | const currentTrendingMovie = movies[1]; 30 | 31 | getVideoApi(currentTrendingMovie?.id).then((response) => { 32 | const { results } = response?.data; 33 | 34 | const currentTrailer = results.filter( 35 | (provider) => provider?.type === "Trailer" 36 | ); 37 | 38 | dispatch( 39 | addCurrentTrailer({ 40 | mediaDetail: currentTrailer[0], 41 | movieDetail: currentTrendingMovie, 42 | }) 43 | ); 44 | }); 45 | }; 46 | 47 | useEffect(() => { 48 | const AbroadController = new AbortController(); 49 | if (!nowPlayingMovie.length) { 50 | NowPlayingMovies(AbroadController.signal).then((res) => { 51 | dispatch(addNowPlayingMovie(res?.data?.results)); 52 | }); 53 | } 54 | if (!trendingMovie.length) { 55 | TrendingMovies(AbroadController.signal).then((res) => { 56 | parseTrendingMovie(res?.data?.results); 57 | dispatch(addTrendingMovie(res?.data?.results)); 58 | }); 59 | } 60 | if (!popularMovie.length) { 61 | PopularMovies(AbroadController.signal).then((res) => { 62 | dispatch(addPopularMovie(res?.data?.results)); 63 | }); 64 | } 65 | return () => { 66 | AbroadController && AbroadController?.abort(); 67 | }; 68 | }, []); 69 | return ( 70 |
71 | {isGptPage ? ( 72 | 73 | ) : ( 74 | <> 75 |
76 | 77 | 78 |
79 | {/* movie lists */} 80 |
81 | 82 | 83 | 84 |
85 | 86 | )} 87 |
88 | ); 89 | }; 90 | 91 | export default LandingPage; -------------------------------------------------------------------------------- /src/views/Authentication.js: -------------------------------------------------------------------------------- 1 | import React, { useRef, useState } from "react"; 2 | import { getValidatedInputs } from "../utils/getValidatedInputs"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { useLocation, useNavigate } from "react-router-dom"; 5 | import { CreateNewUserApi, signInUserApi } from "../api/auth.api"; 6 | import { 7 | UserLoginSignIn, 8 | authLoading, 9 | authError, 10 | } from "../reduxStore/authSlice/index"; 11 | import { LOGO, backgroundImage } from "../utils/constants"; 12 | 13 | const Login = () => { 14 | const [isAlreadyUser, setIsAlreadyUser] = useState(true); 15 | // const [error, setError] = useState(""); 16 | const userName = useRef(); 17 | const userEmail = useRef(); 18 | const userPassword = useRef(); 19 | const dispatch = useDispatch(); 20 | const loc = useLocation(); 21 | 22 | const { errorMessage, loading } = useSelector((store) => store.auth); 23 | const navigate = useNavigate(); 24 | 25 | const handleAddNewUser = () => { 26 | setIsAlreadyUser((prev) => !prev); 27 | }; 28 | 29 | const onSubmit = (e) => { 30 | e.preventDefault(); 31 | 32 | const email = userEmail.current?.value; 33 | const password = userPassword.current?.value; 34 | 35 | // validation check for password and email 36 | const inputsError = getValidatedInputs(email, password); 37 | 38 | if (inputsError) return dispatch(authError(inputsError)); // to immidiate stop the if inputs are wrong 39 | dispatch(authLoading()); 40 | if (isAlreadyUser) { 41 | signInUserApi({ email, password }) 42 | .then((response) => { 43 | const { displayName, stsTokenManager, phoneNumber, photoURL, uid } = 44 | response.user; 45 | const { refreshToken, accessToken, expirationTime } = stsTokenManager; 46 | dispatch( 47 | UserLoginSignIn({ 48 | displayName, 49 | userToken: { refreshToken, accessToken, expirationTime }, 50 | phoneNumber, 51 | photoURL, 52 | uid, 53 | email, 54 | }) 55 | ); 56 | navigate(loc.state?.pathname, { replace: true }); 57 | }) 58 | .catch((error) => { 59 | dispatch(authError(error.message)); 60 | }); 61 | } else { 62 | CreateNewUserApi({ email, password }) 63 | .then((response) => { 64 | const { displayName, stsTokenManager, phoneNumber, photoURL, uid } = 65 | response.user; 66 | const { refreshToken, accessToken, expirationTime } = stsTokenManager; 67 | dispatch( 68 | UserLoginSignIn({ 69 | displayName, 70 | userToken: { refreshToken, accessToken, expirationTime }, 71 | phoneNumber, 72 | photoURL, 73 | uid, 74 | email, 75 | }) 76 | ); 77 | navigate(loc.state?.pathname, { replace: true }); 78 | }) 79 | .catch((error) => { 80 | dispatch(authError(error.message)); 81 | }); 82 | } 83 | }; 84 | 85 | return ( 86 |
87 |
88 | 89 |
90 |
91 | 92 |
93 | logo 94 |
95 | {/* form validiation */} 96 |
97 |
98 |
99 |

100 | {isAlreadyUser ? "Sign in" : "Sign up"} 101 |

102 | {!isAlreadyUser && ( 103 | 109 | )} 110 | 116 | 122 | 128 | 129 |

130 | {errorMessage}{" "} 131 |

132 |

133 | New to Netflix?{" "} 134 | 135 | Sign up now. 136 | 137 |

138 |
139 |
140 |
141 |
142 | ); 143 | }; 144 | 145 | export default Login; 146 | --------------------------------------------------------------------------------