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

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 |

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

59 | {user && (
60 |
61 | {showGptSearch && (
62 |
72 | )}
73 |
79 |

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 |

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