From f5e647a5a2ac9bccadae77bf85c2a9197ddd0337 Mon Sep 17 00:00:00 2001 From: Muhammad Surani <ms02309@surrey.ac.uk> Date: Sat, 20 Apr 2024 02:12:40 +0100 Subject: [PATCH] Wire up existing booking query form. Wire up existing booking list page. Add and wire up booking view page with card listing fields. Add and wire up previous bookings and upcoming bookings list page. --- .../BookingList/BookingCard/BookingCard.tsx | 49 ++++--- .../BookingView/BookingFlightCard.tsx | 27 ++++ .../BookingView/BookingSeatCard.tsx | 25 ++++ .../components/BookingView/BookingView.scss | 13 ++ .../components/BookingView/BookingView.tsx | 18 +++ .../CustomerBookings/CustomerBookingCard.tsx | 67 ++++++++++ .../CustomerBookings/CustomerBookings.scss | 19 +++ .../CustomerBookings/CustomerBookings.tsx | 29 +++++ .../components/Dashboard/Flights/Flights.tsx | 117 ++++++++++------- client/src/components/Register/Register.tsx | 86 +++++++----- client/src/helpers/DateTimeHelpers.ts | 18 +++ client/src/main.tsx | 122 ++++++++++-------- .../src/services/BookingList/BookingList.ts | 35 +++-- .../src/services/BookingList/BookingOrder.ts | 15 +++ .../src/services/BookingView/BookingView.ts | 79 ++++++++++++ .../CustomerBookings/CustomerBookings.ts | 26 ++++ .../services/Dashboard/AirlineDashboard.ts | 20 ++- 17 files changed, 594 insertions(+), 171 deletions(-) create mode 100644 client/src/components/BookingView/BookingFlightCard.tsx create mode 100644 client/src/components/BookingView/BookingSeatCard.tsx create mode 100644 client/src/components/BookingView/BookingView.scss create mode 100644 client/src/components/BookingView/BookingView.tsx create mode 100644 client/src/components/CustomerBookings/CustomerBookingCard.tsx create mode 100644 client/src/components/CustomerBookings/CustomerBookings.scss create mode 100644 client/src/components/CustomerBookings/CustomerBookings.tsx create mode 100644 client/src/helpers/DateTimeHelpers.ts create mode 100644 client/src/services/BookingList/BookingOrder.ts create mode 100644 client/src/services/BookingView/BookingView.ts create mode 100644 client/src/services/CustomerBookings/CustomerBookings.ts diff --git a/client/src/components/BookingList/BookingCard/BookingCard.tsx b/client/src/components/BookingList/BookingCard/BookingCard.tsx index 29b598f..751b0e0 100644 --- a/client/src/components/BookingList/BookingCard/BookingCard.tsx +++ b/client/src/components/BookingList/BookingCard/BookingCard.tsx @@ -1,44 +1,59 @@ -import { useLocation } from 'react-router-dom'; -import { IFlight } from '../../../services/Dashboard/CustomerDashboard'; -import './BookingCard.scss'; +import { useLocation } from "react-router-dom"; +import { IFlight } from "../../../services/Dashboard/CustomerDashboard"; +import "./BookingCard.scss"; +import { + BookingOrder, + bookFlight, +} from "../../../services/BookingList/BookingOrder"; interface IBookingCard { - flight: IFlight + flight: IFlight; } function BookingCard({ flight }: IBookingCard) { const location = useLocation(); - const isEconomy = location.search.split('seatType=')[1] === 'economy'; + const isEconomy = location.search.split("seatType=")[1] === "economy"; - const handleClick = () => { - console.log('ready to redirect:', flight.id); + const handleClick = async (event: any) => { + event.preventDefault(); + //show loader + const bookingInfo: BookingOrder = { + flightId: flight.id, + bookingClass: isEconomy ? 1 : 2, + }; + await bookFlight(bookingInfo); + alert("Flight booked successfully, navigating to payments page..."); }; return ( <> - <div className='booking-card'> - <div className='booking-card-details'> - <div className='item'> + <div className="booking-card"> + <div className="booking-card-details"> + <div className="item"> <span>Departure Time:</span> <span>{flight.departureTime}</span> </div> - <div className='item'> + <div className="item"> <span>Arrival Time:</span> <span>{flight.arrivalTime}</span> </div> </div> - <div className='price'> - <div className='price-label'> - <span>{isEconomy ? 'Economy' : 'Business'} Price:</span> - <span>£{isEconomy ? flight.economyPrice : flight.businessPrice}</span> + <div className="price"> + <div className="price-label"> + <span>{isEconomy ? "Economy" : "Business"} Price:</span> + <span> + £{isEconomy ? flight.economyPrice : flight.businessPrice} + </span> </div> - <button type='button' onClick={handleClick}>Purchase</button> + <button type="button" onClick={handleClick}> + Purchase + </button> </div> </div> </> ); } -export default BookingCard; \ No newline at end of file +export default BookingCard; diff --git a/client/src/components/BookingView/BookingFlightCard.tsx b/client/src/components/BookingView/BookingFlightCard.tsx new file mode 100644 index 0000000..80f23a7 --- /dev/null +++ b/client/src/components/BookingView/BookingFlightCard.tsx @@ -0,0 +1,27 @@ +import { useEffect, useState } from "react"; +import { GetBookingFlight } from "../../services/BookingView/BookingView"; +import "./BookingView.scss"; +import { IFlight } from "../../services/Dashboard/CustomerDashboard"; +import { formatLongDateTime } from "../../helpers/DateTimeHelpers"; + +const BookingFlightCard = ({ flightId }: { flightId: number }) => { + const [flightData, setFlightData] = useState<IFlight | undefined>(); + + useEffect(() => { + GetBookingFlight(flightId).then((data) => { + setFlightData(data); + }); + }, [flightId]); + + return ( + <div className="card booking-view-card"> + <h3>Flight Information</h3> + <div>Origin: {flightData?.origin}</div> + <div>Destination: {flightData?.destination}</div> + <div>Depature: {formatLongDateTime(flightData?.departureTime)}</div> + <strong>Arrival: {formatLongDateTime(flightData?.arrivalTime)}</strong> + </div> + ); +}; + +export default BookingFlightCard; diff --git a/client/src/components/BookingView/BookingSeatCard.tsx b/client/src/components/BookingView/BookingSeatCard.tsx new file mode 100644 index 0000000..2693fcf --- /dev/null +++ b/client/src/components/BookingView/BookingSeatCard.tsx @@ -0,0 +1,25 @@ +import { useEffect, useState } from "react"; +import { GetBookingSeat } from "../../services/BookingView/BookingView"; +import { ISeat } from "../../services/Dashboard/CustomerDashboard"; + +const BookingSeatCard = ({ seatId }: { seatId?: number }) => { + const [seatData, setSeatData] = useState<ISeat | undefined>(); + + useEffect(() => { + if (!seatId) { + return; + } + GetBookingSeat(seatId).then((data) => { + setSeatData(data); + }); + }, [seatId]); + + return ( + <div className="card booking-view-card"> + <h3>Seat Information</h3> + <div>Seat number: {seatData ? seatData.seatNumber : "N/A"}</div> + </div> + ); +}; + +export default BookingSeatCard; diff --git a/client/src/components/BookingView/BookingView.scss b/client/src/components/BookingView/BookingView.scss new file mode 100644 index 0000000..81b2252 --- /dev/null +++ b/client/src/components/BookingView/BookingView.scss @@ -0,0 +1,13 @@ +.booking-view { + display: flex; + align-items: center; + justify-content: space-evenly; + width: 100%; + height: 100%; +} + +.booking-view-card { + width: 20vw; + min-width: 350px; + min-height: 200px; +} diff --git a/client/src/components/BookingView/BookingView.tsx b/client/src/components/BookingView/BookingView.tsx new file mode 100644 index 0000000..0923ff7 --- /dev/null +++ b/client/src/components/BookingView/BookingView.tsx @@ -0,0 +1,18 @@ +import { useLoaderData } from "react-router-dom"; +import BookingFlightCard from "./BookingFlightCard"; +import BookingSeatCard from "./BookingSeatCard"; +import "./BookingView.scss"; +import { IBookingInfo } from "../../services/BookingView/BookingView"; + +const BookingView = () => { + const flightData = useLoaderData() as IBookingInfo | undefined; + + return ( + <div className="booking-view"> + {flightData && <BookingFlightCard flightId={flightData?.flightId} />} + {flightData && <BookingSeatCard seatId={flightData?.seatId} />} + </div> + ); +}; + +export default BookingView; diff --git a/client/src/components/CustomerBookings/CustomerBookingCard.tsx b/client/src/components/CustomerBookings/CustomerBookingCard.tsx new file mode 100644 index 0000000..0369afe --- /dev/null +++ b/client/src/components/CustomerBookings/CustomerBookingCard.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from "react"; +import { formatLongDateTime } from "../../helpers/DateTimeHelpers"; +import { GetBookingFlight } from "../../services/BookingView/BookingView"; +import { IFlight } from "../../services/Dashboard/CustomerDashboard"; +import { useNavigate } from "react-router-dom"; + +const CustomerBookingCard = ({ + flightId, + flightType, + bookingId, +}: { + flightId: number; + flightType: string; + bookingId: number; +}) => { + const [flightData, setFlightData] = useState<IFlight | undefined>(); + const navigate = useNavigate(); + + const onClick = () => { + navigate(`/booking/${bookingId}`); + }; + useEffect(() => { + GetBookingFlight(flightId).then((data) => { + setFlightData(data); + }); + }, [flightId]); + return ( + <div> + {flightData ? ( + <div> + {new Date(flightData.departureTime).getTime() >= + new Date().getTime() && + flightType === "upcoming" && ( + <div className="card customer-booking-view-card"> + <div>Origin: {flightData?.origin}</div> + <div>Destination: {flightData?.destination}</div> + <div> + Depature: {formatLongDateTime(flightData?.departureTime)} + </div> + <button type="submit" className="view-more" onClick={onClick}> + View details + </button> + </div> + )} + + {new Date(flightData.departureTime).getTime() < + new Date().getTime() && + flightType === "previous" && ( + <div className="card customer-booking-view-card"> + <div>Origin: {flightData?.origin}</div> + <div>Destination: {flightData?.destination}</div> + <div> + Depature: {formatLongDateTime(flightData?.departureTime)} + </div> + <button type="submit" className="view-more" onClick={onClick}> + View details + </button> + </div> + )} + </div> + ) : ( + "" + )} + </div> + ); +}; +export default CustomerBookingCard; diff --git a/client/src/components/CustomerBookings/CustomerBookings.scss b/client/src/components/CustomerBookings/CustomerBookings.scss new file mode 100644 index 0000000..37857b6 --- /dev/null +++ b/client/src/components/CustomerBookings/CustomerBookings.scss @@ -0,0 +1,19 @@ +.customer-booking-view { + display: grid; + align-items: center; + width: 100%; + height: 100%; + justify-content: space-evenly; +} + +.customer-booking-view-card { + width: 20vw; + min-width: 350px; + min-height: 100px; + margin-top: 20px; +} + +.page-view { + margin-top: 30px; + margin-bottom: 30px; +} diff --git a/client/src/components/CustomerBookings/CustomerBookings.tsx b/client/src/components/CustomerBookings/CustomerBookings.tsx new file mode 100644 index 0000000..df74fe7 --- /dev/null +++ b/client/src/components/CustomerBookings/CustomerBookings.tsx @@ -0,0 +1,29 @@ +import { useLoaderData } from "react-router-dom"; +import "./CustomerBookings.scss"; +import { IBookingInfo } from "../../services/BookingView/BookingView"; +import CustomerBookingCard from "./CustomerBookingCard"; + +const CustomerBookings = () => { + const bookings = useLoaderData() as IBookingInfo[]; + let flightType = location.search.split("flightType=")[1]?.split("&")[0]; + if (typeof flightType === "undefined") { + flightType = "upcoming"; + } + + return ( + <div className="page-view"> + <div className="customer-booking-view"> + <h3>List of {flightType} bookings</h3> + {bookings.map((booking) => ( + <CustomerBookingCard + flightId={booking.flightId} + flightType={flightType} + bookingId={booking.id} + /> + ))} + </div> + </div> + ); +}; + +export default CustomerBookings; diff --git a/client/src/components/Dashboard/Flights/Flights.tsx b/client/src/components/Dashboard/Flights/Flights.tsx index 20a8245..0fcf82d 100644 --- a/client/src/components/Dashboard/Flights/Flights.tsx +++ b/client/src/components/Dashboard/Flights/Flights.tsx @@ -1,71 +1,96 @@ -import { useLoaderData } from 'react-router'; -import { ICustomerDashboardData } from '../../../services/Dashboard/CustomerDashboard'; -import { IAirlineDashboardData } from '../../../services/Dashboard/AirlineDashboard'; -import FlightCard from '../FlightCard/FlightCard'; -import './Flights.scss'; -import { Link } from 'react-router-dom'; +import { useLoaderData } from "react-router"; +import { ICustomerDashboardData } from "../../../services/Dashboard/CustomerDashboard"; +import { IAirlineDashboardData } from "../../../services/Dashboard/AirlineDashboard"; +import FlightCard from "../FlightCard/FlightCard"; +import "./Flights.scss"; +import { Link, useNavigate } from "react-router-dom"; function Flights() { - const data = useLoaderData() as ICustomerDashboardData | IAirlineDashboardData; - if (data.type === 'customer') { + const data = useLoaderData() as + | ICustomerDashboardData + | IAirlineDashboardData; + const navigate = useNavigate(); + + const onClick = (flightType: string) => { + navigate(`/customer/bookings?flightType=${flightType}`); + }; + if (data.type === "customer") { return ( <> - <div className='flights'> - <div className='flex-row'> - <span className='flights-title'>Upcoming Flights</span> - <Link to={"/"}> - <button type='button' className='view-more'>View more</button> - </Link> + <div className="flights"> + <div className="flex-row"> + <span className="flights-title">Upcoming Flights</span> + <button + type="button" + className="view-more" + onClick={() => onClick("upcoming")} + > + View more + </button> </div> - <div className='flight-list'> - {data.upcomingFlights.length > 0 - ? data.upcomingFlights.slice(0,3).map((item) => { - // Only pass the flight data to the FlightCard - return <FlightCard key={item.flight.id} flight={item.flight}></FlightCard> - }) - : <div>No Upcoming Flights</div>} + <div className="flight-list"> + {data.upcomingFlights.length > 0 ? ( + data.upcomingFlights.slice(0, 3).map((item) => { + // Only pass the flight data to the FlightCard + return <FlightCard key={item.id} flight={item}></FlightCard>; + }) + ) : ( + <div>No Upcoming Flights</div> + )} </div> </div> - - <div className='flights'> - <div className='flex-row'> - <span className='flights-title'>Flights History</span> - <button type='button' className='view-more'>View more</button> + + <div className="flights"> + <div className="flex-row"> + <span className="flights-title">Flights History</span> + <button + type="submit" + className="view-more" + onClick={() => onClick("previous")} + > + View more + </button> </div> - <div className='flight-list'> - {data.flightsHistory.length > 0 - ? data.flightsHistory.slice(0,3).map((item) => { - // Only pass the flight data to the FlightCard - return <FlightCard key={item.flight.id} flight={item.flight}></FlightCard> - }) - : <div>No Flights History</div>} + <div className="flight-list"> + {data.flightsHistory.length > 0 ? ( + data.flightsHistory.slice(0, 3).map((item) => { + // Only pass the flight data to the FlightCard + return <FlightCard key={item.id} flight={item}></FlightCard>; + }) + ) : ( + <div>No Flights History</div> + )} </div> </div> </> ); - } - else { + } else { return ( <> - <div className='flights'> - <div className='flex-row'> - <span className='flights-title'>Flights List</span> + <div className="flights"> + <div className="flex-row"> + <span className="flights-title">Flights List</span> <Link to="/register-flight"> - <button type='button' className='view-more'>Add flight</button> + <button type="button" className="view-more"> + Add flight + </button> </Link> </div> - <div className='flight-list'> - {data.flightList.length > 0 - ? data.flightList.slice(0,3).map((flight) => { - return <FlightCard key={flight.id} flight={flight}></FlightCard> - }) - : <div>No Flights have been created yet.</div>} + <div className="flight-list"> + {data.flightList.length > 0 ? ( + data.flightList.slice(0, 3).map((flight) => { + return ( + <FlightCard key={flight.id} flight={flight}></FlightCard> + ); + }) + ) : ( + <div>No Flights have been created yet.</div> + )} </div> </div> </> ); } - } export default Flights; diff --git a/client/src/components/Register/Register.tsx b/client/src/components/Register/Register.tsx index 5b85c96..e44fae1 100644 --- a/client/src/components/Register/Register.tsx +++ b/client/src/components/Register/Register.tsx @@ -1,11 +1,11 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useForm } from 'react-hook-form'; -import { AxiosError } from 'axios'; -import { registerUser } from '../../services/Register/Register'; -import { useAuth } from '../../hooks/useAuth'; -import './Register.scss'; -import { userToDashboard } from '../../helpers/UserType'; +import { useState } from "react"; +import { useNavigate } from "react-router-dom"; +import { useForm } from "react-hook-form"; +import { AxiosError } from "axios"; +import { registerUser } from "../../services/Register/Register"; +import { useAuth } from "../../hooks/useAuth"; +import "./Register.scss"; +import { userToDashboard } from "../../helpers/UserType"; import UserStorage from "../../helpers/UserStorage"; export interface IRegisterForm { @@ -19,85 +19,101 @@ export interface IRegisterForm { export function Register() { const { giveAuth, updateUser } = useAuth(); const navigate = useNavigate(); - const [error, setError] = useState(''); - const { register, handleSubmit } = useForm<IRegisterForm>({mode: 'onChange'}); + const [error, setError] = useState(""); + const { register, handleSubmit } = useForm<IRegisterForm>({ + mode: "onChange", + }); const onSubmit = async (formValue: IRegisterForm) => { if (formValue.password.length < 7) { - setError('password length must be greater than 6 characters'); + setError("password length must be greater than 6 characters"); return; } if (formValue.password !== formValue.confirmPassword) { - setError('password and confirm password must match'); + setError("password and confirm password must match"); return; } - setError(''); + setError(""); try { const result = await registerUser(formValue); giveAuth(); const userId = result.data.id; - UserStorage.storeUserId(userId) + UserStorage.storeUserId(userId); updateUser(result.data); navigate(`/${userToDashboard(result.data)}`); } catch (error) { const errorMessage = (error as AxiosError).response?.data; - if (typeof errorMessage == 'string') { + if (typeof errorMessage == "string") { setError(errorMessage); } else { - setError('An unexpected error has occurred'); + setError("An unexpected error has occurred"); } } }; return ( <> - <div className='register'> + <div className="register"> <form onSubmit={handleSubmit(onSubmit)}> - <div className='card register-card'> - <div className='form-group'> + <div className="card register-card"> + <div className="form-group"> <label>Full Name</label> - <input type='text' placeholder='Full name' {...register('name', { required: true })} /> + <input + type="text" + placeholder="Full name" + {...register("name", { required: true })} + /> </div> - - <div className='form-group'> + + <div className="form-group"> <label>Email Address</label> - <input type='email' placeholder='Enter email' {...register('email', { required: true })} /> + <input + type="email" + placeholder="Enter email" + {...register("email", { required: true })} + /> </div> - <div className='form-group'> + <div className="form-group"> <label>Password</label> - <input type='password' placeholder='Enter password' {...register('password', { required: true })} /> + <input + type="password" + placeholder="Enter password" + {...register("password", { required: true })} + /> </div> - <div className='form-group'> + <div className="form-group"> <label>Confirm Password</label> - <input type='password' placeholder='Confirm password' {...register('confirmPassword', { required: true })} /> + <input + type="password" + placeholder="Confirm password" + {...register("confirmPassword", { required: true })} + /> </div> - <div className='form-group'> + <div className="form-group"> <label>Customer Type</label> - <select {...register('customerType', { required: true })}> + <select {...register("customerType", { required: true })}> <option value="customer">Customer</option> <option value="airline">Airline</option> </select> </div> - <div className='form-group'> - <button type='submit'>Submit</button> + <div className="form-group"> + <button type="submit">Submit</button> </div> - <div className='form-group'> - {error && <span>{error}</span>} - </div> + <div className="form-group">{error && <span>{error}</span>}</div> </div> </form> </div> </> - ); + ); } export default Register; diff --git a/client/src/helpers/DateTimeHelpers.ts b/client/src/helpers/DateTimeHelpers.ts new file mode 100644 index 0000000..c268c90 --- /dev/null +++ b/client/src/helpers/DateTimeHelpers.ts @@ -0,0 +1,18 @@ +export const formatLongDateTime = (dateTimeInput?: string) => { + if ( + dateTimeInput === null || + typeof dateTimeInput === "undefined" || + dateTimeInput === "" + ) { + return ""; + } else { + return new Date(dateTimeInput).toLocaleString("en-GB", { + hour12: true, + month: "short", + year: "numeric", + day: "2-digit", + hour: "2-digit", + minute: "2-digit", + }); //toLocaleString([], { hour12: true }); + } +}; diff --git a/client/src/main.tsx b/client/src/main.tsx index 1989c3a..432d751 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -1,30 +1,34 @@ -import React from 'react'; -import ReactDOM from 'react-dom/client'; -import { createBrowserRouter, RouterProvider } from 'react-router-dom'; -import App from './App.tsx'; -import Login from './components/Login/Login.tsx'; -import Register from './components/Register/Register.tsx'; -import Logout from './components/Logout/Logout.tsx'; -import ProtectedRoute from './components/ProtectedRoute/ProtectedRoute.tsx'; -import InverseProtectedRoute from './components/ProtectedRoute/InverseProtectedRoute.tsx'; -import Dashboard from './components/Dashboard/Dashboard.tsx'; -import Flight from './components/Flight/Flight.tsx'; -import FlightList from './components/FlightList/FlightList.tsx'; -import BookingQuery from './components/BookingQuery/BookingQuery.tsx'; -import BookingList from './components/BookingList/BookingList.tsx'; -import { AuthoriseUser } from './services/Authorise/Authorise.ts'; -import { LogoutUser } from './services/Logout/Logout.ts'; -import { GetCustomerDashboardData } from './services/Dashboard/CustomerDashboard.ts'; -import { GetAirlineDashboardData } from './services/Dashboard/AirlineDashboard.ts'; -import { GetFlightData } from './services/Flight/Flight.ts'; -import { GetFlightList } from './services/FlightList/FlightList.ts'; -import { GetBookingList } from './services/BookingList/BookingList.ts'; -import './index.scss'; -import FlightCreationForm from './components/FlightCreationForm/FlightCreationForm.tsx'; +import React from "react"; +import ReactDOM from "react-dom/client"; +import { createBrowserRouter, RouterProvider } from "react-router-dom"; +import App from "./App.tsx"; +import Login from "./components/Login/Login.tsx"; +import Register from "./components/Register/Register.tsx"; +import Logout from "./components/Logout/Logout.tsx"; +import ProtectedRoute from "./components/ProtectedRoute/ProtectedRoute.tsx"; +import InverseProtectedRoute from "./components/ProtectedRoute/InverseProtectedRoute.tsx"; +import Dashboard from "./components/Dashboard/Dashboard.tsx"; +import Flight from "./components/Flight/Flight.tsx"; +import FlightList from "./components/FlightList/FlightList.tsx"; +import BookingQuery from "./components/BookingQuery/BookingQuery.tsx"; +import BookingList from "./components/BookingList/BookingList.tsx"; +import { AuthoriseUser } from "./services/Authorise/Authorise.ts"; +import { LogoutUser } from "./services/Logout/Logout.ts"; +import { GetCustomerDashboardData } from "./services/Dashboard/CustomerDashboard.ts"; +import { GetAirlineDashboardData } from "./services/Dashboard/AirlineDashboard.ts"; +import { GetFlightData } from "./services/Flight/Flight.ts"; +import { GetFlightList } from "./services/FlightList/FlightList.ts"; +import { GetBookingList } from "./services/BookingList/BookingList.ts"; +import "./index.scss"; +import FlightCreationForm from "./components/FlightCreationForm/FlightCreationForm.tsx"; +import { GetBookingInformation } from "./services/BookingView/BookingView.ts"; +import { GetAllBookings } from "./services/CustomerBookings/CustomerBookings.ts"; +import CustomerBookings from "./components/CustomerBookings/CustomerBookings.tsx"; +import BookingView from "./components/BookingView/BookingView.tsx"; const router = createBrowserRouter([ { - path: '/', + path: "/", loader: AuthoriseUser, element: <App></App>, children: [ @@ -32,64 +36,74 @@ const router = createBrowserRouter([ element: <InverseProtectedRoute></InverseProtectedRoute>, children: [ { - path: 'login', - element: <Login></Login> + path: "login", + element: <Login></Login>, }, { - path: 'register', - element: <Register></Register> - } - ] + path: "register", + element: <Register></Register>, + }, + ], }, { element: <ProtectedRoute></ProtectedRoute>, children: [ { - path: 'logout', + path: "logout", loader: LogoutUser, - element: <Logout></Logout> + element: <Logout></Logout>, }, { - path: 'customer-dashboard', + path: "customer-dashboard", loader: GetCustomerDashboardData, - element: <Dashboard></Dashboard> + element: <Dashboard></Dashboard>, }, { - path: 'airline-dashboard', + path: "airline-dashboard", loader: GetAirlineDashboardData, - element: <Dashboard></Dashboard> + element: <Dashboard></Dashboard>, }, { - path: 'flight/:id', + path: "flight/:id", loader: GetFlightData, - element: <Flight></Flight> + element: <Flight></Flight>, }, { - path: 'flights', + path: "flights", loader: GetFlightList, - element: <FlightList></FlightList> + element: <FlightList></FlightList>, }, { - path: 'booking/query', - element: <BookingQuery></BookingQuery> + path: "booking/query", + element: <BookingQuery></BookingQuery>, }, { - path: 'booking/list', + path: "booking/list", loader: GetBookingList, - element: <BookingList></BookingList> + element: <BookingList></BookingList>, + }, + { + path: "register-flight", + element: <FlightCreationForm></FlightCreationForm>, }, { - path: 'register-flight', - element: <FlightCreationForm></FlightCreationForm> - } - ] - } - ] - } + path: "booking/:id", + loader: GetBookingInformation, + element: <BookingView></BookingView>, + }, + { + path: "customer/bookings", + loader: GetAllBookings, + element: <CustomerBookings></CustomerBookings>, + }, + ], + }, + ], + }, ]); -ReactDOM.createRoot(document.getElementById('root')!).render( +ReactDOM.createRoot(document.getElementById("root")!).render( <React.StrictMode> <RouterProvider router={router}></RouterProvider> - </React.StrictMode>, -) + </React.StrictMode> +); diff --git a/client/src/services/BookingList/BookingList.ts b/client/src/services/BookingList/BookingList.ts index 5d593cb..6c6d2e9 100644 --- a/client/src/services/BookingList/BookingList.ts +++ b/client/src/services/BookingList/BookingList.ts @@ -1,19 +1,30 @@ -import { getSearchParam } from '../../helpers/SearchParams'; -import { IFlight } from '../Dashboard/CustomerDashboard'; +import Api from "../../helpers/Api"; +import { getSearchParam } from "../../helpers/SearchParams"; +import { IFlight } from "../Dashboard/CustomerDashboard"; export interface IBookingList { - flights: IFlight[] + flights: IFlight[]; } -export async function GetBookingList({ request }: { request: Request}): Promise<IBookingList> { - const origin = getSearchParam(request.url, 'origin'); - const destination = getSearchParam(request.url, 'destination'); - const date = getSearchParam(request.url, 'date'); - const seatType = getSearchParam(request.url, 'seatType'); +export async function GetBookingList({ + request, +}: { + request: Request; +}): Promise<IBookingList> { + const origin = getSearchParam(request.url, "origin"); + const destination = getSearchParam(request.url, "destination"); + const date = getSearchParam(request.url, "date"); + const seatType = getSearchParam(request.url, "seatType"); - console.log('ready to call API with:', origin, destination, date, seatType); + const fullUrl = `Flight?origin=${origin}&destination=${destination}&departureDate=${date}&seatType=${seatType}`; - return { - flights: [] - }; + try { + const flight = await Api.get(`${fullUrl}`, { withCredentials: true }); + + return { + flights: flight.data.$values, + }; + } catch (error) { + throw error; + } } diff --git a/client/src/services/BookingList/BookingOrder.ts b/client/src/services/BookingList/BookingOrder.ts new file mode 100644 index 0000000..a46318c --- /dev/null +++ b/client/src/services/BookingList/BookingOrder.ts @@ -0,0 +1,15 @@ +import { AxiosResponse } from "axios"; +import Api from "../../helpers/Api"; +import { IFlight } from "../Dashboard/CustomerDashboard"; + +export interface BookingOrder { + flightId: number; + bookingClass: number; + seatId?: number; +} + +export async function bookFlight( + form: BookingOrder +): Promise<AxiosResponse<IFlight>> { + return Api.post("Booking", form, { withCredentials: true }); +} diff --git a/client/src/services/BookingView/BookingView.ts b/client/src/services/BookingView/BookingView.ts new file mode 100644 index 0000000..2aa5ef1 --- /dev/null +++ b/client/src/services/BookingView/BookingView.ts @@ -0,0 +1,79 @@ +import { Params } from "react-router-dom"; +import Api from "../../helpers/Api"; +import { IFlight, ISeat } from "../Dashboard/CustomerDashboard"; + +export interface IBookingInfo { + id: number; + flightId: number; + userId: number; + bookingClass: number; + seatId?: number; +} + +export interface IBookingData { + flight: IFlight; + seat: ISeat; +} + +export async function GetBookingInformation({ + params, +}: { + params: Params; +}): Promise<IBookingInfo> { + const url = `Booking/${params.id}`; + try { + const booking = await Api.get(`${url}`, { withCredentials: true }); + + return booking.data as IBookingInfo; + } catch (error) { + throw error; + } +} + +export async function GetBookingFlight( + flightId?: number +): Promise<IFlight | undefined> { + if (!flightId) { + return undefined; + } + try { + const flights = await Api.get(`Flight/${flightId}`, { + withCredentials: true, + }); + + return flights.data as IFlight; + } catch (error) { + throw error; + } +} + +export async function GetBookingSeat(seatId: number): Promise<ISeat> { + try { + const seat = await Api.get(`Seat/${seatId}`, { + withCredentials: true, + }); + + return seat.data as ISeat; + } catch (error) { + throw error; + } +} + +export async function GetFullBookingData( + flightId: number, + seatId: number +): Promise<IBookingData> { + try { + const flight = Api.get(`Flight/${flightId}`, { withCredentials: true }); + const seats = Api.get(`Seat/${seatId}/seats`, { + withCredentials: true, + }); + const [fdata, sdata] = await Promise.all([flight, seats]); + return { + flight: fdata.data, + seat: sdata.data, + }; + } catch (error) { + throw error; + } +} diff --git a/client/src/services/CustomerBookings/CustomerBookings.ts b/client/src/services/CustomerBookings/CustomerBookings.ts new file mode 100644 index 0000000..c01b71f --- /dev/null +++ b/client/src/services/CustomerBookings/CustomerBookings.ts @@ -0,0 +1,26 @@ +import Api from "../../helpers/Api"; +import { IFlight, ISeat } from "../Dashboard/CustomerDashboard"; + +export interface IBookingInfo { + id: number; + flightId: number; + userId: number; + bookingClass: number; + seatId?: number; +} + +export interface IBookingData { + flight: IFlight; + seat: ISeat; +} + +export async function GetAllBookings(): Promise<IBookingInfo[]> { + const url = `Booking`; + try { + const booking = await Api.get(`${url}`, { withCredentials: true }); + + return booking.data as IBookingInfo[]; + } catch (error) { + throw error; + } +} diff --git a/client/src/services/Dashboard/AirlineDashboard.ts b/client/src/services/Dashboard/AirlineDashboard.ts index b0e1dda..b088499 100644 --- a/client/src/services/Dashboard/AirlineDashboard.ts +++ b/client/src/services/Dashboard/AirlineDashboard.ts @@ -23,24 +23,30 @@ export interface IFlight { businessCapacity: number; economyPrice: number; businessPrice: number; - seats: ISeats + seats: ISeats; } export interface IAirlineDashboardData { - type: 'airline' + type: "airline"; flightList: IFlight[]; } -export async function GetAirlineDashboardData({request}: {request: Request}): Promise<IAirlineDashboardData> { +export async function GetAirlineDashboardData({ + request, +}: { + request: Request; +}): Promise<IAirlineDashboardData> { try { const id = UserStorage.getUserId(); - const response = await Api.get(`Flight?airlineId=${id}`, { withCredentials: true }); + const response = await Api.get(`Flight?airlineId=${id}`, { + withCredentials: true, + }); const flights = response.data.$values; return { - type: 'airline', - flightList: flights + type: "airline", + flightList: flights, }; } catch (error) { throw error; } -} \ No newline at end of file +} -- GitLab