├── .gitignore
├── README.md
├── package-lock.json
├── package.json
├── public
├── favicon.ico
├── images
│ ├── no_image.jpg
│ ├── reactMovie_logo.png
│ └── tmdb_logo.png
├── index.html
└── manifest.json
└── src
├── components
├── .gitkeep
├── App
│ └── App.js
├── Home
│ ├── Home.css
│ └── Home.js
├── Movie
│ ├── Movie.css
│ └── Movie.js
└── elements
│ ├── Actor
│ ├── Actor.css
│ └── Actor.js
│ ├── FourColGrid
│ ├── FourColGrid.css
│ └── FourColGrid.js
│ ├── Header
│ ├── Header.css
│ └── Header.js
│ ├── HeroImage
│ ├── HeroImage.css
│ └── HeroImage.js
│ ├── LoadMoreBtn
│ ├── LoadMoreBtn.css
│ └── LoadMoreBtn.js
│ ├── MovieInfo
│ ├── MovieInfo.css
│ └── MovieInfo.js
│ ├── MovieInfoBar
│ ├── MovieInfoBar.css
│ └── MovieInfoBar.js
│ ├── MovieThumb
│ ├── MovieThumb.css
│ └── MovieThumb.js
│ ├── Navigation
│ ├── Navigation.css
│ └── Navigation.js
│ ├── NotFound
│ └── NotFound.js
│ ├── SearchBar
│ ├── SearchBar.css
│ └── SearchBar.js
│ └── Spinner
│ ├── Spinner.css
│ └── Spinner.js
├── config.js
├── helpers.js
├── index.css
├── index.js
├── logo.svg
└── serviceWorker.js
/.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 | # React Movie rmdb
2 | [Link to App](http://phobic-heat.surge.sh/)
3 | 
4 |
5 |
6 | ## Table of Contents
7 |
8 | * [How to Load the App](#howtoloadtheapp)
9 | * [About the App](#about)
10 | * [How to Use the App](#how-to-use-the-app)
11 |
12 | ### How to Load the App
13 | ```
14 | git clone https://github.com/oliver-gomes/react-movie.git
15 | npm install
16 | npm start
17 | ```
18 | ## About
19 | Modern fast movie database web app with React using The Movie DB API. All the new popular movies are populated along with search bar for your own movie search. Clicking on specific movies bring you all data about the movie including actors, directors, time, budget, revenue, rating and many more.
20 |
21 | ## How to Use the App
22 |
23 | - Find the most popular movie at the moment on the front page
24 | - Click on specific movie to view full-blown information
25 | - Use the Search bar to find you favorites movies and their information
26 | - Bookmark and use the App at your own pace!
27 |
28 | # Hope You Enjoy the App !
29 |
30 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "react_movie_db_course",
3 | "version": "0.1.0",
4 | "private": true,
5 | "dependencies": {
6 | "react": "^16.5.2",
7 | "react-dom": "^16.5.2",
8 | "react-fontawesome": "^1.6.1",
9 | "react-router-dom": "^4.3.1",
10 | "react-scripts": "1.1.5"
11 | },
12 | "scripts": {
13 | "start": "react-scripts start",
14 | "build": "react-scripts build",
15 | "test": "react-scripts test --env=jsdom",
16 | "eject": "react-scripts eject"
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oliver-gomes/react-movie/482b96dcb0638b6e8e76710c1e1effcaf24651c7/public/favicon.ico
--------------------------------------------------------------------------------
/public/images/no_image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oliver-gomes/react-movie/482b96dcb0638b6e8e76710c1e1effcaf24651c7/public/images/no_image.jpg
--------------------------------------------------------------------------------
/public/images/reactMovie_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oliver-gomes/react-movie/482b96dcb0638b6e8e76710c1e1effcaf24651c7/public/images/reactMovie_logo.png
--------------------------------------------------------------------------------
/public/images/tmdb_logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/oliver-gomes/react-movie/482b96dcb0638b6e8e76710c1e1effcaf24651c7/public/images/tmdb_logo.png
--------------------------------------------------------------------------------
/public/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
9 |
10 |
14 |
15 |
16 |
17 |
21 |
25 |
34 | React Movie
35 |
36 |
37 |
38 |
39 |
49 |
50 |
51 |
--------------------------------------------------------------------------------
/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": "./index.html",
12 | "display": "standalone",
13 | "theme_color": "#000000",
14 | "background_color": "#ffffff"
15 | }
16 |
--------------------------------------------------------------------------------
/src/components/.gitkeep:
--------------------------------------------------------------------------------
1 | // Must have this so Git won't delete this empty folder
--------------------------------------------------------------------------------
/src/components/App/App.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { BrowserRouter, Route, Switch } from "react-router-dom";
3 | import Header from "../elements/Header/Header";
4 | import Home from "../Home/Home";
5 | import Movie from "../Movie/Movie";
6 | import NotFound from "../elements/NotFound/NotFound";
7 |
8 | class App extends React.Component {
9 | render() {
10 | return (
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 | );
22 | }
23 | }
24 |
25 | export default App;
26 |
--------------------------------------------------------------------------------
/src/components/Home/Home.css:
--------------------------------------------------------------------------------
1 | .rmdb-home {
2 | margin-bottom: 100px;
3 | }
4 |
5 | .rmdb-home-grid {
6 | max-width: 1280px;
7 | margin: 0 auto;
8 | padding: 0 20px;
9 | }
--------------------------------------------------------------------------------
/src/components/Home/Home.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import "./Home.css";
3 |
4 | import {
5 | API_URL,
6 | API_KEY,
7 | IMAGE_BASE_URL,
8 | POSTER_SIZE,
9 | BACKDROP_SIZE
10 | } from "../../config";
11 |
12 | import HeroImage from "../elements/HeroImage/HeroImage";
13 | import SearchBar from "../elements/SearchBar/SearchBar";
14 | import FourColGrid from "../elements/FourColGrid/FourColGrid";
15 | import MovieThumb from "../elements/MovieThumb/MovieThumb";
16 | import LoadMoreBtn from "../elements/LoadMoreBtn/LoadMoreBtn";
17 | import Spinner from "../elements/Spinner/Spinner";
18 |
19 | class Home extends Component {
20 | state = {
21 | movies: [],
22 | heroImage: null,
23 | loading: false,
24 | currentPage: 0,
25 | totalPages: 0,
26 | searchTerm: ""
27 | };
28 |
29 | componentDidMount() {
30 | this.setState({ loading: true });
31 | const endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
32 | this.fetchItems(endpoint);
33 | }
34 |
35 | searchItems = searchTerm => {
36 | console.log(searchTerm);
37 | let endpoint = "";
38 | this.setState({
39 | movies: [],
40 | loading: true,
41 | searchTerm
42 | });
43 |
44 | if (searchTerm === "") {
45 | endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=1`;
46 | } else {
47 | endpoint = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query=${searchTerm}`;
48 | }
49 | this.fetchItems(endpoint);
50 | };
51 |
52 | loadMoreItems = () => {
53 | let endpoint = "";
54 | this.setState({
55 | loading: true
56 | });
57 |
58 | if (this.state.searchTerm === "") {
59 | endpoint = `${API_URL}movie/popular?api_key=${API_KEY}&language=en-US&page=${this
60 | .state.currentPage + 1}`;
61 | } else {
62 | endpoint = `${API_URL}search/movie?api_key=${API_KEY}&language=en-US&query${
63 | this.state.searchTerm
64 | }$page=${this.state.currentPage + 1}`;
65 | }
66 | this.fetchItems(endpoint);
67 | };
68 |
69 | fetchItems = endpoint => {
70 | fetch(endpoint)
71 | .then(result => result.json())
72 | .then(result => {
73 | this.setState({
74 | movies: [...this.state.movies, ...result.results],
75 | heroImage: this.state.heroImage || result.results[0],
76 | loading: false,
77 | currentPage: result.page,
78 | totalPages: result.total_pages
79 | });
80 | });
81 | };
82 |
83 | render() {
84 | return (
85 |
86 | {this.state.heroImage ? (
87 |
88 |
95 |
96 |
97 | ) : null}
98 |
99 |
103 | {this.state.movies.map((element, i) => {
104 | return (
105 |
116 | );
117 | })}
118 |
119 | {this.state.loading ? : null}
120 | {this.state.currentPage <= this.state.totalPages &&
121 | !this.state.loading ? (
122 |
123 | ) : null}
124 |
125 |
126 | );
127 | }
128 | }
129 |
130 | export default Home;
131 |
--------------------------------------------------------------------------------
/src/components/Movie/Movie.css:
--------------------------------------------------------------------------------
1 | .rmdb-movie {
2 | margin-bottom: 100px;
3 | }
4 |
5 | .rmdb-movie-grid {
6 | max-width: 1280px;
7 | margin: 0 auto;
8 | padding: 0 20px;
9 | }
--------------------------------------------------------------------------------
/src/components/Movie/Movie.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import { API_URL, API_KEY } from "../../config";
3 | import Navigation from "../elements/Navigation/Navigation";
4 | import MovieInfo from "../elements/MovieInfo/MovieInfo";
5 | import MovieInfoBar from "../elements/MovieInfoBar/MovieInfoBar";
6 | // import FourColGrid from "../elements/FourColGrid/FourColGrid";
7 | import Actor from "../elements/Actor/Actor";
8 | import Spinner from "../elements/Spinner/Spinner";
9 | import "./Movie.css";
10 | import FourColGrid from "../elements/FourColGrid/FourColGrid";
11 |
12 | class Movie extends Component {
13 | state = {
14 | movie: null,
15 | actors: null,
16 | directors: [],
17 | loading: false
18 | };
19 |
20 | componentDidMount() {
21 | if (localStorage.getItem(`${this.props.match.params.movieId}`)) {
22 | const state = JSON.parse(
23 | localStorage.getItem(`${this.props.match.params.movieId}`)
24 | );
25 | this.setState({ ...state });
26 | } else {
27 | this.setState({ loading: true });
28 | //first fetch the movie data and then actors
29 | const endpoint = `${API_URL}movie/${
30 | this.props.match.params.movieId
31 | }?api_key=${API_KEY}&language=en-US`;
32 | this.fetchMovieData(endpoint);
33 | }
34 | }
35 |
36 | fetchMovieData = endpoint => {
37 | fetch(endpoint)
38 | .then(result => result.json())
39 | .then(result => {
40 | if (result.status_code) {
41 | this.setState({ loading: false });
42 | } else {
43 | this.setState(
44 | {
45 | movie: result
46 | },
47 | () => {
48 | const endpoint_credit = `${API_URL}movie/${
49 | this.props.match.params.movieId
50 | }/credits?api_key=${API_KEY}&language=en-US`;
51 | fetch(endpoint_credit)
52 | .then(result => result.json())
53 | .then(result => {
54 | const directors = result.crew.filter(
55 | member => member.job === "Director"
56 | );
57 | this.setState(
58 | {
59 | actors: result.cast,
60 | directors,
61 | loading: false
62 | },
63 | () => {
64 | localStorage.setItem(
65 | `${this.props.match.params.movieId}`,
66 | JSON.stringify(this.state)
67 | );
68 | }
69 | );
70 | });
71 | }
72 | );
73 | }
74 | })
75 | .catch(error => console.error("Error: ", error));
76 | };
77 |
78 | render() {
79 | return (
80 |
81 | {this.state.movie ? (
82 |
83 |
84 |
88 |
93 |
94 | ) : null}
95 |
96 | {this.state.actors ? (
97 |
98 |
99 | {this.state.actors.map((element, i) => {
100 | return ;
101 | })}
102 |
103 |
104 | ) : null}
105 |
106 | {!this.state.actors && !this.state.loading ? (
107 |
No Movie Found!
108 | ) : null}
109 | {this.state.loading ?
: null}
110 |
111 | );
112 | }
113 | }
114 |
115 | export default Movie;
116 |
--------------------------------------------------------------------------------
/src/components/elements/Actor/Actor.css:
--------------------------------------------------------------------------------
1 | .rmdb-actor {
2 | box-sizing: border-box;
3 | }
4 |
5 | .rmdb-actor img {
6 | width: 40%;
7 | height: auto;
8 | float: left;
9 | box-sizing: border-box;
10 | }
11 |
12 | .rmdb-actor-name {
13 | font-family: 'Abel', sans-serif;
14 | font-size: 22px;
15 | color:#fff;
16 | float: left;
17 | margin: 10px 20px;
18 | width: 40%;
19 | box-sizing: border-box;
20 | }
21 |
22 | .rmdb-actor-character {
23 | font-family: 'Abel', sans-serif;
24 | font-size: 18px;
25 | color:#fff;
26 | float: left;
27 | margin: 0 20px 10px 20px;
28 | width: 40%;
29 | box-sizing: border-box;
30 | }
--------------------------------------------------------------------------------
/src/components/elements/Actor/Actor.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { IMAGE_BASE_URL } from "../../../config";
3 | import "./Actor.css";
4 |
5 | const Actor = props => {
6 | const POSTER_SIZE = "w154";
7 | return (
8 |
9 |

17 |
{props.actor.name}
18 |
{props.actor.character}
19 |
20 | );
21 | };
22 |
23 | export default Actor;
24 |
--------------------------------------------------------------------------------
/src/components/elements/FourColGrid/FourColGrid.css:
--------------------------------------------------------------------------------
1 | .rmdb-grid h1 {
2 | font-family: 'Abel', sans-serif;
3 | font-size:42px;
4 | }
5 |
6 | .rmdb-grid-content {
7 | display: grid;
8 | grid-template-columns: auto auto auto auto;
9 | }
10 |
11 | .rmdb-grid-element {
12 | margin: 0 40px 40px 0;
13 | background: #353535;
14 | max-height: 430px;
15 | animation: animateGrid 0.5s;
16 | overflow: hidden;
17 | }
18 |
19 | @keyframes animateGrid {
20 | from {
21 | opacity:0;
22 | }
23 | to {
24 | opacity:1;
25 | }
26 | }
27 |
28 | /* For a 4-column grid */
29 | .rmdb-grid-element:nth-child(4n+4) {
30 | margin-right: 0;
31 | }
32 |
33 | @media screen and (max-width: 720px) {
34 | .rmdb-grid-content {
35 | grid-template-columns: auto auto;
36 | }
37 |
38 | .rmdb-grid-element:nth-child(2n+2) {
39 | margin-right: 0;
40 | }
41 |
42 | .rmdb-grid h1 {
43 | font-size:22px;
44 | }
45 | }
--------------------------------------------------------------------------------
/src/components/elements/FourColGrid/FourColGrid.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./FourColGrid.css";
3 |
4 | const FourColGrid = props => {
5 | const renderElements = () => {
6 | const gridElements = props.children.map((element, i) => {
7 | return (
8 |
9 | {element}
10 |
11 | );
12 | });
13 | return gridElements;
14 | };
15 |
16 | return (
17 |
18 | {props.header && !props.loading ?
{props.header}
: null}
19 |
{renderElements()}
20 |
21 | );
22 | };
23 |
24 | export default FourColGrid;
25 |
--------------------------------------------------------------------------------
/src/components/elements/Header/Header.css:
--------------------------------------------------------------------------------
1 | .rmdb-header {
2 | width: 100%;
3 | height: auto;
4 | background: #1c1c1c;
5 | padding: 0 20px;
6 | box-sizing: border-box;
7 | }
8 |
9 | .rmdb-header-content {
10 | max-width: 1280px;
11 | min-height: 120px;
12 | height: auto;
13 | padding: 20px 0px;
14 | margin: 0 auto;
15 | box-sizing: border-box;
16 | overflow: hidden;
17 | }
18 |
19 | .rmdb-logo {
20 | width: 300px;
21 | margin-top: 20px;
22 | float: left;
23 | }
24 |
25 | .rmdb-tmdb-logo {
26 | width: 122px;
27 | margin-top: 25px;
28 | float: right;
29 | }
30 |
31 | @media screen and (max-width: 500px) {
32 | .rmdb-header-content {
33 | max-width: 1280px;
34 | min-height: 0px;
35 | }
36 |
37 | .rmdb-tmdb-logo {
38 | display: inline-block;
39 | width:80px;
40 | margin-top: 0px;
41 | }
42 |
43 | .rmdb-logo {
44 | width: 150px;
45 | margin-top: 5px;
46 | }
47 | }
--------------------------------------------------------------------------------
/src/components/elements/Header/Header.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import "./Header.css";
4 |
5 | const Header = () => {
6 | return (
7 |
8 |
9 |
10 |

15 |
16 |

21 |
22 |
23 | );
24 | };
25 |
26 | export default Header;
27 |
--------------------------------------------------------------------------------
/src/components/elements/HeroImage/HeroImage.css:
--------------------------------------------------------------------------------
1 | .rmdb-heroimage {
2 | background-size: 100%, cover !important;
3 | background-position: center, center !important;
4 | width: 100%;
5 | height: 600px;
6 | position: relative;
7 | animation: animateHeroimage 1s;
8 | }
9 |
10 | @keyframes animateHeroimage {
11 | from {
12 | opacity:0;
13 | }
14 | to {
15 | opacity:1;
16 | }
17 | }
18 |
19 | .rmdb-heroimage-content {
20 | max-width: 1280px;
21 | padding: 20px;
22 | margin: 0 auto;
23 | }
24 |
25 | .rmdb-heroimage-text {
26 | z-index: 100;
27 | max-width: 700px;
28 | position: absolute;
29 | bottom: 40px;
30 | margin-right: 20px;
31 | min-height: 100px;
32 | background: rgba(0, 0, 0, 0.0);
33 | color: #fff;
34 | }
35 |
36 | .rmdb-heroimage-text h1 {
37 | font-family: 'Abel', sans-serif;
38 | font-size:48px;
39 | color: #fff;
40 | }
41 |
42 | .rmdb-heroimage-text p {
43 | font-family: 'Abel', sans-serif;
44 | font-size: 22px;
45 | line-height: 26px;
46 | color: #fff;
47 | }
48 |
49 | @media screen and (max-width: 720px) {
50 | .rmdb-heroimage-text {
51 | max-width: 100%;
52 | }
53 |
54 | .rmdb-heroimage-text h1 {
55 | font-size: 38px;
56 | color: #fff;
57 | }
58 |
59 | .rmdb-heroimage-text p {
60 | font-size: 16px;
61 | line-height: 20px;
62 | color: #fff;
63 | }
64 | }
--------------------------------------------------------------------------------
/src/components/elements/HeroImage/HeroImage.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./HeroImage.css";
3 |
4 | const HeroImage = props => {
5 | return (
6 |
15 |
16 |
17 |
{props.title}
18 |
{props.text}
19 |
20 |
21 |
22 | );
23 | };
24 |
25 | export default HeroImage;
26 |
--------------------------------------------------------------------------------
/src/components/elements/LoadMoreBtn/LoadMoreBtn.css:
--------------------------------------------------------------------------------
1 | .rmdb-loadmorebtn {
2 | background: #16d47b;
3 | width: 100%;
4 | min-height: 50px;
5 | text-align: center;
6 | color: #fff;
7 | cursor: pointer;
8 | transition: all 0.3s;
9 | }
10 |
11 | .rmdb-loadmorebtn p {
12 | font-family: 'Abel', sans-serif;
13 | font-size:42px;
14 | padding: 20px;
15 | }
16 |
17 | .rmdb-loadmorebtn:hover {
18 | opacity: 0.8;
19 | }
--------------------------------------------------------------------------------
/src/components/elements/LoadMoreBtn/LoadMoreBtn.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./LoadMoreBtn.css";
3 |
4 | const LoadMoreBtn = props => {
5 | return (
6 |
9 | );
10 | };
11 |
12 | export default LoadMoreBtn;
13 |
--------------------------------------------------------------------------------
/src/components/elements/MovieInfo/MovieInfo.css:
--------------------------------------------------------------------------------
1 | .rmdb-movieinfo {
2 | background-size: cover !important;
3 | background-position: center !important;
4 | width: 100%;
5 | height: 600px;
6 | padding: 40px 20px;
7 | box-sizing: border-box;
8 | animation: animateMovieinfo 1s;
9 | }
10 |
11 | @keyframes animateMovieinfo {
12 | from {
13 | opacity:0;
14 | }
15 | to {
16 | opacity:1;
17 | }
18 | }
19 |
20 | .rmdb-movieinfo-content {
21 | max-width: 1280px;
22 | width: 100%;
23 | height: 100%;
24 | margin: 0 auto;
25 | background: rgb(0, 0, 0, 0.7);
26 | position: relative;
27 | }
28 |
29 | .rmdb-movieinfo-thumb {
30 | width: 350px;
31 | height: 100%;
32 | overflow: hidden;
33 | position: absolute;
34 | left: 0px;
35 | }
36 |
37 | .rmdb-movieinfo-text {
38 | font-family: Arial, Helvetica, sans-serif;
39 | height: 100%;
40 | width: auto;
41 | padding: 40px;
42 | color: #fff;
43 | overflow: hidden;
44 | box-sizing: border-box;
45 | position: absolute;
46 | left: 360px;
47 | }
48 |
49 | .rmdb-movieinfo-text h1 {
50 | font-family: 'Abel', sans-serif;
51 | font-size:48px;
52 | margin: 0;
53 | }
54 |
55 | .rmdb-movieinfo-text h3 {
56 | font-size: 16px;
57 | line-height: 0;
58 | margin-top: 30px;
59 | }
60 |
61 | .rmdb-movieinfo-text p {
62 | font-family: 'Abel', sans-serif;
63 | font-size: 18px;
64 | line-height: 26px;
65 | }
66 |
67 | .rmdb-rating {
68 | width: 250px;
69 | height: 20px;
70 | margin-top: 20px;
71 | position: relative;
72 | }
73 |
74 | .rmdb-score {
75 | position: absolute;
76 | margin: 0;
77 | right: 0px;
78 | top: -3px;
79 | }
80 |
81 | .rmdb-director {
82 | margin: 0;
83 | }
84 |
85 | .fa-film {
86 | position: absolute;
87 | bottom: 40px;
88 | right: 40px;
89 | color: #fff;
90 | }
91 |
92 | meter::-webkit-meter-bar {
93 | background: #FFF;
94 | width:200px;
95 | }
96 | meter::-webkit-meter-optimum-value {
97 | background: linear-gradient(to bottom, #16d47b);
98 | }
99 | meter::-webkit-meter-suboptimum-value {
100 | background: linear-gradient(to bottom, #fbb450);
101 | }
102 | meter::-webkit-meter-even-less-good-value {
103 | background: linear-gradient(to bottom, #ee5f5b);
104 | }
--------------------------------------------------------------------------------
/src/components/elements/MovieInfo/MovieInfo.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { IMAGE_BASE_URL, POSTER_SIZE, BACKDROP_SIZE } from "../../../config";
3 | import FontAwesome from "react-fontawesome";
4 | import MovieThumb from "../MovieThumb/MovieThumb";
5 | import "./MovieInfo.css";
6 |
7 | const MovieInfo = props => {
8 | return (
9 |
19 |
20 |
21 |
29 |
30 |
{props.movie.title}
31 |
PLOT
32 |
{props.movie.overview}
33 |
IMDB RATING
34 |
35 |
43 |
{props.movie.vote_average}
44 |
45 | {props.directors.length > 1 ? (
46 |
DIRECTORS
47 | ) : (
48 |
DIRECTOR
49 | )}{" "}
50 | {props.directors.map((element, i) => {
51 | return (
52 |
53 | {element.name}
54 |
55 | );
56 | })}
57 |
58 |
59 |
60 |
61 |
62 | );
63 | };
64 |
65 | export default MovieInfo;
66 |
--------------------------------------------------------------------------------
/src/components/elements/MovieInfoBar/MovieInfoBar.css:
--------------------------------------------------------------------------------
1 | .rmdb-movieinfobar {
2 | width: 100%;
3 | height: 105px;
4 | background: #1c1c1c;
5 | position: relative;
6 | padding: 25px 20px 0px 20px;
7 | box-sizing: border-box;
8 | font-family: 'Abel', sans-serif;
9 | font-size: 22px;
10 | }
11 |
12 | .rmdb-movieinfobar-content {
13 | max-width: 1280px;
14 | width: 100%;
15 | margin: 0 auto;
16 | color: #fff;
17 | }
18 |
19 | .rmdb-movieinfobar-content-col {
20 | float: left;
21 | width: 33.33%;
22 | box-sizing: border-box;
23 | padding: 10px 20px 0 0;
24 | }
25 |
26 | .rmdb-movieinfobar-info {
27 | padding: 5px 0 0 10px;
28 | float: left;
29 | }
30 |
31 | .fa-time, .fa-revenue {
32 | float: left;
33 | margin-top: -4px;
34 |
35 | }
36 |
37 | .fa-budget {
38 | float: left;
39 | margin-top: -3px;
40 | }
--------------------------------------------------------------------------------
/src/components/elements/MovieInfoBar/MovieInfoBar.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import FontAwesome from "react-fontawesome";
3 |
4 | import { calcTime, convertMoney } from "../../../helpers";
5 | import "./MovieInfoBar.css";
6 |
7 | const MovieInfoBar = props => {
8 | return (
9 |
10 |
11 |
12 |
13 |
14 | Running Time: {calcTime(props.time)}
15 |
16 |
17 |
18 |
19 |
20 | Budget: {convertMoney(props.budget)}
21 |
22 |
23 |
24 |
25 |
26 | Revenue: {convertMoney(props.revenue)}
27 |
28 |
29 |
30 |
31 | );
32 | };
33 |
34 | export default MovieInfoBar;
35 |
--------------------------------------------------------------------------------
/src/components/elements/MovieThumb/MovieThumb.css:
--------------------------------------------------------------------------------
1 | .rmdb-moviethumb img {
2 | width: 500px;
3 | height: auto;
4 | max-width: 100%;
5 | max-height: 100%;
6 | transition: all 0.3s;
7 | box-sizing: border-box;
8 | }
9 |
10 | .clickable {
11 | cursor: pointer;
12 | }
13 |
14 | .clickable:hover {
15 | opacity: 0.8;
16 | }
--------------------------------------------------------------------------------
/src/components/elements/MovieThumb/MovieThumb.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import PropTypes from "prop-types";
4 | import "./MovieThumb.css";
5 |
6 | const MovieThumb = props => {
7 | return (
8 |
9 | {props.clickable ? (
10 |
16 |

17 |
18 | ) : (
19 |

20 | )}
21 |
22 | );
23 | };
24 |
25 | MovieThumb.propTypes = {
26 | image: PropTypes.string,
27 | movieId: PropTypes.number,
28 | movieName: PropTypes.string
29 | };
30 |
31 | export default MovieThumb;
32 |
--------------------------------------------------------------------------------
/src/components/elements/Navigation/Navigation.css:
--------------------------------------------------------------------------------
1 | .rmdb-navigation {
2 | width: 100%;
3 | height: 50px;
4 | background: #353535;
5 | color: #fff;
6 | position: relative;
7 | padding: 20px;
8 | box-sizing: border-box;
9 | margin: 0;
10 | padding-top: 10px;
11 | }
12 |
13 | .rmdb-navigation-content {
14 | max-width: 1280px;
15 | margin: 0 auto;
16 | padding: 0 20px;
17 |
18 | }
19 |
20 | .rmdb-navigation-content p {
21 | font-family: 'Abel', sans-serif;
22 | font-size: 22px;
23 | float: left;
24 | color: #fff;
25 | padding-right: 10px;
26 | text-decoration: none;
27 | margin: 0;
28 | }
--------------------------------------------------------------------------------
/src/components/elements/Navigation/Navigation.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import { Link } from "react-router-dom";
3 | import "./Navigation.css";
4 |
5 | const Navigation = props => {
6 | return (
7 |
8 |
9 |
10 |
Home
11 |
12 |
/
13 |
{props.movie}
14 |
15 |
16 | );
17 | };
18 |
19 | export default Navigation;
20 |
--------------------------------------------------------------------------------
/src/components/elements/NotFound/NotFound.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const NotFound = () => {
4 | return (
5 |
6 |
Whooops. This page doesn't exist
7 |
8 | );
9 | };
10 |
11 | export default NotFound;
12 |
--------------------------------------------------------------------------------
/src/components/elements/SearchBar/SearchBar.css:
--------------------------------------------------------------------------------
1 | .rmdb-searchbar {
2 | width: 100%;
3 | height: 105px;
4 | background: #1c1c1c;
5 | position: relative;
6 | padding: 25px 20px 0px 20px;
7 | box-sizing: border-box;
8 | color: #fff;
9 | }
10 |
11 | .rmdb-searchbar-content {
12 | max-width: 1280px;
13 | width: 100%;
14 | height: 55px;
15 | background:#353535;
16 | margin: 0 auto;
17 | border-radius: 40px;
18 | position: relative;
19 | color: #fff;
20 | }
21 |
22 | .rmdb-fa-search {
23 | position: absolute;
24 | left: 20px;
25 | top: 12px;
26 | color: #fff;
27 | }
28 |
29 | .rmdb-searchbar-input {
30 | font-family: 'Abel', sans-serif;
31 | font-size: 38px;
32 | position: absolute;
33 | left: 70px;
34 | top: 7px;
35 | border: 0;
36 | background: transparent;
37 | height: 40px;
38 | color: #fff;
39 | }
40 |
41 | .rmdb-searchbar-input:focus {
42 | outline: none;
43 | }
44 |
45 | @media screen and (max-width: 720px) {
46 | .rmdb-searchbar-input {
47 | font-size: 28px;
48 | color: #fff;
49 | }
50 | }
--------------------------------------------------------------------------------
/src/components/elements/SearchBar/SearchBar.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import FontAwesome from "react-fontawesome";
3 | import "./SearchBar.css";
4 |
5 | class SearchBar extends Component {
6 | state = {
7 | value: ""
8 | };
9 |
10 | timeout = null;
11 |
12 | doSearch = event => {
13 | this.setState({ value: event.target.value });
14 | clearTimeout(this.timeout);
15 |
16 | this.timeout = setTimeout(() => {
17 | this.props.callback(this.state.value);
18 | }, 500);
19 | };
20 |
21 | render() {
22 | return (
23 |
35 | );
36 | }
37 | }
38 |
39 | export default SearchBar;
40 |
--------------------------------------------------------------------------------
/src/components/elements/Spinner/Spinner.css:
--------------------------------------------------------------------------------
1 | .loader {
2 | border: 5px solid #f3f3f3; /* Light grey */
3 | border-top: 5px solid #16d47b; /* Blue */
4 | border-radius: 50%;
5 | width: 50px;
6 | height: 50px;
7 | animation: spin 0.8s linear infinite;
8 | margin: 20px auto;
9 | }
10 |
11 | @keyframes spin {
12 | 0% { transform: rotate(0deg); }
13 | 100% { transform: rotate(360deg); }
14 | }
--------------------------------------------------------------------------------
/src/components/elements/Spinner/Spinner.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import "./Spinner.css";
3 |
4 | const Spinner = () => {
5 | return ;
6 | };
7 |
8 | export default Spinner;
9 |
--------------------------------------------------------------------------------
/src/config.js:
--------------------------------------------------------------------------------
1 | // Configuration for TMDB
2 | // To se the latest configuration fetch it from https://api.themoviedb.org/3/configuration?api_key=019e8f375549e0bbd4a4191862ebc88f
3 |
4 | const API_URL = 'https://api.themoviedb.org/3/';
5 | const API_KEY = '844dba0bfd8f3a4f3799f6130ef9e335';
6 |
7 | // Images
8 | // An image URL looks like this example:
9 | // http://image.tmdb.org/t/p/w780/bOGkgRGdhrBYJSLpXaxhXVstddV.jpg
10 |
11 | const IMAGE_BASE_URL ='http://image.tmdb.org/t/p/';
12 |
13 | //Sizes: w300, w780, w1280, original
14 | const BACKDROP_SIZE = 'w1280';
15 |
16 | // w92, w154, w185, w342, w500, w780, original
17 | const POSTER_SIZE = 'w500';
18 |
19 | export {
20 | API_URL,
21 | API_KEY,
22 | IMAGE_BASE_URL,
23 | BACKDROP_SIZE,
24 | POSTER_SIZE
25 | }
--------------------------------------------------------------------------------
/src/helpers.js:
--------------------------------------------------------------------------------
1 | // Convert time to hours and minutes
2 | export const calcTime = (time) => {
3 | const hours = Math.floor(time / 60);
4 | const mins = time % 60;
5 | return `${hours}h ${mins}m`;
6 | }
7 |
8 | // Convert a number to $ format
9 | export const convertMoney = (money) => {
10 | var formatter = new Intl.NumberFormat('en-US', {
11 | style: 'currency',
12 | currency: 'USD',
13 | minimumFractionDigits: 0,
14 | });
15 | return formatter.format(money);
16 | }
--------------------------------------------------------------------------------
/src/index.css:
--------------------------------------------------------------------------------
1 | body {
2 | margin: 0;
3 | }
--------------------------------------------------------------------------------
/src/index.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import ReactDOM from "react-dom";
3 | import App from "./components/App/App";
4 | import "./index.css";
5 |
6 | ReactDOM.render(, document.getElementById("root"));
7 |
--------------------------------------------------------------------------------
/src/logo.svg:
--------------------------------------------------------------------------------
1 |
8 |
--------------------------------------------------------------------------------
/src/serviceWorker.js:
--------------------------------------------------------------------------------
1 | // This optional code is used to register a service worker.
2 | // register() is not called by default.
3 |
4 | // This lets the app load faster on subsequent visits in production, and gives
5 | // it offline capabilities. However, it also means that developers (and users)
6 | // will only see deployed updates on subsequent visits to a page, after all the
7 | // existing tabs open on the page have been closed, since previously cached
8 | // resources are updated in the background.
9 |
10 | // To learn more about the benefits of this model and instructions on how to
11 | // opt-in, read http://bit.ly/CRA-PWA
12 |
13 | const isLocalhost = Boolean(
14 | window.location.hostname === 'localhost' ||
15 | // [::1] is the IPv6 localhost address.
16 | window.location.hostname === '[::1]' ||
17 | // 127.0.0.1/8 is considered localhost for IPv4.
18 | window.location.hostname.match(
19 | /^127(?:\.(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}$/
20 | )
21 | );
22 |
23 | export function register(config) {
24 | if (process.env.NODE_ENV === 'production' && 'serviceWorker' in navigator) {
25 | // The URL constructor is available in all browsers that support SW.
26 | const publicUrl = new URL(process.env.PUBLIC_URL, window.location.href);
27 | if (publicUrl.origin !== window.location.origin) {
28 | // Our service worker won't work if PUBLIC_URL is on a different origin
29 | // from what our page is served on. This might happen if a CDN is used to
30 | // serve assets; see https://github.com/facebook/create-react-app/issues/2374
31 | return;
32 | }
33 |
34 | window.addEventListener('load', () => {
35 | const swUrl = `${process.env.PUBLIC_URL}/service-worker.js`;
36 |
37 | if (isLocalhost) {
38 | // This is running on localhost. Let's check if a service worker still exists or not.
39 | checkValidServiceWorker(swUrl, config);
40 |
41 | // Add some additional logging to localhost, pointing developers to the
42 | // service worker/PWA documentation.
43 | navigator.serviceWorker.ready.then(() => {
44 | console.log(
45 | 'This web app is being served cache-first by a service ' +
46 | 'worker. To learn more, visit http://bit.ly/CRA-PWA'
47 | );
48 | });
49 | } else {
50 | // Is not localhost. Just register service worker
51 | registerValidSW(swUrl, config);
52 | }
53 | });
54 | }
55 | }
56 |
57 | function registerValidSW(swUrl, config) {
58 | navigator.serviceWorker
59 | .register(swUrl)
60 | .then(registration => {
61 | registration.onupdatefound = () => {
62 | const installingWorker = registration.installing;
63 | if (installingWorker == null) {
64 | return;
65 | }
66 | installingWorker.onstatechange = () => {
67 | if (installingWorker.state === 'installed') {
68 | if (navigator.serviceWorker.controller) {
69 | // At this point, the updated precached content has been fetched,
70 | // but the previous service worker will still serve the older
71 | // content until all client tabs are closed.
72 | console.log(
73 | 'New content is available and will be used when all ' +
74 | 'tabs for this page are closed. See http://bit.ly/CRA-PWA.'
75 | );
76 |
77 | // Execute callback
78 | if (config && config.onUpdate) {
79 | config.onUpdate(registration);
80 | }
81 | } else {
82 | // At this point, everything has been precached.
83 | // It's the perfect time to display a
84 | // "Content is cached for offline use." message.
85 | console.log('Content is cached for offline use.');
86 |
87 | // Execute callback
88 | if (config && config.onSuccess) {
89 | config.onSuccess(registration);
90 | }
91 | }
92 | }
93 | };
94 | };
95 | })
96 | .catch(error => {
97 | console.error('Error during service worker registration:', error);
98 | });
99 | }
100 |
101 | function checkValidServiceWorker(swUrl, config) {
102 | // Check if the service worker can be found. If it can't reload the page.
103 | fetch(swUrl)
104 | .then(response => {
105 | // Ensure service worker exists, and that we really are getting a JS file.
106 | const contentType = response.headers.get('content-type');
107 | if (
108 | response.status === 404 ||
109 | (contentType != null && contentType.indexOf('javascript') === -1)
110 | ) {
111 | // No service worker found. Probably a different app. Reload the page.
112 | navigator.serviceWorker.ready.then(registration => {
113 | registration.unregister().then(() => {
114 | window.location.reload();
115 | });
116 | });
117 | } else {
118 | // Service worker found. Proceed as normal.
119 | registerValidSW(swUrl, config);
120 | }
121 | })
122 | .catch(() => {
123 | console.log(
124 | 'No internet connection found. App is running in offline mode.'
125 | );
126 | });
127 | }
128 |
129 | export function unregister() {
130 | if ('serviceWorker' in navigator) {
131 | navigator.serviceWorker.ready.then(registration => {
132 | registration.unregister();
133 | });
134 | }
135 | }
136 |
--------------------------------------------------------------------------------