A hotel booking application in React. Homework for the MigraCode React module

Bookings Search page

Installation

  1. Follow the instructions to fork & clone the GitHub repo
  2. Install the dependencies by running npm install
  3. Launch the server using npm start
  4. It should automatically open http://localhost:3000/ in your browser

Exercises

Lesson 1

1. Extract the search button in its own component

Instructions: Extract the search <button> from the src/Search.js file to be its own separate component. You can name it SearchButton. Import and use this new component in src/Search.js.

Test: The search button should still render on the page.

2. Extract the header in its own component

Instructions: Extract the <header> from the src/App.js file to be its own separate component called Heading. Make sure that you import and render the <Heading /> component within src/App.js. In the Heading component, render the hotel's logo in an <img> (you can use https://image.flaticon.com/icons/svg/139/139899.svg or find your own image URL). You can adjust the CSS by editing src/App.css to make your Heading looks better if necessary.

Test: The header should be displayed with a logo on the page.

3. Create and use a new component to show info cards

Instructions: In src/App.js, above the <Bookings /> component add a new component called TouristInfoCards which shows 3 cards. A card is a common user interface pattern with an image at the top and some related text underneath. The cards must link to peoplemakeglasgow.com, visitmanchester.com and visitlondon.com. The cards should contain the name of the city and an image of the city. Here is an example of what an info card should look like:

Info Card

