├── public ├── img │ ├── hotel1.jpeg │ ├── hotel2.jpeg │ ├── hotel3.jpeg │ ├── hotel4.jpeg │ └── hotel5.jpeg └── vite.svg ├── vite.config.js ├── src ├── store.js ├── main.jsx ├── App.jsx ├── components │ ├── BookingForm.jsx │ ├── HotelDetails.jsx │ └── HotelList.jsx └── assets │ └── react.svg ├── .gitignore ├── index.html ├── db.json ├── package.json └── README.md /public/img/hotel1.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosazaustre/hotel-reservation-app/HEAD/public/img/hotel1.jpeg -------------------------------------------------------------------------------- /public/img/hotel2.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosazaustre/hotel-reservation-app/HEAD/public/img/hotel2.jpeg -------------------------------------------------------------------------------- /public/img/hotel3.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosazaustre/hotel-reservation-app/HEAD/public/img/hotel3.jpeg -------------------------------------------------------------------------------- /public/img/hotel4.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosazaustre/hotel-reservation-app/HEAD/public/img/hotel4.jpeg -------------------------------------------------------------------------------- /public/img/hotel5.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/carlosazaustre/hotel-reservation-app/HEAD/public/img/hotel5.jpeg -------------------------------------------------------------------------------- /vite.config.js: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import react from '@vitejs/plugin-react' 3 | 4 | // https://vitejs.dev/config/ 5 | export default defineConfig({ 6 | plugins: [react()], 7 | }) 8 | -------------------------------------------------------------------------------- /src/store.js: -------------------------------------------------------------------------------- 1 | import { create } from "zustand"; 2 | 3 | const useStore = create((set) => ({ 4 | reservations: [], 5 | addReservation: (hotel, dates) => 6 | set((state) => ({ 7 | reservations: [...state.reservations, { hotel, dates }], 8 | })), 9 | })); 10 | 11 | export default useStore; 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | dist 12 | dist-ssr 13 | *.local 14 | 15 | # Editor directories and files 16 | .vscode/* 17 | !.vscode/extensions.json 18 | .idea 19 | .DS_Store 20 | *.suo 21 | *.ntvs* 22 | *.njsproj 23 | *.sln 24 | *.sw? 25 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite + React 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/main.jsx: -------------------------------------------------------------------------------- 1 | import React from "react"; 2 | import ReactDOM from "react-dom/client"; 3 | import App from "./App"; 4 | 5 | import "@fontsource/roboto/300.css"; 6 | import "@fontsource/roboto/400.css"; 7 | import "@fontsource/roboto/500.css"; 8 | import "@fontsource/roboto/700.css"; 9 | 10 | ReactDOM.createRoot(document.getElementById("root")).render( 11 | 12 | 13 | 14 | ); 15 | -------------------------------------------------------------------------------- /src/App.jsx: -------------------------------------------------------------------------------- 1 | import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; 2 | import { Route, Switch } from "wouter"; 3 | import { Toaster } from "react-hot-toast"; 4 | import HotelList from "./components/HotelList"; 5 | import HotelDetails from "./components/HotelDetails"; 6 | 7 | function App() { 8 | return ( 9 |
10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 |
18 | ); 19 | } 20 | 21 | export default App; 22 | -------------------------------------------------------------------------------- /db.json: -------------------------------------------------------------------------------- 1 | { 2 | "hotels": [ 3 | { 4 | "name": "Hotel Ritz", 5 | "description": "Un hotel de lujo ubicado en el corazón de la ciudad", 6 | "id": 1, 7 | "image": "/img/hotel1.jpeg" 8 | }, 9 | { 10 | "name": "Hotel Plaza", 11 | "description": "Un hotel histórico ubicado en la Quinta Avenida", 12 | "id": 2, 13 | "image": "/img/hotel2.jpeg" 14 | }, 15 | { 16 | "name": "Hotel Waldorf Astoria", 17 | "description": "Un hotel de cinco estrellas ubicado en Midtown Manhattan", 18 | "id": 3, 19 | "image": "/img/hotel3.jpeg" 20 | }, 21 | { 22 | "name": "Hotel St. Regis", 23 | "description": "Un hotel de lujo ubicado en Central Park", 24 | "id": 4, 25 | "image": "/img/hotel4.jpeg" 26 | }, 27 | { 28 | "name": "Hotel Peninsula", 29 | "description": "Un hotel de cinco estrellas ubicado en Midtown Manhattan", 30 | "id": 5, 31 | "image": "/img/hotel5.jpeg" 32 | } 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "hotel-reservation-app", 3 | "private": true, 4 | "version": "0.0.0", 5 | "type": "module", 6 | "scripts": { 7 | "dev": "vite", 8 | "build": "vite build", 9 | "preview": "vite preview", 10 | "server": "json-server --watch db.json --port 3001" 11 | }, 12 | "dependencies": { 13 | "@emotion/react": "^11.11.1", 14 | "@emotion/styled": "^11.11.0", 15 | "@fontsource/roboto": "^5.0.5", 16 | "@mui/icons-material": "^5.14.0", 17 | "@mui/material": "^5.14.0", 18 | "@tanstack/react-query": "^4.29.19", 19 | "json-server": "^0.17.3", 20 | "react": "^18.2.0", 21 | "react-dom": "^18.2.0", 22 | "react-hook-form": "^7.45.1", 23 | "react-hot-toast": "^2.4.1", 24 | "wouter": "^2.11.0", 25 | "zustand": "^4.3.9" 26 | }, 27 | "devDependencies": { 28 | "@types/react": "^18.0.27", 29 | "@types/react-dom": "^18.0.10", 30 | "@vitejs/plugin-react": "^3.1.0", 31 | "vite": "^4.1.0" 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # hotel-reservation-app 2 | 3 | Code from the ReactJS Video Tutorial on [YouTube](https://www.youtube.com/watch?v=KRrzBkxxMbc) 4 | [![yt-react-tutorial-query-vouter-zustand](https://github.com/carlosazaustre/hotel-reservation-app/assets/650752/ca8b3b18-2daf-4727-9ecc-1d5b17858adc)](https://www.youtube.com/watch?v=KRrzBkxxMbc) 5 | 6 | You will learn: 7 | 8 | - How to set up a React JS project from scratch. 9 | - Integration and use of **React/TanStack Query** to manage queries and mutations. 10 | - Global state with **Zustand**. 11 | - Navigation with **Wouter**. 12 | - Forms with **React Hooks Forms**. 13 | - Notifications with **React Hot Toast**. 14 | - Design and styles with **Material UI**. 15 | 16 | This tutorial is perfect for both beginners looking for a detailed "react js tutorial" and experienced developers wanting to expand their knowledge and learn about new libraries. 17 | 18 | You don't need to download anything beforehand; I'll show you how to do everything from the beginning. 19 | 20 | So, prepare your development environment and join me on this exciting journey through the world of React JS! 21 | -------------------------------------------------------------------------------- /src/components/BookingForm.jsx: -------------------------------------------------------------------------------- 1 | import toast from "react-hot-toast"; 2 | import { useForm } from "react-hook-form"; 3 | import Input from "@mui/material/Input"; 4 | import Button from "@mui/material/Button"; 5 | import useStore from "../store"; 6 | import { Typography } from "@mui/material"; 7 | 8 | const BookingForm = ({ hotel }) => { 9 | const { 10 | register, 11 | handleSubmit, 12 | formState: { errors }, 13 | } = useForm(); 14 | const addReservation = useStore((state) => state.addReservation); 15 | 16 | const onSubmit = (data) => { 17 | addReservation(hotel, data); 18 | toast.success("Reservation made!"); 19 | }; 20 | 21 | return ( 22 |
23 | 24 | {errors.startDate && ( 25 | Start date is required 26 | )} 27 |
28 | 29 | {errors.endDate && ( 30 | End date is required 31 | )} 32 |
33 |
34 | 37 |
38 | ); 39 | }; 40 | 41 | export default BookingForm; 42 | -------------------------------------------------------------------------------- /public/vite.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/components/HotelDetails.jsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { useRoute } from "wouter"; 3 | import BookingForm from "./BookingForm"; 4 | 5 | import Card from "@mui/material/Card"; 6 | import CardActions from "@mui/material/CardActions"; 7 | import CardContent from "@mui/material/CardContent"; 8 | import CardMedia from "@mui/material/CardMedia"; 9 | import Button from "@mui/material/Button"; 10 | import Typography from "@mui/material/Typography"; 11 | 12 | const fetchHotel = async (id) => { 13 | const response = await fetch(`http://localhost:3001/hotels/${id}`); 14 | if (!response.ok) { 15 | throw new Error("Network response was not ok"); 16 | } 17 | return response.json(); 18 | }; 19 | 20 | const HotelDetails = () => { 21 | const [match, params] = useRoute("/hotel/:id"); 22 | const { 23 | data: hotel, 24 | isLoading, 25 | error, 26 | } = useQuery({ 27 | queryKey: ["hotel", params.id], 28 | queryFn: () => fetchHotel(params.id), 29 | }); 30 | 31 | if (isLoading) { 32 | return
Loading...
; 33 | } 34 | 35 | if (error) { 36 | return
Error fetching Hotel! {error.message}
; 37 | } 38 | 39 | return ( 40 | 41 | 42 | 43 | 44 | {hotel.name} 45 | 46 | 47 | {hotel.description} 48 | 49 | 50 | 51 | 52 | 53 | 54 | ); 55 | }; 56 | 57 | export default HotelDetails; 58 | -------------------------------------------------------------------------------- /src/components/HotelList.jsx: -------------------------------------------------------------------------------- 1 | import { useQuery } from "@tanstack/react-query"; 2 | import { Link } from "wouter"; 3 | 4 | import Stack from "@mui/material/Stack"; 5 | import Card from "@mui/material/Card"; 6 | import CardActions from "@mui/material/CardActions"; 7 | import CardContent from "@mui/material/CardContent"; 8 | import CardMedia from "@mui/material/CardMedia"; 9 | import Button from "@mui/material/Button"; 10 | import Typography from "@mui/material/Typography"; 11 | 12 | const fetchHotels = async () => { 13 | const response = await fetch("http://localhost:3001/hotels"); 14 | if (!response.ok) { 15 | throw new Error("Network response was not ok"); 16 | } 17 | return response.json(); 18 | }; 19 | 20 | const HotelList = () => { 21 | const { 22 | data: hotels, 23 | isLoading, 24 | error, 25 | } = useQuery({ queryKey: ["hotels"], queryFn: fetchHotels }); 26 | 27 | if (isLoading) { 28 | return
Loading...
; 29 | } 30 | 31 | if (error) { 32 | return
Error fetching Hotels! {error.message}
; 33 | } 34 | 35 | return ( 36 | <> 37 | 38 | Booking App 39 | 40 | ; 41 | 42 | {hotels.map((hotel) => ( 43 | 44 | 45 | 50 | 51 | 52 | {hotel.name} 53 | 54 | 55 | {hotel.description} 56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | ))} 64 | 65 | 66 | ); 67 | }; 68 | 69 | export default HotelList; 70 | -------------------------------------------------------------------------------- /src/assets/react.svg: -------------------------------------------------------------------------------- 1 | --------------------------------------------------------------------------------