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

29 |
30 |
31 | - Home
32 | - TV Shows
33 | - Movies
34 | - New & Popular
35 | - My List
36 |
37 |
38 |
39 |
40 |
43 |
Kids
44 |
48 |
49 |

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