├── public ├── robots.txt ├── favicon.ico ├── logo192.png ├── logo512.png ├── manifest.json └── index.html ├── imgs ├── Screenshot_1.jpg ├── Screenshot_2.jpg └── Screenshot_3.jpg ├── src ├── index.js ├── components │ ├── Header │ │ ├── index.js │ │ └── styles.css │ ├── MovieRow │ │ ├── styles.css │ │ └── index.js │ └── FeaturedMovie │ │ ├── index.js │ │ └── styles.css ├── App.css ├── App.js └── Tmdb.js ├── README.md ├── .gitignore └── package.json /public/robots.txt: -------------------------------------------------------------------------------- 1 | # https://www.robotstxt.org/robotstxt.html 2 | User-agent: * 3 | Disallow: 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /public/logo192.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/public/logo192.png -------------------------------------------------------------------------------- /public/logo512.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/public/logo512.png -------------------------------------------------------------------------------- /imgs/Screenshot_1.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/imgs/Screenshot_1.jpg -------------------------------------------------------------------------------- /imgs/Screenshot_2.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/imgs/Screenshot_2.jpg -------------------------------------------------------------------------------- /imgs/Screenshot_3.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/toandriottibertoni/react-netflix-clone-ui/HEAD/imgs/Screenshot_3.jpg -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import ReactDOM from 'react-dom'; 3 | import App from './App'; 4 | 5 | ReactDOM.render( 6 | 7 | 8 | , 9 | document.getElementById('root') 10 | ); 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Clone ui for study react 2 | 3 | by https://www.youtube.com/watch?v=tBweoUiMsDg 4 | 5 | data from https://api.themoviedb.org/3 6 | 7 | ## Get your API_KEY and config in Tmdb API_KEY 8 | 9 | ## imgs 10 | ![Screenshot_1](/imgs/Screenshot_1.jpg "Screenshot_1")![Screenshot_2](/imgs/Screenshot_2.jpg "Screenshot_2")![Screenshot_3](/imgs/Screenshot_1.jpg "Screenshot_3") 11 | -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /src/components/Header/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css'; 3 | 4 | export default ({black})=>{ 5 | return( 6 |
7 |
8 | 9 |
10 |
11 | 12 |
13 |
14 | ) 15 | } -------------------------------------------------------------------------------- /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/components/Header/styles.css: -------------------------------------------------------------------------------- 1 | header{ 2 | position: fixed; 3 | z-index: 999; 4 | top:0px; 5 | left: 0px; 6 | right:0px; 7 | height: 70px; 8 | display:flex; 9 | justify-content: space-between; 10 | align-items: center; 11 | padding: 0 30px; 12 | background: transparent; 13 | transition: all ease 0.5s; 14 | } 15 | header.black{ 16 | background:#141414; 17 | } 18 | .header--logo{ 19 | height: 25px; 20 | } 21 | 22 | .header--logo img{ 23 | height: 100%; 24 | } 25 | 26 | .header--user{ 27 | height: 35px; 28 | } 29 | 30 | .header--user img { 31 | height: 100%; 32 | border-radius: 3px; 33 | } -------------------------------------------------------------------------------- /src/App.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap'); 2 | 3 | * { 4 | box-sizing: border-box; 5 | } 6 | 7 | body{ 8 | background-color: #111; 9 | color :#fff; 10 | margin: 0; 11 | font-family: 'Roboto', sans-serif; 12 | } 13 | 14 | .lists{ 15 | margin-top: -150px; 16 | } 17 | 18 | footer{ 19 | margin: 50px 0; 20 | text-align: center; 21 | } 22 | 23 | .loading{ 24 | position: fixed; 25 | top:0; 26 | left:0; 27 | right: 0; 28 | bottom: 0; 29 | z-index: 99; 30 | background-color: #000; 31 | display:flex; 32 | justify-content: center; 33 | align-items: center; 34 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "netflix-clone-ui", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "@material-ui/core": "^4.11.0", 7 | "@material-ui/icons": "^4.9.1", 8 | "@testing-library/jest-dom": "^4.2.4", 9 | "@testing-library/react": "^9.3.2", 10 | "@testing-library/user-event": "^7.1.2", 11 | "react": "^16.13.1", 12 | "react-dom": "^16.13.1", 13 | "react-scripts": "3.4.3" 14 | }, 15 | "scripts": { 16 | "start": "react-scripts start", 17 | "build": "react-scripts build", 18 | "test": "react-scripts test", 19 | "eject": "react-scripts eject" 20 | }, 21 | "eslintConfig": { 22 | "extends": "react-app" 23 | }, 24 | "browserslist": { 25 | "production": [ 26 | ">0.2%", 27 | "not dead", 28 | "not op_mini all" 29 | ], 30 | "development": [ 31 | "last 1 chrome version", 32 | "last 1 firefox version", 33 | "last 1 safari version" 34 | ] 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/components/MovieRow/styles.css: -------------------------------------------------------------------------------- 1 | .movieRow{ 2 | margin-bottom: 30px; 3 | } 4 | 5 | .movieRow h2 { 6 | margin: 0px 0px 0px 30px; 7 | } 8 | 9 | .movieRow--listarea { 10 | overflow-x : hidden; 11 | padding-left: 30px; 12 | } 13 | 14 | .movieRow--list { 15 | transition : all ease 0.2s; 16 | } 17 | 18 | .movieRow--item{ 19 | display: inline-block; 20 | width:150px; 21 | cursor: pointer; 22 | } 23 | 24 | .movieRow--item img{ 25 | width: 100%; 26 | transform: scale(0.9); 27 | transition : all ease 0.2s; 28 | } 29 | 30 | 31 | .movieRow--item img:hover{ 32 | transform: scale(1); 33 | } 34 | 35 | .movieRow-left, 36 | .movieRow-right { 37 | position:absolute; 38 | width: 40px; 39 | height: 225px; 40 | background-color:rgba(0,0,0,0.6); 41 | z-index: 99; 42 | display: flex; 43 | align-items: center; 44 | justify-content: center; 45 | overflow: hidden; 46 | cursor:pointer; 47 | opacity: 0; 48 | transition : all ease 0.2s; 49 | } 50 | 51 | .movieRow-left{ 52 | left: 0; 53 | } 54 | 55 | .movieRow-right{ 56 | right: 0; 57 | } 58 | 59 | .movieRow:hover .movieRow-left, 60 | .movieRow:hover .movieRow-right{ 61 | opacity: 1; 62 | } 63 | 64 | 65 | @media (max-width: 760px){ 66 | .movieRow-left, 67 | .movieRow-right { 68 | opacity: 1; 69 | } 70 | } 71 | 72 | 73 | -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 12 | 13 | 17 | 18 | 27 | React App 28 | 29 | 30 | 31 |
32 | 42 | 43 | 44 | -------------------------------------------------------------------------------- /src/components/MovieRow/index.js: -------------------------------------------------------------------------------- 1 | import React, {useState} from 'react'; 2 | import './styles.css'; 3 | import NavigateBeforeIcon from '@material-ui/icons/NavigateBefore'; 4 | import NavigateNextIcon from '@material-ui/icons/NavigateNext'; 5 | 6 | export default ({title, items}) =>{ 7 | 8 | const [scrollX, setScrollX]= useState(0); 9 | 10 | const handleLeftArrow = () => { 11 | let x = scrollX + Math.round(window.innerWidth / 2); 12 | if(x > 0){ 13 | x = 0; 14 | } 15 | setScrollX(x); 16 | } 17 | const handleRightArrow = () =>{ 18 | let x = scrollX - Math.round(window.innerWidth / 2); 19 | let listW = items.results.length * 150; 20 | if(window.innerWidth - listW > x) 21 | { 22 | x = (window.innerWidth - listW) - 60; 23 | } 24 | setScrollX(x); 25 | } 26 | 27 | return( 28 |
29 |

{title}

30 |
31 | 32 |
33 |
34 | 35 |
36 | 37 |
38 |
42 | {items.results.length > 0 && items.results.map((item, key)=>( 43 |
44 | 45 |
46 | ))} 47 |
48 |
49 |
50 | ); 51 | } -------------------------------------------------------------------------------- /src/components/FeaturedMovie/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import './styles.css'; 3 | 4 | export default ({item}) => { 5 | 6 | let firstDate = new Date(item.first_air_date); 7 | 8 | let genres = []; 9 | for(let i in item.genres){ 10 | genres.push(item.genres[i].name); 11 | } 12 | 13 | let descr = item.overview; 14 | if(descr.length > 200){ 15 | descr = descr.substring(0, 200) + '...'; 16 | } 17 | 18 | 19 | return ( 20 |
25 |
26 |
27 |
{item.original_name}
28 |
29 |
{item.vote_average} pontos
30 |
{firstDate.getFullYear()}
31 |
{item.number_of_seasons} temporada{item.number_of_season !== 1 ? 's' : ''}
32 |
33 |
{descr}
34 |
35 | ► Assistir 36 | + Minha Lista 37 |
38 |
Gêneros: {genres.join(', ')}
39 | 40 |
41 |
42 | 43 |
44 | ) 45 | } 46 | -------------------------------------------------------------------------------- /src/components/FeaturedMovie/styles.css: -------------------------------------------------------------------------------- 1 | .featured{ 2 | height: 100vh; 3 | } 4 | 5 | .featured--vertical{ 6 | width: inherit; 7 | height: inherit; 8 | background: linear-gradient(to top, #111 10%, transparent); 9 | } 10 | 11 | .featured--horizontal{ 12 | width: inherit; 13 | height: inherit; 14 | background: linear-gradient(to right, #111 30%, transparent 70%); 15 | display: flex; 16 | flex-direction: column; 17 | justify-content: center; 18 | padding-left: 30px; 19 | padding-bottom: 150px; 20 | padding-top : 70px; 21 | } 22 | 23 | .featured--name{ 24 | font-size: 60px; 25 | font-weight: bold; 26 | } 27 | 28 | .featured--info{ 29 | font-size: 18px; 30 | font-weight: bold; 31 | margin-top: 15px; 32 | } 33 | 34 | .featured--points{ 35 | color: #46d369 36 | } 37 | 38 | .featured--description{ 39 | margin-top:15px; 40 | font-size: 20px; 41 | color: #999; 42 | max-width: 40%; 43 | } 44 | 45 | .featured--buttons{ 46 | margin-top:15px; 47 | } 48 | 49 | .featured--mylistbutton, 50 | .featured--watchbutton{ 51 | display: inline-block; 52 | font-size:20px; 53 | font-weight: bold; 54 | padding: 12px 25px; 55 | border-radius: 5px; 56 | text-decoration: none; 57 | margin-right: 10px; 58 | opacity: 1; 59 | transition :all ease 0.2s; 60 | } 61 | 62 | .featured--mylistbutton:hover, 63 | .featured--watchbutton:hover{ 64 | opacity: 0.7; 65 | } 66 | .featured--watchbutton{ 67 | background-color: #fff; 68 | color: #000; 69 | } 70 | 71 | .featured--mylistbutton{ 72 | background-color: #333; 73 | color: #fff; 74 | } 75 | 76 | .featured--genres{ 77 | margin-top: 15px; 78 | font-size: 18px; 79 | color: #999; 80 | } 81 | 82 | @media (max-width:760px){ 83 | .featured{ 84 | height: 90vh; 85 | } 86 | .featured--name{ 87 | font-size:40px; 88 | } 89 | .featured--info{ 90 | font-size:16px; 91 | } 92 | .featured--description{ 93 | font-size:14px; 94 | max-width: 100%; 95 | margin-right: 30px; 96 | } 97 | .featured--mylistbutton, 98 | .featured--watchbutton{ 99 | font-size:10px; 100 | } 101 | .featured--genres{ 102 | font-size:14px; 103 | } 104 | } -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, {useEffect, useState} from 'react'; 2 | import './App.css'; 3 | import Tmdb from './Tmdb'; 4 | import MovieRow from './components/MovieRow'; 5 | import FeaturedMovie from './components/FeaturedMovie'; 6 | import Header from './components/Header'; 7 | 8 | export default () =>{ 9 | 10 | const [movieList, setMovieList] = useState([]); 11 | const [featuredData, setFeaturedData]= useState(null); 12 | const [blackHeader, setblackHeader]= useState(false); 13 | 14 | useEffect(()=>{ 15 | const loadAll = async () => { 16 | let list = await Tmdb.getHomeList(); 17 | setMovieList(list); 18 | 19 | let originals = list.filter(i=> i.slug === 'originals'); 20 | let randonChosen = Math.floor(Math.random() * (originals[0].items.results.length - 1)); 21 | let chosen = originals[0].items.results[randonChosen] 22 | let chosenInfo = await Tmdb.getMovieInfo(chosen.id, 'tv'); 23 | setFeaturedData(chosenInfo); 24 | } 25 | 26 | loadAll(); 27 | }, []); 28 | 29 | useEffect(()=>{ 30 | const scrollListener = () =>{ 31 | if(window.scrollY > 10){ 32 | setblackHeader(true); 33 | }else{ 34 | setblackHeader(false); 35 | } 36 | } 37 | 38 | window.addEventListener('scroll', scrollListener); 39 | 40 | return () =>{ 41 | window.removeEventListener('scroll', scrollListener); 42 | } 43 | }, []); 44 | 45 | return( 46 |
47 | 48 |
49 | 50 | {featuredData && 51 | 52 | } 53 | 54 |
55 | {movieList.map((item, key) =>( 56 | 57 | )) 58 | 59 | } 60 |
61 |
62 | Feito em Live (https://www.youtube.com/watch?v=tBweoUiMsDg) para estudo de react, todos os direitos das imagens são da Netflix. 63 | Dados Extraidos de https://www.themoviedb.org/ 64 |
65 | 66 | 67 | {movieList.length <= 0 && 68 |
69 | loading 70 |
71 | } 72 |
73 | ) 74 | } 75 | 76 | 77 | 78 | // https://youtu.be/tBweoUiMsDg?t=8054 -------------------------------------------------------------------------------- /src/Tmdb.js: -------------------------------------------------------------------------------- 1 | const API_KEY = ''; 2 | const API_BASE = 'https://api.themoviedb.org/3'; 3 | 4 | 5 | const basicFecth = async (endpoint) =>{ 6 | return (await fetch(`${API_BASE}${endpoint}`)).json(); 7 | } 8 | 9 | export default { 10 | getHomeList : async () =>{ 11 | return [ 12 | { 13 | slug: 'originals', 14 | title : "Originais do Netflix", 15 | items : await basicFecth(`/discover/tv/?with_network=213&language=pt-BR&api_key=${API_KEY}`) 16 | }, 17 | { 18 | slug: 'trending', 19 | title : "Recomendados para Você", 20 | items : await basicFecth(`/trending/all/week?language=pt-BR&api_key=${API_KEY}`) 21 | }, 22 | { 23 | slug: 'toprated', 24 | title : "Em Alta", 25 | items : await basicFecth(`/movie/top_rated?&language=pt-BR&api_key=${API_KEY}`) 26 | }, 27 | { 28 | slug: 'action', 29 | title : "Ação", 30 | items : await basicFecth(`/discover/movie?with_genres=28&language=pt-BR&api_key=${API_KEY}`) 31 | }, 32 | { 33 | slug: 'comedy', 34 | title : "Comédia", 35 | items : await basicFecth(`/discover/movie?with_genres=35&language=pt-BR&api_key=${API_KEY}`) 36 | }, 37 | { 38 | slug: 'horror', 39 | title : "Terror", 40 | items : await basicFecth(`/discover/movie?with_genres=27&language=pt-BR&api_key=${API_KEY}`) 41 | }, 42 | { 43 | slug: 'romance', 44 | title : "Romance", 45 | items : await basicFecth(`/discover/movie?with_genres=10749&language=pt-BR&api_key=${API_KEY}`) 46 | }, 47 | { 48 | slug: 'documentary', 49 | title : "Documentários", 50 | items : await basicFecth(`/discover/movie?with_genres=99&language=pt-BR&api_key=${API_KEY}`) 51 | }, 52 | ] 53 | }, 54 | 55 | getMovieInfo : async (movieId, type) =>{ 56 | let info = {}; 57 | if(movieId) { 58 | switch(type){ 59 | case 'movei': 60 | info = await basicFecth(`/movie/${movieId}?language=pt-BR&api_key=${API_KEY}`); 61 | break; 62 | case 'tv': 63 | info = await basicFecth(`/tv/${movieId}?language=pt-BR&api_key=${API_KEY}`); 64 | break; 65 | default: 66 | info = null; 67 | break; 68 | } 69 | } 70 | 71 | return info; 72 | } 73 | } --------------------------------------------------------------------------------