This commit is contained in:
2025-03-12 22:10:59 +05:30
parent f677224f5c
commit 2ea4a0957f
9 changed files with 386 additions and 37 deletions

24
src/templates/footer.html Normal file
View File

@@ -0,0 +1,24 @@
</main>
<footer class="bg-dark text-light py-4 mt-5">
<div class="container">
<div class="row">
<div class="col-md-6">
<h5>Air Bookings</h5>
<p>Your trusted partner for air travel bookings.</p>
<p>&copy; 2024 Air Bookings. All rights reserved.</p>
</div>
<div class="col-md-6">
<h5>Connect With Us</h5>
<div class="social-links">
<a href="#" class="text-light me-3"><i class="bi bi-facebook"></i></a>
<a href="#" class="text-light me-3"><i class="bi bi-twitter"></i></a>
<a href="#" class="text-light me-3"><i class="bi bi-instagram"></i></a>
<a href="#" class="text-light"><i class="bi bi-linkedin"></i></a>
</div>
</div>
</div>
</div>
</footer>
@js '/static/js/bootstrap.js'
</body>
</html>

35
src/templates/header.html Normal file
View File

@@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Air Bookings</title>
@css '/static/css/bootstrap.css'
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.7.2/font/bootstrap-icons.css">
</head>
<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-primary">
<div class="container">
<a class="navbar-brand" href="/">Air Bookings</a>
<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-target="#navbarNav">
<span class="navbar-toggler-icon"></span>
</button>
<div class="collapse navbar-collapse" id="navbarNav">
<ul class="navbar-nav ms-auto">
<li class="nav-item">
<a class="nav-link" href="/">Home</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/profile">Profile</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/login">Login</a>
</li>
<li class="nav-item">
<a class="nav-link" href="/signup">Sign Up</a>
</li>
</ul>
</div>
</div>
</nav>
<main>

View File

