├── public
├── favicon.ico
├── manifest.json
└── index.html
├── src
├── header
│ ├── Footer.js
│ ├── Home.js
│ ├── Navbar.js
│ ├── Search.js
│ └── Header.js
├── App.test.js
├── index.css
├── index.js
├── App.js
├── movieslist
│ ├── SingleMovie.js
│ ├── ListsOfMovies.js
│ └── List.js
├── MovieInfo.js
├── serviceWorker.js
└── style.css
├── .gitignore
├── package.json
└── README.md
/public/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/dmorozin/movies-database/HEAD/public/favicon.ico
--------------------------------------------------------------------------------
/src/header/Footer.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 |
3 | const Footer = () => {
4 | return (
5 |
8 | );
9 | };
10 |
11 | export default Footer;
12 |
--------------------------------------------------------------------------------
/src/App.test.js:
--------------------------------------------------------------------------------
1 | import React from 'react';
2 | import ReactDOM from 'react-dom';
3 | import App from './App';
4 |
5 | it('renders without crashing', () => {
6 | const div = document.createElement('div');
7 | ReactDOM.render( , div);
8 | ReactDOM.unmountComponentAtNode(div);
9 | });
10 |
--------------------------------------------------------------------------------
/src/header/Home.js:
--------------------------------------------------------------------------------
1 | import React from "react";
2 | import Header from "./Header";
3 | import Footer from "./Footer";
4 | import ListsOfMovies from "../movieslist/ListsOfMovies";
5 |
6 | const Home = () => {
7 | return (
8 |
56 | this.setState({ i: index }, () => {
57 | clearTimeout(this.timeout);
58 | this.startTimeout();
59 | })
60 | }
61 | />
62 | );
63 | } else return null;
64 | })
65 | : null;
66 |
67 | const moviesList = movies.length ? (
68 |
69 |
70 |
78 |
79 |
{movies[i].title}
80 |
81 | Rating: {movies[i].vote_average}
82 |
83 |
84 | Release Date: {new Date(movies[i].release_date).toDateString()}
85 |
86 |
{movies[i].overview}
87 |
88 |
See More
89 |
90 |
91 |
{divs}
92 |
93 |
94 |
95 | ) : (
96 |
Loading
97 | );
98 |
99 | return (
100 |
101 |
102 | {moviesList}
103 |
104 | );
105 | }
106 | }
107 | export default Header;
108 |
--------------------------------------------------------------------------------
/src/MovieInfo.js:
--------------------------------------------------------------------------------
1 | import React, { Component } from "react";
2 | import Navbar from "./header/Navbar";
3 | import Footer from "./header/Footer";
4 |
5 | export default class MovieInfo extends Component {
6 | constructor(props) {
7 | super(props);
8 | this.mounted = false;
9 | }
10 |
11 | state = {
12 | movie: {},
13 | credits: [],
14 | video: []
15 | };
16 |
17 | fetchMovie = () => {
18 | const urlMovie = fetch(
19 | `https://api.themoviedb.org/3/movie/${
20 | this.props.match.params.movie_id
21 | }?api_key=17117ab9c18276d48d8634390c025df4&language=en-US`
22 | );
23 | const urlCredits = fetch(`https://api.themoviedb.org/3/movie/${
24 | this.props.match.params.movie_id
25 | }/credits?api_key=17117ab9c18276d48d8634390c025df4
26 | `);
27 | const urlVideos = fetch(`https://api.themoviedb.org/3/movie/${
28 | this.props.match.params.movie_id
29 | }/videos?api_key=17117ab9c18276d48d8634390c025df4
30 | `);
31 | const urls = [urlMovie, urlCredits, urlVideos];
32 |
33 | Promise.all(urls)
34 | .then(([r1, r2, r3]) => Promise.all([r1.json(), r2.json(), r3.json()]))
35 | .then(([data1, data2, data3]) => {
36 | if (this.mounted)
37 | this.setState({
38 | movie: data1,
39 | credits: data2,
40 | video: data3.results
41 | });
42 | })
43 | .catch(err => console.log(err));
44 | };
45 |
46 | componentDidMount() {
47 | this.mounted = true;
48 | this.fetchMovie();
49 | }
50 |
51 | componentDidUpdate(prevProps) {
52 | if (prevProps !== this.props) {
53 | this.fetchMovie();
54 | }
55 | }
56 |
57 | componentWillUnmount() {
58 | this.mounted = false;
59 | }
60 |
61 | time_convert = num => {
62 | const hours = Math.floor(num / 60);
63 | const minutes = num % 60;
64 | return `${hours}h ${minutes}min`;
65 | };
66 |
67 | render() {
68 | const { movie, credits, video } = this.state;
69 |
70 | const backgroundImg = {
71 | backgroundImage: `linear-gradient(rgba(0, 0, 0, 0.7) , rgba(0, 0, 0, 0.7)), url("https://image.tmdb.org/t/p/original/${
72 | movie.backdrop_path
73 | }")`
74 | };
75 |
76 | const backwithPoster = {
77 | backgroundImage: `linear-gradient(90deg, rgba(0, 0, 0, 0.8) 40%, rgba(0, 0, 0, 0.8) 60%), url("https://image.tmdb.org/t/p/original/${
78 | movie.poster_path
79 | }")`
80 | };
81 |
82 | const content =
83 | Object.keys(movie).length > 0 ? (
84 |
88 |
89 |
{movie.title}
90 | {video.length ? (
91 |
92 | VIDEO
96 |
97 | ) : null}
98 |
99 |
100 |
101 | {new Date(movie.release_date).getFullYear()}
102 |
103 |
104 | {" "}
105 | {movie.runtime && this.time_convert(movie.runtime)}
106 |
107 |
108 | {movie.vote_average}
109 |
110 |
111 |
112 |
{movie.overview}
113 |
114 | Starring:
115 | {credits.cast &&
116 | credits.cast.map((cast, i) => {
117 | if (i < 4)
118 | return {cast.name}, ;
119 | if (i === 4)
120 | return {cast.name} ;
121 | else return null;
122 | })}
123 |
124 |
125 |
126 | Genres:
127 | {movie.genres.map((genre, i, arr) => {
128 | if (i === arr.length - 1)
129 | return {genre.name} ;
130 | return {genre.name}, ;
131 | })}
132 |
133 |
134 | {credits && credits.crew.length > 0 && (
135 |
136 | Director: {" "}
137 | {credits.crew[0].name}
138 |
139 | )}
140 |
141 |
142 |
143 | ) : (
144 |
Loading...
145 | );
146 |
147 | return (
148 |
149 |
150 |
{content}
151 |
152 |
153 | );
154 | }
155 | }
156 |
--------------------------------------------------------------------------------
/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 |
--------------------------------------------------------------------------------
/src/style.css:
--------------------------------------------------------------------------------
1 | body,
2 | html {
3 | height: 100%;
4 | margin: 0;
5 | }
6 |
7 | body {
8 | background: #0d0d0d;
9 | color: white;
10 | font-family: Helvetica;
11 | }
12 |
13 | .App {
14 | margin: 0 auto;
15 | }
16 |
17 | /*header*/
18 | nav {
19 | background: black;
20 | color: red;
21 | height: 65px;
22 | padding-left: 6%;
23 | padding-right: 6%;
24 | padding-top: 13px;
25 | box-sizing: border-box;
26 | }
27 |
28 | .logo {
29 | float: left;
30 | font-size: 30px;
31 | }
32 |
33 | .logo a {
34 | color: #cc0000;
35 | text-decoration: none;
36 | }
37 |
38 | .logo a:hover {
39 | color: #e60000;
40 | }
41 |
42 | .search-bar {
43 | float: right;
44 | }
45 |
46 | .search-input {
47 | background: none;
48 | color: #cc0000;
49 | border: 1px solid #cc0000;
50 | outline: none;
51 | border-radius: 8px;
52 | padding: 5px;
53 | padding-left: 15px;
54 | width: 250px;
55 | height: 20px;
56 | font-weight: 600;
57 | margin-top: 5px;
58 | position: relative;
59 | }
60 |
61 | .search-values {
62 | background: #0d0d0d;
63 | width: 265px;
64 | border-radius: 5px;
65 | border: 2px solid #cc0000;
66 | border-top: 0;
67 | border-top-left-radius: 0;
68 | border-top-right-radius: 0;
69 |
70 | position: absolute;
71 | margin-left: 2px;
72 | box-shadow: 0px 8px 16px 0px rgba(0, 0, 0, 0.2);
73 | padding: 0;
74 | z-index: 1;
75 | }
76 |
77 | .search-values ul {
78 | margin: 0;
79 | padding: 10px;
80 | list-style: none;
81 | }
82 |
83 | .search-values ul li {
84 | margin: 8px;
85 | }
86 |
87 | .search-values a {
88 | text-decoration: none;
89 | color: #e60000;
90 | }
91 |
92 | .search-values a:hover {
93 | text-decoration: underline;
94 | font-weight: 600;
95 | }
96 |
97 | .bgImage {
98 | height: 750px;
99 | width: 100%;
100 | background-position: center top;
101 | background-repeat: no-repeat;
102 | background-size: cover;
103 | position: relative;
104 | color: white;
105 | z-index: 0;
106 | }
107 |
108 | .popularInfo {
109 | position: absolute;
110 | top: 20%;
111 | left: 6%;
112 | width: 40%;
113 | }
114 |
115 | .popularInfo h1 {
116 | margin: 0;
117 | margin-bottom: 30px;
118 | }
119 |
120 | .popularInfo button {
121 | background: #cc0000;
122 | color: white;
123 | height: 50px;
124 | width: 200px;
125 | margin-top: 20px;
126 | font-size: 20px;
127 | outline: none;
128 | border: 1px solid red;
129 | cursor: pointer;
130 | font-weight: lighter;
131 | }
132 |
133 | .popularInfo button:hover {
134 | background: #e60000;
135 | }
136 |
137 | .switchImg {
138 | position: absolute;
139 | top: 90%;
140 | left: 50%;
141 | margin-right: -50%;
142 | transform: translate(-50%, -50%);
143 | }
144 |
145 | .switchImg div {
146 | width: 30px;
147 | height: 10px;
148 | background: transparent;
149 | border: 1px solid white;
150 | border-radius: 2px;
151 | display: inline-block;
152 | margin: 20px;
153 | cursor: pointer;
154 | }
155 |
156 | .switchImg div.active {
157 | background: #404040;
158 | }
159 |
160 | /*lists of movies*/
161 | .lists {
162 | margin: 100px 0;
163 | }
164 |
165 | .lists:first-child {
166 | margin-top: 50px;
167 | }
168 |
169 | .lists h2 {
170 | padding-left: 70px;
171 | margin-bottom: 10px;
172 | }
173 |
174 | .menu-item {
175 | padding: 0px;
176 | margin: 5px 2px;
177 | user-select: none;
178 | cursor: pointer;
179 | border: none;
180 | }
181 | .menu-item-wrapper.active {
182 | border: 1px blue solid;
183 | }
184 | .menu-item.active {
185 | border: 1px green solid;
186 | }
187 |
188 | .movie-card {
189 | position: relative;
190 | width: 300px;
191 | height: 170px;
192 | transition: all 0.7s;
193 | background: black;
194 | }
195 |
196 | img {
197 | width: 300px;
198 | height: 170px;
199 | vertical-align: 0px;
200 | transition: all 0.7s;
201 | user-select: none;
202 | }
203 |
204 | .movie-title {
205 | position: absolute;
206 | top: 40%;
207 | left: 50%;
208 | margin-right: -50%;
209 | transform: translate(-50%, -50%);
210 | text-align: center;
211 | width: 100%;
212 | white-space: initial;
213 | color: #fff;
214 | visibility: hidden;
215 | opacity: 0;
216 | /* transition effect. not necessary */
217 | transition: opacity 0.2s, visibility 0.2s;
218 | }
219 |
220 | .movie-card:hover {
221 | width: 330px;
222 | height: 190px;
223 | }
224 |
225 | .movie-card:hover .movie-title {
226 | visibility: visible;
227 | opacity: 1;
228 | }
229 |
230 | .movie-card:hover img {
231 | width: 330px;
232 | height: 190px;
233 | vertical-align: -12px;
234 | opacity: 0.3;
235 | }
236 |
237 | .scroll-menu-arrow {
238 | padding: 20px;
239 | cursor: pointer;
240 | }
241 |
242 | .horizontal-menu {
243 | height: 220px;
244 | }
245 |
246 | .arrow-prev,
247 | .arrow-next {
248 | color: red;
249 | font-size: 40px;
250 | }
251 |
252 | /* movie info */
253 |
254 | .content {
255 | width: 50%;
256 | margin: 0 auto;
257 | padding: 20px;
258 | min-height: 410px;
259 | font-size: 18px;
260 | color: #e6e6e6;
261 | font-weight: 500;
262 | }
263 |
264 | .content h1 {
265 | text-align: center;
266 | margin: 30px;
267 | margin-top: 10px;
268 | }
269 |
270 | .content p {
271 | line-height: 19px;
272 | }
273 |
274 | .video {
275 | padding-top: 56.25%;
276 | position: relative;
277 | -webkit-box-shadow: 0 0 30px grey;
278 | box-shadow: 0 0 30px grey;
279 | }
280 |
281 | iframe {
282 | border: 0;
283 | height: 100%;
284 | left: 0;
285 | position: absolute;
286 | top: 0;
287 | width: 100%;
288 | }
289 |
290 | .overview-container {
291 | margin-top: 10px;
292 | }
293 |
294 | .year-run-vote span {
295 | font-size: 14px;
296 | padding-right: 6px;
297 | }
298 |
299 | .run,
300 | .vote {
301 | border-left: 1px solid white;
302 | padding-left: 6px;
303 | }
304 |
305 | .greyed {
306 | color: #9c9c9c;
307 | }
308 |
309 | i {
310 | color: gold;
311 | }
312 |
313 | footer {
314 | height: 60px;
315 | background: black;
316 | color: white;
317 | border-top: 1px solid red;
318 | text-align: center;
319 | }
320 |
321 | footer p {
322 | margin: 20px auto;
323 | }
324 |
325 | .back-height {
326 | height: 100%;
327 | background-repeat: no-repeat;
328 | background-size: cover;
329 | background-position: center;
330 | }
331 |
332 | @media only screen and (max-width: 900px) {
333 | .back-height {
334 | height: 100vh;
335 | }
336 | /*header*/
337 | .bgImage {
338 | height: 700px;
339 | }
340 |
341 | .header-overview {
342 | display: none;
343 | }
344 |
345 | .popularInfo {
346 | text-align: center;
347 | top: 50%;
348 | left: 50%;
349 | margin-right: -50%;
350 | transform: translate(-50%, -50%);
351 | width: 90%;
352 | }
353 |
354 | .switchImg div {
355 | margin: 10px;
356 | }
357 |
358 | /*lists of movies*/
359 |
360 | .lists {
361 | margin: 0;
362 | }
363 |
364 | .lists h2 {
365 | margin-bottom: 0;
366 | }
367 |
368 | .movie-card,
369 | img {
370 | width: 230px;
371 | height: 140px;
372 | }
373 |
374 | .movie-card:hover,
375 | .movie-card:hover img {
376 | width: 260px;
377 | height: 160px;
378 | }
379 |
380 | .arrow-prev,
381 | .arrow-next {
382 | font-size: 20px;
383 | }
384 |
385 | .scroll-menu-arrow {
386 | padding: 10px;
387 | }
388 |
389 | /*movie-info*/
390 | .content {
391 | width: 90%;
392 | font-size: 93%;
393 | }
394 |
395 | .content h1 {
396 | margin: 30px 0;
397 | }
398 | }
399 |
400 | @media only screen and (max-width: 690px) {
401 | nav {
402 | height: 100px;
403 | }
404 | .logo,
405 | .search-bar {
406 | float: none;
407 | }
408 |
409 | .logo {
410 | text-align: center;
411 | }
412 |
413 | .search-input {
414 | width: 95%;
415 | }
416 |
417 | .search-values {
418 | width: 87%;
419 | }
420 | .bgImage {
421 | height: 400px;
422 | background-size: cover;
423 | }
424 |
425 | .popularInfo button {
426 | margin: 0;
427 | }
428 |
429 | .lists h2 {
430 | padding-left: 5px;
431 | margin-bottom: 10px;
432 | }
433 |
434 | .movie-card,
435 | img {
436 | width: 190px;
437 | height: 100px;
438 | }
439 |
440 | .movie-card:hover,
441 | .movie-card:hover img {
442 | width: 220px;
443 | height: 120px;
444 | }
445 |
446 | .movie-title {
447 | font-size: 14px;
448 | }
449 | }
450 |
451 | @media only screen and (max-width: 450px) {
452 | }
453 |
454 | @media only screen and (max-width: 350px) {
455 | .back-height {
456 | height: 100%;
457 | }
458 |
459 | .popularInfo {
460 | text-align: center;
461 | top: 40%;
462 | left: 50%;
463 | margin-right: -50%;
464 | transform: translate(-50%, -50%);
465 | width: 90%;
466 | }
467 |
468 | .popularInfo button {
469 | height: 40px;
470 | width: 100px;
471 | font-size: 90%;
472 | margin-top: 10px;
473 | }
474 |
475 | /* list of movies*/
476 |
477 | .horizontal-menu {
478 | height: 190px;
479 | }
480 |
481 | .movie-card,
482 | img {
483 | width: 160px;
484 | height: 90px;
485 | }
486 |
487 | .movie-card:hover,
488 | .movie-card:hover img {
489 | width: 190px;
490 | height: 110px;
491 | }
492 | }
493 |
494 | @media only screen and (max-height: 800px) {
495 | .back-height {
496 | height: 100%;
497 | }
498 | }
499 |
--------------------------------------------------------------------------------