diff --git a/package-lock.json b/package-lock.json index 8197e6f22..884ad0497 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,8 +8,10 @@ "name": "react-hotel", "version": "0.1.0", "dependencies": { + "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.10", "react-scripts": "^5.0.1" }, "devDependencies": { @@ -11681,6 +11683,14 @@ "mkdirp": "bin/cmd.js" } }, + "node_modules/moment": { + "version": "2.29.4", + "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz", + "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==", + "engines": { + "node": "*" + } + }, "node_modules/mri": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", @@ -14036,6 +14046,17 @@ "react": "^18.2.0" } }, + "node_modules/react-error-boundary": { + "version": "4.0.10", + "resolved": "https://registry.npmjs.org/react-error-boundary/-/react-error-boundary-4.0.10.tgz", + "integrity": "sha512-pvVKdi77j2OoPHo+p3rorgE43OjDWiqFkaqkJz8sJKK6uf/u8xtzuaVfj5qJ2JnDLIgF1De3zY5AJDijp+LVPA==", + "dependencies": { + "@babel/runtime": "^7.12.5" + }, + "peerDependencies": { + "react": ">=16.13.1" + } + }, "node_modules/react-error-overlay": { "version": "6.0.11", "resolved": "https://registry.npmjs.org/react-error-overlay/-/react-error-overlay-6.0.11.tgz", diff --git a/package.json b/package.json index e3e1562a7..a40fd416c 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,10 @@ "version": "0.1.0", "private": true, "dependencies": { + "moment": "^2.29.4", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-error-boundary": "^4.0.10", "react-scripts": "^5.0.1" }, "scripts": { diff --git a/public/5-stars.png b/public/5-stars.png new file mode 100644 index 000000000..5f1e8dc01 Binary files /dev/null and b/public/5-stars.png differ diff --git a/public/london.webp b/public/london.webp new file mode 100644 index 000000000..2cc253c07 Binary files /dev/null and b/public/london.webp differ diff --git a/public/manchester.webp b/public/manchester.webp new file mode 100644 index 000000000..30845aa20 Binary files /dev/null and b/public/manchester.webp differ diff --git a/public/university-of-glasgow.jpg b/public/university-of-glasgow.jpg new file mode 100644 index 000000000..cf2aec7f8 Binary files /dev/null and b/public/university-of-glasgow.jpg differ diff --git a/src/AddNewBooking.js b/src/AddNewBooking.js new file mode 100644 index 000000000..48c19b1ab --- /dev/null +++ b/src/AddNewBooking.js @@ -0,0 +1,168 @@ +import { useState, useContext } from "react"; +import { BookingForForm } from "./data/BookingForm"; + +function AddNewBooking (props) { + const {newBooking, setNewBooking} = useContext(BookingForForm); + const [validationErrors, setValidationErrors] = useState({}); + + function handleChange(event) { + const updatedUserData = { + ...newBooking, + [event.target.name]: event.target.value, + }; + setValidationErrors(validate(updatedUserData)) + setNewBooking(updatedUserData); + } + + const validate = (values) => { + const errors = {} + const regex = /^[a-z0-9]+@[a-z]+\.[a-z]{2,3}/; + let date = new Date().toJSON().slice(0, 10); + + if (!values.title) { + errors.title = "Title is required"; + } + if (!values.firstName) { + errors.firstName = "First Name is required"; + } + if (!values.surname) { + errors.surname = "Surname is required"; + } + if (!values.email) { + errors.email = "email is required"; + } else if(!regex.test(values.email)){ + errors.email = "INVALID email" + } + if (!values.roomId) { + errors.roomId = "Room Number is required"; + } + if (!values.checkInDate) { + errors.checkInDate = "CheckIn Date is required"; + } + // else if(values.checkInDate < date) { + // errors.checkInDate = "Date can't be in the past" + // } + if (!values.checkOutDate) { + errors.checkOutDate = "CheckOut Date is required"; + } + // else if (values.checkOutDate < date) { + // errors.checkOutDate = "Date can't be in the past"; + // } + return errors; + } + + function submitForm(event) { + event.preventDefault(); + if(newBooking.title === ""){ + alert("Fill in the Form!") + } else { + + fetch("https://booking-server-98w3.onrender.com/bookings", { + method: "PUT", // or 'PUT' + headers: { + "Content-Type": "application/json", + }, + body: JSON.stringify(newBooking), + }) + .then((response) => response.json()) + .then((data) => { + console.log(data), props.adding(); + }); + } + + } + return ( +
+
+ + + + + + + +
+ +
+ ); +} + +export default AddNewBooking; \ No newline at end of file diff --git a/src/App.css b/src/App.css index 05fe2d52e..87715c9f4 100644 --- a/src/App.css +++ b/src/App.css @@ -7,14 +7,21 @@ height: 80px; } +.Logo-header { + width: 150px; + margin-right: 200px; +} + .App-header { background-color: #222; - height: 50px; + display: flex; + align-items: center; + height: auto; padding: 20px; color: white; text-align: left; font-family: Arial, Helvetica, sans-serif; - font-size: 1em; + font-size: 2em; font-weight: bold; } @@ -27,6 +34,12 @@ font-size: large; } +.InfoCards { + display: flex; + flex-wrap: wrap; + justify-content: center; +} + @keyframes App-logo-spin { from { transform: rotate(0deg); @@ -54,4 +67,44 @@ tr { .card { width: 18rem; + margin: 15px; + padding: 5px; + text-align: justify; +} + +h3 { + font-weight: 800; + text-align: center; + margin: 5px; +} + +.Highlighted { + background-color: aquamarine; } + +.formDiv { + display: flex; + align-items: flex-end; + border: 5px dashed black; + margin: 10px; + padding: 20px 25px 20px 25px; + justify-content: center; +} + +form { + display: grid; + grid-template-columns: 1fr 1fr 1fr; + column-gap: 20px; +} + +.dates { + grid-row-start: 3; +} + +.form-label { + margin-right: 10px; +} + +.errorMsg { + color: red; +} \ No newline at end of file diff --git a/src/App.js b/src/App.js index 953c98560..c5c089046 100644 --- a/src/App.js +++ b/src/App.js @@ -1,13 +1,71 @@ import React from "react"; - import Bookings from "./Bookings"; +// import SearchResults from "./SearchResults"; +import Restaurant from "./Restaurant"; + import "./App.css"; +const Heading = () => ( +
+ +
CYF Hotel
+
+); + +const TouristInfoCards = (props) => ( +
+ +

{props.name}

+

+ Pellentesque habitant morbi tristique senectus et netus et malesuada fames + ac turpis egestas. Ut congue volutpat erat. Suspendisse nec vestibulum + risus, at sollicitudin lectus. Sed rhoncus odio ac magna ultrices, in + consectetur tortor convallis. Sed sed dui ante. Etiam efficitur lacus + velit, eget placerat massa rutrum ut. Integer posuere elit eget imperdiet + molestie. +

+
+ + More information + +
+
+); + +const Footer = (props) => ( + +) + const App = () => { return (
-
CYF Hotel
+ +
+ + + +
+ + +
); }; diff --git a/src/Bookings.js b/src/Bookings.js index e0d911b13..b6bb585b4 100644 --- a/src/Bookings.js +++ b/src/Bookings.js @@ -1,18 +1,76 @@ -import React from "react"; +import React, { useEffect, useState } from "react"; import Search from "./Search.js"; -// import SearchResults from "./SearchResults.js"; -// import FakeBookings from "./data/fakeBookings.json"; +import SearchResults from "./SearchResults.js"; +import { ErrorBoundary } from "react-error-boundary"; +import AddNewBooking from "./AddNewBooking.js"; +import { BookingForForm } from "./data/BookingForm.js"; + const Bookings = () => { - const search = searchVal => { - console.info("TO DO!", searchVal); - }; + let [bookings, setBookings] = useState([]); + let [searchResults, setSearchResults] = useState(bookings); + let [loading, setLoading] = useState(false); + let [updated, setUpdated] = useState(false); + const [newBooking, setNewBooking] = useState( + { + id: "", + title: "", + firstName: "", + surname: "", + email: "", + roomId: "", + checkInDate: "", + checkOutDate: "", + } + ); + + useEffect(() => { + setLoading(true); + fetch("https://booking-server-98w3.onrender.com/bookings") + .then((response) => response.json()) + .then((data) => { + console.log(data); + setBookings(data); + setSearchResults(data); + setLoading(false); + });}, [updated]); + + const search = (searchVal) => { + console.info("TO DO!", searchVal); + searchVal !== "" + ? setSearchResults( + bookings.filter((booking) => + booking.firstName + .toLowerCase() + .includes( + searchVal.toLowerCase()) || + booking.surname.toLowerCase().includes(searchVal.toLowerCase() + ) + ) + ) + : setSearchResults(bookings); + }; + + const addBooking = () => { + console.log("Added") + setUpdated(!updated) + }; + + const deleteRaw = () => { + setUpdated(!updated); + }; - return ( + return (
- {/* */} + + Unable to load data, sorry....
}> + {loading && "Loading......"} + {!loading && } + + +
); diff --git a/src/CustomerProfile.js b/src/CustomerProfile.js new file mode 100644 index 000000000..794ad366e --- /dev/null +++ b/src/CustomerProfile.js @@ -0,0 +1,38 @@ +import { useEffect, useState } from "react"; + +function CustomerProfile (props) { + let [userInfo, setUserInfo] = useState(""); + useEffect(() => { + if(props.id) { + fetch(`https://cyf-react.glitch.me/customers/${props.id}`) + .then((response) => response.json()) + .then((data) => setUserInfo(data)); + }}, [props.id]); + + return userInfo ? + ( +
+ +
+ ) + : null +} + +export default CustomerProfile; \ No newline at end of file diff --git a/src/Restaurant.js b/src/Restaurant.js index ecb2b43a2..d66a7ae80 100644 --- a/src/Restaurant.js +++ b/src/Restaurant.js @@ -1,14 +1,31 @@ import React from "react"; +import { useState } from "react"; + +function RestaurantButton (props) { + return +} + +const Order = (props) => { + let [order, setOrders] = useState(0); + function orderOne() { + setOrders(order + 1); + } + return ( +
  • + {props.orderType}: {order} +
  • + ); +} const Restaurant = () => { - const pizzas = 0; + return (

    Restaurant Orders

    ); diff --git a/src/Search.js b/src/Search.js index 7bd5871c0..5c75a0732 100644 --- a/src/Search.js +++ b/src/Search.js @@ -1,6 +1,18 @@ import React from "react"; +import { useState } from "react"; -const Search = () => { +const SearchButton = () => ; + +const Search = (props) => { + let [searchInput, setSearchInput] = useState(""); + function handleSearchInput (event) { + setSearchInput(event.target.value); + } + function submitHandler (event) { + event.preventDefault(); + props.search(searchInput); + + } return (
    @@ -8,7 +20,7 @@ const Search = () => {
    -
    +
    { id="customerName" className="form-control" placeholder="Customer name" + value={searchInput} + onChange={handleSearchInput} /> - +
    @@ -26,4 +40,6 @@ const Search = () => { ); }; + export default Search; + diff --git a/src/SearchResults.js b/src/SearchResults.js new file mode 100644 index 000000000..509b0eac6 --- /dev/null +++ b/src/SearchResults.js @@ -0,0 +1,159 @@ +import React, { useState, useContext } from "react"; +import moment from "moment"; +moment().format(); +import CustomerProfile from "./CustomerProfile"; +import { BookingForForm } from "./data/BookingForm"; + + +const SearchResults = (props) => { + const [customerId, setCustomerId] = useState(null); + const [descendingOrder, setDescendingOrder] = useState(true); + const [valueToSort, setValueToSort] = useState("id"); + const { setNewBooking } = useContext(BookingForForm); + + function highlighted(event) { + event.target.parentElement.className === "Highlighted" + ? event.target.parentElement.className = "" + : event.target.parentElement.className = "Highlighted"; + + } + + const handleSortingValue = (event) => { + let item = event.target.name; + console.log(item) + setValueToSort(item) + setDescendingOrder(!descendingOrder); + } + + function sortedResults () { + if(descendingOrder) { + return props.results.slice().sort((a, b) => a[valueToSort].toString().localeCompare(b[valueToSort].toString())); + } else { + return props.results + .slice() + .sort((a, b) => b[valueToSort].toString().localeCompare(a[valueToSort].toString())); + } + } + + function deleteBooking(id) { + fetch(`https://booking-server-98w3.onrender.com/bookings/${id}`, { + method: "DELETE", // + headers: { + "Content-Type": "application/json", + }, + }) + .then(response => { + if (!response.ok) { + throw new Error('Network response was not ok'); + } + return response.json(); + }) + .then((data) => {console.log(data), props.updates()}); + } + + function updateBooking(id){ + let chosenBooking = props.results.find(booking => booking.id === id); + setNewBooking(chosenBooking); + } + + return ( + <> + + + + + + + + + + + + + + + + + + {sortedResults().map((result) => { + function nightsTotal() { + let dateOut = moment(result.checkOutDate); + let dateIn = moment(result.checkInDate); + return dateOut.diff(dateIn, "days"); + } + + let nights = nightsTotal(); + return ( + + + + + + + + + + + + + + ); + })} + +
    + id{" "} + + + title + + + first name + + + surname + + + email

    + +
    + room id + + + check in date + + check out datenights total
    {result.id}{result.title}{result.firstName}{result.surname}{result.email}{result.roomId}{result.checkInDate}{result.checkOutDate}{nights} +
    + +
    +
    +
    + + +
    +
    + + + ); +} + +export default SearchResults; diff --git a/src/data/BookingForm.js b/src/data/BookingForm.js new file mode 100644 index 000000000..6b953173c --- /dev/null +++ b/src/data/BookingForm.js @@ -0,0 +1,3 @@ +import { createContext } from "react"; + +export const BookingForForm = createContext({}); \ No newline at end of file