├── .env ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── react-for-beginners.sketch ├── src ├── Components │ ├── App.js │ ├── GlobalStyles.js │ ├── Header.js │ ├── Loader.js │ ├── Message.js │ ├── Poster.js │ ├── Router.js │ └── Section.js ├── Routes │ ├── Detail │ │ ├── DetailContainer.js │ │ ├── DetailPresenter.js │ │ └── index.js │ ├── Home │ │ ├── HomeContainer.js │ │ ├── HomePresenter.js │ │ └── index.js │ ├── Search │ │ ├── SearchContainer.js │ │ ├── SearchPresenter.js │ │ └── index.js │ └── TV │ │ ├── TVContainer.js │ │ ├── TVPresenter.js │ │ └── index.js ├── api.js ├── assets │ └── noPosterSmall.png └── index.js └── yarn.lock /.env: -------------------------------------------------------------------------------- 1 | NODE_PATH=src -------------------------------------------------------------------------------- /.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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Nomflix 2 | 3 | Learning React and ES6 by building a Movie Discovery App. 4 | 5 | ## Screens 6 | 7 | - [ ] Home 8 | - [ ] TV Shows 9 | - [ ] Search 10 | - [ ] Detail 11 | 12 | ## API Verbs 13 | 14 | - [x] Now playing (Movie) 15 | - [x] Upcoming (Movie) 16 | - [x] Top Rated (TV) 17 | - [x] Popular (TV, X) 18 | - [x] Airing Today (TV) 19 | - [x] TV Show Detail 20 | - [x] Movie Detail 21 | - [x] Search (Movie, TV) 22 | 23 | ## Code Challenges 24 | 25 | - [ ] IMDB Link 26 | - [ ] Tabs inside of Movie / Show Details (YT Videos, Production Company & Countries) 27 | - [ ] Collections Link 28 | - [ ] /collections Route 29 | - [ ] On TV Show, show seasons and creators 30 | 31 | # Preview: 32 | 33 | Try it on [netlify](https://hungry-noether-8ac1d6.netlify.com/#/) 34 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nomflix", 3 | "version": "0.1.0", 4 | "private": true, 5 | "dependencies": { 6 | "axios": "^0.18.0", 7 | "gh-pages": "^2.0.1", 8 | "prop-types": "^15.6.2", 9 | "react": "^16.6.3", 10 | "react-dom": "^16.6.3", 11 | "react-helmet": "^5.2.0", 12 | "react-router-dom": "^4.3.1", 13 | "react-scripts": "2.1.1", 14 | "styled-components": "^4.1.2", 15 | "styled-reset": "^1.6.2" 16 | }, 17 | "scripts": { 18 | "start": "react-scripts start", 19 | "build": "react-scripts build", 20 | "test": "react-scripts test", 21 | "eject": "react-scripts eject", 22 | "deploy": "gh-pages -d build", 23 | "predeploy": "yarn run build" 24 | }, 25 | "eslintConfig": { 26 | "extends": "react-app" 27 | }, 28 | "homepage": "https://hungry-noether-8ac1d6.netlify.com", 29 | "browserslist": [ 30 | ">0.2%", 31 | "not dead", 32 | "not ie <= 11", 33 | "not op_mini all" 34 | ] 35 | } 36 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/nomflix/645335c722fee7c536aba87d6bee8e25c5935e3d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 10 | 11 | 15 | 16 | 25 | Nomflix 26 | 27 | 28 | 29 |
30 | 40 | 41 | 42 | -------------------------------------------------------------------------------- /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 | "start_url": ".", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /react-for-beginners.sketch: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/nomflix/645335c722fee7c536aba87d6bee8e25c5935e3d/react-for-beginners.sketch -------------------------------------------------------------------------------- /src/Components/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from "react"; 2 | import Router from "Components/Router"; 3 | import GlobalStyles from "Components/GlobalStyles"; 4 | 5 | class App extends Component { 6 | render() { 7 | return ( 8 | <> 9 | 10 | 11 | 12 | ); 13 | } 14 | } 15 | 16 | export default App; 17 | -------------------------------------------------------------------------------- /src/Components/GlobalStyles.js: -------------------------------------------------------------------------------- 1 | import { createGlobalStyle } from "styled-components"; 2 | import reset from "styled-reset"; 3 | 4 | const globalStyles = createGlobalStyle` 5 | ${reset}; 6 | a{ 7 | text-decoration:none; 8 | color:inherit; 9 | } 10 | *{ 11 | box-sizing:border-box; 12 | } 13 | body{ 14 | font-family:-apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, 'Open Sans', 'Helvetica Neue', sans-serif; 15 | font-size:12px; 16 | background-color:rgba(20, 20, 20, 1); 17 | color:white; 18 | padding-top:50px; 19 | } 20 | `; 21 | 22 | export default globalStyles; 23 | -------------------------------------------------------------------------------- /src/Components/Header.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { Link, withRouter } from "react-router-dom"; 3 | import styled from "styled-components"; 4 | 5 | const Header = styled.header` 6 | color: white; 7 | position: fixed; 8 | top: 0; 9 | left: 0; 10 | width: 100%; 11 | height: 50px; 12 | display: flex; 13 | align-items: center; 14 | background-color: rgba(20, 20, 20, 0.8); 15 | z-index: 10; 16 | box-shadow: 0px 1px 5px 2px rgba(0, 0, 0, 0.8); 17 | `; 18 | 19 | const List = styled.ul` 20 | display: flex; 21 | `; 22 | 23 | const Item = styled.li` 24 | width: 80px; 25 | height: 50px; 26 | text-align: center; 27 | border-bottom: 3px solid 28 | ${props => (props.current ? "#3498db" : "transparent")}; 29 | transition: border-bottom 0.5s ease-in-out; 30 | `; 31 | 32 | const SLink = styled(Link)` 33 | height: 50px; 34 | display: flex; 35 | align-items: center; 36 | justify-content: center; 37 | `; 38 | 39 | export default withRouter(({ location: { pathname } }) => ( 40 |
41 | 42 | 43 | Movies 44 | 45 | 46 | TV 47 | 48 | 49 | Search 50 | 51 | 52 |
53 | )); 54 | -------------------------------------------------------------------------------- /src/Components/Loader.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import styled from "styled-components"; 3 | 4 | const Container = styled.div` 5 | height: 100vh; 6 | width: 100vw; 7 | display: flex; 8 | justify-content: center; 9 | font-size: 28px; 10 | margin-top: 20px; 11 | `; 12 | 13 | export default () => ( 14 | 15 | 16 | ⏰ 17 | 18 | 19 | ); 20 | -------------------------------------------------------------------------------- /src/Components/Message.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | 5 | const Container = styled.div` 6 | width: 100vw; 7 | display: flex; 8 | justify-content: center; 9 | `; 10 | 11 | const Text = styled.span` 12 | color: ${props => props.color}; 13 | `; 14 | 15 | const Message = ({ text, color }) => ( 16 | 17 | {text} 18 | 19 | ); 20 | 21 | Message.propTypes = { 22 | text: PropTypes.string.isRequired, 23 | color: PropTypes.string.isRequired 24 | }; 25 | 26 | export default Message; 27 | -------------------------------------------------------------------------------- /src/Components/Poster.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import { Link } from "react-router-dom"; 4 | import styled from "styled-components"; 5 | 6 | const Container = styled.div` 7 | font-size: 12px; 8 | `; 9 | 10 | const Image = styled.div` 11 | background-image: url(${props => props.bgUrl}); 12 | height: 180px; 13 | background-size: cover; 14 | border-radius: 4px; 15 | background-position: center center; 16 | transition: opacity 0.1s linear; 17 | `; 18 | 19 | const Rating = styled.span` 20 | bottom: 5px; 21 | right: 5px; 22 | position: absolute; 23 | opacity: 0; 24 | transition: opacity 0.1s linear; 25 | `; 26 | 27 | const ImageContainer = styled.div` 28 | margin-bottom: 5px; 29 | position: relative; 30 | &:hover { 31 | ${Image} { 32 | opacity: 0.3; 33 | } 34 | ${Rating} { 35 | opacity: 1; 36 | } 37 | } 38 | `; 39 | 40 | const Title = styled.span` 41 | display: block; 42 | margin-bottom: 3px; 43 | `; 44 | 45 | const Year = styled.span` 46 | font-size: 10px; 47 | color: rgba(255, 255, 255, 0.5); 48 | `; 49 | 50 | const Poster = ({ id, imageUrl, title, rating, year, isMovie = false }) => ( 51 | 52 | 53 | 54 | 61 | 62 | 63 | 64 | ⭐️ 65 | {" "} 66 | {rating}/10 67 | 68 | 69 | 70 | {title.length > 18 ? `${title.substring(0, 18)}...` : title} 71 | 72 | {year} 73 | 74 | 75 | ); 76 | 77 | Poster.propTypes = { 78 | id: PropTypes.number.isRequired, 79 | imageUrl: PropTypes.string, 80 | title: PropTypes.string.isRequired, 81 | rating: PropTypes.number, 82 | year: PropTypes.string, 83 | isMovie: PropTypes.bool 84 | }; 85 | 86 | export default Poster; 87 | -------------------------------------------------------------------------------- /src/Components/Router.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import { 3 | HashRouter as Router, 4 | Route, 5 | Redirect, 6 | Switch 7 | } from "react-router-dom"; 8 | import Home from "Routes/Home"; 9 | import TV from "Routes/TV"; 10 | import Header from "Components/Header"; 11 | import Search from "Routes/Search"; 12 | import Detail from "Routes/Detail"; 13 | 14 | export default () => ( 15 | 16 | <> 17 |
18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | ); 29 | -------------------------------------------------------------------------------- /src/Components/Section.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | 5 | const Container = styled.div` 6 | :not(:last-child) { 7 | margin-bottom: 50px; 8 | } 9 | `; 10 | 11 | const Title = styled.span` 12 | font-size: 14px; 13 | font-weight: 600; 14 | `; 15 | 16 | const Grid = styled.div` 17 | margin-top: 25px; 18 | display: grid; 19 | grid-template-columns: repeat(auto-fill, 125px); 20 | grid-gap: 25px; 21 | `; 22 | 23 | const Section = ({ title, children }) => ( 24 | 25 | {title} 26 | {children} 27 | 28 | ); 29 | 30 | Section.propTypes = { 31 | title: PropTypes.string.isRequired, 32 | children: PropTypes.oneOfType([ 33 | PropTypes.arrayOf(PropTypes.node), 34 | PropTypes.node 35 | ]) 36 | }; 37 | 38 | export default Section; 39 | -------------------------------------------------------------------------------- /src/Routes/Detail/DetailContainer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import DetailPresenter from "./DetailPresenter"; 3 | import { moviesApi, tvApi } from "../../api"; 4 | 5 | export default class extends React.Component { 6 | constructor(props) { 7 | super(props); 8 | const { 9 | location: { pathname } 10 | } = props; 11 | this.state = { 12 | result: null, 13 | error: null, 14 | loading: true, 15 | isMovie: pathname.includes("/movie/") 16 | }; 17 | } 18 | 19 | async componentDidMount() { 20 | const { 21 | match: { 22 | params: { id } 23 | }, 24 | history: { push } 25 | } = this.props; 26 | const { isMovie } = this.state; 27 | const parsedId = parseInt(id); 28 | if (isNaN(parsedId)) { 29 | return push("/"); 30 | } 31 | let result = null; 32 | try { 33 | if (isMovie) { 34 | ({ data: result } = await moviesApi.movieDetail(parsedId)); 35 | } else { 36 | ({ data: result } = await tvApi.showDetail(parsedId)); 37 | } 38 | } catch { 39 | this.setState({ error: "Can't find anything." }); 40 | } finally { 41 | this.setState({ loading: false, result }); 42 | } 43 | } 44 | 45 | render() { 46 | const { result, error, loading } = this.state; 47 | return ; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/Routes/Detail/DetailPresenter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | import Helmet from "react-helmet"; 5 | import Loader from "Components/Loader"; 6 | 7 | const Container = styled.div` 8 | height: calc(100vh - 50px); 9 | width: 100%; 10 | position: relative; 11 | padding: 50px; 12 | `; 13 | 14 | const Backdrop = styled.div` 15 | position: absolute; 16 | top: 0; 17 | left: 0; 18 | width: 100%; 19 | height: 100%; 20 | background-image: url(${props => props.bgImage}); 21 | background-position: center center; 22 | background-size: cover; 23 | filter: blur(3px); 24 | opacity: 0.5; 25 | z-index: 0; 26 | `; 27 | 28 | const Content = styled.div` 29 | display: flex; 30 | width: 100%; 31 | position: relative; 32 | z-index: 1; 33 | height: 100%; 34 | `; 35 | 36 | const Cover = styled.div` 37 | width: 30%; 38 | background-image: url(${props => props.bgImage}); 39 | background-position: center center; 40 | background-size: cover; 41 | height: 100%; 42 | border-radius: 5px; 43 | `; 44 | 45 | const Data = styled.div` 46 | width: 70%; 47 | margin-left: 10px; 48 | `; 49 | 50 | const Title = styled.h3` 51 | font-size: 32px; 52 | `; 53 | 54 | const ItemContainer = styled.div` 55 | margin: 20px 0; 56 | `; 57 | 58 | const Item = styled.span``; 59 | 60 | const Divider = styled.span` 61 | margin: 0 10px; 62 | `; 63 | 64 | const Overview = styled.p` 65 | font-size: 12px; 66 | opacity: 0.7; 67 | line-height: 1.5; 68 | width: 50%; 69 | `; 70 | 71 | const DetailPresenter = ({ result, loading, error }) => 72 | loading ? ( 73 | <> 74 | 75 | Loading | Nomflix 76 | 77 | 78 | 79 | ) : ( 80 | 81 | 82 | 83 | {result.original_title ? result.original_title : result.original_name}{" "} 84 | | Nomflix 85 | 86 | 87 | 90 | 91 | 98 | 99 | 100 | {result.original_title 101 | ? result.original_title 102 | : result.original_name} 103 | 104 | 105 | 106 | {result.release_date 107 | ? result.release_date.substring(0, 4) 108 | : result.first_air_date.substring(0, 4)} 109 | 110 | 111 | 112 | {result.runtime ? result.runtime : result.episode_run_time[0]} min 113 | 114 | 115 | 116 | {result.genres && 117 | result.genres.map((genre, index) => 118 | index === result.genres.length - 1 119 | ? genre.name 120 | : `${genre.name} / ` 121 | )} 122 | 123 | 124 | {result.overview} 125 | 126 | 127 | 128 | ); 129 | 130 | DetailPresenter.propTypes = { 131 | result: PropTypes.object, 132 | loading: PropTypes.bool.isRequired, 133 | error: PropTypes.string 134 | }; 135 | 136 | export default DetailPresenter; 137 | -------------------------------------------------------------------------------- /src/Routes/Detail/index.js: -------------------------------------------------------------------------------- 1 | import DetailContainer from "./DetailContainer"; 2 | 3 | export default DetailContainer; 4 | -------------------------------------------------------------------------------- /src/Routes/Home/HomeContainer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import HomePresenter from "./HomePresenter"; 3 | import { moviesApi } from "api"; 4 | 5 | export default class extends React.Component { 6 | state = { 7 | nowPlaying: null, 8 | upcoming: null, 9 | popular: null, 10 | error: null, 11 | loading: true 12 | }; 13 | 14 | async componentDidMount() { 15 | try { 16 | const { 17 | data: { results: nowPlaying } 18 | } = await moviesApi.nowPlaying(); 19 | const { 20 | data: { results: upcoming } 21 | } = await moviesApi.upcoming(); 22 | const { 23 | data: { results: popular } 24 | } = await moviesApi.popular(); 25 | this.setState({ 26 | nowPlaying, 27 | upcoming, 28 | popular 29 | }); 30 | } catch { 31 | this.setState({ 32 | error: "Can't find movie information." 33 | }); 34 | } finally { 35 | this.setState({ 36 | loading: false 37 | }); 38 | } 39 | } 40 | 41 | render() { 42 | const { nowPlaying, upcoming, popular, error, loading } = this.state; 43 | return ( 44 | 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/Routes/Home/HomePresenter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | import Helmet from "react-helmet"; 5 | import Section from "Components/Section"; 6 | import Loader from "../../Components/Loader"; 7 | import Message from "../../Components/Message"; 8 | import Poster from "../../Components/Poster"; 9 | 10 | const Container = styled.div` 11 | padding: 20px; 12 | `; 13 | 14 | const HomePresenter = ({ nowPlaying, popular, upcoming, loading, error }) => ( 15 | <> 16 | 17 | Movies | Nomflix 18 | 19 | {loading ? ( 20 | 21 | ) : ( 22 | 23 | 24 | Movies | Nomflix 25 | 26 | {nowPlaying && nowPlaying.length > 0 && ( 27 |
28 | {nowPlaying.map(movie => ( 29 | 38 | ))} 39 |
40 | )} 41 | {upcoming && upcoming.length > 0 && ( 42 |
43 | {upcoming.map(movie => ( 44 | 53 | ))} 54 |
55 | )} 56 | {popular && popular.length > 0 && ( 57 |
58 | {popular.map(movie => ( 59 | 68 | ))} 69 |
70 | )} 71 | {error && } 72 |
73 | )} 74 | 75 | ); 76 | 77 | HomePresenter.propTypes = { 78 | nowPlaying: PropTypes.array, 79 | popular: PropTypes.array, 80 | upcoming: PropTypes.array, 81 | loading: PropTypes.bool.isRequired, 82 | error: PropTypes.string 83 | }; 84 | 85 | export default HomePresenter; 86 | -------------------------------------------------------------------------------- /src/Routes/Home/index.js: -------------------------------------------------------------------------------- 1 | import HomeContainer from "./HomeContainer"; 2 | 3 | export default HomeContainer; 4 | -------------------------------------------------------------------------------- /src/Routes/Search/SearchContainer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import SearchPresenter from "./SearchPresenter"; 3 | import { moviesApi, tvApi } from "../../api"; 4 | 5 | export default class extends React.Component { 6 | state = { 7 | movieResults: null, 8 | tvResults: null, 9 | searchTerm: "", 10 | loading: false, 11 | error: null 12 | }; 13 | 14 | handleSubmit = event => { 15 | event.preventDefault(); 16 | const { searchTerm } = this.state; 17 | if (searchTerm !== "") { 18 | this.searchByTerm(); 19 | } 20 | }; 21 | 22 | updateTerm = event => { 23 | const { 24 | target: { value } 25 | } = event; 26 | this.setState({ 27 | searchTerm: value 28 | }); 29 | }; 30 | 31 | searchByTerm = async () => { 32 | const { searchTerm } = this.state; 33 | this.setState({ loading: true }); 34 | try { 35 | const { 36 | data: { results: movieResults } 37 | } = await moviesApi.search(searchTerm); 38 | const { 39 | data: { results: tvResults } 40 | } = await tvApi.search(searchTerm); 41 | this.setState({ 42 | movieResults, 43 | tvResults 44 | }); 45 | } catch { 46 | this.setState({ error: "Can't find results." }); 47 | } finally { 48 | this.setState({ loading: false }); 49 | } 50 | }; 51 | 52 | render() { 53 | const { movieResults, tvResults, searchTerm, loading, error } = this.state; 54 | return ( 55 | 64 | ); 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /src/Routes/Search/SearchPresenter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | import Helmet from "react-helmet"; 5 | import Loader from "Components/Loader"; 6 | import Section from "Components/Section"; 7 | import Message from "../../Components/Message"; 8 | import Poster from "../../Components/Poster"; 9 | 10 | const Container = styled.div` 11 | padding: 20px; 12 | `; 13 | 14 | const Form = styled.form` 15 | margin-bottom: 50px; 16 | width: 100%; 17 | `; 18 | 19 | const Input = styled.input` 20 | all: unset; 21 | font-size: 28px; 22 | width: 100%; 23 | `; 24 | 25 | const SearchPresenter = ({ 26 | movieResults, 27 | tvResults, 28 | loading, 29 | searchTerm, 30 | handleSubmit, 31 | error, 32 | updateTerm 33 | }) => ( 34 | 35 | 36 | Search | Nomflix 37 | 38 |
39 | 44 |
45 | {loading ? ( 46 | 47 | ) : ( 48 | <> 49 | {movieResults && movieResults.length > 0 && ( 50 |
51 | {movieResults.map(movie => ( 52 | 61 | ))} 62 |
63 | )} 64 | {tvResults && tvResults.length > 0 && ( 65 |
66 | {tvResults.map(show => ( 67 | 75 | ))} 76 |
77 | )} 78 | {error && } 79 | {tvResults && 80 | movieResults && 81 | tvResults.length === 0 && 82 | movieResults.length === 0 && ( 83 | 84 | )} 85 | 86 | )} 87 |
88 | ); 89 | 90 | SearchPresenter.propTypes = { 91 | movieResults: PropTypes.array, 92 | tvResults: PropTypes.array, 93 | error: PropTypes.string, 94 | searchTerm: PropTypes.string, 95 | loading: PropTypes.bool.isRequired, 96 | handleSubmit: PropTypes.func.isRequired, 97 | updateTerm: PropTypes.func.isRequired 98 | }; 99 | 100 | export default SearchPresenter; 101 | -------------------------------------------------------------------------------- /src/Routes/Search/index.js: -------------------------------------------------------------------------------- 1 | import SearchContainer from "./SearchContainer"; 2 | 3 | export default SearchContainer; 4 | -------------------------------------------------------------------------------- /src/Routes/TV/TVContainer.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import TVPresenter from "./TVPresenter"; 3 | import { tvApi } from "../../api"; 4 | 5 | export default class extends React.Component { 6 | state = { 7 | topRated: null, 8 | popular: null, 9 | airingToday: null, 10 | loading: true, 11 | error: null 12 | }; 13 | 14 | async componentDidMount() { 15 | try { 16 | const { 17 | data: { results: topRated } 18 | } = await tvApi.topRated(); 19 | const { 20 | data: { results: popular } 21 | } = await tvApi.popular(); 22 | const { 23 | data: { results: airingToday } 24 | } = await tvApi.airingToday(); 25 | this.setState({ topRated, popular, airingToday }); 26 | } catch { 27 | this.setState({ 28 | error: "Can't find TV information." 29 | }); 30 | } finally { 31 | this.setState({ loading: false }); 32 | } 33 | } 34 | 35 | render() { 36 | const { topRated, popular, airingToday, loading, error } = this.state; 37 | return ( 38 | 45 | ); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/Routes/TV/TVPresenter.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import PropTypes from "prop-types"; 3 | import styled from "styled-components"; 4 | import Helmet from "react-helmet"; 5 | import Section from "../../Components/Section"; 6 | import Loader from "../../Components/Loader"; 7 | import Message from "../../Components/Message"; 8 | import Poster from "../../Components/Poster"; 9 | 10 | const Container = styled.div` 11 | padding: 20px; 12 | `; 13 | 14 | const TVPresenter = ({ topRated, popular, airingToday, loading, error }) => ( 15 | <> 16 | 17 | TV Shows | Nomflix 18 | 19 | {loading ? ( 20 | 21 | ) : ( 22 | 23 | {topRated && topRated.length > 0 && ( 24 |
25 | {topRated.map(show => ( 26 | 34 | ))} 35 |
36 | )} 37 | {popular && popular.length > 0 && ( 38 |
39 | {popular.map(show => ( 40 | 48 | ))} 49 |
50 | )} 51 | {airingToday && airingToday.length > 0 && ( 52 |
53 | {airingToday.map(show => ( 54 | 62 | ))} 63 |
64 | )} 65 | {error && } 66 |
67 | )} 68 | 69 | ); 70 | 71 | TVPresenter.propTypes = { 72 | topRated: PropTypes.array, 73 | popular: PropTypes.array, 74 | airingToday: PropTypes.array, 75 | loading: PropTypes.bool.isRequired, 76 | error: PropTypes.string 77 | }; 78 | 79 | export default TVPresenter; 80 | -------------------------------------------------------------------------------- /src/Routes/TV/index.js: -------------------------------------------------------------------------------- 1 | import TVContainer from "./TVContainer"; 2 | 3 | export default TVContainer; 4 | -------------------------------------------------------------------------------- /src/api.js: -------------------------------------------------------------------------------- 1 | import axios from "axios"; 2 | 3 | const api = axios.create({ 4 | baseURL: "https://api.themoviedb.org/3/", 5 | params: { 6 | api_key: "10923b261ba94d897ac6b81148314a3f", 7 | language: "en-US" 8 | } 9 | }); 10 | 11 | export const moviesApi = { 12 | nowPlaying: () => api.get("movie/now_playing"), 13 | upcoming: () => api.get("movie/upcoming"), 14 | popular: () => api.get("movie/popular"), 15 | movieDetail: id => 16 | api.get(`movie/${id}`, { 17 | params: { 18 | append_to_response: "videos" 19 | } 20 | }), 21 | search: term => 22 | api.get("search/movie", { 23 | params: { 24 | query: encodeURIComponent(term) 25 | } 26 | }) 27 | }; 28 | 29 | export const tvApi = { 30 | topRated: () => api.get("tv/top_rated"), 31 | popular: () => api.get("tv/popular"), 32 | airingToday: () => api.get("tv/airing_today"), 33 | showDetail: id => 34 | api.get(`tv/${id}`, { 35 | params: { 36 | append_to_response: "videos" 37 | } 38 | }), 39 | search: term => 40 | api.get("search/tv", { 41 | params: { 42 | query: encodeURIComponent(term) 43 | } 44 | }) 45 | }; 46 | -------------------------------------------------------------------------------- /src/assets/noPosterSmall.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/nomadcoders/nomflix/645335c722fee7c536aba87d6bee8e25c5935e3d/src/assets/noPosterSmall.png -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom"; 3 | import App from "Components/App"; 4 | 5 | ReactDOM.render(, document.getElementById("root")); 6 | --------------------------------------------------------------------------------