diff --git a/Templates/login-register/login.html b/Templates/login-register/login.html new file mode 100644 index 0000000000000000000000000000000000000000..092908ccc5f9ce4e7c31f84b25ec6bd3f07dd3a2 --- /dev/null +++ b/Templates/login-register/login.html @@ -0,0 +1,31 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Airline Booking - Login</title> + <link rel="stylesheet" href="login_register_style.css"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> +</head> +<body> + <div class="container"> + <form> + <div class="form-group"> + <label for="exampleInputEmail1">Email address</label> + <input type="email" class="form-control" id="exampleInputEmail1" aria-describedby="emailHelp" placeholder="Enter email"> + </div> + <div class="form-group"> + <label for="exampleInputPassword1">Password</label> + <input type="password" class="form-control" id="exampleInputPassword1" placeholder="Password"> + </div> + <div class="form-group form-check"> + <!-- <input type="checkbox" class="form-check-input" id="exampleCheck1"> --> + <!-- <label class="form-check-label" for="exampleCheck1">Check me out</label> --> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + </div> +</body> +</html> \ No newline at end of file diff --git a/Templates/login-register/login_register_style.css b/Templates/login-register/login_register_style.css new file mode 100644 index 0000000000000000000000000000000000000000..51fe1c5f4cf17c3b25ed2b1397b08be2a1046dad --- /dev/null +++ b/Templates/login-register/login_register_style.css @@ -0,0 +1,15 @@ +form { + width: 30%; + border: 1px solid #ccc; /* Example border: 1px solid with color #ccc */ + padding: 20px; + border-radius: 5px; /* Optional: if you want rounded corners */ + background-color: #ffffff; + +} + +.container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; +} \ No newline at end of file diff --git a/Templates/login-register/register.html b/Templates/login-register/register.html new file mode 100644 index 0000000000000000000000000000000000000000..88252690fc4f234f0faa3399d12a9a320effc4e0 --- /dev/null +++ b/Templates/login-register/register.html @@ -0,0 +1,36 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Airline Booking - Register</title> + <link rel="stylesheet" href="login_register_style.css"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> + <script src="register.js"></script> +</head> +<body> + <div class="container"> + <form onsubmit="return checkPasswords()"> + <div class="form-group"> + <label for="inputFullName">Full name</label> + <input type="text" class="form-control" id="inputFullName" placeholder="Full name"> + </div> + <div class="form-group"> + <label for="inputEmail">Email address</label> + <input type="email" class="form-control" id="inputEmail" placeholder="Enter email"> + </div> + <div class="form-group"> + <label for="inputPassword">Password</label> + <input type="password" class="form-control" id="inputPassword" placeholder="Password"> + </div> + <div class="form-group"> + <label for="inputConfirmPassword">Confirm password</label> + <input type="password" class="form-control" id="inputConfirmPassword" placeholder="Confirm Password"> + </div> + <button type="submit" class="btn btn-primary">Submit</button> + </form> + </div> +</body> +</html> \ No newline at end of file diff --git a/Templates/login-register/register.js b/Templates/login-register/register.js new file mode 100644 index 0000000000000000000000000000000000000000..a4f443ddbcb5f90f6b395c532028511aa0b5ea74 --- /dev/null +++ b/Templates/login-register/register.js @@ -0,0 +1,15 @@ +function checkPasswords() { + var password = document.getElementById('inputPassword').value; + var confirmPassword = document.getElementById('inputConfirmPassword').value; + + if (password === confirmPassword) { + // The passwords match + // You can add code here to handle the form submission + return true; // return true to submit the form + } else { + // The passwords do not match + // Alert the user + alert("The passwords do not match!"); + return false; // return false to prevent form submission + } +} \ No newline at end of file diff --git a/Templates/user_profile/avatar.jpg b/Templates/user_profile/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6cc1140f400c6e47c6ac1ddf985ee188f92c4ae2 Binary files /dev/null and b/Templates/user_profile/avatar.jpg differ diff --git a/Templates/user_profile/profile.css b/Templates/user_profile/profile.css new file mode 100644 index 0000000000000000000000000000000000000000..44709d293b4e4371a128b360ee799ddb76f9813c --- /dev/null +++ b/Templates/user_profile/profile.css @@ -0,0 +1,53 @@ +body{ + margin-top:20px; + color: #1a202c; + text-align: left; + background-color: #e2e8f0; +} +.main-body { + padding: 15px; +} +.card { + box-shadow: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06); +} + +.card { + position: relative; + display: flex; + flex-direction: column; + min-width: 0; + word-wrap: break-word; + background-color: #fff; + background-clip: border-box; + border: 1px solid #ddd; /* Light grey border */ + border-radius: 4px; +} + +.card-body { + flex: 1 1 auto; + min-height: 1px; + padding: 1rem; +} + +.gutters-sm { + margin-right: -8px; + margin-left: -8px; +} + +.gutters-sm>.col, .gutters-sm>[class*=col-] { + padding-right: 8px; + padding-left: 8px; +} +.mb-3, .my-3 { + margin-bottom: 1rem!important; +} + +.bg-gray-300 { + background-color: #e2e8f0; +} +.h-100 { + height: 100%!important; +} +.shadow-none { + box-shadow: none!important; +} \ No newline at end of file diff --git a/Templates/user_profile/profile.html b/Templates/user_profile/profile.html new file mode 100644 index 0000000000000000000000000000000000000000..68302e72d1107e459360f835062c811d8b6eab52 --- /dev/null +++ b/Templates/user_profile/profile.html @@ -0,0 +1,158 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Airline Booking - Profile</title> + <link rel="stylesheet" href="profile.css"> + <link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/css/bootstrap.min.css"> + <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.7.1/jquery.min.js"></script> + <script src="https://maxcdn.bootstrapcdn.com/bootstrap/3.4.1/js/bootstrap.min.js"></script> + <script src="profile_script.js"></script> +</head> +<body> + <div class="container"> + <div class="main-body"> + + <div class="row gutters-sm"> + <div class="col-md-4 mb-3"> + <div class="card"> + <div class="card-body"> + <div class="d-flex flex-column align-items-center text-center"> + <img src="avatar.jpg" alt="Admin" class="rounded-circle" width="150"> + <div class="mt-3"> + <h4 id="profileName">Illya Globa</h4> + <p class="text-secondary mb-1">Loyal Customer</p> + <p class="text-muted font-size-sm">Guildford</p> + + </div> + </div> + </div> + </div> + </div> + <div class="col-md-8"> + <div class="card mb-3"> + <div class="card-body"> + <div class="row"> + <div class="col-sm-3"> + <h6 class="mb-0">Full Name</h6> + </div> + <div class="col-sm-9 text-secondary"> + <input type="text" id="fullName" class="form-control" value="Illya Globa" readonly> + </div> + </div> + <hr> + <div class="row"> + <div class="col-sm-3"> + <h6 class="mb-0">Email</h6> + </div> + <div class="col-sm-9 text-secondary"> + <input type="email" id="email" class="form-control" value="ig@surrey.ac.uk" readonly> + </div> + </div> + <hr> + <div class="row"> + <div class="col-sm-3"> + <h6 class="mb-0">Password</h6> + </div> + <div class="col-sm-9 text-secondary"> + <input type="password" id="password" class="form-control" value="password" readonly> + </div> + </div> + <hr> + <div class="row"> + <div class="col-sm-12"> + <button class="btn btn-primary" onclick="editProfile()">Edit</button> + </div> + </div> + </div> + </div> + </div> + + </div> + + </div> + <div class="row"> + <div class="col-md-12"> + <h3>Upcoming Flights</h3> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <!-- If you have another flight card add it here as another .col-md-6 --> + </div> + + <div class="row" style="margin-top: 20px;"> + <div class="col-md-12"> + <h3>Flights History</h3> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <div class="col-md-3"> + <div class="card" style="width: 25rem;"> + <div class="card-body"> + <h5 class="card-title">Flight number</h5> + <h6 class="card-subtitle mb-2 text-muted">LTN - MLG</h6> + <p class="card-text">London(LTN) - Spain(MLG)</p> + <a href="#" class="card-link">Card link</a> + <a href="#" class="card-link">Another link</a> + </div> + </div> + </div> + <!-- If you have another flight card add it here as another .col-md-6 --> + </div> + <button style="margin-top: 10px;" type="submit" class="btn btn-primary">View more</button> + </div> + </div> +</body> +</html> \ No newline at end of file diff --git a/Templates/user_profile/profile_script.js b/Templates/user_profile/profile_script.js new file mode 100644 index 0000000000000000000000000000000000000000..0a70508a83911d2a9ea0943f159c7e5d093a0580 --- /dev/null +++ b/Templates/user_profile/profile_script.js @@ -0,0 +1,19 @@ +function editProfile() { + // Get the input fields + var fullNameField = document.getElementById('fullName'); + var emailField = document.getElementById('email'); + var passwordField = document.getElementById('password'); + + // Check if the input fields are readonly - if so, make them editable + if (fullNameField.readOnly === true) { + fullNameField.readOnly = false; + emailField.readOnly = false; + passwordField.readOnly = false; + fullNameField.focus(); // Set focus on the name field to start editing + } else { + fullNameField.readOnly = true; + emailField.readOnly = true; + passwordField.readOnly = true; + // Here you can also add an AJAX call to save the data if needed + } +} \ No newline at end of file diff --git a/Templates/user_profile/user.png b/Templates/user_profile/user.png new file mode 100644 index 0000000000000000000000000000000000000000..063717636aec642b496d4c65e975311aa01fd429 Binary files /dev/null and b/Templates/user_profile/user.png differ diff --git a/client/Dockerfile b/client/Dockerfile index cccc489a054978e3e1022c349a8a8827a1c3b523..4a1664c0911d5b65f6c67e052ac4ffa1fc2e4d14 100644 --- a/client/Dockerfile +++ b/client/Dockerfile @@ -18,4 +18,4 @@ COPY nginx.conf /etc/nginx/nginx.conf EXPOSE 4200 -CMD ["nginx", "-g", "daemon off;"] \ No newline at end of file +CMD ["nginx", "-g", "daemon off;"] diff --git a/client/nginx.conf b/client/nginx.conf index 59ed87c13ae13e5d21fe314e1fbc2759fe9c4db4..3cdf0cf6541a390af6f9d2bd2e20695d65200264 100644 --- a/client/nginx.conf +++ b/client/nginx.conf @@ -1,5 +1,3 @@ -# nginx.conf - events {} http { @@ -22,4 +20,4 @@ http { try_files $uri $uri/ /index.html; } } -} \ No newline at end of file +} diff --git a/client/package-lock.json b/client/package-lock.json index 73992c36fcca27fe55d8775b1a95e946b24e0b03..2c4f76b8a7810c379ce095d0021fc084bb5774f7 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -11,6 +11,7 @@ "axios": "^1.6.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.51.1", "react-router-dom": "^6.22.3" }, "devDependencies": { @@ -3007,6 +3008,21 @@ "react": "^18.2.0" } }, + "node_modules/react-hook-form": { + "version": "7.51.1", + "resolved": "https://registry.npmjs.org/react-hook-form/-/react-hook-form-7.51.1.tgz", + "integrity": "sha512-ifnBjl+kW0ksINHd+8C/Gp6a4eZOdWyvRv0UBaByShwU8JbVx5hTcTWEcd5VdybvmPTATkVVXk9npXArHmo56w==", + "engines": { + "node": ">=12.22.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/react-hook-form" + }, + "peerDependencies": { + "react": "^16.8.0 || ^17 || ^18" + } + }, "node_modules/react-refresh": { "version": "0.14.0", "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", diff --git a/client/package.json b/client/package.json index 0edf71c37302d8bbb91d17b3d715c495494be36a..a21d66b99490c3203bd3046bb815f5dd457cc81a 100644 --- a/client/package.json +++ b/client/package.json @@ -13,6 +13,7 @@ "axios": "^1.6.7", "react": "^18.2.0", "react-dom": "^18.2.0", + "react-hook-form": "^7.51.1", "react-router-dom": "^6.22.3" }, "devDependencies": { diff --git a/client/src/App.css b/client/src/App.css deleted file mode 100644 index 3876ac70a4d8e150fd30970bb8d172a08238dddb..0000000000000000000000000000000000000000 --- a/client/src/App.css +++ /dev/null @@ -1,8 +0,0 @@ -.wrapper { - display: flex; - flex-direction: column; -} - -.main { - height: 85vh; -} \ No newline at end of file diff --git a/client/src/App.scss b/client/src/App.scss new file mode 100644 index 0000000000000000000000000000000000000000..859adee2c1a10d1779f529e7c580a9c393c53aae --- /dev/null +++ b/client/src/App.scss @@ -0,0 +1,15 @@ +.wrapper { + display: flex; + flex-direction: column; + height: 100vh; +} + +.main { + height: 100%; + overflow-y: scroll; + padding: 2rem 0; +} + +.footer-wrapper { + margin-top: auto; +} \ No newline at end of file diff --git a/client/src/App.tsx b/client/src/App.tsx index 8da0dfa36131a00464969e6c8a759a9f9871d6cf..da27b35aa08b60d36e2467903ecb2c49970abaf2 100644 --- a/client/src/App.tsx +++ b/client/src/App.tsx @@ -1,21 +1,22 @@ -import './App.css'; import Header from './components/Header/Header'; import Footer from './components/Footer/Footer'; -import { Outlet, useLoaderData } from 'react-router-dom'; -import { IFact } from './services/Api/Api'; +import { Outlet } from 'react-router-dom'; +import './App.scss'; function App() { - const data = useLoaderData() as IFact; + // const data = useLoaderData() as IFact; return ( <> <div className='wrapper'> <Header></Header> <div className='main'> - {data.fact} + {/* {data.fact} */} <Outlet></Outlet> </div> - <Footer></Footer> + <div className='footer-wrapper'> + <Footer></Footer> + </div> </div> </> ); diff --git a/client/src/components/.gitkeep b/client/src/components/.gitkeep deleted file mode 100644 index e69de29bb2d1d6434b8b29ae775ad8c2e48c5391..0000000000000000000000000000000000000000 diff --git a/client/src/components/CustomerDashboard/CustomerDashboard.scss b/client/src/components/CustomerDashboard/CustomerDashboard.scss new file mode 100644 index 0000000000000000000000000000000000000000..559bc051b0f758a22437dd4468203ffcade689d7 --- /dev/null +++ b/client/src/components/CustomerDashboard/CustomerDashboard.scss @@ -0,0 +1,105 @@ +.customer-dashboard { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + width: 100%; + gap: 2rem; +} + +.avatar { + width: 150px; +} + +.customer-profile { + display: flex; + flex-direction: row; + gap: 1.5rem; +} + +.bio-card { + padding: 1.5rem 5rem !important; // Sorry +} + +.form-h { + display: flex; + flex-direction: column; + gap: 1rem; + + .form-group-h { + display: flex; + justify-content: center; + align-items: center; + gap: 1rem; + + label { + font-weight: 700; + width: 180px; + } + + input { + font-size: 1rem; + padding: 6px 12px; + border: 1px solid gray; + border-radius: 5px; + width: 300px; + } + + button { + font-size: 1rem; + padding: 6px 12px; + border: 1px solid gray; + border-radius: 5px; + background-color: var(--main); + color: white; + } + + button:hover { + filter: brightness(50%); + cursor: pointer; + } + + button:disabled { + filter: brightness(40%); + } + + button:disabled:hover { + cursor: auto; + } + + span { + overflow: hidden; + color: red; + } + } +} + +.flights-title { + font-size: 2rem; +} + +.flights { + display: flex; + flex-direction: column; + width: 80vw; + gap: 0.5rem; +} + +.flight-list { + display: flex; + gap: 1rem; +} + +.view-more { + font-size: 1rem; + padding: 6px 12px; + border: 1px solid gray; + border-radius: 5px; + background-color: var(--main); + color: white; +} + +.view-more:hover { + filter: brightness(50%); + cursor: pointer; +} \ No newline at end of file diff --git a/client/src/components/CustomerDashboard/CustomerDashboard.tsx b/client/src/components/CustomerDashboard/CustomerDashboard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..006b7dda350f5c8376938dfa61fd91babf31b3a2 --- /dev/null +++ b/client/src/components/CustomerDashboard/CustomerDashboard.tsx @@ -0,0 +1,128 @@ +import { useState } from 'react'; +import { useLoaderData } from 'react-router-dom'; +import { useForm } from 'react-hook-form'; +import { ICustomerDashboardData } from '../../services/CustomerDashboard/CustomerDashboard'; +import FlightCard from './FlightCard/FlightCard'; +import avatar from './avatar.jpg'; +import './CustomerDashboard.scss'; + +interface ICustomerDashboardForm { + name: string; + email: string; + password: string; + confirmPassword: string; +} + +function CustomerDashboard() { + const [error, setError] = useState(''); + const [disabled, setDisabled] = useState(true); + const data = useLoaderData() as ICustomerDashboardData; + const formValues: ICustomerDashboardForm = { + name: data.name, + email: data.email, + password: '', + confirmPassword: '' + }; + const { register, handleSubmit } = useForm<ICustomerDashboardForm>({mode: 'onChange', defaultValues: formValues}); + + const toggleEdit = () => { + setDisabled(!disabled); + }; + + const onSubmit = (formValue: ICustomerDashboardForm) => { + if (formValue.password.length < 7) { + setError('password length must be greater than 7 characters'); + return; + } + + if (formValue.password !== formValue.confirmPassword) { + setError('password and confirm password must match'); + return; + } + + setError(''); + console.log('ready to make update details api call'); + }; + + return ( + <> + <div className='customer-dashboard'> + <div className='customer-profile'> + <div className='customer-profile-bio'> + <div className='card bio-card'> + <div className='flex'> + <img src={avatar} alt='avatar' className='avatar'></img> + <span>{data.name}</span> + <span>Loyal Customer</span> + </div> + </div> + </div> + + <div className='customer-profile-fields'> + <div className='card'> + <form onSubmit={handleSubmit(onSubmit)}> + <div className='form-h'> + <div className='form-group-h'> + <label>Full Name</label> + <input type='text' {...register('name', { required: true, disabled })} /> + </div> + + <div className='form-group-h'> + <label>Email</label> + <input type='email' {...register('email', { required: true, disabled })} /> + </div> + + <div className='form-group-h'> + <label>Password</label> + <input type='password' placeholder='Enter new password' {...register('password', { required: true, disabled })} /> + </div> + + <div className='form-group-h'> + <label>Confirm Password</label> + <input type='password' placeholder='Confirm new password' {...register('confirmPassword', { required: true, disabled })} /> + </div> + + <div className='form-group-h'> + <button type='button' onClick={toggleEdit}>Toggle Edit</button> + <button type='submit' disabled={disabled}>Submit</button> + </div> + + <div className='form-group-h'> + {error && <span>{error}</span>} + </div> + </div> + </form> + </div> + </div> + </div> + + <div className='flights'> + <span className='flights-title'>Upcoming Flights</span> + <div className='flight-list'> + {data.upcomingFlights.length > 0 + ? data.upcomingFlights.map((flight) => { + return <FlightCard key={flight.id} flight={flight}></FlightCard> + }) + : <div>No Upcoming Flights</div>} + </div> + </div> + + <div className='flights'> + <span className='flights-title'>Flights History</span> + <div className='flight-list'> + {data.upcomingFlights.length > 0 + ? data.flightsHistory.map((flight) => { + return <FlightCard key={flight.id} flight={flight}></FlightCard> + }) + : <div>No Flights History</div>} + </div> + <div> + <button type='button' className='view-more'>View More</button> + </div> + </div> + </div> + </> + ); +} + +export default CustomerDashboard; \ No newline at end of file diff --git a/client/src/components/CustomerDashboard/FlightCard/FlightCard.scss b/client/src/components/CustomerDashboard/FlightCard/FlightCard.scss new file mode 100644 index 0000000000000000000000000000000000000000..639c3a9598ca594a7abf90b6cb70ff7498ba02ce --- /dev/null +++ b/client/src/components/CustomerDashboard/FlightCard/FlightCard.scss @@ -0,0 +1,16 @@ +.flight-card { + display: flex; + flex-direction: column; + gap: 1rem; + border: 1px solid #ddd; + border-radius: 4px; + background-color: white; + padding: 1rem; + box-shadow: 0 1px 3px 0 rgba(0,0,0,.1), 0 1px 2px 0 rgba(0,0,0,.06); + width: max-content; +} + +.flight-path { + color: #777; + font-size: 0.8rem; +} \ No newline at end of file diff --git a/client/src/components/CustomerDashboard/FlightCard/FlightCard.tsx b/client/src/components/CustomerDashboard/FlightCard/FlightCard.tsx new file mode 100644 index 0000000000000000000000000000000000000000..9e20e2b1af7b11c7302a951085858012948aad26 --- /dev/null +++ b/client/src/components/CustomerDashboard/FlightCard/FlightCard.tsx @@ -0,0 +1,28 @@ +import { Link } from 'react-router-dom'; +import './FlightCard.scss'; + +interface IFlight { + id: number; + flightNumber: string; + flightPath: string; + flightPathFull: string; +} + +interface IFlightCard { + flight: IFlight; +} + +function FlightCard({ flight }: IFlightCard) { + return ( + <> + <div className='flight-card'> + <span>{flight.flightNumber}</span> + <span className='flight-path'>{flight.flightPath}</span> + <span>{flight.flightPathFull}</span> + <Link to={'/flights/' + flight.id}>View Flight</Link> + </div> + </> + ); +} + +export default FlightCard; \ No newline at end of file diff --git a/client/src/components/CustomerDashboard/avatar.jpg b/client/src/components/CustomerDashboard/avatar.jpg new file mode 100644 index 0000000000000000000000000000000000000000..6cc1140f400c6e47c6ac1ddf985ee188f92c4ae2 Binary files /dev/null and b/client/src/components/CustomerDashboard/avatar.jpg differ diff --git a/client/src/components/Login/Login.scss b/client/src/components/Login/Login.scss new file mode 100644 index 0000000000000000000000000000000000000000..1d2060b561763fe1b18e3ca65d965688d744dbc6 --- /dev/null +++ b/client/src/components/Login/Login.scss @@ -0,0 +1,12 @@ +.login { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.login-card { + width: 20vw; + min-width: 350px; +} \ No newline at end of file diff --git a/client/src/components/Login/Login.tsx b/client/src/components/Login/Login.tsx new file mode 100644 index 0000000000000000000000000000000000000000..3c3ecf232ce68c4ff92b11173b38d45b9b9b36af --- /dev/null +++ b/client/src/components/Login/Login.tsx @@ -0,0 +1,48 @@ +import { useForm } from 'react-hook-form'; +import { useState } from 'react'; +import './Login.scss'; + +interface ILogin { + email: string; + password: string; +} + +export function Login() { + const [error, setError] = useState(''); + const { register, handleSubmit } = useForm<ILogin>({mode: 'onChange'}); + + const onSubmit = (formValue: ILogin) => { + setError('TODO: remove me once actual errors are implemented'); + console.log('ready to make login api call', formValue); + }; + + return ( + <> + <div className='login'> + <form onSubmit={handleSubmit(onSubmit)}> + <div className='card login-card'> + <div className='form-group'> + <label>Email Address</label> + <input type='email' placeholder='Enter email' {...register('email', { required: true })} /> + </div> + + <div className='form-group'> + <label>Password</label> + <input type='password' placeholder='Enter password' {...register('password', { required: true })} /> + </div> + + <div className='form-group'> + <button type='submit'>Submit</button> + </div> + + <div className='form-group'> + {error && <span>{error}</span>} + </div> + </div> + </form> + </div> + </> + ); +} + +export default Login; \ No newline at end of file diff --git a/client/src/components/Register/Register.scss b/client/src/components/Register/Register.scss new file mode 100644 index 0000000000000000000000000000000000000000..c5cef15a81dd904f3284c00cb0c1a361b1072d2a --- /dev/null +++ b/client/src/components/Register/Register.scss @@ -0,0 +1,12 @@ +.register { + display: flex; + align-items: center; + justify-content: center; + width: 100%; + height: 100%; +} + +.register-card { + width: 20vw; + min-width: 350px; +} \ No newline at end of file diff --git a/client/src/components/Register/Register.tsx b/client/src/components/Register/Register.tsx new file mode 100644 index 0000000000000000000000000000000000000000..8c216b6fd6bfd66db4c80a04d8f47b8a5aa5f830 --- /dev/null +++ b/client/src/components/Register/Register.tsx @@ -0,0 +1,70 @@ +import { useForm } from 'react-hook-form'; +import { useState } from 'react'; +import './Register.scss'; + +interface IRegister { + name: string; + email: string; + password: string; + confirmPassword: string; +} + +export function Register() { + const [error, setError] = useState(''); + const { register, handleSubmit } = useForm<IRegister>({mode: 'onChange'}); + + const onSubmit = (formValue: IRegister) => { + if (formValue.password.length < 7) { + setError('password length must be greater than 6 characters'); + return; + } + + if (formValue.password !== formValue.confirmPassword) { + setError('password and confirm password must match'); + return; + } + + setError(''); + console.log('ready to make register api call'); + }; + + return ( + <> + <div className='register'> + <form onSubmit={handleSubmit(onSubmit)}> + <div className='card register-card'> + <div className='form-group'> + <label>Full Name</label> + <input type='text' placeholder='Full name' {...register('name', { required: true })} /> + </div> + + <div className='form-group'> + <label>Email Address</label> + <input type='email' placeholder='Enter email' {...register('email', { required: true })} /> + </div> + + <div className='form-group'> + <label>Password</label> + <input type='password' placeholder='Enter password' {...register('password', { required: true })} /> + </div> + + <div className='form-group'> + <label>Confirm Password</label> + <input type='password' placeholder='Confirm password' {...register('confirmPassword', { required: true })} /> + </div> + + <div className='form-group'> + <button type='submit'>Submit</button> + </div> + + <div className='form-group'> + {error && <span>{error}</span>} + </div> + </div> + </form> + </div> + </> + ); +} + +export default Register; \ No newline at end of file diff --git a/client/src/index.css b/client/src/index.css deleted file mode 100644 index d8a001f0ec7136515ce361a7c36f6859ef69031b..0000000000000000000000000000000000000000 --- a/client/src/index.css +++ /dev/null @@ -1,18 +0,0 @@ -@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap'); - -/* Unifies font across frontend */ -* { - font-family: "Lato", sans-serif; - font-weight: 400; - font-style: normal; -} - -/* Stops whitespace surrounding page */ -body { - margin: 0; -} - -/* CSS Variables */ -:root { - --main: #000055; -} \ No newline at end of file diff --git a/client/src/index.scss b/client/src/index.scss new file mode 100644 index 0000000000000000000000000000000000000000..7c138bb925cbdd8450961fe028f74e52289f6638 --- /dev/null +++ b/client/src/index.scss @@ -0,0 +1,78 @@ +@import url('https://fonts.googleapis.com/css2?family=Lato&display=swap'); + +/* Unifies font across frontend */ +* { + font-family: "Lato", sans-serif; + font-weight: 400; + font-style: normal; +} + +/* Stops whitespace surrounding page */ +body { + margin: 0; +} + +/* CSS Variables */ +:root { + --main: #000055; +} + +/* +** +** Global CSS classes: +** +*/ + +.card { + display: flex; + flex-direction: column; + gap: 1rem; + border: 1px solid gray; + border-radius: 5px; + background-color: white; + padding: 1.5rem; +} + +.form-group { + display: flex; + flex-direction: column; + + label { + font-weight: 700; + margin-bottom: 0.5rem; + } + + input { + font-size: 1rem; + padding: 6px 12px; + border: 1px solid gray; + border-radius: 5px; + } + + button { + font-size: 1rem; + padding: 6px 12px; + border: 1px solid gray; + border-radius: 5px; + background-color: var(--main); + color: white; + } + + button:hover { + filter: brightness(50%); + cursor: pointer; + } + + span { + overflow: hidden; + color: red; + } +} + +.flex { + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 1rem; +} \ No newline at end of file diff --git a/client/src/main.tsx b/client/src/main.tsx index ac9c0a0a28db92a146f2c0e16465922fcbe7df7f..b877eb5a8c28e76afc62b128e75e4dcde01b11ca 100644 --- a/client/src/main.tsx +++ b/client/src/main.tsx @@ -2,20 +2,30 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import { createBrowserRouter, RouterProvider } from 'react-router-dom'; import App from './App.tsx'; -import './index.css'; -import { getFact } from './services/Api/Api.ts'; +import Login from './components/Login/Login.tsx'; +import Register from './components/Register/Register.tsx'; +import CustomerDashboard from './components/CustomerDashboard/CustomerDashboard.tsx'; +import { GetCustomerDashboardData } from './services/CustomerDashboard/CustomerDashboard.ts'; +import './index.scss'; const router = createBrowserRouter([ { path: '/', element: <App></App>, - loader: getFact, + // loader: getFact, children: [ - // TODO: Remove this path { - path: 'test', - element: <div>test</div>, - // loader: getFact, + path: 'login', + element: <Login></Login> + }, + { + path: 'register', + element: <Register></Register> + }, + { + path: 'customer-dashboard', + loader: GetCustomerDashboardData, + element: <CustomerDashboard></CustomerDashboard> } ] } diff --git a/client/src/services/CustomerDashboard/CustomerDashboard.ts b/client/src/services/CustomerDashboard/CustomerDashboard.ts new file mode 100644 index 0000000000000000000000000000000000000000..81c852efa4bd605cf45282c2f66e463fb7fd395d --- /dev/null +++ b/client/src/services/CustomerDashboard/CustomerDashboard.ts @@ -0,0 +1,61 @@ + +export interface IFlight { + id: number; + flightNumber: string; + flightPath: string; + flightPathFull: string; +} + +export interface ICustomerDashboardData { + name: string; + email: string; + upcomingFlights: IFlight[]; + flightsHistory: IFlight[]; +} + +export async function GetCustomerDashboardData(): Promise<ICustomerDashboardData> { + return { + name: 'fname lname', + email: 'test@test.com', + upcomingFlights: [ + { + id: 4, + flightNumber: '0004', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + }, + { + id: 5, + flightNumber: '0005', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + }, + { + id: 6, + flightNumber: '0006', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + } + ], + flightsHistory: [ + { + id: 1, + flightNumber: '0001', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + }, + { + id: 2, + flightNumber: '0002', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + }, + { + id: 3, + flightNumber: '0003', + flightPath: 'LTN - MLG', + flightPathFull: 'London(LTN) - Spain(MLG)' + } + ] + } +} \ No newline at end of file