├── .eslintignore ├── .eslintrc.json ├── .gitignore ├── .prettierignore ├── .prettierrc.json ├── README.md ├── components ├── Cast │ ├── CastItem.tsx │ └── CastList.tsx ├── Input.tsx ├── ItemDetailView.tsx ├── ItemGrid.tsx ├── ItemView.tsx ├── Layout │ ├── Footer.tsx │ ├── Meta.tsx │ ├── Navbar.tsx │ ├── NavbarLink.tsx │ ├── Pagination.tsx │ └── Search.tsx ├── Modal.tsx ├── PageHeader.tsx ├── Rating.tsx ├── Slider │ ├── ItemCard.tsx │ ├── ItemList.tsx │ ├── ItemSlider.tsx │ ├── MainSlider.tsx │ └── MainSliderItem.tsx ├── Video │ └── VideoItem.tsx ├── Watch │ └── AboutDetail.tsx ├── buttons │ └── Button.tsx └── index.tsx ├── model ├── movie.ts └── tv.ts ├── next-env.d.ts ├── next.config.js ├── package.json ├── pages ├── _app.tsx ├── index.tsx ├── movie │ ├── [id] │ │ ├── index.tsx │ │ └── watch.tsx │ └── index.tsx ├── search.tsx └── tv │ ├── [id] │ ├── episode.tsx │ ├── index.tsx │ └── watch.tsx │ └── index.tsx ├── postcss.config.js ├── public ├── footer-bg.jpg ├── icon.png ├── preview.png └── tmovie.png ├── redux ├── hooks.ts ├── modalSlice.ts ├── movieSlice.ts ├── store.ts └── tvSlice.ts ├── styles ├── _index.scss ├── _variable.scss └── globals.scss ├── tailwind.config.js ├── tsconfig.json ├── ultis ├── apiConfig.ts ├── axiosClient.ts ├── constants.ts └── tmdbApi.ts └── yarn.lock /.eslintignore: -------------------------------------------------------------------------------- 1 | /node_modules -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true 5 | }, 6 | "extends": [ 7 | "next/core-web-vitals", 8 | "eslint:recommended", 9 | "plugin:react/recommended", 10 | // "plugin:@typescript-eslint/recommended", 11 | // "plugin:@typescript-eslint/eslint-recommended", 12 | "plugin:prettier/recommended" 13 | ], 14 | // "parser": "@typescript-eslint/parser", 15 | "parserOptions": { 16 | "ecmaFeatures": { 17 | "jsx": true, 18 | "js": true 19 | }, 20 | "ecmaVersion": 12, 21 | "sourceType": "module" 22 | }, 23 | "plugins": [ 24 | "react" 25 | // "@typescript-eslint" 26 | ], 27 | "rules": { 28 | "react/display-name": 0, 29 | "react/jsx-props-no-spreading": 0, 30 | "react/state-in-constructor": 0, 31 | "react/static-property-placement": 0, 32 | "react/destructuring-assignment": "off", 33 | "react/jsx-filename-extension": "off", 34 | "react/no-array-index-key": "warn", 35 | "react-hooks/rules-of-hooks": "error", // Checks rules of Hooks 36 | // "react-hooks/exhaustive-deps": "warn", // Checks deps of Hooks 37 | "react/require-default-props": 0, 38 | "react/jsx-fragments": 0, 39 | "react/jsx-wrap-multilines": 0, 40 | "react/prop-types": 0, 41 | "react/forbid-prop-types": 0, 42 | "react/sort-comp": 0, 43 | "react/react-in-jsx-scope": 0, 44 | "react/jsx-one-expression-per-line": 0, 45 | "generator-star-spacing": 0, 46 | "function-paren-newline": 0, 47 | "import/no-unresolved": 0, 48 | "import/order": 0, 49 | "import/no-named-as-default": 0, 50 | "import/no-cycle": 0, 51 | "import/prefer-default-export": 0, 52 | "import/no-default-export": 0, 53 | "import/no-extraneous-dependencies": 0, 54 | "import/named": 0, 55 | "import/no-named-as-default-member": 0, 56 | "import/no-duplicates": 0, 57 | "import/no-self-import": 0, 58 | "import/extensions": 0, 59 | "import/no-useless-path-segments": 0, 60 | "jsx-a11y/no-noninteractive-element-interactions": 0, 61 | "jsx-a11y/click-events-have-key-events": 0, 62 | "jsx-a11y/no-static-element-interactions": 0, 63 | "jsx-a11y/anchor-is-valid": 0, 64 | "sort-imports": 0, 65 | "class-methods-use-this": 0, 66 | "no-confusing-arrow": 0, 67 | "linebreak-style": 0, 68 | "no-prototype-builtins": "off", 69 | "unicorn/prevent-abbreviations": "off", 70 | // Conflict with prettier 71 | "arrow-body-style": 0, 72 | "arrow-parens": 0, 73 | "object-curly-newline": 0, 74 | "implicit-arrow-linebreak": 0, 75 | "operator-linebreak": 0, 76 | "eslint-comments/no-unlimited-disable": 0, 77 | "no-param-reassign": 2, 78 | "space-before-function-paren": 0, 79 | "@next/next/no-img-element": "off", 80 | "no-unused-vars": [ 81 | "error", 82 | { 83 | "vars": "all", 84 | "args": "after-used", 85 | "ignoreRestSiblings": false, 86 | "argsIgnorePattern": "^_" 87 | } 88 | ], 89 | "prettier/prettier": [ 90 | "error", 91 | { 92 | "usePrettierrc": true 93 | }, 94 | {} 95 | ] 96 | } 97 | } -------------------------------------------------------------------------------- /.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 | .vscode 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 | .pnpm-debug.log* 28 | 29 | # local env files 30 | .env*.local 31 | 32 | # vercel 33 | .vercel 34 | 35 | # typescript 36 | *.tsbuildinfo 37 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/itz-dude/Tv---flim/5d88820f4e634d343a5197c33770054fa2fb9bbd/.prettierignore -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "semi": true, 3 | "trailingComma": "es5", 4 | "singleQuote": true, 5 | "tabWidth": 2, 6 | "useTabs": false, 7 | "endOfLine": "auto" 8 | } 9 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | you can use vercel to deploy 2 | set the tmdb api as env and you are up 3 | well the ui is a fork but the api integration is custom made so dont edit anyhting or else it'll break 4 | 5 | 6 | 7 | DEMO: 8 | https://fggg.vercel.app/ 9 | ![image](https://user-images.githubusercontent.com/108194557/188052023-7af11493-7144-45d6-a811-e6469c95c263.png) 10 | -------------------------------------------------------------------------------- /components/Cast/CastItem.tsx: -------------------------------------------------------------------------------- 1 | import { Cast } from '@/model/movie'; 2 | import { w500Image } from '@/ultis/constants'; 3 | 4 | interface Props { 5 | item: Cast; 6 | } 7 | 8 | const CastItem = ({ item }: Props) => { 9 | return ( 10 |
11 |
15 |

{item.name}

16 |

{item.character}

17 |
18 | ); 19 | }; 20 | 21 | export default CastItem; 22 | -------------------------------------------------------------------------------- /components/Cast/CastList.tsx: -------------------------------------------------------------------------------- 1 | import { CastItem } from '@/components'; 2 | import { Cast } from '@/model/movie'; 3 | 4 | interface Props { 5 | items: Cast[]; 6 | } 7 | 8 | const CastList = ({ items }: Props) => { 9 | return ( 10 |
11 | {items.map((item) => ( 12 | 13 | ))} 14 |
15 | ); 16 | }; 17 | 18 | export default CastList; 19 | -------------------------------------------------------------------------------- /components/Input.tsx: -------------------------------------------------------------------------------- 1 | import { ChangeEvent, KeyboardEvent } from 'react'; 2 | 3 | interface InputProps { 4 | type: string; 5 | placeholder: string; 6 | value?: string; 7 | onChange?: (_e: ChangeEvent) => void; 8 | onKeyup?: (_e: KeyboardEvent) => void; 9 | } 10 | 11 | const Input = ({ type, placeholder, value, onChange, onKeyup }: InputProps) => { 12 | return ( 13 | 22 | ); 23 | }; 24 | 25 | export default Input; 26 | -------------------------------------------------------------------------------- /components/ItemDetailView.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | import { useRouter } from 'next/router'; 3 | import Link from 'next/link'; 4 | 5 | import { MovieItemProps, Cast, VideoTrailer } from '@/model/movie'; 6 | import { Rating, Button, CastList, VideoItem, ItemList } from '@/components'; 7 | import { imageOriginal } from '@/ultis/constants'; 8 | 9 | interface Props { 10 | data: MovieItemProps; 11 | casts: Cast[]; 12 | videos: VideoTrailer[]; 13 | similar: MovieItemProps[]; 14 | } 15 | 16 | const ItemDetailView: NextPage = ({ data, casts, videos, similar }) => { 17 | const router = useRouter(); 18 | return ( 19 | <> 20 |
26 |
27 |
28 |
34 |
35 |
36 |

{data.title || data.name}

37 |
38 | 39 |
40 |
41 |

Release Date: {data.release_date}

42 |
43 |
44 | {data.genres.map((genre) => ( 45 | 49 | {genre.name} 50 | 51 | ))} 52 |
53 |

{data.overview}

54 |

55 | Official website : 56 | 57 | 58 | {data.homepage} 59 | 60 | 61 |

62 | 65 |
66 |
67 |
68 |
69 |

CASTS

70 | 71 |
72 | 73 |
74 | {videos.map((video) => ( 75 | 76 | ))} 77 |
78 |
79 |

SIMILAR

80 | 81 |
82 |
83 | 84 | ); 85 | }; 86 | 87 | export default ItemDetailView; 88 | -------------------------------------------------------------------------------- /components/ItemGrid.tsx: -------------------------------------------------------------------------------- 1 | import { ItemCard } from '@/components'; 2 | import { MovieItemProps } from '@/model/movie'; 3 | 4 | interface Props { 5 | items: MovieItemProps[]; 6 | } 7 | 8 | const ItemGrid = ({ items }: Props) => { 9 | return ( 10 |
11 | {items.map((item) => ( 12 | 13 | ))} 14 |
15 | ); 16 | }; 17 | 18 | export default ItemGrid; 19 | -------------------------------------------------------------------------------- /components/ItemView.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | import { ClipLoader } from 'react-spinners'; 3 | 4 | import { MovieSliceParams } from '@/redux/movieSlice'; 5 | import { ItemGrid, PageHeader, Button } from '@/components'; 6 | 7 | interface Props extends MovieSliceParams { 8 | loadMore: () => void; 9 | media_type: 'movie' | 'tv'; 10 | } 11 | 12 | const ItemView: NextPage = ({ 13 | loading, 14 | items, 15 | loadingMore, 16 | page, 17 | total_pages, 18 | loadMore, 19 | media_type, 20 | }) => { 21 | return ( 22 | <> 23 | 24 | 25 |
26 | {loading ? ( 27 |
28 | 29 |
30 | ) : ( 31 | <> 32 |
33 | 34 |
35 |
36 | 37 |
38 | {page < total_pages && ( 39 |
40 | 43 |
44 | )} 45 | 46 | )} 47 |
48 | 49 | ); 50 | }; 51 | 52 | export default ItemView; 53 | -------------------------------------------------------------------------------- /components/Layout/Footer.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | import { FaGithub, FaFacebook } from 'react-icons/fa'; 3 | 4 | const Footer = () => { 5 | return ( 6 |
7 |
8 | Contact Me : 9 | 10 | 15 | 16 | 17 | 18 | 19 | 24 | 25 | 26 | 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default Footer; 33 | -------------------------------------------------------------------------------- /components/Layout/Meta.tsx: -------------------------------------------------------------------------------- 1 | import Head from 'next/head'; 2 | import { NextPage } from 'next'; 3 | 4 | interface MetaProps { 5 | title: string; 6 | description: string; 7 | image: string; 8 | } 9 | 10 | const Meta: NextPage = ({ title, description, image }) => { 11 | return ( 12 | 13 | {title} 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | }; 30 | 31 | export default Meta; 32 | -------------------------------------------------------------------------------- /components/Layout/Navbar.tsx: -------------------------------------------------------------------------------- 1 | import { useRef, useState, useEffect } from 'react'; 2 | import Link from 'next/link'; 3 | import Image from 'next/image'; 4 | import Cn from 'classnames'; 5 | 6 | import { NavbarLink, Search } from '@/components'; 7 | 8 | const Navbar = () => { 9 | const navbarRef = useRef(null); 10 | const [isShrink, setIsShrink] = useState(false); 11 | 12 | useEffect(() => { 13 | const shrinkHeader = () => { 14 | if ( 15 | document.body.scrollTop > 100 || 16 | document.documentElement.scrollTop > 100 17 | ) { 18 | setIsShrink(true); 19 | } else { 20 | setIsShrink(false); 21 | } 22 | }; 23 | window.addEventListener('scroll', shrinkHeader); 24 | return () => { 25 | window.removeEventListener('scroll', shrinkHeader); 26 | }; 27 | }, []); 28 | 29 | return ( 30 |
38 |
39 |
40 | 50 |
51 |
52 |
53 | 54 |
55 |
56 |
57 | 58 |
59 |
60 |
61 | ); 62 | }; 63 | 64 | export default Navbar; 65 | -------------------------------------------------------------------------------- /components/Layout/NavbarLink.tsx: -------------------------------------------------------------------------------- 1 | import Link from 'next/link'; 2 | 3 | const headerNav = [ 4 | { 5 | display: 'Home', 6 | path: '/', 7 | }, 8 | { 9 | display: 'Movies', 10 | path: '/movie', 11 | }, 12 | { 13 | display: 'TV Series', 14 | path: '/tv', 15 | }, 16 | ]; 17 | 18 | const NavbarLink = () => { 19 | return ( 20 |
21 |
    22 | {headerNav.map((item) => ( 23 | 24 | {item.display} 25 | 26 | ))} 27 |
28 |
29 | ); 30 | }; 31 | 32 | export default NavbarLink; 33 | -------------------------------------------------------------------------------- /components/Layout/Pagination.tsx: -------------------------------------------------------------------------------- 1 | import { NextPage } from 'next'; 2 | import { GrPrevious, GrNext } from 'react-icons/gr'; 3 | 4 | interface Props { 5 | total: number; 6 | } 7 | 8 | const Pagination: NextPage = () => { 9 | return ( 10 |
11 |
12 | 13 |
14 |
1
15 |
1
16 |
17 | 18 |
19 |
20 | ); 21 | }; 22 | 23 | export default Pagination; 24 | -------------------------------------------------------------------------------- /components/Layout/Search.tsx: -------------------------------------------------------------------------------- 1 | import { useEffect, useState } from 'react'; 2 | import { useRouter } from 'next/router'; 3 | import { AiOutlineSearch } from 'react-icons/ai'; 4 | import cn from 'classnames'; 5 | 6 | import { MediaType } from '@/model/movie'; 7 | import { mediaTypes } from '@/ultis/constants'; 8 | import { Input } from '@/components'; 9 | 10 | const Search = () => { 11 | const router = useRouter(); 12 | const [value, setValue] = useState(''); 13 | const [typeFillter, setTypeFillter] = useState('all'); 14 | const onChange = (e: any) => setValue(e.target.value); 15 | const handleSearch = () => { 16 | router.push({ 17 | pathname: '/search', 18 | query: { 19 | type: typeFillter, 20 | q: value, 21 | }, 22 | }); 23 | }; 24 | 25 | const type = router.query.type as MediaType; 26 | const q = router.query.q as string; 27 | useEffect(() => { 28 | if (type && q) { 29 | setValue(q); 30 | setTypeFillter(type); 31 | } 32 | }, [type, q]); 33 | 34 | return ( 35 |
36 | e.key === 'Enter' && handleSearch()} 42 | /> 43 |
44 |
48 | 49 |
50 |
    51 | {mediaTypes.map((mediaType) => ( 52 |
  • setTypeFillter(mediaType)} 65 | > 66 | {mediaType} 67 |
  • 68 | ))} 69 |
70 |
71 |
72 | ); 73 | }; 74 | 75 | export default Search; 76 | -------------------------------------------------------------------------------- /components/Modal.tsx: -------------------------------------------------------------------------------- 1 | import Cn from 'classnames'; 2 | import { AiOutlineClose } from 'react-icons/ai'; 3 | 4 | interface Props { 5 | isVisible: boolean; 6 | id?: number; 7 | onClose: () => void; 8 | trailerEndPoint?: string; 9 | } 10 | 11 | const Modal = ({ isVisible, id, onClose, trailerEndPoint }: Props) => { 12 | return ( 13 |
20 |
21 |