├── .env.example ├── constants └── movie.ts ├── prettier.config.js ├── public ├── avatar.png ├── favicon.ico ├── login_background.jpg ├── vercel.svg └── netflix_logo.svg ├── postcss.config.js ├── next.config.js ├── next-env.d.ts ├── atoms └── modalAtom.ts ├── pages ├── api │ └── hello.ts ├── _app.tsx ├── login.tsx └── index.tsx ├── tailwind.config.js ├── .gitignore ├── tsconfig.json ├── typings.d.ts ├── utils └── requests.ts ├── components ├── Thumbnail.tsx ├── Header.tsx ├── Row.tsx ├── Banner.tsx └── Modal.tsx ├── firebase.ts ├── styles └── globals.css ├── package.json ├── README.md └── hooks └── useAuth.tsx /.env.example: -------------------------------------------------------------------------------- 1 | NEXT_PUBLIC_API_KEY=YOUR TMDB API KEY -------------------------------------------------------------------------------- /constants/movie.ts: -------------------------------------------------------------------------------- 1 | export const baseUrl = 'https://image.tmdb.org/t/p/original/' 2 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | semi: false, 4 | } 5 | -------------------------------------------------------------------------------- /public/avatar.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SudoKMaar/netflix-clone-nextjs/HEAD/public/avatar.png -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SudoKMaar/netflix-clone-nextjs/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /public/login_background.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SudoKMaar/netflix-clone-nextjs/HEAD/public/login_background.jpg -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | module.exports = { 3 | reactStrictMode: true, 4 | images: { 5 | domains: ['image.tmdb.org', 'rb.gy'], 6 | }, 7 | } 8 | -------------------------------------------------------------------------------- /next-env.d.ts: -------------------------------------------------------------------------------- 1 | /// 2 | /// 3 | 4 | // NOTE: This file should not be edited 5 | // see https://nextjs.org/docs/basic-features/typescript for more information. 6 | -------------------------------------------------------------------------------- /atoms/modalAtom.ts: -------------------------------------------------------------------------------- 1 | import { DocumentData } from 'firebase/firestore' 2 | import { atom } from 'recoil' 3 | import { Movie } from '../typings' 4 | 5 | export const modalState = atom({ 6 | key: 'modalState', 7 | default: false, 8 | }) 9 | 10 | export const movieState = atom({ 11 | key: 'movieState', 12 | default: null, 13 | }) 14 | -------------------------------------------------------------------------------- /pages/api/hello.ts: -------------------------------------------------------------------------------- 1 | // Next.js API route support: https://nextjs.org/docs/api-routes/introduction 2 | import type { NextApiRequest, NextApiResponse } from 'next' 3 | 4 | type Data = { 5 | name: string 6 | } 7 | 8 | export default function handler( 9 | req: NextApiRequest, 10 | res: NextApiResponse 11 | ) { 12 | res.status(200).json({ name: 'John Doe' }) 13 | } 14 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | import { AuthProvider } from '../hooks/useAuth' 4 | import { RecoilRoot } from 'recoil' 5 | 6 | function MyApp({ Component, pageProps }: AppProps) { 7 | return ( 8 | 9 | {/* Higher Order Component */} 10 | 11 | 12 | 13 | 14 | ) 15 | } 16 | 17 | export default MyApp 18 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | content: [ 3 | './pages/**/*.{js,ts,jsx,tsx}', 4 | './components/**/*.{js,ts,jsx,tsx}', 5 | ], 6 | theme: { 7 | extend: { 8 | backgroundImage: { 9 | 'gradient-to-b': 10 | 'linear-gradient(to bottom,rgba(20,20,20,0) 0,rgba(20,20,20,.15) 15%,rgba(20,20,20,.35) 29%,rgba(20,20,20,.58) 44%,#141414 68%,#141414 100%);', 11 | }, 12 | }, 13 | }, 14 | plugins: [ 15 | require('tailwindcss-textshadow'), 16 | require('tailwind-scrollbar-hide'), 17 | require('tailwind-scrollbar'), 18 | ], 19 | } 20 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | /asdf 7 | .pnp.js 8 | 9 | # testing 10 | /coverage 11 | 12 | # next.js 13 | /.next/ 14 | /out/ 15 | 16 | # production 17 | /build 18 | 19 | # misc 20 | .DS_Store 21 | *.pem 22 | 23 | # debug 24 | npm-debug.log* 25 | yarn-debug.log* 26 | yarn-error.log* 27 | 28 | # local env files 29 | .env.local 30 | .env.development.local 31 | .env.test.local 32 | .env.production.local 33 | 34 | # vercel 35 | .vercel 36 | 37 | # typescript 38 | *.tsbuildinfo 39 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es5", 4 | "lib": ["dom", "dom.iterable", "esnext"], 5 | "allowJs": true, 6 | "skipLibCheck": true, 7 | "strict": true, 8 | "forceConsistentCasingInFileNames": true, 9 | "noEmit": true, 10 | "esModuleInterop": true, 11 | "module": "esnext", 12 | "moduleResolution": "node", 13 | "resolveJsonModule": true, 14 | "isolatedModules": true, 15 | "jsx": "preserve", 16 | "incremental": true 17 | }, 18 | "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"], 19 | "exclude": ["node_modules"] 20 | } 21 | -------------------------------------------------------------------------------- /typings.d.ts: -------------------------------------------------------------------------------- 1 | export interface Genre { 2 | id: number 3 | name: string 4 | } 5 | 6 | export interface Movie { 7 | title: string 8 | backdrop_path: string 9 | media_type?: string 10 | release_date?: string 11 | first_air_date: string 12 | genre_ids: number[] 13 | id: number 14 | name: string 15 | origin_country: string[] 16 | original_language: string 17 | original_name: string 18 | overview: string 19 | popularity: number 20 | poster_path: string 21 | vote_average: number 22 | vote_count: number 23 | } 24 | 25 | export interface Element { 26 | type: 27 | | 'Bloopers' 28 | | 'Featurette' 29 | | 'Behind the Scenes' 30 | | 'Clip' 31 | | 'Trailer' 32 | | 'Teaser' 33 | } 34 | -------------------------------------------------------------------------------- /utils/requests.ts: -------------------------------------------------------------------------------- 1 | const API_KEY = process.env.NEXT_PUBLIC_API_KEY 2 | const BASE_URL = 'https://api.themoviedb.org/3' 3 | 4 | const requests = { 5 | fetchTrending: `${BASE_URL}/trending/all/week?api_key=${API_KEY}&language=en-US`, 6 | fetchNetflixOriginals: `${BASE_URL}/discover/movie?api_key=${API_KEY}&with_networks=213`, 7 | fetchTopRated: `${BASE_URL}/movie/top_rated?api_key=${API_KEY}&language=en-US`, 8 | fetchActionMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-US&with_genres=28`, 9 | fetchComedyMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-US&with_genres=35`, 10 | fetchHorrorMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-US&with_genres=27`, 11 | fetchRomanceMovies: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-US&with_genres=10749`, 12 | fetchDocumentaries: `${BASE_URL}/discover/movie?api_key=${API_KEY}&language=en-US&with_genres=99`, 13 | } 14 | 15 | export default requests 16 | -------------------------------------------------------------------------------- /components/Thumbnail.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { useRecoilState } from 'recoil' 3 | import { modalState, movieState } from '../atoms/modalAtom' 4 | import { Movie } from '../typings' 5 | 6 | interface Props { 7 | // When using firebase 8 | movie: Movie 9 | } 10 | 11 | function Thumbnail({ movie }: Props) { 12 | const [showModal, setShowModal] = useRecoilState(modalState) 13 | const [currentMovie, setCurrentMovie] = useRecoilState(movieState) 14 | 15 | return ( 16 |
{ 19 | setCurrentMovie(movie) 20 | setShowModal(true) 21 | }} 22 | > 23 | 31 |
32 | ) 33 | } 34 | 35 | export default Thumbnail 36 | -------------------------------------------------------------------------------- /firebase.ts: -------------------------------------------------------------------------------- 1 | // Import the functions you need from the SDKs you need 2 | import { initializeApp, getApp, getApps } from 'firebase/app' 3 | import { getFirestore } from 'firebase/firestore' 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: 'AIzaSyAaA8grxg2mGlgsZyE26Q0XhHmdos7nOUA', 13 | authDomain: 'netflix-clone-v2-55b33.firebaseapp.com', 14 | projectId: 'netflix-clone-v2-55b33', 15 | storageBucket: 'netflix-clone-v2-55b33.appspot.com', 16 | messagingSenderId: '635770941028', 17 | appId: '1:635770941028:web:2c8c63862ccad7ed2af971', 18 | measurementId: 'G-ZYN0LJ1WWL', 19 | } 20 | // Initialize Firebase 21 | const app = !getApps().length ? initializeApp(firebaseConfig) : getApp() 22 | const db = getFirestore() 23 | const auth = getAuth() 24 | 25 | export default app 26 | export { auth, db } 27 | -------------------------------------------------------------------------------- /public/vercel.svg: -------------------------------------------------------------------------------- 1 | 3 | 4 | -------------------------------------------------------------------------------- /public/netflix_logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | /* html, body, heading */ 6 | @layer base { 7 | body { 8 | @apply bg-[#141414] text-white !scrollbar-thin !scrollbar-track-transparent !scrollbar-thumb-red-600; 9 | } 10 | 11 | header { 12 | @apply fixed top-0 z-50 flex w-full items-center justify-between px-4 py-4 transition-all lg:px-10 lg:py-6; 13 | } 14 | } 15 | 16 | /* custom classNames */ 17 | @layer components { 18 | .headerLink { 19 | @apply cursor-pointer text-sm font-light text-[#e5e5e5] transition duration-[.4s] hover:text-[#b3b3b3]; 20 | } 21 | 22 | .bannerButton { 23 | @apply flex items-center gap-x-2 rounded px-5 py-1.5 text-sm font-semibold transition hover:opacity-75 md:py-2.5 md:px-8 md:text-xl; 24 | } 25 | 26 | .input { 27 | @apply w-full rounded bg-[#333] px-5 py-3.5 placeholder-[gray] outline-none focus:bg-[#454545]; 28 | } 29 | 30 | .modalButton { 31 | @apply flex h-11 w-11 items-center justify-center rounded-full border-2 border-[gray] bg-[#2a2a2a]/60 transition hover:border-white hover:bg-white/10; 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-clone-nextjs", 3 | "version": "0.1.0", 4 | "private": true, 5 | "scripts": { 6 | "dev": "next dev", 7 | "build": "next build", 8 | "start": "next start", 9 | "lint": "next lint" 10 | }, 11 | "dependencies": { 12 | "@emotion/react": "^11.11.1", 13 | "@emotion/styled": "^11.11.0", 14 | "@heroicons/react": "^2.0.18", 15 | "@mui/material": "^5.14.1", 16 | "@types/node": "20.4.4", 17 | "@types/react": "18.2.15", 18 | "@types/react-dom": "18.2.7", 19 | "eslint": "8.45.0", 20 | "eslint-config-next": "13.4.12", 21 | "firebase": "^10.1.0", 22 | "next": "13.4.12", 23 | "next-transpile-modules": "^10.0.0", 24 | "react": "18.2.0", 25 | "react-dom": "18.2.0", 26 | "react-hook-form": "^7.45.2", 27 | "react-hot-toast": "^2.4.1", 28 | "react-icons": "^4.10.1", 29 | "react-player": "^2.12.0", 30 | "recoil": "^0.7.7", 31 | "tailwind-scrollbar": "^3.0.4", 32 | "tailwind-scrollbar-hide": "^1.1.7", 33 | "tailwindcss-textshadow": "^2.1.3", 34 | "typescript": "5.1.6", 35 | "uglify-es": "^3.3.9" 36 | }, 37 | "devDependencies": { 38 | "autoprefixer": "^10.4.14", 39 | "postcss": "^8.4.27", 40 | "tailwindcss": "^3.3.3" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { BellIcon, MagnifyingGlassIcon } from '@heroicons/react/24/solid' 3 | import Link from 'next/link' 4 | import { useEffect, useState } from 'react' 5 | import useAuth from '../hooks/useAuth' 6 | 7 | function Header() { 8 | const [isScrolled, setIsScrolled] = useState(false) 9 | const { logout } = useAuth() 10 | 11 | useEffect(() => { 12 | const handleScroll = () => { 13 | if (window.scrollY > 0) { 14 | setIsScrolled(true) 15 | } else { 16 | setIsScrolled(false) 17 | } 18 | } 19 | 20 | window.addEventListener('scroll', handleScroll) 21 | 22 | return () => { 23 | window.removeEventListener('scroll', handleScroll) 24 | } 25 | }, []) 26 | 27 | return ( 28 |
29 |
30 | netflix logo 37 | 38 |
    39 |
  • Home
  • 40 |
  • TV Shows
  • 41 |
  • Movies
  • 42 |
  • New & Popular
  • 43 |
  • My List
  • 44 |
