├── .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 | [](http://www.w3.org/)[](https://github.com/feross/standard)[](http://www.ecma-international.org/ecma-262/6.0/)[](https://webpack.github.io/)[](https://www.npmjs.com/)[](https://facebook.github.io/react/)[](https://ant.design/)
2 | [](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 | 
16 |
17 |
18 | 
19 |
20 |
21 | ###### Filter films:
22 |
23 | 
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 |

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 |
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 |
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 |
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 |
--------------------------------------------------------------------------------