Hint: Use the same className as the example below to benefit from Bootstrap library which is already imported for you in the project. Use the JSX code below as an example of one card (note that in JSX, you'll need to use className instead of class):

<div className="card">
	<img src="..." className="card-img-top" />
	<div className="card-body">
		<a href="#" className="btn btn-primary">Go somewhere</a>
	</div>
</div>

Test: 3 info cards should be displayed on the page for each city (Glasgow, Manchester, London). Each card should link to the correct website.

4. Create a Footer component

Instructions: Create a <Footer /> component which should be rendered at the bottom of the page. Pass the following array as a prop to this component: ["123 Fake Street, London, E1 4UD", "hello@fakehotel.com", "0123 456789"]. Inside the component, use the data you passed as a prop to render a <ul> list with each item of the array displayed as a <li>.

Hint: The .map() method will by useful.

Test: The footer should render at the bottom of the page with each address property displayed as a list item.

5. Create a table to show hotel bookings

Instructions: Create a <SearchResults /> component that shows hotel bookings in a <table> element. Each booking will have an id, title, first name, surname, email, room id, check in date and check out date. You can make up data in the <SearchResults /> component to show in the table. Then show <SearchResults /> component within the <Bookings /> component that is provided. Be sure to split out your components into small well-named components, similar to the method used in exercise 1.

Hint: You will find some useful <table> examples in the Bootstrap documentation for tables.

Test: A table should render with a column for each booking attribute. The table can show more than one booking. The bookings that are displayed can be made up and hardcoded for now.

6. Show more bookings in the table

Instructions: Instead of using your hard-coded data in the <SearchResults /> component, load data from the src/data/fakeBookings.json file in the <Bookings /> component and pass it as a prop to <SearchResults />. All the bookings in src/data/fakeBookings.json should now be displayed in your table.

Hint: Look in the <Bookings /> component for how to import data from a JSON file and by assigning data to a component as props you are adding properties to it and you can access it as such

Test: All the bookings in the file src/data/fakeBookings.json should be displayed in your table.

7. Calculate and show the number of nights for each booking

Instructions: Add another column to your <SearchResults /> table which shows the number of nights a guest is staying.

Hint: Try installing the moment.js library (you'll need to install it with npm install moment --save) and using the .diff() method to compare dates, you may need to declare variables for the dates and declare the "unit of measurement" without leaving spaces in the ""

Test: Each booking in your table should show the number of nights in a separate column. For example, Mr John Doe has a booking for 2 nights.

Lesson 2

8. Render the Restaurant component

Instructions: Within the src/App.js file, render the <Restaurant /> component (that is provided for you in src/Restaurant.js) underneath the <Bookings /> component.

Test: The restaurant orders should render on the page.

9. Preparing to add more pizzas

Instructions: At the moment, the number of pizzas a guest can order is static and set to 0, even if they click on the 'Add' button. We will change that in the following to let a guest add more pizzas to their order. First, declare a new state variable orders along with the function to set the orders state setOrders. The initial value of the orders state should be 0. Use the new orders variable instead of the pizzas variable (that you can now delete).

Hint: You need to use the React function useState to create a state variable. Remember to import the function at the top with import React, {useState} from "react"; also remember to use the rules of destructuring.

Test: Verify the number of ordered pizzas it still 0 on the screen.

10. Add more pizzas

Instructions: In the <Restaurant /> component, create a new function named orderOne. The orderOne function doesn't take any parameters and should use the setOrders function to increment the orders state variable by 1. Then, add a onClick handler to the Add <button> that calls the orderOne function when it's being clicked.

Test: Try to click on the Add button a few times and verify that the number of pizzas increases accordingly, also in the orderOne function we need both elements of the usestate.

11. Extract the Add button to its own component

Instructions: Extract the <button> currently in the <Restaurant /> component to a new component named RestaurantButton. Pass the orderOne function as a prop to the <RestaurantButton /> component and use this prop in the onClick handler.

Tips Functions can be saved as a props by giving it a name and then used as property of PROPS.

Test: Clicking the button should still increment the number of pizzas.

12. Extract pizza order to its own Order component

Instructions: Extract the <li> containing "Pizzas" from the <Restaurant /> component to a new component named Order. Also, move the declaration of the orders state and the orderOne function from the <Restaurant /> component to the new <Order /> component. Use the <Order /> component in the <ul> list of the <Restaurant /> component.

Test: Make sure the pizza order is still rendered on the page and that clicking on the "Add" button still increments the number of orders, we extract a part of the page/front by defining it as a new.

13. Render more orders

Instructions: Pass a new prop named orderType to the <Order /> component with the value "Pizzas". Then render the orderType prop instead of "Pizzas" in the <Order /> component. Make sure that "Pizzas" is still displayed on the screen. In the <ul> list of the <Restaurant /> component, render 2 others <Order /> components but this time pass different values for the orderType prop: "Salads" and "Chocolate cake".

Test: For each order, the number of items can be incremented independently. Verify that you are able to explain what is happening also props can contain strings and the way to use them is the same.

14. Passing bookings from a state variable

Instructions: In the <Bookings /> component, declare a new state bookings with the corresponding setter function setBookings to hold the FakeBookings data. Instead of passing FakeBookings directly to the <SearchResults /> component, pass the new bookings state variable.

Hint: The new bookings state should be initialised with the FakeBookings, remember that an useState can contain an imported file.

Test: Check that the bookings are still rendered correctly in the page.

15. Highlight booking row when clicked

Instructions: Within the <SearchResults /> component or its child components, add an onClick handler to each row in the table (hint: on the <tr> element). When clicked, the row is "selected" and highlighted with a different colour. When clicked again, the row is unselected and the coloured highlighting is removed.

Hint: Use a new state variable for each row to record if the row is selected or not, and use this value to set a class to the className prop of the row, the className value can contain an usestate and change its value depending on it

Test: Verify that each row of your table can be highlighted (on and off) independently when being clicked.

Lesson 3

16. Load bookings remotely

Instructions: Instead of getting the existing bookings from the file data/fakeBookings.json, we will get and load the bookings from a remote API. In the <Bookings /> component, use the React function useEffect to console.log() some text only when the page first renders on the screen. Verify that when you refresh the page, the text appears once in the console. Then, in the useEffect function, use the fetch() function to get data from https://cyf-react.glitch.me.

Hints:

  • Replace FakeBookings in the bookings state and initialise it with [] (because we haven't fetched any results yet!)
  • After calling the fetch() function within useEffect() method ', use .then() to handle the response. Try looking at your Pokemon app that you worked on in class for an example
  • When the response comes back, use setBookings within useEffect() method to update the results

Test: Verify the customers data are still displayed correctly in the table.

17. Storing the search input in a state

Instructions: In the following, we will implement the functionality to search for a customer name given the text typed into the customer name field. In the src/Search.js file, declare a new state variable named searchInput with the corresponding setter function setSearchInput (hint: use the React function useState). The initial value of the searchInput variable can be an empty string - (""). Add a value property to the <input> tag, that is set to the new searchInput state variable. Create a new function handleSearchInput taking an event parameter. This function should use the setSearchInput function to update the state variable searchInput with what the user typed in the input field. Finally, add a onChange prop to the <input> tag that is set to the function handleSearchInput. Use console.log() to output the value received in the handleSearchInput function.

Hint: Use event.target.value to get the input value. Read more about - reacting on changes: https://syllabus.migracode.org/course-content/react/week-2 to use it within <input> tag.

Test: In the developer console, check that everything you type in the search input is printed successively for each new character you enter.

18. Triggering search when submitting the form

Instructions: Still in the <Search /> component, add a onSubmit handler to the <form> tag. When the form is submitted (try clicking the search button), create a new function handleSubmit to get the value of the state searchInput and pass it as a parameter to the search prop function that has been provided for you (the search prop is passed from the <Bookings /> component).

Note: Also your submit handler should take an event parameter and add the line event.preventDefault() to prevent the browser to implicitely submit the form).

Test: Look in the console, you should see the text that is typed in the search input field when submitting the form.

19. Implementing the search functionality

Instructions: Still in the <Bookings /> component, implement the search method. It must use the searchVal variable (that you just passed from the <Search /> component) to filter the search results. The filter function should return bookings where firstName or surname match searchVal. Once filtered, use the setBookings function to update the results rendered in <SearchResults /> component.

Hint: pass searchVal as a parameter to the search method.

Test: Verify that when you enter an existing first name or surname and submit the form, the results are filtered accordingly in the customers table.

20. Display a customer profile - step 1

Instructions: Add a new column in the table of the <SearchResults /> component and display a <button> for each row. The text of the button should read "Show profile". Then, create a new <CustomerProfile /> component. This component should be rendered next to the table in the <SearchResults /> component. This component should receive one prop id. When clicking on a "Show profile" button for a given row, the component <CustomerProfile /> should display the text "Customer Profile", where is the id of the selected customer. Initially, the <CustomerProfile /> component doesn't show anything.

Hint: You need to record the selected customer id after clicking on a "Show profile" button, use onClick method. Pass props as a parameter to the CustomerProfile. In which component do you think this state should be defined?

Test: When first showing the page, no customer profile is displayed. When clicking the first "Show profile" button of the table, the text "Customer 1 profile" appears. When clickong the second "Show profile" button of the table, the text "Customer 2 profile" appears instead.

21. Display a customer profile - step 2

Instructions: When a "Show profile" button is clicked in the table, fetch the corresponding customer profile from https://cyf-react.glitch.me/customers/<ID> in the <CustomerProfile /> component. A customer profile should show the customer ID, their email, if they are VIP and their phone number in a list.

Hint: You need to use useEffect and the correct dependency array and to fetch customers data from the API every time a "Show profile" button is clicked, render it accordingly. Check the status of the response and pass the result to the json. Inside the html part, add table for the results of fetch. Check the vip status of the user, using boolean.

Test: When you click on a "Show profile" button in the table, the corresponding customer profile is loaded and rendered on the screen.

22. Show a loading message

Instructions: Do you remember in the Homework of the Lesson 2, we fetched the bookings from a remote API. Now show a loading state in <Bookings /> while the data from the server is being fetched. To test this, try loading data from https://cyf-react.glitch.me/delayed, which has a 5 second delay before returning the data. You will need to use another state to record when your application is loading data (this can be a boolean) and display a loading message whenever the application is loading data.

Hint: You need to create a new vairable with the text or an object, that will fill the screen during the loading, add it to the useState(), that will collect the gotten data from the API. Try looking at your Pokemon app that you worked on in class for an example.

Test: A message inviting the user to wait should be displayed on the screen until bookings data can be rendered on the screen. When bookings are rendered, the loading message should be hidden.

23. Show an error message

Instructions: Finally, display an error message in <Bookings /> if there is an HTTP error when fetching data from the server. To test this, try loading data from https://cyf-react.glitch.me/error, which will return a 500 HTTP error.

Hint: Inside the fetch method, use .then with if method to return a message with the error, after not getting the fetched data with the right status. Try looking at your Pokemon app that you worked on in class for an example.

Test: When loading bookings data from the /error endpoint, an error message should be displayed on the screen.

Stretch Goals

24. Create a new booking

Instructions: Add a form with <input>s for each of the booking fields (first name, surname, title, room id, check in date, check out date) to the bottom of the page. Submitting the form adds the booking to the result table. Note that the new booking won't persist if you refresh the page.

Hint: Add onSubmit method to the form, and onChange method to each <input>. Use value for each row to add inserted data. Create a function that will collect the data and send it to the table as props and use it inside the onSubmit method.

Test: When adding a new booking in the form, it should be displayed in the table.

25. Sort table columns

Instructions: Add an onClick handler to the columns of the result table, which sorts the results ascending (A -> Z). Clicking the column again will reverse the sort order to descending (Z -> A).

Hint: Try using the .sort() method with a callback to do custom sorting.

Test: Each column in the table should be clickable to sort results in ascending or descending order.