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