├── src ├── index.css ├── utils │ ├── openai.js │ ├── languageConstants.js │ ├── configSlice.js │ ├── validate.js │ ├── userSlice.js │ ├── appStore.js │ ├── gptSlice.js │ ├── moviesSlice.js │ ├── firebase.js │ └── constants.js ├── setupTests.js ├── App.js ├── components │ ├── MovieCard.js │ ├── GptSearch.js │ ├── MovieList.js │ ├── Body.js │ ├── MainContainer.js │ ├── GptMovieSuggestions.js │ ├── VideoTitle.js │ ├── VideoBackground.js │ ├── Browse.js │ ├── SecondaryContainer.js │ ├── GptSearchBar.js │ ├── Header.js │ └── Login.js ├── reportWebVitals.js ├── index.js └── hooks │ ├── usePopularMovies.js │ ├── useNowPlayingMovies.js │ └── useMovieTrailer.js ├── .firebaserc ├── public ├── favicon.ico ├── logo192.png ├── logo512.png ├── robots.txt ├── manifest.json └── index.html ├── firebase.json ├── tailwind.config.js ├── .gitignore ├── package.json ├── .firebase └── hosting.YnVpbGQ.cache └── README.md /src/index.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /.firebaserc: -------------------------------------------------------------------------------- 1 | { 2 | "projects": { 3 | "default": "netflixgpt-57a56" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshaymarch7/netflix-gpt/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshaymarch7/netflix-gpt/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/akshaymarch7/netflix-gpt/HEAD/public/logo512.png -------------------------------------------------------------------------------- /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /firebase.json: -------------------------------------------------------------------------------- 1 | { 2 | "hosting": { 3 | "public": "build", 4 | "ignore": ["firebase.json", "**/.*", "**/node_modules/**"] 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: ["./src/**/*.{js,jsx,ts,tsx}"], 4 | theme: { 5 | extend: {}, 6 | }, 7 | plugins: [], 8 | }; 9 | -------------------------------------------------------------------------------- /src/utils/openai.js: -------------------------------------------------------------------------------- 1 | import OpenAI from "openai"; 2 | import { OPENAI_KEY } from "./constants"; 3 | 4 | const openai = new OpenAI({ 5 | apiKey: OPENAI_KEY, 6 | dangerouslyAllowBrowser: true, 7 | }); 8 | 9 | export default openai; 10 | -------------------------------------------------------------------------------- /src/setupTests.js: -------------------------------------------------------------------------------- 1 | // jest-dom adds custom jest matchers for asserting on DOM nodes. 2 | // allows you to do things like: 3 | // expect(element).toHaveTextContent(/react/i) 4 | // learn more: https://github.com/testing-library/jest-dom 5 | import '@testing-library/jest-dom'; 6 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import { Provider } from "react-redux"; 2 | import Body from "./components/Body"; 3 | import appStore from "./utils/appStore"; 4 | 5 | function App() { 6 | return ( 7 | 8 | 9 | 10 | ); 11 | } 12 | 13 | export default App; 14 | -------------------------------------------------------------------------------- /src/components/MovieCard.js: -------------------------------------------------------------------------------- 1 | import { IMG_CDN_URL } from "../utils/constants"; 2 | 3 | const MovieCard = ({ posterPath }) => { 4 | if (!posterPath) return null; 5 | return ( 6 |
7 | Movie Card 8 |
9 | ); 10 | }; 11 | export default MovieCard; 12 | -------------------------------------------------------------------------------- /src/utils/languageConstants.js: -------------------------------------------------------------------------------- 1 | const lang = { 2 | en: { 3 | search: "Search", 4 | gptSearchPlaceholder: "What would you like to watch today?", 5 | }, 6 | hindi: { 7 | search: "खोज", 8 | gptSearchPlaceholder: "आज आप क्या देखना चाहेंगे?", 9 | }, 10 | spanish: { 11 | search: "buscar", 12 | gptSearchPlaceholder: "¿Qué te gustaría ver hoy?", 13 | }, 14 | }; 15 | 16 | export default lang; 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # production 12 | /build 13 | 14 | # misc 15 | .DS_Store 16 | .env.local 17 | .env.development.local 18 | .env.test.local 19 | .env.production.local 20 | 21 | npm-debug.log* 22 | yarn-debug.log* 23 | yarn-error.log* 24 | 25 | .env -------------------------------------------------------------------------------- /src/reportWebVitals.js: -------------------------------------------------------------------------------- 1 | const reportWebVitals = onPerfEntry => { 2 | if (onPerfEntry && onPerfEntry instanceof Function) { 3 | import('web-vitals').then(({ getCLS, getFID, getFCP, getLCP, getTTFB }) => { 4 | getCLS(onPerfEntry); 5 | getFID(onPerfEntry); 6 | getFCP(onPerfEntry); 7 | getLCP(onPerfEntry); 8 | getTTFB(onPerfEntry); 9 | }); 10 | } 11 | }; 12 | 13 | export default reportWebVitals; 14 | -------------------------------------------------------------------------------- /src/utils/configSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const configSlice = createSlice({ 4 | name: "config", 5 | initialState: { 6 | lang: "en", 7 | }, 8 | reducers: { 9 | changeLanguage: (state, action) => { 10 | state.lang = action.payload; 11 | }, 12 | }, 13 | }); 14 | 15 | export const { changeLanguage } = configSlice.actions; 16 | 17 | export default configSlice.reducer; 18 | -------------------------------------------------------------------------------- /src/utils/validate.js: -------------------------------------------------------------------------------- 1 | export const checkValidData = (email, password) => { 2 | const isEmailValid = /^([a-zA-Z0-9._%-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,})$/.test( 3 | email 4 | ); 5 | const isPasswordValid = 6 | /^(?=.*\d)(?=.*[a-z])(?=.*[A-Z])(?=.*[a-zA-Z]).{8,}$/.test(password); 7 | 8 | if (!isEmailValid) return "Email ID is not valid"; 9 | if (!isPasswordValid) return "Password is not valid"; 10 | 11 | return null; 12 | }; 13 | -------------------------------------------------------------------------------- /src/utils/userSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const userSlice = createSlice({ 4 | name: "user", 5 | initialState: null, 6 | reducers: { 7 | addUser: (state, action) => { 8 | return action.payload; 9 | }, 10 | removeUser: (state, action) => { 11 | return null; 12 | }, 13 | }, 14 | }); 15 | 16 | export const { addUser, removeUser } = userSlice.actions; 17 | 18 | export default userSlice.reducer; 19 | -------------------------------------------------------------------------------- /src/utils/appStore.js: -------------------------------------------------------------------------------- 1 | import { configureStore } from "@reduxjs/toolkit"; 2 | import userReducer from "./userSlice"; 3 | import moviesReducer from "./moviesSlice"; 4 | import gptReducer from "./gptSlice"; 5 | import configReducer from "./configSlice"; 6 | 7 | const appStore = configureStore({ 8 | reducer: { 9 | user: userReducer, 10 | movies: moviesReducer, 11 | gpt: gptReducer, 12 | config: configReducer, 13 | }, 14 | }); 15 | 16 | export default appStore; 17 | -------------------------------------------------------------------------------- /src/components/GptSearch.js: -------------------------------------------------------------------------------- 1 | import { BG_URL } from "../utils/constants"; 2 | import GptMovieSuggestions from "./GptMovieSuggestions"; 3 | import GptSearchBar from "./GptSearchBar"; 4 | 5 | const GPTSearch = () => { 6 | return ( 7 | <> 8 |
9 | logo 10 |
11 |
12 | 13 | 14 |
15 | 16 | ); 17 | }; 18 | export default GPTSearch; 19 | -------------------------------------------------------------------------------- /src/components/MovieList.js: -------------------------------------------------------------------------------- 1 | import MovieCard from "./MovieCard"; 2 | 3 | const MovieList = ({ title, movies }) => { 4 | return ( 5 |
6 |

{title}

7 |
8 |
9 | {movies?.map((movie) => ( 10 | 11 | ))} 12 |
13 |
14 |
15 | ); 16 | }; 17 | export default MovieList; 18 | -------------------------------------------------------------------------------- /src/components/Body.js: -------------------------------------------------------------------------------- 1 | import { createBrowserRouter } from "react-router-dom"; 2 | import Browse from "./Browse"; 3 | import Login from "./Login"; 4 | import { RouterProvider } from "react-router-dom"; 5 | 6 | const Body = () => { 7 | const appRouter = createBrowserRouter([ 8 | { 9 | path: "/", 10 | element: , 11 | }, 12 | { 13 | path: "/browse", 14 | element: , 15 | }, 16 | ]); 17 | 18 | return ( 19 |
20 | 21 |
22 | ); 23 | }; 24 | export default Body; 25 | -------------------------------------------------------------------------------- /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 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import "./index.css"; 4 | import App from "./App"; 5 | import reportWebVitals from "./reportWebVitals"; 6 | 7 | const root = ReactDOM.createRoot(document.getElementById("root")); 8 | root.render( 9 | // 10 | 11 | // 12 | ); 13 | 14 | // If you want to start measuring performance in your app, pass a function 15 | // to log results (for example: reportWebVitals(console.log)) 16 | // or send to an analytics endpoint. Learn more: https://bit.ly/CRA-vitals 17 | reportWebVitals(); 18 | -------------------------------------------------------------------------------- /src/components/MainContainer.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import VideoBackground from "./VideoBackground"; 3 | import VideoTitle from "./VideoTitle"; 4 | 5 | const MainContainer = () => { 6 | const movies = useSelector((store) => store.movies?.nowPlayingMovies); 7 | 8 | if (!movies) return; 9 | 10 | const mainMovie = movies[0]; 11 | 12 | const { original_title, overview, id } = mainMovie; 13 | 14 | return ( 15 |
16 | 17 | 18 |
19 | ); 20 | }; 21 | export default MainContainer; 22 | -------------------------------------------------------------------------------- /src/components/GptMovieSuggestions.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import MovieList from "./MovieList"; 3 | 4 | const GptMovieSuggestions = () => { 5 | const { movieResults, movieNames } = useSelector((store) => store.gpt); 6 | if (!movieNames) return null; 7 | 8 | return ( 9 |
10 |
11 | {movieNames.map((movieName, index) => ( 12 | 17 | ))} 18 |
19 |
20 | ); 21 | }; 22 | export default GptMovieSuggestions; 23 | -------------------------------------------------------------------------------- /src/utils/gptSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const gptSlice = createSlice({ 4 | name: "gpt", 5 | initialState: { 6 | showGptSearch: false, 7 | movieResults: null, 8 | movieNames: null, 9 | }, 10 | reducers: { 11 | toggleGptSearchView: (state) => { 12 | state.showGptSearch = !state.showGptSearch; 13 | }, 14 | addGptMovieResult: (state, action) => { 15 | const { movieNames, movieResults } = action.payload; 16 | state.movieNames = movieNames; 17 | state.movieResults = movieResults; 18 | }, 19 | }, 20 | }); 21 | 22 | export const { toggleGptSearchView, addGptMovieResult } = gptSlice.actions; 23 | 24 | export default gptSlice.reducer; 25 | -------------------------------------------------------------------------------- /src/utils/moviesSlice.js: -------------------------------------------------------------------------------- 1 | import { createSlice } from "@reduxjs/toolkit"; 2 | 3 | const moviesSlice = createSlice({ 4 | name: "movies", 5 | initialState: { 6 | nowPlayingMovies: null, 7 | popularMovies: null, 8 | trailerVideo: null, 9 | }, 10 | reducers: { 11 | addNowPlayingMovies: (state, action) => { 12 | state.nowPlayingMovies = action.payload; 13 | }, 14 | addPopularMovies: (state, action) => { 15 | state.popularMovies = action.payload; 16 | }, 17 | addTrailerVideo: (state, action) => { 18 | state.trailerVideo = action.payload; 19 | }, 20 | }, 21 | }); 22 | 23 | export const { addNowPlayingMovies, addTrailerVideo, addPopularMovies } = 24 | moviesSlice.actions; 25 | 26 | export default moviesSlice.reducer; 27 | -------------------------------------------------------------------------------- /src/components/VideoTitle.js: -------------------------------------------------------------------------------- 1 | const VideoTitle = ({ title, overview }) => { 2 | return ( 3 |
4 |

{title}

5 |

{overview}

6 |
7 | 10 | 13 |
14 |
15 | ); 16 | }; 17 | export default VideoTitle; 18 | -------------------------------------------------------------------------------- /src/components/VideoBackground.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import useMovieTrailer from "../hooks/useMovieTrailer"; 3 | 4 | const VideoBackground = ({ movieId }) => { 5 | const trailerVideo = useSelector((store) => store.movies?.trailerVideo); 6 | 7 | useMovieTrailer(movieId); 8 | 9 | return ( 10 |
11 | 21 |
22 | ); 23 | }; 24 | export default VideoBackground; 25 | -------------------------------------------------------------------------------- /src/components/Browse.js: -------------------------------------------------------------------------------- 1 | import Header from "./Header"; 2 | import useNowPlayingMovies from "../hooks/useNowPlayingMovies"; 3 | import MainContainer from "./MainContainer"; 4 | import SecondaryContainer from "./SecondaryContainer"; 5 | import usePopularMovies from "../hooks/usePopularMovies"; 6 | import GptSearch from "./GptSearch"; 7 | import { useSelector } from "react-redux"; 8 | 9 | const Browse = () => { 10 | const showGptSearch = useSelector((store) => store.gpt.showGptSearch); 11 | 12 | useNowPlayingMovies(); 13 | usePopularMovies(); 14 | 15 | return ( 16 |
17 |
18 | {showGptSearch ? ( 19 | 20 | ) : ( 21 | <> 22 | 23 | 24 | 25 | )} 26 |
27 | ); 28 | }; 29 | export default Browse; 30 | -------------------------------------------------------------------------------- /src/hooks/usePopularMovies.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { API_OPTIONS } from "../utils/constants"; 4 | import { addPopularMovies } from "../utils/moviesSlice"; 5 | 6 | const usePopularMovies = () => { 7 | // Fetch Data from TMDB API and update store 8 | const dispatch = useDispatch(); 9 | 10 | const popularMovies = useSelector((store) => store.movies.popularMovies); 11 | 12 | const getPopularMovies = async () => { 13 | const data = await fetch( 14 | "https://api.themoviedb.org/3/movie/popular?page=1", 15 | API_OPTIONS 16 | ); 17 | const json = await data.json(); 18 | dispatch(addPopularMovies(json.results)); 19 | }; 20 | 21 | useEffect(() => { 22 | !popularMovies && getPopularMovies(); 23 | }, []); 24 | }; 25 | 26 | export default usePopularMovies; 27 | -------------------------------------------------------------------------------- /src/hooks/useNowPlayingMovies.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { API_OPTIONS } from "../utils/constants"; 4 | import { addNowPlayingMovies } from "../utils/moviesSlice"; 5 | 6 | const useNowPlayingMovies = () => { 7 | // Fetch Data from TMDB API and update store 8 | const dispatch = useDispatch(); 9 | 10 | const nowPlayingMovies = useSelector( 11 | (store) => store.movies.nowPlayingMovies 12 | ); 13 | 14 | const getNowPlayingMovies = async () => { 15 | const data = await fetch( 16 | "https://api.themoviedb.org/3/movie/now_playing?page=1", 17 | API_OPTIONS 18 | ); 19 | const json = await data.json(); 20 | dispatch(addNowPlayingMovies(json.results)); 21 | }; 22 | 23 | useEffect(() => { 24 | !nowPlayingMovies && getNowPlayingMovies(); 25 | }, []); 26 | }; 27 | 28 | export default useNowPlayingMovies; 29 | -------------------------------------------------------------------------------- /src/components/SecondaryContainer.js: -------------------------------------------------------------------------------- 1 | import { useSelector } from "react-redux"; 2 | import MovieList from "./MovieList"; 3 | 4 | const SecondaryContainer = () => { 5 | const movies = useSelector((store) => store.movies); 6 | 7 | return ( 8 | movies.nowPlayingMovies && ( 9 |
10 |
11 | 12 | 13 | 14 | 18 | 19 |
20 |
21 | ) 22 | ); 23 | }; 24 | export default SecondaryContainer; 25 | -------------------------------------------------------------------------------- /src/hooks/useMovieTrailer.js: -------------------------------------------------------------------------------- 1 | import { useEffect } from "react"; 2 | import { useDispatch, useSelector } from "react-redux"; 3 | import { API_OPTIONS } from "../utils/constants"; 4 | import { addTrailerVideo } from "../utils/moviesSlice"; 5 | 6 | const useMovieTrailer = (movieId) => { 7 | const dispatch = useDispatch(); 8 | 9 | const trailerVideo = useSelector((store) => store.movies.trailerVideo); 10 | 11 | const getMovieVideos = async () => { 12 | const data = await fetch( 13 | "https://api.themoviedb.org/3/movie/" + 14 | movieId + 15 | "/videos?language=en-US", 16 | API_OPTIONS 17 | ); 18 | const json = await data.json(); 19 | 20 | const filterData = json.results.filter((video) => video.type === "Trailer"); 21 | const trailer = filterData.length ? filterData[0] : json.results[0]; 22 | dispatch(addTrailerVideo(trailer)); 23 | }; 24 | useEffect(() => { 25 | !trailerVideo && getMovieVideos(); 26 | }, []); 27 | }; 28 | 29 | export default useMovieTrailer; 30 | -------------------------------------------------------------------------------- /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: "AIzaSyAwKbz6Wo1UVp1BzKSJVXV6hTz3vwy74k8", 13 | authDomain: "netflixgpt-57a56.firebaseapp.com", 14 | projectId: "netflixgpt-57a56", 15 | storageBucket: "netflixgpt-57a56.appspot.com", 16 | messagingSenderId: "1093133802935", 17 | appId: "1:1093133802935:web:c284961959cce2a54adc32", 18 | measurementId: "G-DEHFXGZ999", 19 | }; 20 | 21 | // Initialize Firebase 22 | const app = initializeApp(firebaseConfig); 23 | const analytics = getAnalytics(app); 24 | 25 | export const auth = getAuth(); 26 | -------------------------------------------------------------------------------- /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 | 4 | export const USER_AVATAR = 5 | "https://occ-0-6247-2164.1.nflxso.net/dnm/api/v6/K6hjPJd6cR6FpVELC5Pd6ovHRSk/AAAABdpkabKqQAxyWzo6QW_ZnPz1IZLqlmNfK-t4L1VIeV1DY00JhLo_LMVFp936keDxj-V5UELAVJrU--iUUY2MaDxQSSO-0qw.png?r=e6e"; 6 | 7 | export const API_OPTIONS = { 8 | method: "GET", 9 | headers: { 10 | accept: "application/json", 11 | Authorization: "Bearer " + process.env.REACT_APP_TMDB_KEY, 12 | }, 13 | }; 14 | 15 | export const IMG_CDN_URL = "https://image.tmdb.org/t/p/w500"; 16 | 17 | export const BG_URL = 18 | "https://assets.nflxext.com/ffe/siteui/vlv3/fc164b4b-f085-44ee-bb7f-ec7df8539eff/d23a1608-7d90-4da1-93d6-bae2fe60a69b/IN-en-20230814-popsignuptwoweeks-perspective_alpha_website_large.jpg"; 19 | 20 | export const SUPPORTED_LANGUAGES = [ 21 | { identifier: "en", name: "English" }, 22 | { identifier: "hindi", name: "Hindi" }, 23 | { identifier: "spanish", name: "Spanish" }, 24 | ]; 25 | 26 | export const OPENAI_KEY = process.env.REACT_APP_OPENAI_KEY; 27 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-gpt", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@testing-library/jest-dom": "^5.17.0", 7 | "@testing-library/react": "^13.4.0", 8 | "@testing-library/user-event": "^13.5.0", 9 | "firebase": "^10.2.0", 10 | "openai": "^4.0.1", 11 | "react": "^18.2.0", 12 | "react-dom": "^18.2.0", 13 | "react-redux": "^8.1.2", 14 | "react-scripts": "5.0.1", 15 | "web-vitals": "^2.1.4" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject" 22 | }, 23 | "eslintConfig": { 24 | "extends": [ 25 | "react-app", 26 | "react-app/jest" 27 | ] 28 | }, 29 | "browserslist": { 30 | "production": [ 31 | ">0.2%", 32 | "not dead", 33 | "not op_mini all" 34 | ], 35 | "development": [ 36 | "last 1 chrome version", 37 | "last 1 firefox version", 38 | "last 1 safari version" 39 | ] 40 | }, 41 | "devDependencies": { 42 | "@reduxjs/toolkit": "^1.9.5", 43 | "react-router-dom": "^6.15.0", 44 | "tailwindcss": "^3.3.3" 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /.firebase/hosting.YnVpbGQ.cache: -------------------------------------------------------------------------------- 1 | asset-manifest.json,1692534198522,b42a58cb8bd37af5db8fe2c01565a5de64f29a79019571295a74b644902a3c24 2 | favicon.ico,1692534193587,b72f7455f00e4e58792d2bca892abb068e2213838c0316d6b7a0d6d16acd1955 3 | logo192.png,1692534193587,caff018b7f1e8fd481eb1c50d75b0ef236bcd5078b1d15c8bb348453fee30293 4 | index.html,1692534198522,324452da18464a522758e76c5df2f0e0111c6534c1967c1255e1904d88500493 5 | robots.txt,1692534193588,391d14b3c2f8c9143a27a28c7399585142228d4d1bdbe2c87ac946de411fa9a2 6 | logo512.png,1692534193587,191fc21360b4ccfb1cda11a1efb97f489ed22672ca83f4064316802bbfdd750e 7 | manifest.json,1692534193587,341d52628782f8ac9290bbfc43298afccb47b7cbfcee146ae30cf0f46bc30900 8 | static/css/main.5f8b0740.css.map,1692534198524,fccbb1dac9fba7c8326b15a08f65f506097988d6301d025eb30e99976b1e37ed 9 | static/js/787.8e11e584.chunk.js,1692534198524,0e4f804981c1a7cbe1d871d33e7bf989a0a6a7d6613822dbdc8a0916ca1c881f 10 | static/css/main.5f8b0740.css,1692534198524,b520b7fdc73b5f4cf489d2cac24a28d43f29c73c021cc71952c0f2a6086f5bb2 11 | static/js/main.a176fa42.js.LICENSE.txt,1692534198524,b4ac81822c749cf6a5a15304e6c9ee1036dcc245d7703ffec17e7c7c6a7a8d41 12 | static/js/787.8e11e584.chunk.js.map,1692534198524,bd58d13d5312bd6291f66aa7538000a654dd2fc48099fae2b81e911c21d11c4e 13 | static/js/main.a176fa42.js,1692534198524,6ae5e4c2c12115b1aa824057ff0f7ee8919d3e343036a4d4b0b1dbe9d1cbcccc 14 | static/js/main.a176fa42.js.map,1692534198524,8d45e0be79958a871183b7651925aca869fab4b7b63c79477c74283e20a27ac0 15 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | Netflix GPT 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netflix GPT 2 | 3 | - Create React App 4 | - Configured TailwindCSS 5 | - Header 6 | - Routing of App 7 | - Login Form 8 | - Sign up Form 9 | - Form Validation 10 | - useRef Hook 11 | - Firebase Setup 12 | - Deploying our app to production 13 | - Create SignUp User Account 14 | - Implement Sign In user Api 15 | - Created Redux Store with userSlice 16 | - Implemented Sign out 17 | - Update Profile 18 | - BugFix: Sign up user displayName and profile picture update 19 | - BugFix: if the user is not logged in Redirect /browse to Login Page and vice-versa 20 | - Unsubscibed to the onAuthStateChanged callback 21 | - Add hardcoded values to the constants file 22 | - Regiter TMDB API & create an app & get access token 23 | - Get Data from TMDB now playing movies list API 24 | - Custom Hook for Now Playing Movies 25 | - Create movieSlice 26 | - Update Store with movies Data 27 | - Planning for MainContauiner & secondary container 28 | - Fetch Data for Trailer Video 29 | - Update Store with Trailer Video Data 30 | - Embedded the Yotube video and make it autoplay and mute 31 | - Tailwind Classes to make Main Container look awesome 32 | - Build Secondary Component 33 | - Build Movie List 34 | - build Movie Card 35 | - TMDB Image CDN URL 36 | - Made the Browsre page amazing with Tailwind CSS 37 | - usePopularMovies Custom hook 38 | - GPT Search Page 39 | - GPT Search Bar 40 | - (BONUS) Multi-language Feature in our App) 41 | - Get Open AI Api Key 42 | - Gpt Search API Call 43 | - fetched gptMoviesSuggestions from TMDB 44 | - created gptSlice added data 45 | - Resused Movie List component to make movie suggestion container 46 | - Memoization 47 | - Added .env file 48 | - Adding .env file to gitignore 49 | - Made our Site Responsive 50 | 51 | # Features 52 | - Login/Sign Up 53 | - Sign In /Sign up Form 54 | - redirect to Browse Page 55 | - Browse (after authentication) 56 | - Header 57 | - Main Movie 58 | - Tailer in Background 59 | - Title & Description 60 | - MovieSuggestions 61 | - MovieLists * N 62 | - NetflixGPT 63 | - Search Bar 64 | - Movie Suggestions 65 | 66 | 67 | 68 | # Project Setup 69 | - Before starting the project please add .env file and add TMDB and OPENAI KEY into it. -------------------------------------------------------------------------------- /src/components/GptSearchBar.js: -------------------------------------------------------------------------------- 1 | import openai from "../utils/openai"; 2 | import { useRef } from "react"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import lang from "../utils/languageConstants"; 5 | import { API_OPTIONS } from "../utils/constants"; 6 | import { addGptMovieResult } from "../utils/gptSlice"; 7 | 8 | const GptSearchBar = () => { 9 | const dispatch = useDispatch(); 10 | const langKey = useSelector((store) => store.config.lang); 11 | const searchText = useRef(null); 12 | 13 | // search movie in TMDB 14 | const searchMovieTMDB = async (movie) => { 15 | const data = await fetch( 16 | "https://api.themoviedb.org/3/search/movie?query=" + 17 | movie + 18 | "&include_adult=false&language=en-US&page=1", 19 | API_OPTIONS 20 | ); 21 | const json = await data.json(); 22 | 23 | return json.results; 24 | }; 25 | 26 | const handleGptSearchClick = async () => { 27 | console.log(searchText.current.value); 28 | // Make an API call to GPT API and get Movie Results 29 | 30 | const gptQuery = 31 | "Act as a Movie Recommendation system and suggest some movies for the query : " + 32 | searchText.current.value + 33 | ". only give me names of 5 movies, comma seperated like the example result given ahead. Example Result: Gadar, Sholay, Don, Golmaal, Koi Mil Gaya"; 34 | 35 | const gptResults = await openai.chat.completions.create({ 36 | messages: [{ role: "user", content: gptQuery }], 37 | model: "gpt-3.5-turbo", 38 | }); 39 | 40 | if (!gptResults.choices) { 41 | // TODO: Write Error Handling 42 | } 43 | 44 | console.log(gptResults.choices?.[0]?.message?.content); 45 | 46 | // Andaz Apna Apna, Hera Pheri, Chupke Chupke, Jaane Bhi Do Yaaro, Padosan 47 | const gptMovies = gptResults.choices?.[0]?.message?.content.split(","); 48 | 49 | // ["Andaz Apna Apna", "Hera Pheri", "Chupke Chupke", "Jaane Bhi Do Yaaro", "Padosan"] 50 | 51 | // For each movie I will search TMDB API 52 | 53 | const promiseArray = gptMovies.map((movie) => searchMovieTMDB(movie)); 54 | // [Promise, Promise, Promise, Promise, Promise] 55 | 56 | const tmdbResults = await Promise.all(promiseArray); 57 | 58 | console.log(tmdbResults); 59 | 60 | dispatch( 61 | addGptMovieResult({ movieNames: gptMovies, movieResults: tmdbResults }) 62 | ); 63 | }; 64 | 65 | return ( 66 |
67 |
e.preventDefault()} 70 | > 71 | 77 | 83 |
84 |
85 | ); 86 | }; 87 | export default GptSearchBar; 88 | -------------------------------------------------------------------------------- /src/components/Header.js: -------------------------------------------------------------------------------- 1 | import { onAuthStateChanged, signOut } from "firebase/auth"; 2 | import { useEffect } from "react"; 3 | import { useDispatch, useSelector } from "react-redux"; 4 | import { useNavigate } from "react-router-dom"; 5 | import { LOGO, SUPPORTED_LANGUAGES } from "../utils/constants"; 6 | import { auth } from "../utils/firebase"; 7 | import { addUser, removeUser } from "../utils/userSlice"; 8 | import { toggleGptSearchView } from "../utils/gptSlice"; 9 | import { changeLanguage } from "../utils/configSlice"; 10 | 11 | const Header = () => { 12 | const dispatch = useDispatch(); 13 | const navigate = useNavigate(); 14 | const user = useSelector((store) => store.user); 15 | const showGptSearch = useSelector((store) => store.gpt.showGptSearch); 16 | const handleSignOut = () => { 17 | signOut(auth) 18 | .then(() => {}) 19 | .catch((error) => { 20 | navigate("/error"); 21 | }); 22 | }; 23 | 24 | useEffect(() => { 25 | const unsubscribe = onAuthStateChanged(auth, (user) => { 26 | if (user) { 27 | const { uid, email, displayName, photoURL } = user; 28 | dispatch( 29 | addUser({ 30 | uid: uid, 31 | email: email, 32 | displayName: displayName, 33 | photoURL: photoURL, 34 | }) 35 | ); 36 | navigate("/browse"); 37 | } else { 38 | dispatch(removeUser()); 39 | navigate("/"); 40 | } 41 | }); 42 | 43 | // Unsiubscribe when component unmounts 44 | return () => unsubscribe(); 45 | }, []); 46 | 47 | const handleGptSearchClick = () => { 48 | // Toggle GPT Search 49 | dispatch(toggleGptSearchView()); 50 | }; 51 | 52 | const handleLanguageChange = (e) => { 53 | dispatch(changeLanguage(e.target.value)); 54 | }; 55 | 56 | return ( 57 |
58 | logo 59 | {user && ( 60 |
61 | {showGptSearch && ( 62 | 72 | )} 73 | 79 | usericon 84 | 87 |
88 | )} 89 |
90 | ); 91 | }; 92 | export default Header; 93 | -------------------------------------------------------------------------------- /src/components/Login.js: -------------------------------------------------------------------------------- 1 | import { useState, useRef } from "react"; 2 | import Header from "./Header"; 3 | import { checkValidData } from "../utils/validate"; 4 | import { 5 | createUserWithEmailAndPassword, 6 | signInWithEmailAndPassword, 7 | updateProfile, 8 | } from "firebase/auth"; 9 | import { auth } from "../utils/firebase"; 10 | import { useDispatch } from "react-redux"; 11 | import { addUser } from "../utils/userSlice"; 12 | import { BG_URL, USER_AVATAR } from "../utils/constants"; 13 | 14 | const Login = () => { 15 | const [isSignInForm, setIsSignInForm] = useState(true); 16 | const [errorMessage, setErrorMessage] = useState(null); 17 | const dispatch = useDispatch(); 18 | 19 | const name = useRef(null); 20 | const email = useRef(null); 21 | const password = useRef(null); 22 | 23 | const handleButtonClick = () => { 24 | const message = checkValidData(email.current.value, password.current.value); 25 | setErrorMessage(message); 26 | if (message) return; 27 | 28 | if (!isSignInForm) { 29 | // Sign Up Logic 30 | createUserWithEmailAndPassword( 31 | auth, 32 | email.current.value, 33 | password.current.value 34 | ) 35 | .then((userCredential) => { 36 | const user = userCredential.user; 37 | updateProfile(user, { 38 | displayName: name.current.value, 39 | photoURL: USER_AVATAR, 40 | }) 41 | .then(() => { 42 | const { uid, email, displayName, photoURL } = auth.currentUser; 43 | dispatch( 44 | addUser({ 45 | uid: uid, 46 | email: email, 47 | displayName: displayName, 48 | photoURL: photoURL, 49 | }) 50 | ); 51 | }) 52 | .catch((error) => { 53 | setErrorMessage(error.message); 54 | }); 55 | }) 56 | .catch((error) => { 57 | const errorCode = error.code; 58 | const errorMessage = error.message; 59 | setErrorMessage(errorCode + "-" + errorMessage); 60 | }); 61 | } else { 62 | // Sign In Logic 63 | signInWithEmailAndPassword( 64 | auth, 65 | email.current.value, 66 | password.current.value 67 | ) 68 | .then((userCredential) => { 69 | // Signed in 70 | const user = userCredential.user; 71 | }) 72 | .catch((error) => { 73 | const errorCode = error.code; 74 | const errorMessage = error.message; 75 | setErrorMessage(errorCode + "-" + errorMessage); 76 | }); 77 | } 78 | }; 79 | 80 | const toggleSignInForm = () => { 81 | setIsSignInForm(!isSignInForm); 82 | }; 83 | return ( 84 |
85 |
86 |
87 | logo 88 |
89 |
e.preventDefault()} 91 | className="w-full md:w-3/12 absolute p-12 bg-black my-36 mx-auto right-0 left-0 text-white rounded-lg bg-opacity-80" 92 | > 93 |

94 | {isSignInForm ? "Sign In" : "Sign Up"} 95 |

96 | 97 | {!isSignInForm && ( 98 | 104 | )} 105 | 111 | 117 |

{errorMessage}

118 | 124 |

125 | {isSignInForm 126 | ? "New to Netflix? Sign Up Now" 127 | : "Already registered? Sign In Now."} 128 |

129 |
130 |
131 | ); 132 | }; 133 | export default Login; 134 | --------------------------------------------------------------------------------