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 = () => (
+
+
+
+
+);
+
+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.
+
+
+
+);
+
+const Footer = (props) => (
+
+ {props.text.map(item => {item} )}
+
+)
+
const App = () => {
return (
);
};
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 ?
+ (
+
+
+
+ Client's ID:
+ {userInfo.id}
+
+
+ Client's email:
+ {userInfo.email}
+
+
+ VIP Client:
+ {userInfo.vip && "YES"}
+
+
+ Client's phone number:
+ {userInfo.phoneNumber}
+
+
+
+ )
+ : 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 Add
+}
+
+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
-
- Pizzas: {pizzas} Add
-
+
+
+
);
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 = () => Search ;
+
+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 = () => {
@@ -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 (
+ <>
+
+
+
+
+ id{" "}
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+
+ title
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+ first name
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+ surname
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+ email
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+ room id
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+
+ check in date
+
+ {descendingOrder ? "A -> Z" : "Z-> A"}
+
+
+ check out date
+ nights total
+
+
+
+
+ {sortedResults().map((result) => {
+ function nightsTotal() {
+ let dateOut = moment(result.checkOutDate);
+ let dateIn = moment(result.checkInDate);
+ return dateOut.diff(dateIn, "days");
+ }
+
+ let nights = nightsTotal();
+ return (
+
+ {result.id}
+ {result.title}
+ {result.firstName}
+ {result.surname}
+ {result.email}
+ {result.roomId}
+ {result.checkInDate}
+ {result.checkOutDate}
+ {nights}
+
+
+ setCustomerId(result.id)}>
+ Show profile
+
+
+
+
+
+ deleteBooking(result.id)}>
+ Delete
+
+ updateBooking(result.id)}>
+ Update
+
+
+
+
+ );
+ })}
+
+
+
+ >
+ );
+}
+
+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