45 |
46 | 47 |
48 | 49 |

Kids

50 | 51 | {/* */} 52 | 58 | {/* */} 59 |
60 |
61 | ) 62 | } 63 | 64 | export default Header 65 | -------------------------------------------------------------------------------- /components/Row.tsx: -------------------------------------------------------------------------------- 1 | import { ChevronLeftIcon, ChevronRightIcon } from '@heroicons/react/24/outline' 2 | import { useRef, useState } from 'react' 3 | import { Movie } from '../typings' 4 | import Thumbnail from './Thumbnail' 5 | 6 | interface Props { 7 | title: string 8 | // When using firebase 9 | // movie: Movie | DocumentData[] 10 | movies: Movie[] 11 | } 12 | 13 | function Row({ title, movies }: Props) { 14 | const rowRef = useRef(null) 15 | const [isMoved, setIsMoved] = useState(false) 16 | 17 | const handleClick = (direction: string) => { 18 | setIsMoved(true) 19 | 20 | if (rowRef.current) { 21 | const { scrollLeft, clientWidth } = rowRef.current 22 | 23 | const scrollTo = 24 | direction === 'left' 25 | ? scrollLeft - clientWidth 26 | : scrollLeft + clientWidth 27 | 28 | rowRef.current.scrollTo({ left: scrollTo, behavior: 'smooth' }) 29 | } 30 | } 31 | 32 | return ( 33 |
34 |

35 | {title} 36 |

37 |
38 | handleClick('left')} 43 | /> 44 | 45 |
49 | {movies.map((movie) => ( 50 | 51 | ))} 52 |
53 | 54 | handleClick('right')} 57 | /> 58 |
59 |
60 | ) 61 | } 62 | 63 | export default Row 64 | -------------------------------------------------------------------------------- /components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { useEffect, useState } from 'react' 3 | import { baseUrl } from '../constants/movie' 4 | import { Movie } from '../typings' 5 | import { FaPlay } from 'react-icons/fa' 6 | import { InformationCircleIcon } from '@heroicons/react/24/outline' 7 | import { useRecoilState } from 'recoil' 8 | import { modalState, movieState } from '../atoms/modalAtom' 9 | 10 | interface Props { 11 | netflixOriginals: Movie[] 12 | } 13 | 14 | function Banner({ netflixOriginals }: Props) { 15 | const [movie, setMovie] = useState(null) 16 | const [showModal, setShowModal] = useRecoilState(modalState) 17 | const [currentMovie, setCurrentMovie] = useRecoilState(movieState) 18 | 19 | useEffect(() => { 20 | setMovie( 21 | netflixOriginals[Math.floor(Math.random() * netflixOriginals.length)] 22 | ) 23 | }, [netflixOriginals]) 24 | 25 | return ( 26 |
27 |
28 | {movie?.title || movie?.name || movie?.original_name} 34 |
35 | 36 |

37 | {movie?.title || movie?.name || movie?.original_name} 38 |

39 |

40 | {movie?.overview} 41 |

42 | 43 |
44 | 47 | 56 |
57 |
58 | ) 59 | } 60 | 61 | export default Banner 62 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Netflix Clone - Next.js 2 | 3 | ## Overview 4 | 5 | This project is a Netflix clone built using Next.js, React, Tailwind CSS, Recoil, Material-UI, and Heroicons, with Firebase for authentication. The goal of this project is to recreate the user interface and functionality of the popular streaming platform Netflix. Users can browse through a list of movies and TV shows, view details about each title, watch trailers, and even sign up or log in using Firebase authentication. 6 | 7 | Deployed at [Fakeflix](https://fakeflix-kmaar.vercel.app/) 8 | 9 | ## Features 10 | 11 | - **Authentication**: Users can sign up and log in to their accounts using their email and password. Firebase handles the authentication process. 12 | 13 | - **Browse Movies and TV Shows**: Users can explore a curated list of movies and TV shows, including Netflix originals, top-rated titles, trending content, action movies, comedies, horror movies, romance movies, and documentaries. 14 | 15 | - **Movie Details**: Clicking on a movie or TV show card displays more information about the title, including its release date, genre, original language, overview, and voting statistics. 16 | 17 | - **Watch Trailers**: Users can watch trailers for movies and TV shows by clicking on the play button, which opens a modal with the trailer video. 18 | 19 | - **Responsive Design**: The application is responsive and adapts to different screen sizes, providing a seamless experience across devices. 20 | 21 | - **Stripe Integration (Upcoming Feature)**: In future updates, we plan to integrate Stripe to enable users to subscribe and make payments for premium content or subscription plans. 22 | 23 | ## Tech Stack 24 | 25 | - **Next.js**: A React framework for building server-side rendered applications. 26 | - **React**: A JavaScript library for building user interfaces. 27 | - **Tailwind CSS**: A utility-first CSS framework for rapid UI development. 28 | - **Recoil**: A state management library for React applications. 29 | - **Material-UI**: A popular React UI framework providing pre-built components following the Material Design guidelines. 30 | - **Heroicons**: A set of free SVG icons designed by the Heroicons team. 31 | 32 | ## Getting Started 33 | 34 | Follow these steps to set up the project on your local machine: 35 | 36 | 1. Clone the repository: 37 | 38 | ```bash 39 | git clone https://github.com/SudoKMaar/netflix-clone-nextjs.git 40 | ``` 41 | 42 | 2. Install dependencies: 43 | 44 | ```bash 45 | cd netflix-clone-nextjs 46 | npm install 47 | ``` 48 | 49 | 3. Rename `.env.example` to `.env.local`: 50 | 51 | - Rename the `.env.example` file in the root directory to `.env.local`. 52 | - Replace the placeholder values in `.env.local` with your actual Firebase configuration. 53 | 54 | 4. Run the development server: 55 | 56 | ```bash 57 | npm run dev 58 | ``` 59 | 60 | 5. Open your browser and visit `http://localhost:3000` to see the application running. 61 | 62 | ## Contributing 63 | 64 | Contributions to this project are welcome! If you have any bug fixes, new features, or improvements to suggest, please create a pull request. We follow the "fork-and-pull" Git workflow. 65 | 66 | ## Authors 67 | 68 | - [Abhishek Kumar](https://kmaar.vercel.app) 69 | 70 | ## License 71 | 72 | This project is licensed under the MIT License - see the LICENSE.md file for details. 73 | -------------------------------------------------------------------------------- /hooks/useAuth.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | createUserWithEmailAndPassword, 3 | onAuthStateChanged, 4 | signInWithEmailAndPassword, 5 | signOut, 6 | User, 7 | } from 'firebase/auth' 8 | 9 | import { useRouter } from 'next/router' 10 | import { createContext, useContext, useEffect, useMemo, useState } from 'react' 11 | import { auth } from '../firebase' 12 | 13 | interface IAuth { 14 | user: User | null 15 | signUp: (email: string, password: string) => Promise 16 | signIn: (email: string, password: string) => Promise 17 | logout: () => Promise 18 | error: string | null 19 | loading: boolean 20 | } 21 | 22 | const AuthContext = createContext({ 23 | user: null, 24 | signUp: async () => {}, 25 | signIn: async () => {}, 26 | logout: async () => {}, 27 | error: null, 28 | loading: false, 29 | }) 30 | 31 | interface AuthProviderProps { 32 | children: React.ReactNode 33 | } 34 | 35 | export const AuthProvider = ({ children }: AuthProviderProps) => { 36 | const [loading, setLoading] = useState(false) 37 | const [user, setUser] = useState(null) 38 | const [error, setError] = useState(null) 39 | const [initialLoading, setInitialLoading] = useState(true) 40 | const router = useRouter() 41 | 42 | // Persisting the user 43 | useEffect( 44 | () => 45 | onAuthStateChanged(auth, (user) => { 46 | if (user) { 47 | // Logged in... 48 | setUser(user) 49 | setLoading(false) 50 | } else { 51 | // Not logged in... 52 | setUser(null) 53 | setLoading(true) 54 | router.push('/login') 55 | } 56 | 57 | setInitialLoading(false) 58 | }), 59 | [auth] 60 | ) 61 | 62 | const signUp = async (email: string, password: string) => { 63 | setLoading(true) 64 | 65 | await createUserWithEmailAndPassword(auth, email, password) 66 | .then((userCredential) => { 67 | setUser(userCredential.user) 68 | router.push('/') 69 | setLoading(false) 70 | }) 71 | .catch((error) => alert(error.message)) 72 | .finally(() => setLoading(false)) 73 | } 74 | 75 | const signIn = async (email: string, password: string) => { 76 | setLoading(true) 77 | 78 | await signInWithEmailAndPassword(auth, email, password) 79 | .then((userCredential) => { 80 | setUser(userCredential.user) 81 | router.push('/') 82 | setLoading(false) 83 | }) 84 | .catch((error) => alert(error.message)) 85 | .finally(() => setLoading(false)) 86 | } 87 | 88 | const logout = async () => { 89 | setLoading(true) 90 | 91 | signOut(auth) 92 | .then(() => { 93 | setUser(null) 94 | }) 95 | .catch((error) => alert(error.message)) 96 | .finally(() => setLoading(false)) 97 | } 98 | 99 | const memoedValue = useMemo( 100 | () => ({ 101 | user, 102 | signUp, 103 | signIn, 104 | loading, 105 | logout, 106 | error, 107 | }), 108 | [user, loading] 109 | ) 110 | 111 | return ( 112 | 113 | {!initialLoading && children} 114 | 115 | ) 116 | } 117 | 118 | export default function useAuth() { 119 | return useContext(AuthContext) 120 | } 121 | -------------------------------------------------------------------------------- /pages/login.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import Image from 'next/image' 3 | import { useState } from 'react' 4 | import { SubmitHandler, useForm } from 'react-hook-form' 5 | import useAuth from '../hooks/useAuth' 6 | 7 | interface Inputs { 8 | email: string 9 | password: string 10 | } 11 | 12 | function Login() { 13 | const [login, setLogin] = useState(false) 14 | const { signIn, signUp } = useAuth() 15 | 16 | const { 17 | register, 18 | handleSubmit, 19 | formState: { errors }, 20 | } = useForm() 21 | 22 | const onSubmit: SubmitHandler = async ({ email, password }) => { 23 | if (login) { 24 | await signIn(email, password) 25 | } else { 26 | await signUp(email, password) 27 | } 28 | } 29 | 30 | return ( 31 |
32 | 33 | Netflix 34 | 35 | 36 | 43 | 44 | 51 | 52 |
56 |