@@ -101,7 +101,7 @@
margin-top: 5px; margin-top: 5px;
display: none; display: none;
} }
/* Field error styling */ /* Field error styling */
.field-error { .field-error {
color: #e74c3c; color: #e74c3c;
@@ -109,37 +109,37 @@
font-weight: normal; font-weight: normal;
margin-left: 5px; margin-left: 5px;
} }
/* Style for input fields with errors */ /* Style for input fields with errors */
.input-error { .input-error {
border: 1px solid #e74c3c !important; border: 1px solid #e74c3c !important;
background-color: #fff8f8; background-color: #fff8f8;
} }
/* Spinner styling */ /* Spinner styling */
.spinner { .spinner {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
color: #666; color: #666;
} }
.htmx-request .htmx-indicator { .htmx-request .htmx-indicator {
display: block !important; display: block !important;
} }
/* Alert styling */ /* Alert styling */
.alert { .alert {
padding: 15px; padding: 15px;
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 4px; border-radius: 4px;
} }
.alert-danger { .alert-danger {
background-color: #f8d7da; background-color: #f8d7da;
color: #721c24; color: #721c24;
border: 1px solid #f5c6cb; border: 1px solid #f5c6cb;
} }
.alert-success { .alert-success {
background-color: #d4edda; background-color: #d4edda;
color: #155724; color: #155724;
@@ -177,7 +177,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email Address <span class="field-error email-error"></span></label> <label for="email"
>Email Address
<span class="field-error email-error"></span
></label>
<input <input
type="email" type="email"
id="email" id="email"
@@ -190,7 +193,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password <span class="field-error password-error"></span></label> <label for="password"
>Password
<span class="field-error password-error"></span
></label>
<input <input
type="password" type="password"
id="password" id="password"
@@ -203,17 +209,21 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<button type="submit" class="login-btn"> <button type="submit" class="login-btn">Sign In</button>
Sign In
</button>
</div> </div>
<div class="form-group text-center" style="margin-top: 20px;"> <div
<p>Don't have an account? <a href="/signup">Sign up now</a></p> class="form-group text-center"
style="margin-top: 20px"
>
<p>
Don't have an account?
<a href="/signup">Sign up now</a>
</p>
</div> </div>
</form> </form>
</section> </section>
</div> </div>
</body> </body>
@js '/static/js/bootstrap.js' @js '/static/js/bootstrap.js'
</html> </html>

View File

@@ -0,0 +1,85 @@
@include 'header.html'
<div class="container mt-5">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card">
<div class="card-header bg-primary text-white">
<h3>My Profile</h3>
</div>
<div class="card-body">
<div class="mb-4">
<h4>@{user.first_name} @{user.last_name}</h4>
<p><i class="bi bi-envelope"></i> @{user.email}</p>
<p><i class="bi bi-gender-ambiguous"></i> @{user.gender}</p>
</div>
<div class="card mb-4">
<div class="card-header">
<h5>Update Profile</h5>
</div>
<div class="card-body">
<form action="/controller/user/update" method="post">
<input type="hidden" name="id" value="@{user.id}">
<div class="mb-3">
<label for="first_name" class="form-label">First Name</label>
<input type="text" class="form-control" id="first_name" name="first_name" value="@{user.first_name}">
<div class="text-danger first_name-error field-error"></div>
</div>
<div class="mb-3">
<label for="last_name" class="form-label">Last Name</label>
<input type="text" class="form-control" id="last_name" name="last_name" value="@{user.last_name}">
<div class="text-danger last_name-error field-error"></div>
</div>
<div class="mb-3">
<label for="password" class="form-label">New Password (leave empty to keep current)</label>
<input type="password" class="form-control" id="password" name="password">
<div class="text-danger password-error field-error"></div>
</div>
<div class="d-grid">
<button type="submit" class="btn btn-primary">Update Profile</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Account Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/logout" class="btn btn-danger">Logout</a>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="response-container"></div>
<script>
document.querySelector("form").addEventListener("submit", function (e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
fetch(form.action, {
method: "POST",
body: formData,
})
.then((response) => response.text())
.then((html) => {
document.getElementById("response-container").innerHTML = html;
});
});
</script>
@include 'footer.html'

View File

@@ -133,7 +133,7 @@
margin-top: 5px; margin-top: 5px;
display: none; display: none;
} }
/* Field error styling */ /* Field error styling */
.field-error { .field-error {
color: #e74c3c; color: #e74c3c;
@@ -141,37 +141,37 @@
font-weight: normal; font-weight: normal;
margin-left: 5px; margin-left: 5px;
} }
/* Style for input fields with errors */ /* Style for input fields with errors */
.input-error { .input-error {
border: 1px solid #e74c3c !important; border: 1px solid #e74c3c !important;
background-color: #fff8f8; background-color: #fff8f8;
} }
/* Spinner styling */ /* Spinner styling */
.spinner { .spinner {
text-align: center; text-align: center;
padding: 10px; padding: 10px;
color: #666; color: #666;
} }
.htmx-request .htmx-indicator { .htmx-request .htmx-indicator {
display: block !important; display: block !important;
} }
/* Alert styling */ /* Alert styling */
.alert { .alert {
padding: 15px; padding: 15px;
margin-bottom: 20px; margin-bottom: 20px;
border-radius: 4px; border-radius: 4px;
} }
.alert-danger { .alert-danger {
background-color: #f8d7da; background-color: #f8d7da;
color: #721c24; color: #721c24;
border: 1px solid #f5c6cb; border: 1px solid #f5c6cb;
} }
.alert-success { .alert-success {
background-color: #d4edda; background-color: #d4edda;
color: #155724; color: #155724;
@@ -210,7 +210,12 @@
<div class="form-row"> <div class="form-row">
<div class="form-group"> <div class="form-group">
<label for="first-name">First Name <span class="field-error first_name-error"></span></label> <label for="first-name"
>First Name
<span
class="field-error first_name-error"
></span
></label>
<input <input
type="text" type="text"
id="first-name" id="first-name"
@@ -222,7 +227,10 @@
</div> </div>
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="last-name">Last Name <span class="field-error last_name-error"></span></label> <label for="last-name"
>Last Name
<span class="field-error last_name-error"></span
></label>
<input <input
type="text" type="text"
id="last-name" id="last-name"
@@ -236,7 +244,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="email">Email Address <span class="field-error email-error"></span></label> <label for="email"
>Email Address
<span class="field-error email-error"></span
></label>
<input <input
type="email" type="email"
id="email" id="email"
@@ -249,7 +260,10 @@
</div> </div>
<div class="form-group"> <div class="form-group">
<label for="password">Password <span class="field-error password-error"></span></label> <label for="password"
>Password
<span class="field-error password-error"></span
></label>
<input <input
type="password" type="password"
id="password" id="password"
@@ -275,7 +289,10 @@
</div> </div>
</div> --> </div> -->
<div class="form-group"> <div class="form-group">
<label>Gender <span class="field-error gender-error"></span></label> <label
>Gender
<span class="field-error gender-error"></span
></label>
<div class="gender-group"> <div class="gender-group">
<div class="form-group"> <div class="form-group">
<input <input

131
src/templates/user.html Normal file
View File

@@ -0,0 +1,131 @@
@include 'header.html'
<div class="container mt-5">
<div class="row">
<div class="col-md-8 mx-auto">
<div class="card">
<div class="card-header bg-primary text-white">
<h3>User Profile</h3>
</div>
<div class="card-body">
<div class="mb-4">
<h4>@{user.first_name} @{user.last_name}</h4>
<p><i class="bi bi-envelope"></i> @{user.email}</p>
<p><i class="bi bi-gender-ambiguous"></i> @{user.gender}</p>
</div>
<div class="card mb-4">
<div class="card-header">
<h5>Update Profile</h5>
</div>
<div class="card-body">
<form
action="/controller/user/update"
method="post"
>
<input
type="hidden"
name="id"
value="@{user.id}"
/>
<div class="mb-3">
<label for="first_name" class="form-label"
>First Name</label
>
<input
type="text"
class="form-control"
id="first_name"
name="first_name"
value="@{user.first_name}"
/>
<div
class="text-danger first_name-error field-error"
></div>
</div>
<div class="mb-3">
<label for="last_name" class="form-label"
>Last Name</label
>
<input
type="text"
class="form-control"
id="last_name"
name="last_name"
value="@{user.last_name}"
/>
<div
class="text-danger last_name-error field-error"
></div>
</div>
<div class="mb-3">
<label for="password" class="form-label"
>New Password (leave empty to keep
current)</label
>
<input
type="password"
class="form-control"
id="password"
name="password"
/>
<div
class="text-danger password-error field-error"
></div>
</div>
<div class="d-grid">
<button
type="submit"
class="btn btn-primary"
>
Update Profile
</button>
</div>
</form>
</div>
</div>
<div class="card">
<div class="card-header">
<h5>Account Actions</h5>
</div>
<div class="card-body">
<div class="d-grid gap-2">
<a href="/logout" class="btn btn-danger"
>Logout</a
>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div id="response-container"></div>
<script>
// Handle form submission via AJAX
document.querySelector("form").addEventListener("submit", function (e) {
e.preventDefault();
const form = e.target;
const formData = new FormData(form);
fetch(form.action, {
method: "POST",
body: formData,
})
.then((response) => response.text())
.then((html) => {
document.getElementById("response-container").innerHTML = html;
});
});
</script>
@include 'footer.html'

View File

@@ -52,7 +52,7 @@ pub fn (mut app App) controller_create_user(mut ctx Context, first_name string,
// Generate and insert the token using user ID // Generate and insert the token using user ID
token := app.auth.add_token(x.id) or { '' } token := app.auth.add_token(x.id) or { '' }
// Authenticate the user by adding the token to the cookies // Authenticate the user by adding the token to the cookies
ctx.set_cookie(name: 'token', value: token) ctx.set_cookie(name: 'token', value: token, path: '/')
} }
// Return success message with HTML // Return success message with HTML
@@ -110,7 +110,7 @@ pub fn (mut app App) controller_get_user(mut ctx Context, email string, password
token := app.auth.add_token(user.id) or { '' } token := app.auth.add_token(user.id) or { '' }
// Authenticate the user by adding the token to the cookies // Authenticate the user by adding the token to the cookies
ctx.set_cookie(name: 'token', value: token) ctx.set_cookie(name: 'token', value: token, path: '/')
// Return success message with HTML and redirect // Return success message with HTML and redirect
success_html := '<div class="alert alert-success"> success_html := '<div class="alert alert-success">

View File

@@ -41,35 +41,55 @@ fn (app &App) service_update_user(id ?string, first_name string, last_name strin
return error('User ID is required') return error('User ID is required')
} }
// Unwrap the id value
user_id := id or { return error('Invalid ID') }
// Get current user data // Get current user data
current_user := sql app.db { current_user := sql app.db {
select from User where id == id limit 1 select from User where id == user_id limit 1
}! }!
if current_user.len == 0 { if current_user.len == 0 {
return error('User not found') return error('User not found')
} }
mut updates := []string{}
// Check which fields have changed // Check which fields have changed
if first_name != current_user[0].first_name { if first_name != current_user[0].first_name {
sql app.db { sql app.db {
update User set first_name = first_name where id == id update User set first_name = first_name where id == user_id
}! }!
} }
if last_name != current_user[0].last_name { if last_name != current_user[0].last_name {
sql app.db { sql app.db {
update User set last_name = last_name where id == id update User set last_name = last_name where id == user_id
}! }!
} }
if password != '' { // Only update password if a new one is provided if !auth.compare_password_with_hash(password, current_user[0].salt, current_user[0].password) {
salt := auth.generate_salt() salt := auth.generate_salt()
hashed_password := auth.hash_password_with_salt(password, salt) hashed_password := auth.hash_password_with_salt(password, salt)
sql app.db { sql app.db {
update User set password = hashed_password, salt = salt where id == id update User set password = hashed_password, salt = salt where id == user_id
}! }!
} }
return return
} }
fn (app &App) service_get_user(id int) !User {
if id == 0 {
return error('User ID is required')
}
user_id := id
// Get user data
user := sql app.db {
select from User where id == user_id limit 1
}!
if user.len == 0 {
return error('User not found')
}
return user[0]
}

View File

@@ -9,3 +9,30 @@ pub fn (app &App) signup(mut ctx Context) veb.Result {
pub fn (app &App) login(mut ctx Context) veb.Result { pub fn (app &App) login(mut ctx Context) veb.Result {
return $veb.html() return $veb.html()
} }
pub fn (app &App) user(mut ctx Context) veb.Result {
user_id := ctx.get_cookie('token') or { '' }
token := app.auth.find_token(user_id) or { return ctx.redirect('/login') }
if token.user_id == 0 {
// Redirect to login if not logged in
return ctx.redirect('/login')
}
user := app.service_get_user(token.user_id) or { return ctx.redirect('/login') }
return $veb.html()
}
pub fn (app &App) profile(mut ctx Context) veb.Result {
user_id := ctx.get_cookie('token') or { '' }
token := app.auth.find_token(user_id) or { return ctx.redirect('/login') }
if token.user_id == 0 {
return ctx.redirect('/login')
}
user := app.service_get_user(token.user_id) or { return ctx.redirect('/login') }
return $veb.html()
}