007
This commit is contained in:
@@ -3,12 +3,14 @@ module main
|
||||
import veb
|
||||
import db.sqlite
|
||||
import veb.auth
|
||||
import veb.assets
|
||||
|
||||
pub struct App {
|
||||
veb.StaticHandler
|
||||
pub mut:
|
||||
db sqlite.DB
|
||||
auth auth.Auth[sqlite.DB]
|
||||
am assets.AssetManager
|
||||
}
|
||||
|
||||
pub struct Context {
|
||||
|
@@ -4,6 +4,7 @@
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>User Registration</title>
|
||||
@css '/static/css/bootstrap.css'
|
||||
<style>
|
||||
/* Reset and Base Styles */
|
||||
* {
|
||||
@@ -132,8 +133,52 @@
|
||||
margin-top: 5px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Field error styling */
|
||||
.field-error {
|
||||
color: #e74c3c;
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
margin-left: 5px;
|
||||
}
|
||||
|
||||
/* Style for input fields with errors */
|
||||
.input-error {
|
||||
border: 1px solid #e74c3c !important;
|
||||
background-color: #fff8f8;
|
||||
}
|
||||
|
||||
/* Spinner styling */
|
||||
.spinner {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.htmx-request .htmx-indicator {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
/* Alert styling */
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
</style>
|
||||
<script src="https://unpkg.com/htmx.org@2.0.4"></script>
|
||||
@js 'https://unpkg.com/htmx.org@2.0.4'
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
@@ -147,34 +192,42 @@
|
||||
id="user-registration-form"
|
||||
hx-trigger="submit"
|
||||
hx-post="/controller/user/create"
|
||||
hx-target="#user-registration-form"
|
||||
hx-target="#form-response"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#form-spinner"
|
||||
>
|
||||
<!-- Response container at the top of the form -->
|
||||
<div id="form-response"></div>
|
||||
|
||||
<!-- Loading indicator -->
|
||||
<div
|
||||
id="form-spinner"
|
||||
class="htmx-indicator"
|
||||
style="display: none"
|
||||
>
|
||||
<div class="spinner">Processing...</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="first-name">First Name</label>
|
||||
<label for="first-name">First Name <span class="field-error first_name-error"></span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="first-name"
|
||||
name="first_name"
|
||||
placeholder="Enter your first name"
|
||||
required
|
||||
/>
|
||||
<div class="error-message" id="first-name-error">
|
||||
Please enter a valid first name
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
class="form-group"
|
||||
hx-target="this"
|
||||
hx-swap="outerHTML"
|
||||
>
|
||||
<label for="last-name">Last Name</label>
|
||||
<div class="form-group">
|
||||
<label for="last-name">Last Name <span class="field-error last_name-error"></span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="last-name"
|
||||
name="last_name"
|
||||
placeholder="Enter your last name"
|
||||
required
|
||||
/>
|
||||
<div class="error-message" id="last-name-error">
|
||||
Please enter a valid last name
|
||||
@@ -183,13 +236,12 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<label for="email">Email Address <span class="field-error email-error"></span></label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
name="email"
|
||||
placeholder="Enter your email"
|
||||
required
|
||||
/>
|
||||
<div class="error-message" id="email-error">
|
||||
Please enter a valid email address
|
||||
@@ -197,13 +249,12 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<label for="password">Password <span class="field-error password-error"></span></label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
name="password"
|
||||
placeholder="Create a strong password"
|
||||
required
|
||||
/>
|
||||
<div class="error-message" id="password-error">
|
||||
Password must be at least 8 characters long
|
||||
@@ -224,7 +275,7 @@
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="form-group">
|
||||
<label>Gender</label>
|
||||
<label>Gender <span class="field-error gender-error"></span></label>
|
||||
<div class="gender-group">
|
||||
<div class="form-group">
|
||||
<input
|
||||
@@ -232,7 +283,6 @@
|
||||
id="male"
|
||||
name="gender"
|
||||
value="male"
|
||||
required
|
||||
/>
|
||||
<label for="male">Male</label>
|
||||
</div>
|
||||
@@ -242,7 +292,6 @@
|
||||
id="female"
|
||||
name="gender"
|
||||
value="female"
|
||||
required
|
||||
/>
|
||||
<label for="female">Female</label>
|
||||
</div>
|
||||
@@ -252,7 +301,6 @@
|
||||
id="other"
|
||||
name="gender"
|
||||
value="other"
|
||||
required
|
||||
/>
|
||||
<label for="other">Other</label>
|
||||
</div>
|
||||
@@ -283,95 +331,6 @@
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Basic client-side validation
|
||||
document
|
||||
.getElementById("user-registration-form")
|
||||
.addEventListener("submit", function (event) {
|
||||
let isValid = true;
|
||||
|
||||
// First Name Validation
|
||||
const firstName = document.getElementById("first-name");
|
||||
const firstNameError =
|
||||
document.getElementById("first-name-error");
|
||||
if (firstName.value.trim().length < 2) {
|
||||
firstNameError.style.display = "block";
|
||||
isValid = false;
|
||||
} else {
|
||||
firstNameError.style.display = "none";
|
||||
}
|
||||
|
||||
// Last Name Validation
|
||||
const lastName = document.getElementById("last-name");
|
||||
const lastNameError =
|
||||
document.getElementById("last-name-error");
|
||||
if (lastName.value.trim().length < 2) {
|
||||
lastNameError.style.display = "block";
|
||||
isValid = false;
|
||||
} else {
|
||||
lastNameError.style.display = "none";
|
||||
}
|
||||
|
||||
// Email Validation
|
||||
const email = document.getElementById("email");
|
||||
const emailError = document.getElementById("email-error");
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRegex.test(email.value)) {
|
||||
emailError.style.display = "block";
|
||||
isValid = false;
|
||||
} else {
|
||||
emailError.style.display = "none";
|
||||
}
|
||||
|
||||
// Password Validation
|
||||
const password = document.getElementById("password");
|
||||
const passwordError =
|
||||
document.getElementById("password-error");
|
||||
if (password.value.length < 8) {
|
||||
passwordError.style.display = "block";
|
||||
isValid = false;
|
||||
} else {
|
||||
passwordError.style.display = "none";
|
||||
}
|
||||
|
||||
// // Date of Birth Validation
|
||||
// const dob = document.getElementById("date-of-birth");
|
||||
// const dobError = document.getElementById("dob-error");
|
||||
// const today = new Date();
|
||||
// const birthDate = new Date(dob.value);
|
||||
// let age = today.getFullYear() - birthDate.getFullYear();
|
||||
// const monthDiff = today.getMonth() - birthDate.getMonth();
|
||||
// if (
|
||||
// monthDiff < 0 ||
|
||||
// (monthDiff === 0 &&
|
||||
// today.getDate() < birthDate.getDate())
|
||||
// ) {
|
||||
// age--;
|
||||
// }
|
||||
// if (age < 18) {
|
||||
// dobError.style.display = "block";
|
||||
// isValid = false;
|
||||
// } else {
|
||||
// dobError.style.display = "none";
|
||||
// }
|
||||
|
||||
// // Country Validation
|
||||
// const country = document.getElementById("country");
|
||||
// const countryError =
|
||||
// document.getElementById("country-error");
|
||||
// if (country.value === "") {
|
||||
// countryError.style.display = "block";
|
||||
// isValid = false;
|
||||
// } else {
|
||||
// countryError.style.display = "none";
|
||||
// }
|
||||
|
||||
// Prevent form submission if validation fails
|
||||
if (!isValid) {
|
||||
event.preventDefault();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
@js '/static/js/bootstrap.js'
|
||||
</html>
|
||||
|
@@ -2,11 +2,21 @@
|
||||
id="user-registration-form"
|
||||
hx-trigger="submit"
|
||||
hx-post="/controller/user/create"
|
||||
hx-target="#user-registration-form"
|
||||
hx-target="#form-response"
|
||||
hx-swap="innerHTML"
|
||||
hx-indicator="#form-spinner"
|
||||
>
|
||||
<!-- Add a response container at the top of the form -->
|
||||
<div id="form-response"></div>
|
||||
|
||||
<!-- Add a loading indicator -->
|
||||
<div id="form-spinner" class="htmx-indicator" style="display: none;">
|
||||
<div class="spinner">Processing...</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-group">
|
||||
<label for="first-name">First Name</label>
|
||||
<label for="first-name">First Name <span class="field-error first_name-error"></span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="first-name"
|
||||
@@ -18,8 +28,8 @@
|
||||
Please enter a valid first name
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group" hx-target="this" hx-swap="outerHTML">
|
||||
<label for="last-name">Last Name</label>
|
||||
<div class="form-group">
|
||||
<label for="last-name">Last Name <span class="field-error last_name-error"></span></label>
|
||||
<input
|
||||
type="text"
|
||||
id="last-name"
|
||||
@@ -34,7 +44,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="email">Email Address</label>
|
||||
<label for="email">Email Address <span class="field-error email-error"></span></label>
|
||||
<input
|
||||
type="email"
|
||||
id="email"
|
||||
@@ -48,7 +58,7 @@
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">Password</label>
|
||||
<label for="password">Password <span class="field-error password-error"></span></label>
|
||||
<input
|
||||
type="password"
|
||||
id="password"
|
||||
@@ -130,3 +140,63 @@
|
||||
<button type="submit" class="register-btn">Create Account</button>
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<style>
|
||||
/* Add styles for the alerts and spinner */
|
||||
.alert {
|
||||
padding: 15px;
|
||||
margin-bottom: 20px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.alert-danger {
|
||||
background-color: #f8d7da;
|
||||
color: #721c24;
|
||||
border: 1px solid #f5c6cb;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
/* Field error styling */
|
||||
.field-error {
|
||||
color: #e74c3c;
|
||||
font-size: 0.9em;
|
||||
font-weight: normal;
|
||||
}
|
||||
|
||||
/* Style for input fields with errors */
|
||||
.input-error {
|
||||
border: 1px solid #e74c3c !important;
|
||||
background-color: #fff8f8;
|
||||
}
|
||||
|
||||
.btn {
|
||||
display: inline-block;
|
||||
padding: 8px 16px;
|
||||
text-decoration: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
}
|
||||
|
||||
.mt-3 {
|
||||
margin-top: 15px;
|
||||
}
|
||||
|
||||
.spinner {
|
||||
text-align: center;
|
||||
padding: 10px;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.htmx-request .htmx-indicator {
|
||||
display: block !important;
|
||||
}
|
||||
</style>
|
||||
|
@@ -3,20 +3,14 @@ module main
|
||||
import veb
|
||||
|
||||
@['/controller/user/create'; post]
|
||||
pub fn (mut app App) controller_create_user(mut ctx Context) veb.Result {
|
||||
first_name := ctx.form['first_name']
|
||||
last_name := ctx.form['last_name']
|
||||
email := ctx.form['email']
|
||||
password := ctx.form['password']
|
||||
gender := ctx.form['gender']
|
||||
|
||||
pub fn (mut app App) controller_create_user(mut ctx Context, first_name string, last_name string, email string, password string, gender string) veb.Result {
|
||||
// Create a map of field names and their values
|
||||
fields := {
|
||||
'first_name': first_name
|
||||
'last_name': last_name
|
||||
'email': email
|
||||
'password': password
|
||||
// Gender can be optional, so not including it in validation
|
||||
'gender': gender
|
||||
}
|
||||
|
||||
// Check for empty fields
|
||||
@@ -27,13 +21,38 @@ pub fn (mut app App) controller_create_user(mut ctx Context) veb.Result {
|
||||
}
|
||||
}
|
||||
|
||||
// If any fields are empty, return error message
|
||||
// If any fields are empty, return field-specific error messages
|
||||
if empty_fields.len > 0 {
|
||||
return ctx.text('The following fields cannot be empty: ${empty_fields.join(', ')}')
|
||||
mut response := '<script>'
|
||||
|
||||
// Reset all field errors first
|
||||
response += 'document.querySelectorAll(".field-error").forEach(el => el.textContent = "");'
|
||||
response += 'document.querySelectorAll("input").forEach(el => el.classList.remove("input-error"));'
|
||||
|
||||
// Set error for each empty field
|
||||
for field in empty_fields {
|
||||
response += 'document.querySelector(".' + field + '-error").textContent = "(Required)";'
|
||||
response += 'document.querySelector("[name=' + field + ']").classList.add("input-error");'
|
||||
}
|
||||
|
||||
response += '</script>'
|
||||
response += '<div class="alert alert-danger">Please fill in all required fields</div>'
|
||||
|
||||
return ctx.html(response)
|
||||
}
|
||||
|
||||
// Try to add the user
|
||||
app.service_add_user(first_name, last_name, email, password, gender) or {
|
||||
return ctx.text('error: ${err}')
|
||||
error_html := '<div class="alert alert-danger">Error: ${err}</div>'
|
||||
return ctx.html(error_html)
|
||||
}
|
||||
return ctx.text('User created successfully')
|
||||
|
||||
// Return success message with HTML
|
||||
success_html := '<div class="alert alert-success">
|
||||
<h4>User created successfully!</h4>
|
||||
<p>Welcome, ${first_name}! Your account has been created.</p>
|
||||
<a href="/login" class="btn btn-primary mt-3">Login Now</a>
|
||||
</div>'
|
||||
|
||||
return ctx.html(success_html)
|
||||
}
|
||||
|
12057
static/css/bootstrap.css
vendored
Normal file
12057
static/css/bootstrap.css
vendored
Normal file
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user