Sign In

57 |
58 | 71 | 84 |
85 | 86 | 92 | 93 |
94 | New to Netflix?{' '} 95 | 102 |
103 |
104 |
105 | ) 106 | } 107 | 108 | export default Login 109 | -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { useRecoilValue } from 'recoil' 3 | import { modalState } from '../atoms/modalAtom' 4 | import Banner from '../components/Banner' 5 | import Header from '../components/Header' 6 | import Modal from '../components/Modal' 7 | import Row from '../components/Row' 8 | import useAuth from '../hooks/useAuth' 9 | import { Movie } from '../typings' 10 | import requests from '../utils/requests' 11 | 12 | interface Props { 13 | netflixOriginals: Movie[] 14 | trendingNow: Movie[] 15 | topRated: Movie[] 16 | actionMovies: Movie[] 17 | comedyMovies: Movie[] 18 | horrorMovies: Movie[] 19 | romanceMovies: Movie[] 20 | documentaries: Movie[] 21 | } 22 | 23 | const Home = ({ 24 | netflixOriginals, 25 | actionMovies, 26 | comedyMovies, 27 | documentaries, 28 | horrorMovies, 29 | romanceMovies, 30 | topRated, 31 | trendingNow, 32 | }: Props) => { 33 | const { loading } = useAuth() 34 | const showModal = useRecoilValue(modalState) 35 | 36 | if (loading) return null 37 | 38 | return ( 39 |
44 | 45 | Home - Netflix 46 | 47 | 48 | 49 |
50 |
51 | 52 |
53 | 54 | 55 | 56 | {/* My List Component */} 57 | 58 | 59 | 60 | 61 |
62 |
63 | {showModal && } 64 |
65 | ) 66 | } 67 | 68 | export default Home 69 | 70 | export const getServerSideProps = async () => { 71 | const [ 72 | netflixOriginals, 73 | trendingNow, 74 | topRated, 75 | actionMovies, 76 | comedyMovies, 77 | horrorMovies, 78 | romanceMovies, 79 | documentaries, 80 | ] = await Promise.all([ 81 | fetch(requests.fetchNetflixOriginals).then((res) => res.json()), 82 | fetch(requests.fetchTrending).then((res) => res.json()), 83 | fetch(requests.fetchTopRated).then((res) => res.json()), 84 | fetch(requests.fetchActionMovies).then((res) => res.json()), 85 | fetch(requests.fetchComedyMovies).then((res) => res.json()), 86 | fetch(requests.fetchHorrorMovies).then((res) => res.json()), 87 | fetch(requests.fetchRomanceMovies).then((res) => res.json()), 88 | fetch(requests.fetchDocumentaries).then((res) => res.json()), 89 | ]) 90 | 91 | return { 92 | props: { 93 | netflixOriginals: netflixOriginals.results, 94 | trendingNow: trendingNow.results, 95 | topRated: topRated.results, 96 | actionMovies: actionMovies.results, 97 | comedyMovies: comedyMovies.results, 98 | horrorMovies: horrorMovies.results, 99 | romanceMovies: romanceMovies.results, 100 | documentaries: documentaries.results, 101 | }, 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import { 2 | PlusIcon, 3 | HandThumbUpIcon, 4 | SpeakerXMarkIcon, 5 | XMarkIcon, 6 | } from '@heroicons/react/24/outline' 7 | import { SpeakerWaveIcon } from '@heroicons/react/24/solid' 8 | import MuiModal from '@mui/material/Modal' 9 | import { useEffect, useState } from 'react' 10 | import { FaPlay } from 'react-icons/fa' 11 | import ReactPlayer from 'react-player/lazy' 12 | import { useRecoilState } from 'recoil' 13 | import { modalState, movieState } from '../atoms/modalAtom' 14 | import { Element, Genre } from '../typings' 15 | 16 | function Modal() { 17 | const [showModal, setShowModal] = useRecoilState(modalState) 18 | const [movie, setMovie] = useRecoilState(movieState) 19 | const [trailer, setTrailer] = useState('') 20 | const [genres, setGenres] = useState([]) 21 | const [muted, setMuted] = useState(true) 22 | 23 | useEffect(() => { 24 | if (!movie) return 25 | 26 | async function fetchMovie() { 27 | const data = await fetch( 28 | `https://api.themoviedb.org/3/${ 29 | movie?.media_type === 'tv' ? 'tv' : 'movie' 30 | }/${movie?.id}?api_key=${ 31 | process.env.NEXT_PUBLIC_API_KEY 32 | }&language=en-US&append_to_response=videos` 33 | ) 34 | .then((response) => response.json()) 35 | .catch((err) => console.log(err.message)) 36 | 37 | if (data?.videos) { 38 | const index = data.videos.results.findIndex( 39 | (element: Element) => element.type === 'Trailer' 40 | ) 41 | setTrailer(data.videos?.results[index]?.key) 42 | } 43 | if (data?.genres) { 44 | setGenres(data.genres) 45 | } 46 | } 47 | 48 | fetchMovie() 49 | }, [movie]) 50 | 51 | const handleClose = () => { 52 | setShowModal(false) 53 | } 54 | 55 | console.log(trailer) 56 | 57 | return ( 58 | 63 | <> 64 | 70 | 71 |
72 | 80 |
81 |
82 | 86 | 87 | 90 | 91 | 94 |
95 | 102 |
103 |
104 | 105 |
106 |
107 |
108 |

109 | {movie!.vote_average * 10}% Match 110 |

111 |

112 | {movie?.release_date || movie?.first_air_date} 113 |

114 |
115 | HD 116 |
117 |
118 | 119 |
120 |

{movie?.overview}

121 |
122 |
123 | Genres: 124 | {genres.map((genre) => genre.name).join(', ')} 125 |
126 | 127 |
128 | Original language: 129 | {movie?.original_language} 130 |
131 | 132 |
133 | Total votes: 134 | {movie?.vote_count} 135 |
136 |
137 |
138 |
139 |
140 | 141 |
142 | ) 143 | } 144 | 145 | export default Modal 146 | --------------------------------------------------------------------------------