diff --git a/MRN.png b/MRN.png new file mode 100644 index 0000000..0b18066 Binary files /dev/null and b/MRN.png differ diff --git a/MRNmissing.png b/MRNmissing.png new file mode 100644 index 0000000..cf25a89 Binary files /dev/null and b/MRNmissing.png differ diff --git a/MRNmissing2.png b/MRNmissing2.png new file mode 100644 index 0000000..9cfc3a7 Binary files /dev/null and b/MRNmissing2.png differ diff --git a/MRNmobile.png b/MRNmobile.png new file mode 100644 index 0000000..206ae08 Binary files /dev/null and b/MRNmobile.png differ diff --git a/MRNmobile2.png b/MRNmobile2.png new file mode 100644 index 0000000..1199d61 Binary files /dev/null and b/MRNmobile2.png differ diff --git a/MRNnarrow.png b/MRNnarrow.png new file mode 100644 index 0000000..3429c17 Binary files /dev/null and b/MRNnarrow.png differ diff --git a/MRNred.png b/MRNred.png new file mode 100644 index 0000000..860c35f Binary files /dev/null and b/MRNred.png differ diff --git a/MRNvintage.png b/MRNvintage.png new file mode 100644 index 0000000..ac40f7f Binary files /dev/null and b/MRNvintage.png differ diff --git a/README.md b/README.md index d79a252..2700f4d 100644 --- a/README.md +++ b/README.md @@ -1,50 +1,48 @@ -# React Cinema - -Let's revisit our first project where we built a movie search engine using the Open Movie Database. This time we want to implement it using React. It should be a Single Page App, that is all the functionality should be on a single page, rather switch between multiple pages. - -Before starting draw a plan of your application. What are some of the components you are going to need? Which components will fetch data and how will that data be displayed? Which components should be re-used? Rather than re-implementing your previous solution again have a think about what you have learned in the past week and how you can apply it here. +# React Cinema (aka Mission Impossible 2) ## The brief -We want to create a movie search engine. To power it we will use the [Open Movie Database](http://www.omdbapi.com). It provides access to a huge database of films via an **API**, which stands for **Application Programming Interface**. In short, it is a set of rules and procedures you need to follow to use a remote system. - -To start using the OMDB API you will first need to sign up with them to receive and API key. The key issued to you will allow you 1000 requests per day and you will need to include this key as part of every request. +To create a movie search engine, completing as many of the following tasks, using our newly-aquired REACT skills -To get started, fork and clone this repo. Please submit a pull request after your first commit and push commits regularly. - -You should complete as many of the following tasks as you can. You can use Bootstrap to help you - -- [ ] Create an HTML page which should have a `form` at the top which contains a `text input` and a `submit button`. Below it should have a placeholder for the returned results. -- [ ] Make sure your design is responsive and looks great at different screen widths -- [ ] Use JavaScript to capture the submit event on in your search form, extract the query string from your input and use that to make an API call to the Open Movie Database API to search for films which match the query string. Console.log the results -- [ ] Display the data returned by the API including title, year and poster picture -- [ ] Adjust your layout to create room for a detailed view of movie information -- [ ] Capture clicks on your movie results items and use that information to make another request to the API for detailed movie information. Console.log the returned result -- [ ] Display the detailed movie result in the in the details view you created earlier +## Checklist +- [x] Create an HTML page which should have a `form` at the top which contains a `text input` and a `submit button`. Below it should have a placeholder for the returned results. +- [x] Make sure your design is responsive and looks great at different screen widths +- [x] Use JavaScript to capture the submit event on in your search form, extract the query string from your input and use that to make an API call to the Open Movie Database API to search for films which match the query string. Console.log the results +- [x] Display the data returned by the API including title, year and poster picture +- [x] Adjust your layout to create room for a detailed view of movie information +- [x] Capture clicks on your movie results items and use that information to make another request to the API for detailed movie information. Console.log the returned result +- [x] Display the detailed movie result in the in the details view you created earlier **Your own feature** -- [ ] Implement any feature you would find useful or interesting. Include some info in the `README.md` about what it is. +- [x] Click on 'Rating' radio button to sort from highest rated to lowest rated movie +- [x] Click on 'Year' radio button to sort from newest to oldest movie +- [x] Next page and previous page pagination included **Stretch goals** +- Ran out of time to attempt 'favourites' stretch goals but would like to include at a later date + +## Instructions -- [ ] Let's create a search preview. It should listen for keydown events and submit a search request with current query string. Display the search preview results in an absolute positioned container just below the search box. - Hint: You may want to kick of the searching after you have at least 3 characters typed. Also, you may want to implement a `debounce` function to put a small delay on the search. This will reduce the number of requests you send and may the functionality more efficient. You could write your own function if you want a challenge or use one from a library such as `lodash` +- Open index.html in a browser +- Enter the full or partial title of the movie you wish to search for (results will be loaded 10 at a time on a screen) +- Click >> next to search button to go to next page of results (page number will be displayed as well as back << button) +- Click on Rating radio button to display movies sorted from highest to lowest rated according to IMDB score +- Click on Year radio button to display movies sorted from newest to oldest released +- Click on movie name to be taken to IMDB website for chosen film +- Click on movie poster to retrieve more information about the movie and again to remove the additional information -- [ ] Create a favourites list. It's up to you how you would add items to favourites. You could add a button or otherwise. Display a list of favourites somewhere on your page. +## Golden Globes ## -- [ ] Make the favourites list sortable. Add up and down buttons to your favourites which on click will move the result in relevant in your list +I am pleased with the layout of the website. I think there is a lot of content without being too cluttered or detracting from the posters. I am also happy that this version of the website is much more polished than the last attempt. My html and css skills have improved greatly since then, although it feels as though there was less javascript to write than the last. -- [ ] Save favourites locally using either `cookies` or `localStorage` so that favourites persist in browser after refresh -## Objectives +## Golden Raspberries ## -* I want to see great looking webpages which work well at all screen widths -* Your code should have consistent indentation and sensible naming -* Use lots of concise functions with a clear purpose -* Add code comments where it is not immediately obvious what your code does -* Your code should not throw errors and handle edge cases gracefully +- The sort feature was the last thing to be implemented and was rushed which is why the radio buttons are poorly positioned on the page and do not 'check' as expected. This is a definite bug that should be fixed. Each sort only works once. I suspect this to be down to the 'checking issue'. +- I have realised that if a film title is particularly short, it will wrap next to the film poster rather than beneath it. +- If less than 10 films are displayed, the next button still appears, although an error message will be displayed if the user clicks it and the page will remain unchanged. -## README.md +## Conclusion ## -When finished, include a README.md in your repo. This should explain what the project is, how to run it and how to use it. Someone who is not familiar with the project should be able to look at it and understand what it is and what to do with it. +Although this website is more dynamic and has more features than previous projects, it has a similar layout to the News Reader one so I will be looking to challenge myself and create a different style for my next website. It definitely represents progression though. diff --git a/cine_index.css b/cine_index.css new file mode 100644 index 0000000..1d9c7b9 --- /dev/null +++ b/cine_index.css @@ -0,0 +1,206 @@ +* { + box-sizing: border-box; +} + +html { + margin: auto; + font-family: Haettenschweiler, "Arial Narrow Bold", sans-serif; + background-color: #242033f1; + color: #f2faf2; + text-align: center; +} + +p{ + margin:0px +} + +.App__header{ + display:flex; + flex-direction: row; + flex-wrap:wrap; + + height:130px; + background:url(MRNmobile2.png); + + background-repeat: no-repeat; + background-position: center; + + text-align:center; +} + +input { + width: 80%; + font-size:18px; + padding-left:20px; + background-color: #242033f1; + color:white; + border-radius: 4px; +} + +a { + text-decoration: none; + color: inherit; +} + +.search__form{ + align-content: left; +} + +.search__sort{ + float:left; + margin-top:40px; +} + +h3{ + font-size:15px; +} + +h2{ + font-size: 15px; +} + +.results__body{ + margin-top:10px; +} + +.results__sort{ + align-items:left; +} + +article{ + border:2px solid #fff; + border-radius: 4px; + background-color: #5f597596 +} + +.article__picture{ + display:flex; + flex-direction:row; + flex-wrap:wrap; +} + +#article__main{ + display:flex; + flex-direction:row +} + +.hidden__text{ + margin-left:10px; + margin-right:10px; + font-size:12px; +} + +/*thumnails for mobile*/ +img { + border: 2px solid #000; + border-radius: 4px; + padding: 5px; + margin-left:5px; + width: 150px; + height:auto; +} + +.grow img{ + transition: 1s ease; +} + +.grow img:hover{ + -webkit-transform: scale(1.2); + -ms-transform: scale(1.2); + transform: scale(1.2); + transition: 1s ease; +} + +/*tablet view*/ +@media (min-width: 768px) { + .App__header{ + background-color: hotpink; + height:175px; + background:url(MRNnarrow.png); + background-repeat: no-repeat; + background-position: center; + text-align:center; + } + + p{ + margin:0px; + font-size: 15px; + text-decoration: none; + } + + .hidden__text{ + margin-left:10px; + margin-right:10px; + font-size:18px; + } + + input { + width: 60%; + font-size:28px; + } + + button{ + width: 10%; + height:38px ; + font-size:28px; + border-radius: 4px; + } +} + +@media (min-width: 1400px) { + + .App__header{ + height:175px; + background:url(MRNnarrow.png); + background-repeat: no-repeat; + background-position: center; + text-align:center; + } + + input { + width: 60%; + font-size:28px; + } + + button{ + width: 10%; + height:38px ; + font-size:28px; + } + + .results__body{ + display:flex; + flex:10; + flex-direction:row; + flex-wrap:wrap; + justify-content: center; + } + + article{ + width:20%; + } + .article__picture{ + flex: 2; + font-size:20px; + padding: 10px; + } + + #article__main{ + align-self: center; + flex-wrap: wrap; + justify-content: center; + } + + img { + max-width:100%; + padding: 5px; + width: auto; + height:300px; + } + + .hidden__text{ + margin-left:10px; + } +} + + diff --git a/index.html b/index.html index 44fcd2a..3827cdf 100644 --- a/index.html +++ b/index.html @@ -1,12 +1,17 @@ - - - - Hello World - - -
- - - + + + + + Movies Right Now + + + + + +
+ + + + \ No newline at end of file diff --git a/movieDetail.html b/movieDetail.html new file mode 100644 index 0000000..dcbff18 --- /dev/null +++ b/movieDetail.html @@ -0,0 +1,16 @@ + + + + + + + Movie Detail + + + + + + + + + \ No newline at end of file diff --git a/src/App.js b/src/App.js index 9520f77..0406b8a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,16 +1,96 @@ -import React from 'react'; +import React from "react"; +import Search from "./Search"; +import Results from "./Results"; +// import Info from "./Info"; + +let movieArray = []; class App extends React.Component { - constructor(){ + constructor(props) { super(); + this.state = { + movies: [], + currentMovie: "", + currentInfo: "", + sortBy: "" + + }; + this.sortMovie = this.sortMovie.bind(this) + this.receiver = this.receiver.bind(this); + this.receiveMovie = this.receiveMovie.bind(this); + this.clearResults = this.clearResults.bind(this); + } + + //receiver function to populate movies in state + receiver(results) { + movieArray.unshift(results); + this.setState({ movies: movieArray }); + console.log(this.state.movies) + } + + //clear current results so that more movies can be fetched + clearResults() { + movieArray.length = 0; + } + + //receive user-clicked movie to set more info state variables + receiveMovie(movieID, plot) { + this.setState({ + ["currentMovie"]: movieID, + ["currentInfo"]: plot + }); } - render(){ + //sort movies by imdbRating or Year depending on which radio button was pushed + sortMovie(event) { + event.preventDefault(); + this.setState({ + sortBy: event.target.name + }) + + const sorted = (event.target.name === "Rating") ? this.state.movies.sort((a, b) => { + return b.imdbRating - a.imdbRating + }) : this.state.movies.sort((a, b) => (parseInt(b.Year, 10) - parseInt(a.Year, 10)) + ) + + this.setState({ + sortBy: "" + }) + } + + render() { + // const display = (this.state.currentMovie != "") ? : null; + return ( -
- React cinema app +
+
+ Rating + + Year +
+
+
+ + + + +
- ) + ); } } diff --git a/src/Info.js b/src/Info.js new file mode 100644 index 0000000..134d4c8 --- /dev/null +++ b/src/Info.js @@ -0,0 +1,28 @@ +import React from "react"; + + +class Info extends React.Component { + + constructor() { + super(); + + } + + render() { + + return ( + +

+ {/* {alert (this.props.currentInfo)} */} + {this.props.currentMovie} +
+ {this.props.currentInfo} + {/* Age Rating: {this.props.movie.Rated} Runtime: {this.props.movie.Runtime}
+ IMDB Rating: {this.props.movie.imdbRating}/10 */} +

+ + + ) + }; +} +export default Info; \ No newline at end of file diff --git a/src/Result.js b/src/Result.js new file mode 100644 index 0000000..13b1f7c --- /dev/null +++ b/src/Result.js @@ -0,0 +1,60 @@ +import React from "react"; + +class Result extends React.Component { + constructor() { + super(); + + this.state = { infoHidden: true } + + this.toggleInfo = this.toggleInfo.bind(this); + } + + toggleInfo() { + this.setState({ infoHidden: !this.state.infoHidden }) + } + + render() { + + const movieLinkUrl = `https://imdb.com/title/${this.props.movie.imdbID}`; + const infoText = this.state.infoHidden ? '' : `${this.props.movie.Plot} `; + const ratingText = this.state.infoHidden ? '' : `${this.props.movie.Rated}`; + const textString = this.state.infoHidden ? '' : `\nRated:${ratingText} Runtime: ${this.props.movie.Runtime}` + + return ( + + ); + } +} +export default Result; diff --git a/src/Results.js b/src/Results.js new file mode 100644 index 0000000..1583771 --- /dev/null +++ b/src/Results.js @@ -0,0 +1,24 @@ +import React from "react"; +import Result from "./Result"; + +function Results({ movies }) { + + return ( + +
+ {movies.map(movie => { + + return ( + + ); + })} +
+ ); + + +} + +export default Results; diff --git a/src/Search.js b/src/Search.js new file mode 100644 index 0000000..4c01ae2 --- /dev/null +++ b/src/Search.js @@ -0,0 +1,101 @@ +import React from "react"; + +class Search extends React.Component { + constructor() { + super(); + + this.state = { + searchMovie: "", + pageNum: 1, + nextMovie: "" + }; + + this.handleChange = this.handleChange.bind(this); + this.nextPage = this.nextPage.bind(this); + this.prevPage = this.prevPage.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.fetchMovie = this.fetchMovie.bind(this); + } + + fetchMovie() { + + let searchURL = `http://www.omdbapi.com/?s=${this.state.searchMovie}&page=${this.state.pageNum}&apikey=39d7228f`; + + + // fetch matching film titles but also get more info for each match + + fetch(searchURL) + .then(response => response.json()) + .then(results => results.Search.map(movie => { + + fetch(`http://www.omdbapi.com/?i=${movie.imdbID}&plot=short&apikey=39d7228f`) + .then(response => response.json()) + .then(movieDetails => this.props.receiver(movieDetails)) + })).catch(error => { + console.log("Sorry the following error occurred: ", error) + alert("Sorry not found, try again") + }); + + } + + handleChange(event) { + event.preventDefault(); + this.setState({ + searchMovie: event.target.value, + nextMovie: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.props.clearResults(); + this.fetchMovie(); + this.setState({ + searchMovie: "" + }) + } + + nextPage() { + this.setState({ searchMovie: this.state.nextMovie }); + this.setState({ pageNum: this.state.pageNum + 1 }, () => this.fetchMovie()); + this.props.clearResults(); + + } + + prevPage() { + + this.setState({ pageNum: this.state.pageNum - 1 }, () => this.fetchMovie()) + this.props.clearResults(); + } + + render() { + return ( +
+ + + + + + {this.state.searchMovie !== "" ? "<<" : ""} + {this.state.searchMovie !== "" ? `Page ${this.state.pageNum}` : ""} + {this.state.nextMovie !== "" ? ">>" : ""} + +
+ ); + } +} +export default Search; diff --git a/src/Search_backup.js b/src/Search_backup.js new file mode 100644 index 0000000..c3423f1 --- /dev/null +++ b/src/Search_backup.js @@ -0,0 +1,65 @@ +import React from "react"; + +class Search extends React.Component { + constructor() { + super(); + + this.state = { searchMovie: "" }; + + this.handleChange = this.handleChange.bind(this); + this.handleSubmit = this.handleSubmit.bind(this); + this.fetchMovie = this.fetchMovie.bind(this); + } + + fetchMovie() { + let pageNum = 1; + let searchURL = `http://www.omdbapi.com/?s=${ + this.state.searchMovie + }&page=${pageNum}&apikey=39d7228f`; + // console.log(searchURL); + + fetch(searchURL) + .then(response => response.json()) + .then(movieData => { + this.props.receiver(movieData); + // console.log(movieData); + }); + } + + + handleChange(event) { + event.preventDefault(); + this.setState({ + searchMovie: event.target.value + }); + } + + handleSubmit(event) { + event.preventDefault(); + this.fetchMovie(); + } + + render() { + return ( +
+ + +
+ ); + } +} +export default Search;