Project in Action - Booking Hotel MERN
Find the App Useful? You can always buy me a coffee
npm run install-dependencies
- rename .env.temp to .env
- setup values for - MONGO_URL, JWT_SECRET, JWT_LIFETIME
npm start
- visit url http://localhost:3000/
- create client folder
- open terminal
cd client
npx create-react-app .
npm start
- set editor/browser side by side
- copy/paste assets from complete project
- in src remove
- App.css
- App.test.js
- logo.svg
- reportWebVitals.js
- setupTests.js
- fix App.js and index.js
- change title in public/index.html
- replace favicon.ico in public
- resource Generate Favicons
- CSS in JS (styled-components)
- saves times on the setup
- less lines of css
- speeds up the development
- normalize.css
- small CSS file that provides cross-browser consistency in the default styling of HTML elements.
- normalize docs
npm install normalize.css
- import 'normalize.css' in index.js
- SET BEFORE 'index.css'
- replace contents of index.css
- if any questions about normalize or specific styles
- Coding Addict - Default Starter Video
- Repo - Default Starter Repo
- npm i react-date-range npm package
- react date range official doc
//Header.jsx
import { DateRange } from "react-date-range";
import "react-date-range/dist/styles.css"; // main css file
import "react-date-range/dist/theme/default.css"; // theme css file
import { format } from "date-fns";
...
/// hides the range-date upon first load of the front page
const [openDate, setOpenDate] = useState(false);
const [date, setDate] = useState([
{
startDate: new Date(), //
endDate: new Date(), // format because its a pure javascript
key: "selection"
}
]);
...
{/* <span className="headerSearchText">date to date</span> */}
<span
/// !openDate opposite | hide and show calendar
onClick={() => setOpenDate(!openDate)}
className="headerSearchText"
>
{`
${
// diri mo reflect ang output sa gi select sa <DateRange>
// gi convert sa format() from plain javascript to react deadable
// data[0] zero means first index
format(date[0].startDate, "MM/dd/yyyy")} to ${format(
date[0].endDate,
"MM/dd/yyyy"
)}
`}</span>
// ...
{ /// state ni sa calendar sa searchbar kung iclick mo gawas ang <DateRange> which is ang calendar selection
openDate && (
<DateRange
editableDateInputs={true}
onChange={(item) => setDate([item.selection])}
moveRangeOnFirstSelection={false}
ranges={date}
className="date"
/>
);
}
// ...more line of code ...
// In Header.jsx
// note:
// <div className="header"> sulod sa header selector ang mga child selector
// <div
//
// ang mga child select are : headerSearchItem, headerSearchInput, .date, headerSearchText
//
// Dili mo gana ang position absolute kung walay naka assign na position relative sa parent element
// Reminder to myself sa styling sa scss/css
// Child Selector = position: absolute
// Parent Selector = position: relative
//header.scss
// parent element
.header{
background-color: #003580;
color: #fff;
display: flex;
justify-content: center;
position: relative; //// parent element is here
.headerSearchItem{
display: flex;
align-items: center;
gap: 10px;
.headerSearchInput{
border: none;
outline: none; /// removes the border outline sa input box
}
.date{
position: absolute;
top: 40px; /// mo baba sa search bar tungod sa position absolute ug relative:parent element
}
.headerSearchText{
color: lightgray;
cursor: pointer;
}
- code analysis / it will show Option & Calendar section if Toggle click back and forth and updates the UI
// Header.jsx
// --------------------------------------------------
// option for adult , children and rooms state
// ---- openOptions - onClick A ref
const [openOptions, setOpenOptions] = useState(false);
// ang mo consume sa option is a pointer and setOption is the updater sa output
const [options, setOptions] = useState({
// initial value sa options
adult: 1,
children: 0,
room: 1
});
// --------------------------------------------------
const handleOption = (name, operation) => {
setOptions((prev) => {
return {
...prev,
//// how did this happen? name? and the ui connection confuse?
[name]: operation === "i" ? options[name] + 1 : options[name] - 1
};
});
};
// ----------------------------------------------------
// ---- onClick A ref
<span
// !openOptions opposite | hide and show options: adult, children, room selection
onClick={() => setOpenOptions(!openOptions)}
className="headerSearchText"
>
{/* how did this happen? name? and the ui connection confuse? */}
{`${options.adult} adult - ${options.children} children - ${options.room} room`}
</span>;
// ---- my side note: ----------------------
{
/* how did this happen? name? and the ui connection confuse? */
// My Understanding:
// diri ipagawas sa cosnt [options, setOptions]
// ang initial value updated value
}
{
`${options.adult} adult - ${options.children} children - ${options.room} room `;
}
// -----------------------------------------------------
// ma trigger nga ma disable kung mo baba equal or less than zero ang count pag iclick sa user ang minus/d = for decrement
<div className="optionItem">
<span className="optionText">Adult</span>
<div className="optionCounter">
<button
/// prevents going to negative number
disabled={options.adult <= 0}
className="optionCounterButton"
onClick={() => handleOption("adult", "d")}
>
-
</button>
<span className="optionCounterNumber">{options.adult}</span>
<button
className="optionCounterButton"
onClick={() => handleOption("adult", "i")}
>
+
</button>
</div>
</div>;
// Hotel.jsx
<Navbar />
<Header type="list" />
// Header.jsx
const Header = ({ type }) => {
...
<div className="header">
<div
/// mo trigger kung naa ko sa url / or /hotel
className={
type === "list" ? "headerContainer listMode" : "headerContainer"
}
>
....
{/* pag ang type dili equal sa list ihide nya,
if kung mo navigate ang user sa default ur which is '/' makita ang Header section and if pag mo navigate sa /hotel ihide ang Header section ug SearchBar
*/}
{type !== "list" && (
<>
// ...code
</>
}
- start time 0:43:06 - 0:47:40 end
- create /component/featured/Featured.jsx
- create /component/featured/featured.scss
- add to Home.jsx
/// Home.jsx
import Navbar from "../../components/navbar/Navbar";
import Header from "../../components/header/Header";
import "./home.scss";
import React from "react";
import Featured from "../../components/featured/Featured";
const Home = () => {
return (
<div>
<Navbar />
<Header />
<div className="homeContainer">
<Featured />
</div>
</div>
);
};
export default Home;
// home.scss
.homeContainer {
margin-top: 50px;
display: flex;
flex-direction: column;
align-items: center;
gap: 30px;
}
/// Featured.jsx
import React from "react";
import "./featured.scss";
const Featured = () => {
return (
<div className="featured">
<div className="featuredItem">
<img
src="https://images.unsplash.com/photo-1444201983204-c43cbd584d93?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
alt="hotel"
className="featuredImg"
/>
<div className="featuredTitles">
<h1>Austin</h1>
<h2>123 Properties</h2>
</div>
</div>
<div className="featuredItem">
<img
src="https://images.unsplash.com/photo-1495365200479-c4ed1d35e1aa?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1170&q=80"
alt="hotel"
className="featuredImg"
/>
<div className="featuredTitles">
<h1>Bombai</h1>
<h2>123 Properties</h2>
</div>
</div>
<div className="featuredItem">
<img
src="https://images.unsplash.com/photo-1496417263034-38ec4f0b665a?ixlib=rb-4.0.3&ixid=MnwxMjA3fDB8MHxwaG90by1wYWdlfHx8fGVufDB8fHx8&auto=format&fit=crop&w=1171&q=80"
alt="hotel"
className="featuredImg"
/>
<div className="featuredTitles">
<h1>Corota</h1>
<h2>123 Properties</h2>
</div>
</div>
</div>
);
};
export default Featured;
// featured.scss
.featured {
width: 100%;
max-width: 1024px;
display: flex;
justify-content: space-between;
gap: 20px;
z-index: 1; /// para dili mo overlap ang featured
.featuredItem {
position: relative;
color: #fff;
border-radius: 10px; /// border-radius will not work without overflow hidden
overflow: hidden;
height: 215px; // 250px
padding-bottom: 10px;
margin-bottom: 10px;
.featuredTitles {
position: absolute;
bottom: 20px;
left: 20px;
}
.featuredImg {
width: 100%;
object-fit: cover;
}
}
}
// header.scss
// z-index ang pinaka gamay ug value 1 is behind and the largest value will be the front
.header {
background-color: #003580;
color: #fff;
display: flex;
justify-content: center;
position: relative;
.headerContainer {
width: 100%;
max-width: 1024px;
margin: 20px 0px 100px 0px;
.headerList {
display: flex;
gap: 40px;
margin-bottom: 50px;
}
.headerListItem {
display: flex;
align-items: center;
gap: 10px;
cursor: pointer;
&.active {
border: 1px solid #fff;
padding: 10px;
border-radius: 25px;
}
}
.headerTitle {
}
.headerDesc {
margin: 20px 0px;
}
.headerBtn {
background-color: #0071c2;
color: #fff;
font-weight: 500;
border: none;
padding: 10px;
cursor: pointer;
}
.headerSearch {
height: 50px;
background-color: #fff;
border: 3px solid #febb02;
display: flex;
align-items: center;
justify-content: space-around;
padding: 20px 0px;
border-radius: 5px;
position: absolute;
bottom: -25px;
width: 100%;
max-width: 1024px;
z-index: 999; /// para dili mo overlap ang featured
.headerSearchItem {
display: flex;
align-items: center;
gap: 10px;
.headerSearchInput {
border: none;
outline: none;
}
.date {
position: absolute;
top: 40px;
z-index: 2; /// para dili mo overlap ang featured
}
.headerSearchText {
color: lightgray;
cursor: pointer;
}
.headerIcon {
color: lightgray;
}
}
.options {
z-index: 2; /// para dili mo overlap ang featured
position: absolute;
top: 40px;
background-color: #fff;
color: gray;
border-radius: 5px;
-webkit-box-shadow: 0px 0px 10px -5px rgba(0, 0, 0, 0.4);
box-shadow: 0px 0px 10px -5px rgba(0, 0, 0, 0.4);
.optionItem {
width: 200px;
display: flex;
justify-content: space-between;
margin: 10px;
.optionText {
}
.optionCounter {
display: flex;
align-items: center;
gap: 10px;
font-size: 12px;
color: #000;
.optionCounterButton {
width: 30px;
height: 30px;
border: 1px solid #0071c2;
color: #0071c2;
cursor: pointer;
background-color: #fff;
&:disabled {
cursor: not-allowed;
}
}
.optionCounterNumber {
}
}
}
}
}
}
.headerContainer.listMode {
margin: 20px 0px 0px 0px;
}
}
// 0:52:00
// PropertyList.jsx
// <div className="pListItem">
// propertyList.scss
.pListItem{
flex:1; /// para ang kada image the same size mao ni purpose sa flex: 1 mag base sya kada element
/// If an element has flex: 1, this means the size of all of the other elements will have the same width as their content, but the element with flex: 1 will have the remaining full space given to it.
// 0.52.00 Create FeaturedProperties Start
// 0:57:03 MailList Component start
// declare to return built in Date function and getFullyear function
const getCurrentYear = () => {
return new Date().getFullYear();
};
/// return output in frontent
{
getCurrentYear();
}
// 1:07:57 start
// Header.jsx
// step 1:
<button className="headerBtn" onClick={handleSearch}>
Search
</button>;
// step 2: create handleSearch
const handleSearch = () => {
// console.log("Handle search");
};
// Step 3: import useNavigate from react-router-dom
import { useNavigate } from "react-router-dom";
// ...
const navigate = useNavigate();
// then refactor handleSearch
const handleSearch = () => {
navigate("/hotels", { state: {} });
};
// step 4: refactor input searchbox onChange={(e) => setDestination(e.target.value)}
<input
type="text"
placeholder="Where are you going?"
className="headerSearchInput"
onChange={(e) => setDestination(e.target.value)}
/>;
/// step 5: update handleSearch state: {destination, date, options }
const handleSearch = () => {
navigate("/hotels", { state: { destination, date, options } });
};
// =================================================================
// HOW TO USE IT IN List.jsx
//
//
// steps:
import { useLocation } from "react-router-dom";
// ...
const List = () => {
const location = useLocation();
/// console.log(location) to check and display the state in DOM
/// then next step is to create a state of each value
const [destination, setDestination] = useState(location.state.destination);
const [date, setDate] = useState(location.state.date);
const [options, setOptions] = useState(location.state.options);
/// next step
/// also import { format } from "date-fns";
<div className="lsItem">
<label>Check-in Date</label>
<span>
{`${format(date[0].startDate, "MM/dd/yyyy")} to ${format(
date[0].endDate,
"MM/dd/yyyy"
)} `}
</span>
</div>
/// also update the destination placeholder =====
div className="lsItem">
<label>Destination</label>
<input placeholder={destination} type="text" />