├── README.md ├── .eslintrc.json ├── constants └── movie.ts ├── public └── favicon.ico ├── components ├── Row.tsx ├── Banner.tsx └── Header.tsx ├── postcss.config.js ├── pages ├── _app.tsx ├── _document.tsx ├── api │ └── hello.ts └── index.tsx ├── next.config.js ├── tailwind.config.js ├── .gitignore ├── tsconfig.json ├── typings.d.ts ├── package.json ├── styles └── globals.css └── utils └── request.ts /README.md: -------------------------------------------------------------------------------- 1 | ### Netflix clone with next js and typescript. 2 | -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /constants/movie.ts: -------------------------------------------------------------------------------- 1 | export const baseUrl = 'https://image.tmdb.org/t/p/original/' -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/OgaDavid/netflix-clone/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /components/Row.tsx: -------------------------------------------------------------------------------- 1 | function Row() { 2 | return ( 3 |
Row
4 | ) 5 | } 6 | 7 | export default Row -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: { 3 | tailwindcss: {}, 4 | autoprefixer: {}, 5 | }, 6 | } 7 | -------------------------------------------------------------------------------- /pages/_app.tsx: -------------------------------------------------------------------------------- 1 | import '../styles/globals.css' 2 | import type { AppProps } from 'next/app' 3 | 4 | export default function App({ Component, pageProps }: AppProps) { 5 | return 6 | } 7 | -------------------------------------------------------------------------------- /next.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('next').NextConfig} */ 2 | const nextConfig = { 3 | reactStrictMode: true, 4 | } 5 | 6 | module.exports = { 7 | reactStrictMode: true, 8 | images: { 9 | domains: ["image.tmdb.org"] 10 | }, 11 | } 12 | -------------------------------------------------------------------------------- /pages/_document.tsx: -------------------------------------------------------------------------------- 1 | import { Html, Head, Main, NextScript } from 'next/document' 2 | 3 | export default function Document() { 4 | return ( 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | ) 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 | -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | content: [ 4 | "./pages/**/*.{js,ts,jsx,tsx}", 5 | "./components/**/*.{js,ts,jsx,tsx}", 6 | ], 7 | theme: { 8 | extend: { 9 | colors: { 10 | 'grey-nav': '#e5e5e5', 11 | 'grey-hover': '#b3b3b3', 12 | 'bg-black': '#141414' 13 | }, 14 | 15 | transitionDuration: { 16 | '400': '.4s' 17 | } 18 | }, 19 | }, 20 | plugins: [ 21 | require('tailwindcss-textshadow') 22 | ] 23 | } -------------------------------------------------------------------------------- /.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 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /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 type Genre = { 2 | id: number 3 | name: string 4 | } 5 | 6 | export type 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 type Element = { 26 | type: 27 | | 'Bloopers' 28 | | 'Featurette' 29 | | 'Behind the Scenes' 30 | | 'Clip' 31 | | 'Trailer' 32 | | 'Teaser' 33 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-clone", 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 | "@heroicons/react": "^2.0.13", 13 | "@next/font": "13.1.1", 14 | "@types/node": "18.11.18", 15 | "@types/react": "18.0.26", 16 | "@types/react-dom": "18.0.10", 17 | "eslint": "8.31.0", 18 | "eslint-config-next": "13.1.1", 19 | "next": "13.1.1", 20 | "react": "18.2.0", 21 | "react-dom": "18.2.0", 22 | "react-icons": "^4.7.1", 23 | "typescript": "4.9.4" 24 | }, 25 | "devDependencies": { 26 | "autoprefixer": "^10.4.13", 27 | "postcss": "^8.4.20", 28 | "tailwindcss": "^3.2.4", 29 | "tailwindcss-textshadow": "^2.1.3" 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /styles/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | 5 | @layer base { 6 | body { 7 | @apply bg-bg-black text-white 8 | } 9 | 10 | header { 11 | @apply fixed top-0 z-50 w-full flex justify-between items-center px-4 py-4 transition-all duration-500 lg:px-10 lg:py-6 12 | } 13 | } 14 | 15 | @layer components { 16 | .menuLink { 17 | @apply cursor-pointer text-sm text-grey-nav transition duration-400 hover:text-grey-hover 18 | } 19 | 20 | .glassmorphism { 21 | background-color: rgba(255, 255, 255, 0.274); 22 | backdrop-filter: blur(5px); 23 | } 24 | 25 | .header-padding { 26 | padding: 15px 35px 15px 35px; 27 | 28 | } 29 | 30 | .home-btn { 31 | @apply flex items-center gap-x-2 rounded px-5 py-1.5 text-sm font-semibold transition duration-400 hover:opacity-75 md:py-2.5 md:px-8 md:text-xl 32 | } 33 | } -------------------------------------------------------------------------------- /utils/request.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 -------------------------------------------------------------------------------- /components/Banner.tsx: -------------------------------------------------------------------------------- 1 | import Image from 'next/image' 2 | import { useEffect, useState } from 'react' 3 | import { Movie } from '../typings' 4 | import { baseUrl } from '../constants/movie' 5 | import { FaPlay } from "react-icons/fa" 6 | 7 | 8 | type Props = { 9 | netflixOriginals: Movie[] 10 | } 11 | 12 | function Banner({ netflixOriginals }: Props) { 13 | const [movie, setMovie] = useState(null) 14 | 15 | useEffect(() => { 16 | setMovie(netflixOriginals[Math.floor(Math.random() * netflixOriginals.length)]) 17 | },[netflixOriginals]) 18 | console.log(movie); 19 | return ( 20 | <> 21 |
22 |
23 | Movie backdrop 24 |
25 | 26 |

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

27 |

{movie?.overview}

28 | 29 |
30 | 31 | 37 |
38 | 39 |
40 | 41 | 42 | ) 43 | } 44 | 45 | export default Banner -------------------------------------------------------------------------------- /components/Header.tsx: -------------------------------------------------------------------------------- 1 | import Link from "next/link"; 2 | import { useState, useEffect } from 'react'; 3 | 4 | function Header() { 5 | const [isScrolled, setIsScrolled] = useState(false); 6 | 7 | useEffect(() => { 8 | const handleScroll = () => { 9 | if(window.scrollY > 0) { 10 | setIsScrolled(true); 11 | } 12 | else { 13 | setIsScrolled(false); 14 | } 15 | } 16 | window.addEventListener("scroll", handleScroll); 17 | return () => window.removeEventListener("scroll", handleScroll); 18 | }, []) 19 | 20 | 21 | return ( 22 |
23 |
24 | Netflix logo 29 | 30 |
    31 |
  • Home
  • 32 |
  • TV Shows
  • 33 |
  • Movies
  • 34 |
  • New & Popular
  • 35 |
  • My List
  • 36 |
37 |
38 | 39 |
40 | 41 | 42 | 43 |

Kids

44 | 45 | 46 | 47 | 48 | 49 | profile icon 50 | 51 |
52 |
53 | ) 54 | } 55 | 56 | export default Header -------------------------------------------------------------------------------- /pages/index.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head' 2 | import { Inter } from '@next/font/google' 3 | import Header from '../components/Header' 4 | import Banner from '../components/Banner' 5 | import requests from '../utils/request' 6 | import { Movie } from '../typings' 7 | const inter = Inter({ subsets: ['latin'] }) 8 | 9 | export const getServerSideProps = async() => { 10 | const [ 11 | netflixOriginals, 12 | trendingNow, 13 | topRated, 14 | actionMovies, 15 | comedyMovies, 16 | horrorMovies, 17 | romanceMovies, 18 | documentaries, 19 | ] = await Promise.all([ 20 | fetch(requests.fetchNetflixOriginals).then((res) => res.json()), 21 | fetch(requests.fetchTrending).then((res) => res.json()), 22 | fetch(requests.fetchTopRated).then((res) => res.json()), 23 | fetch(requests.fetchActionMovies).then((res) => res.json()), 24 | fetch(requests.fetchComedyMovies).then((res) => res.json()), 25 | fetch(requests.fetchHorrorMovies).then((res) => res.json()), 26 | fetch(requests.fetchRomanceMovies).then((res) => res.json()), 27 | fetch(requests.fetchDocumentaries).then((res) => res.json()), 28 | ]) 29 | 30 | return { 31 | props: { 32 | netflixOriginals: netflixOriginals.results, 33 | trendingNow: trendingNow.results, 34 | topRated: topRated.results, 35 | actionMovies: actionMovies.results, 36 | comedyMovies: comedyMovies.results, 37 | horrorMovies: horrorMovies.results, 38 | romanceMovies: romanceMovies.results, 39 | documentaries: documentaries.results 40 | } 41 | } 42 | 43 | } 44 | 45 | type Props = { 46 | netflixOriginals: Movie[]; 47 | trendingNow: Movie[] 48 | topRated: Movie[] 49 | actionMovies: Movie[] 50 | comedyMovies: Movie[] 51 | horrorMovies: Movie[] 52 | romanceMovies: Movie[] 53 | documentaries: Movie[] 54 | } 55 | 56 | export default function Home({ 57 | netflixOriginals, 58 | actionMovies, 59 | comedyMovies, 60 | documentaries, 61 | horrorMovies, 62 | romanceMovies, 63 | topRated, 64 | trendingNow 65 | }: Props) { 66 | return ( 67 |
68 | 69 | Netflix - Watch TV Shows Online, Watch Movies Online. 70 | 71 | 72 | 73 | 74 | 75 | 76 |
77 |
78 | 79 |
80 |
81 | ) 82 | } 83 | 84 | //bg-gradient-to-b from-gray-900/10 to-[#010511] 85 | --------------------------------------------------------------------------------