├── .flowconfig ├── .gitignore ├── README.md ├── package.json ├── public ├── favicon.ico ├── index.html └── manifest.json ├── src ├── App.js ├── Components │ ├── CardTemplate │ │ ├── CardTemplate.css │ │ └── CardTemplate.jsx │ ├── Home │ │ ├── Home.css │ │ └── Home.jsx │ ├── Movie │ │ ├── Movie.css │ │ └── Movie.jsx │ ├── Navbar │ │ └── Navbar.jsx │ ├── Routes │ │ └── Routes.jsx │ ├── SearchForm │ │ ├── SearchForm.css │ │ └── SearchForm.jsx │ ├── Showfilms │ │ ├── Showfilms.css │ │ └── Showfilms.jsx │ └── Table │ │ ├── Table.css │ │ └── Table.jsx ├── Services │ ├── dataService.js │ └── utilsService.js └── index.js └── yarn.lock /.flowconfig: -------------------------------------------------------------------------------- 1 | [ignore] 2 | 3 | [include] 4 | 5 | [libs] 6 | 7 | [lints] 8 | 9 | [options] 10 | 11 | [strict] 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/ignore-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | 6 | # testing 7 | /coverage 8 | 9 | # production 10 | /build 11 | 12 | # misc 13 | .DS_Store 14 | .env.local 15 | .env.development.local 16 | .env.test.local 17 | .env.production.local 18 | 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![HTML5,CSS3 and JS](https://github.com/FransLopez/logo-images/blob/master/logos/html5-css3-js.png)](http://www.w3.org/)[![Standard - JavaScript Style Guide](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard)[![ES6](https://github.com/MarioTerron/logo-images/blob/master/logos/es6.png)](http://www.ecma-international.org/ecma-262/6.0/)[![babel](https://raw.githubusercontent.com/ddmarin94/React-Webpack-Github/master/img/babel.png)](https://webpack.github.io/)[![npm](https://github.com/MarioTerron/logo-images/blob/master/logos/npm.png)](https://www.npmjs.com/)[![React](https://github.com/FransLopez/logo-images/blob/master/logos/react.png)](https://facebook.github.io/react/)[![Ant-Design](https://github.com/jalbertsr/logo-badge-images/blob/master/img/rsz_ant-design.png?raw=true)](https://ant.design/) 2 | [![flow](https://github.com/jalbertsr/logo-badge-images/blob/master/img/rsz_2flow.png?raw=true)](https://flow.org/) 3 | 4 | --- 5 | 6 | 7 | # [React-movie-finder](http://react-movie-finder.surge.sh) 8 | 9 | 10 | Simple single web page application made with [create-react-app](https://github.com/facebookincubator/create-react-app) to experiment with **react routing v4** and [Ant-design](https://github.com/ant-design/ant-design/) an UI design language and React-based implementation. 11 | 12 | ### Screenshots: 13 | 14 | 15 | ![Screenshot](https://i.gyazo.com/f6885fbab9e50982174632cd204ca7b8.png) 16 | 17 | 18 | ![Screenshot](https://i.gyazo.com/3ceadcf9f354830157419b99fb2ea487.png) 19 | 20 | 21 | ###### Filter films: 22 | 23 | ![Gif](https://i.imgur.com/Sdd40Li.gif) 24 | 25 | 26 | 27 | 28 | This application relies on external API service to get films information: 29 | 30 | * [TheMovieDB API](https://developers.themoviedb.org/3) 31 | 32 | --- 33 | 34 | ###### TODO: 35 | 36 | - Mobile Responsive 37 | - Add propTypes validation 38 | - Experiment with HashRouter, withRouter, ... 39 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "movies-finder-react", 3 | "version": "0.1.0", 4 | "private": true, 5 | "homepage": "http://react-movie-finder.surge.sh", 6 | "dependencies": { 7 | "antd": "^2.13.6", 8 | "axios": "^0.16.2", 9 | "flow": "^0.2.3", 10 | "prop-types": "^15.6.0", 11 | "react": "^16.0.0", 12 | "react-dom": "^16.0.0", 13 | "react-router-dom": "^4.2.2", 14 | "react-scripts": "1.0.14", 15 | "react-youtube": "^7.5.0", 16 | "uuid": "^3.1.0" 17 | }, 18 | "scripts": { 19 | "start": "react-scripts start", 20 | "build": "react-scripts build", 21 | "test": "react-scripts test --env=jsdom", 22 | "eject": "react-scripts eject", 23 | "typecheck": "flow check src" 24 | }, 25 | "devDependencies": {} 26 | } 27 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jalbertsr/react-movies-app/93710328a27bf004f59716642e9e46f51adf3c5d/public/favicon.ico -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 11 | 12 | 13 | 14 | 23 | React Movies 24 | 25 | 26 | 29 |
Loading ....
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": "192x192", 8 | "type": "image/png" 9 | } 10 | ], 11 | "start_url": "./index.html", 12 | "display": "standalone", 13 | "theme_color": "#000000", 14 | "background_color": "#ffffff" 15 | } 16 | -------------------------------------------------------------------------------- /src/App.js: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import Navbar from './Components/Navbar/Navbar' 3 | import Routes from './Components/Routes/Routes' 4 | 5 | export default class App extends Component { 6 | render () { 7 | return ( 8 |
9 | 10 | 11 |
12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/Components/CardTemplate/CardTemplate.css: -------------------------------------------------------------------------------- 1 | .custom-image img { 2 | display: block; 3 | } 4 | .custom-card { 5 | padding: 10px 16px; 6 | } 7 | .custom-card p { 8 | color: #999; 9 | } -------------------------------------------------------------------------------- /src/Components/CardTemplate/CardTemplate.jsx: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React from 'react' 3 | import { Link } from 'react-router-dom' 4 | import { Card } from 'antd' 5 | 6 | type Props = { 7 | name: number, 8 | date: string, 9 | vote: number, 10 | image: number, 11 | id: number 12 | } 13 | 14 | const CardTamplate = ({ name, date, vote, image, id }: Props) => ( 15 | 16 | 17 |
18 | {name} 19 |
20 |
21 |

{name}

22 |

{`Date: ${date} || Votes: ${vote}`}

23 |
24 |
25 | 26 | ) 27 | 28 | export default CardTamplate 29 | -------------------------------------------------------------------------------- /src/Components/Home/Home.css: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/jalbertsr/react-movies-app/93710328a27bf004f59716642e9e46f51adf3c5d/src/Components/Home/Home.css -------------------------------------------------------------------------------- /src/Components/Home/Home.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import SearchForm from '../SearchForm/SearchForm' 3 | import ListTable from '../Table/Table' 4 | 5 | const Home = () => ( 6 |
7 | 8 | 9 |
10 | ) 11 | 12 | export default Home 13 | -------------------------------------------------------------------------------- /src/Components/Movie/Movie.css: -------------------------------------------------------------------------------- 1 | img { 2 | margin-top: 4%; 3 | } 4 | 5 | h1 { 6 | margin-top: 2%; 7 | } 8 | 9 | hr { 10 | display: block; 11 | height: 1px; 12 | border: 0; 13 | border-top: 1px solid #ccc; 14 | margin: 1em 0; 15 | padding: 0; 16 | } 17 | 18 | .rate { 19 | margin-top: 1%; 20 | display: block; 21 | } 22 | 23 | .genere { 24 | margin-top: 1%; 25 | } 26 | 27 | .genereTitle { 28 | margin-right: 1%; 29 | } 30 | 31 | .trailer { 32 | margin-bottom: 1%; 33 | } -------------------------------------------------------------------------------- /src/Components/Movie/Movie.jsx: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import React, { Component } from 'react' 3 | import { Row, Col, Rate, Tag } from 'antd' 4 | import YouTube from 'react-youtube' 5 | 6 | import Utils from '../../Services/utilsService' 7 | import Api from '../../Services/dataService' 8 | 9 | import './Movie.css' 10 | 11 | type State = { 12 | name: number, 13 | description: string, 14 | urlImage: string, 15 | stars: number, 16 | genres: , 17 | release_date: string, 18 | videoId: number 19 | } 20 | 21 | export default class Movie extends Component { 22 | constructor (props) { 23 | super(prosp) 24 | 25 | this.state = { 26 | name: '', 27 | description: '', 28 | urlImage: '', 29 | stars: 0, 30 | genres: [], 31 | release_date: '', 32 | videoId: 0 33 | } 34 | } 35 | 36 | componentDidMount () { 37 | const idFilm = parseInt(this.props.match.params.id, 10) 38 | Api.getMovieById(idFilm) 39 | .then(data => { 40 | console.log('dataApi', data) 41 | this.setState({ 42 | urlImage: data.poster_path, 43 | name: data.title, 44 | stars: data.vote_average / 2, 45 | description: data.overview, 46 | genres: (data.genres: Array), 47 | release_date: data.release_date, 48 | videoId: data.videos.results['0'].key 49 | }) 50 | }) 51 | } 52 | 53 | render () { 54 | return ( 55 | 56 | 57 | {this.state.name} 58 | 59 | 60 |

{this.state.name}

61 |
62 | Description: 63 |

{this.state.description}

64 |
65 |
66 | 67 | Generes: 68 | 69 | {this.state.genres.map(genere => {genere.name})} 70 |
71 | 72 |
73 |
74 | Trailer: 75 |
76 | 77 | 78 |
79 | ) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /src/Components/Navbar/Navbar.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Link } from 'react-router-dom' 3 | 4 | import 'antd/dist/antd.css' 5 | import { Menu, Icon } from 'antd' 6 | 7 | export default function Navbar () { 8 | return ( 9 | 10 | 11 | 12 | Home 13 | 14 | 15 | 16 | 17 | Popular 18 | 19 | 20 | 21 | 22 | Up Coming 23 | 24 | 25 | 26 | 27 | Now Playing 28 | 29 | 30 | 31 | 32 | Top Rated 33 | 34 | 35 | 36 | ) 37 | } 38 | -------------------------------------------------------------------------------- /src/Components/Routes/Routes.jsx: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import { Switch, Route } from 'react-router-dom' 3 | 4 | import Home from '../Home/Home' 5 | import Movie from '../Movie/Movie' 6 | import Showfilms from '../Showfilms/Showfilms' 7 | 8 | const Routes = () => ( 9 | 10 | 11 | ( 13 | )} /> 14 | ( 16 | )} /> 17 | ( 19 | )} /> 20 | ( 22 | )} /> 23 | ( 25 | )} /> 26 | 27 | 28 | ) 29 | 30 | export default Routes 31 | -------------------------------------------------------------------------------- /src/Components/SearchForm/SearchForm.css: -------------------------------------------------------------------------------- 1 | .input{ 2 | margin-top: 10%; 3 | margin-bottom: 12%; 4 | margin-right: 1%; 5 | width: 80%; 6 | } -------------------------------------------------------------------------------- /src/Components/SearchForm/SearchForm.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Input, Col, Row, Button } from 'antd' 3 | import { Redirect } from 'react-router-dom' 4 | import './SearchForm.css' 5 | 6 | export default class SearchForm extends Component { 7 | constructor (props) { 8 | super(props) 9 | 10 | this.state = { 11 | value: '', 12 | fireRedirect: false 13 | } 14 | } 15 | 16 | handleChange = (e) => { 17 | this.setState({ value: e.target.value }) 18 | } 19 | 20 | handleSubmit = (e) => { 21 | e.preventDefault() 22 | this.setState({ fireRedirect: true }) 23 | } 24 | 25 | render () { 26 | const { fireRedirect, value: query } = this.state 27 | return ( 28 | 29 | 30 |
31 | 32 | 33 |
34 | 35 | { 36 | fireRedirect && query && 37 | 38 | } 39 |
40 | ) 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Components/Showfilms/Showfilms.css: -------------------------------------------------------------------------------- 1 | .title{ 2 | margin-top: 10%; 3 | margin-bottom: 12%; 4 | text-align: center; 5 | font-family: 'Asap', sans-serif; 6 | font-size: 5em; 7 | } -------------------------------------------------------------------------------- /src/Components/Showfilms/Showfilms.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import CardTemplate from '../CardTemplate/CardTemplate' 3 | import Api from '../../Services/dataService.js' 4 | import { Row, Col } from 'antd' 5 | import uuidv4 from 'uuid/v4' 6 | import './Showfilms.css' 7 | 8 | export default class Showfilms extends Component { 9 | constructor (props) { 10 | super(props) 11 | this.state = { 12 | results: [] 13 | } 14 | } 15 | 16 | handleApiCall (props) { 17 | if (props.match.params.query) { 18 | Api.getSearch(props.match.params.query) 19 | .then(data => { 20 | this.setState({ 21 | results: data.results 22 | }) 23 | }) 24 | } else { 25 | Api.getMovies(props.category) 26 | .then(data => { 27 | this.setState({ 28 | results: data.results 29 | }) 30 | }) 31 | } 32 | } 33 | 34 | componentWillReceiveProps (nextProps) { 35 | this.handleApiCall(nextProps) 36 | } 37 | 38 | componentDidMount () { 39 | this.handleApiCall(this.props) 40 | } 41 | 42 | render () { 43 | return ( 44 |
45 | 46 | 47 |

{ this.props.currentPage }

48 | 49 |
50 | 51 | { 52 | this.state.results.map(film => { 53 | return ( 54 | 55 | 62 | 63 | ) 64 | }) 65 | } 66 | 67 |
68 | ) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /src/Components/Table/Table.css: -------------------------------------------------------------------------------- 1 | .custom-filter-dropdown { 2 | padding: 8px; 3 | border-radius: 6px; 4 | background: #fff; 5 | box-shadow: 0 1px 6px rgba(0, 0, 0, .2); 6 | } 7 | 8 | .custom-filter-dropdown input { 9 | width: 130px; 10 | margin-right: 8px; 11 | } 12 | 13 | .highlight { 14 | color: #f50; 15 | } -------------------------------------------------------------------------------- /src/Components/Table/Table.jsx: -------------------------------------------------------------------------------- 1 | import React, { Component } from 'react' 2 | import { Table, Col, Row } from 'antd' 3 | import uuidv4 from 'uuid/v4' 4 | import Api from '../../Services/dataService' 5 | import Utils from '../../Services/utilsService' 6 | 7 | export default class ListTable extends Component { 8 | constructor () { 9 | super() 10 | this.state = { 11 | data: [] 12 | } 13 | } 14 | 15 | componentDidMount () { 16 | Api.getMostVoted() 17 | .then(info => { 18 | this.setState({ 19 | data: info.results.map(movie => ({ 20 | key: uuidv4(), 21 | title: movie.title, 22 | popularity: movie.popularity, 23 | genres: movie.genre_ids.reduce((acc, id) => (acc += Utils.getGenres(id) + ' '), '') 24 | })) 25 | }) 26 | }) 27 | } 28 | 29 | render () { 30 | const columns = [{ 31 | title: 'Title', 32 | dataIndex: 'title', 33 | onFilter: (value, record) => record.name.indexOf(value) === 0, 34 | sorter: (a, b) => a.title.length - b.title.length 35 | }, { 36 | title: 'Popularity', 37 | dataIndex: 'popularity', 38 | sorter: (a, b) => b.popularity - a.popularity 39 | }, { 40 | title: 'Genres', 41 | dataIndex: 'genres', 42 | filters: Utils.getFilter(), 43 | filterMultiple: true, 44 | onFilter: (value, record) => record.genres.indexOf(value) === 0, 45 | sorter: (a, b) => a.genres.length - b.genres.length 46 | }] 47 | 48 | return ( 49 | 50 | 51 | 52 | 53 | 54 | ) 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Services/dataService.js: -------------------------------------------------------------------------------- 1 | /* @flow */ 2 | import axios from 'axios' 3 | 4 | const apiKey: string = '8d181bcb5e80a929053da01f6921e4a9' 5 | 6 | export default { 7 | getMovies: (category: string) => { 8 | const url = `https://api.themoviedb.org/3/movie/${category}?api_key=${apiKey}&language=en-US&page=1` 9 | return axios.get(url).then(info => info.data) 10 | }, 11 | getSearch: (query: string) => { 12 | const url = `https://api.themoviedb.org/3/search/movie?query=${query}&api_key=${apiKey}` 13 | return axios.get(url).then(info => info.data) 14 | }, 15 | getMovieById: (movieId: number) => { 16 | const url = `https://api.themoviedb.org/3/movie/${movieId}?api_key=${apiKey}&append_to_response=videos` 17 | return axios.get(url).then(info => info.data) 18 | }, 19 | getMostVoted: () => { 20 | const url = `https://api.themoviedb.org/3/discover/movie?api_key=${apiKey}&language=en-US&sort_by=vote_average.asc&include_adult=true&include_video=false&page=1` 21 | return axios.get(url).then(info => info.data) 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Services/utilsService.js: -------------------------------------------------------------------------------- 1 | const tagCategories = [ 2 | 'pink', 3 | 'red', 4 | 'orange', 5 | 'green', 6 | 'cyan', 7 | 'blue', 8 | 'purple', 9 | 'black', 10 | 'yellow', 11 | 'brown', 12 | 'coral', 13 | 'turquoise', 14 | 'gold' 15 | ] 16 | const genresFilter = [{ 17 | text: 'Action', 18 | value: 'Action' 19 | }, { 20 | text: 'Adventure', 21 | value: 'Adventure' 22 | }, { 23 | text: 'Animation', 24 | value: 'Animation' 25 | }, { 26 | text: 'Comedy', 27 | value: 'Comedy' 28 | }, { 29 | text: 'Crime', 30 | value: 'Crime' 31 | }, { 32 | text: 'Documentary', 33 | value: 'Documentary' 34 | }, { 35 | text: 'Drama', 36 | value: 'Drama' 37 | }, { 38 | text: 'Family', 39 | value: 'Family' 40 | }, { 41 | text: 'Fantasy', 42 | value: 'Fantasy' 43 | }, { 44 | text: 'History', 45 | value: 'History' 46 | }, { 47 | text: 'Horror', 48 | value: 'Horror' 49 | }, { 50 | text: 'Music', 51 | value: 'Music' 52 | }, { 53 | text: 'Mystery', 54 | value: 'Mystery' 55 | }, { 56 | text: 'Romance', 57 | value: 'Romance' 58 | }, { 59 | text: 'Science Fiction', 60 | value: 'Science Fiction' 61 | }, { 62 | text: 'TV Movie', 63 | value: 'TV Movie' 64 | }, { 65 | text: 'Thriller', 66 | value: 'Thriller' 67 | }, { 68 | text: 'War', 69 | value: 'War' 70 | }, { 71 | text: 'Western', 72 | value: 'Western' 73 | }] 74 | 75 | const dictGenres = [ 76 | { 77 | 'id': 28, 78 | 'name': 'Action' 79 | }, 80 | { 81 | 'id': 12, 82 | 'name': 'Adventure' 83 | }, 84 | { 85 | 'id': 16, 86 | 'name': 'Animation' 87 | }, 88 | { 89 | 'id': 35, 90 | 'name': 'Comedy' 91 | }, 92 | { 93 | 'id': 80, 94 | 'name': 'Crime' 95 | }, 96 | { 97 | 'id': 99, 98 | 'name': 'Documentary' 99 | }, 100 | { 101 | 'id': 18, 102 | 'name': 'Drama' 103 | }, 104 | { 105 | 'id': 10751, 106 | 'name': 'Family' 107 | }, 108 | { 109 | 'id': 14, 110 | 'name': 'Fantasy' 111 | }, 112 | { 113 | 'id': 36, 114 | 'name': 'History' 115 | }, 116 | { 117 | 'id': 27, 118 | 'name': 'Horror' 119 | }, 120 | { 121 | 'id': 10402, 122 | 'name': 'Music' 123 | }, 124 | { 125 | 'id': 9648, 126 | 'name': 'Mystery' 127 | }, 128 | { 129 | 'id': 10749, 130 | 'name': 'Romance' 131 | }, 132 | { 133 | 'id': 878, 134 | 'name': 'Science Fiction' 135 | }, 136 | { 137 | 'id': 10770, 138 | 'name': 'TV Movie' 139 | }, 140 | { 141 | 'id': 53, 142 | 'name': 'Thriller' 143 | }, 144 | { 145 | 'id': 10752, 146 | 'name': 'War' 147 | }, 148 | { 149 | 'id': 37, 150 | 'name': 'Western' 151 | } 152 | ] 153 | 154 | export default { 155 | randomColor: () => tagCategories[Math.floor(Math.random() * (tagCategories.length + 1))], 156 | getGenres: (id) => dictGenres.reduce((acc, genre) => (id === genre.id) ? (acc += genre.name) : acc, ''), 157 | getFilter: () => genresFilter 158 | } 159 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import React from 'react' 2 | import ReactDOM from 'react-dom' 3 | import { BrowserRouter } from 'react-router-dom' 4 | import { LocaleProvider } from 'antd' 5 | import enUS from 'antd/lib/locale-provider/en_US' 6 | import App from './App' 7 | 8 | ReactDOM.render( 9 | 10 | 11 | 12 | 13 | , 14 | document.getElementById('root') 15 | ) 16 | --------------------------------------------------------------------------------