591 lines
17 KiB
HTML
591 lines
17 KiB
HTML
{% extends "base.html" %} {% block title %}Create User - QR Code Management{%
|
|
endblock %} {% block content %}
|
|
<div class="form-page">
|
|
<div class="form-card">
|
|
<div class="form-header">
|
|
<h1>Create New User</h1>
|
|
<p>Add a new user to the QR management system</p>
|
|
</div>
|
|
|
|
<form method="POST" class="form" id="createUserForm">
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="full_name">
|
|
<i class="fas fa-id-card"></i>
|
|
Full Name
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="full_name"
|
|
name="full_name"
|
|
required
|
|
placeholder="Enter user's full name"
|
|
/>
|
|
<small class="form-help"
|
|
>Legal name as it should appear in the system</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="email">
|
|
<i class="fas fa-envelope"></i>
|
|
Email Address
|
|
</label>
|
|
<input
|
|
type="email"
|
|
id="email"
|
|
name="email"
|
|
required
|
|
placeholder="user@company.com"
|
|
/>
|
|
<small class="form-help"
|
|
>Primary email for notifications and account recovery</small
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-row">
|
|
<div class="form-group">
|
|
<label for="username">
|
|
<i class="fas fa-user"></i>
|
|
Username
|
|
</label>
|
|
<input
|
|
type="text"
|
|
id="username"
|
|
name="username"
|
|
required
|
|
placeholder="Enter username"
|
|
pattern="[a-zA-Z0-9_]+"
|
|
title="Username can only contain letters, numbers, and underscores"
|
|
/>
|
|
<small class="form-help"
|
|
>Must be unique. Letters, numbers, and underscores only</small
|
|
>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="role">
|
|
<i class="fas fa-user-shield"></i>
|
|
User Role
|
|
</label>
|
|
<select id="role" name="role" required>
|
|
<option value="">Select Role</option>
|
|
<option value="staff">Staff User</option>
|
|
<option value="admin">Administrator</option>
|
|
</select>
|
|
<small class="form-help"
|
|
>Staff can manage QR codes; Admin has full system access</small
|
|
>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-group">
|
|
<label for="password">
|
|
<i class="fas fa-lock"></i>
|
|
Initial Password
|
|
</label>
|
|
<input
|
|
type="password"
|
|
id="password"
|
|
name="password"
|
|
required
|
|
minlength="6"
|
|
placeholder="Enter initial password"
|
|
/>
|
|
<div class="password-strength" id="passwordStrength"></div>
|
|
<small class="form-help"
|
|
>Minimum 6 characters. User should change on first login</small
|
|
>
|
|
</div>
|
|
|
|
<!-- Role Information Panel -->
|
|
<div class="info-panel" id="roleInfo" style="display: none">
|
|
<div class="info-header">
|
|
<i class="fas fa-info-circle"></i>
|
|
<span>Role Permissions</span>
|
|
</div>
|
|
<div class="info-content" id="roleContent">
|
|
<!-- Dynamic content based on selected role -->
|
|
</div>
|
|
</div>
|
|
|
|
<!-- User Creation Summary -->
|
|
<div class="creation-summary" id="creationSummary" style="display: none">
|
|
<h3>
|
|
<i class="fas fa-check-circle"></i>
|
|
User Creation Summary
|
|
</h3>
|
|
<div class="summary-grid">
|
|
<div class="summary-item">
|
|
<strong>Name:</strong> <span id="summaryName">-</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<strong>Email:</strong> <span id="summaryEmail">-</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<strong>Username:</strong> <span id="summaryUsername">-</span>
|
|
</div>
|
|
<div class="summary-item">
|
|
<strong>Role:</strong> <span id="summaryRole">-</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="form-actions">
|
|
<a href="{{ url_for('users') }}" class="btn btn-secondary">
|
|
<i class="fas fa-arrow-left"></i>
|
|
Back to Users
|
|
</a>
|
|
<button type="button" class="btn btn-info" id="previewUser">
|
|
<i class="fas fa-eye"></i>
|
|
Preview User
|
|
</button>
|
|
<button type="submit" class="btn btn-primary">
|
|
<i class="fas fa-user-plus"></i>
|
|
Create User
|
|
</button>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
{% endblock %} {% block extra_scripts %}
|
|
<script>
|
|
document.addEventListener("DOMContentLoaded", function () {
|
|
const form = document.getElementById("createUserForm");
|
|
const roleSelect = document.getElementById("role");
|
|
const roleInfo = document.getElementById("roleInfo");
|
|
const roleContent = document.getElementById("roleContent");
|
|
const previewBtn = document.getElementById("previewUser");
|
|
const creationSummary = document.getElementById("creationSummary");
|
|
|
|
// Form inputs
|
|
const fullNameInput = document.getElementById("full_name");
|
|
const emailInput = document.getElementById("email");
|
|
const usernameInput = document.getElementById("username");
|
|
const passwordInput = document.getElementById("password");
|
|
const passwordStrength = document.getElementById("passwordStrength");
|
|
|
|
// Role information
|
|
const rolePermissions = {
|
|
staff: {
|
|
title: "Staff User Permissions",
|
|
permissions: [
|
|
"Create and edit QR codes",
|
|
"View all QR codes in the system",
|
|
"Download QR code images",
|
|
"Update personal profile information",
|
|
"Access dashboard and reports",
|
|
],
|
|
restrictions: [
|
|
"Cannot delete QR codes",
|
|
"Cannot manage other users",
|
|
"Cannot access admin settings",
|
|
],
|
|
},
|
|
admin: {
|
|
title: "Administrator Permissions",
|
|
permissions: [
|
|
"Full QR code management (create, edit, delete)",
|
|
"Complete user management capabilities",
|
|
"System configuration access",
|
|
"View all system analytics",
|
|
"Bulk operations and data export",
|
|
"Access to all admin features",
|
|
],
|
|
restrictions: ["With great power comes great responsibility!"],
|
|
},
|
|
};
|
|
|
|
// Show role information when role is selected
|
|
roleSelect.addEventListener("change", function () {
|
|
const selectedRole = this.value;
|
|
|
|
if (selectedRole && rolePermissions[selectedRole]) {
|
|
const permissions = rolePermissions[selectedRole];
|
|
|
|
roleContent.innerHTML = `
|
|
<h4>${permissions.title}</h4>
|
|
<div class="permissions-grid">
|
|
<div class="permissions-column">
|
|
<h5><i class="fas fa-check text-success"></i> Allowed Actions</h5>
|
|
<ul>
|
|
${permissions.permissions
|
|
.map((perm) => `<li>${perm}</li>`)
|
|
.join("")}
|
|
</ul>
|
|
</div>
|
|
<div class="permissions-column">
|
|
<h5><i class="fas fa-times text-danger"></i> Restrictions</h5>
|
|
<ul>
|
|
${permissions.restrictions
|
|
.map((rest) => `<li>${rest}</li>`)
|
|
.join("")}
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
roleInfo.style.display = "block";
|
|
setTimeout(() => roleInfo.classList.add("fade-in"), 10);
|
|
} else {
|
|
roleInfo.style.display = "none";
|
|
roleInfo.classList.remove("fade-in");
|
|
}
|
|
});
|
|
|
|
// Password strength checker
|
|
passwordInput.addEventListener("input", function () {
|
|
const password = this.value;
|
|
let strength = 0;
|
|
let feedback = [];
|
|
|
|
// Check password criteria
|
|
if (password.length >= 6) strength++;
|
|
else feedback.push("At least 6 characters");
|
|
|
|
if (/[A-Z]/.test(password)) strength++;
|
|
else feedback.push("One uppercase letter");
|
|
|
|
if (/[a-z]/.test(password)) strength++;
|
|
else feedback.push("One lowercase letter");
|
|
|
|
if (/[0-9]/.test(password)) strength++;
|
|
else feedback.push("One number");
|
|
|
|
if (/[^A-Za-z0-9]/.test(password)) strength++;
|
|
else feedback.push("One special character");
|
|
|
|
// Update strength display
|
|
let strengthText = "";
|
|
let strengthClass = "";
|
|
|
|
if (strength < 2) {
|
|
strengthText = "Weak";
|
|
strengthClass = "weak";
|
|
} else if (strength < 4) {
|
|
strengthText = "Medium";
|
|
strengthClass = "medium";
|
|
} else {
|
|
strengthText = "Strong";
|
|
strengthClass = "strong";
|
|
}
|
|
|
|
passwordStrength.className = `password-strength ${strengthClass}`;
|
|
passwordStrength.textContent = `Strength: ${strengthText}`;
|
|
|
|
if (feedback.length > 0 && password.length > 0) {
|
|
passwordStrength.textContent += ` (Missing: ${feedback.join(", ")})`;
|
|
}
|
|
});
|
|
|
|
// Username validation and formatting
|
|
usernameInput.addEventListener("input", function () {
|
|
// Remove invalid characters
|
|
this.value = this.value.replace(/[^a-zA-Z0-9_]/g, "");
|
|
|
|
// Convert to lowercase for consistency
|
|
this.value = this.value.toLowerCase();
|
|
});
|
|
|
|
// Auto-generate username from full name
|
|
fullNameInput.addEventListener("input", function () {
|
|
if (!usernameInput.value) {
|
|
const generatedUsername = this.value
|
|
.toLowerCase()
|
|
.replace(/[^a-zA-Z0-9\s]/g, "")
|
|
.replace(/\s+/g, "_")
|
|
.substring(0, 20);
|
|
|
|
usernameInput.value = generatedUsername;
|
|
}
|
|
});
|
|
|
|
// Preview user functionality
|
|
previewBtn.addEventListener("click", function () {
|
|
const formData = {
|
|
full_name: fullNameInput.value.trim(),
|
|
email: emailInput.value.trim(),
|
|
username: usernameInput.value.trim(),
|
|
role: roleSelect.value,
|
|
};
|
|
|
|
// Validate required fields
|
|
if (
|
|
!formData.full_name ||
|
|
!formData.email ||
|
|
!formData.username ||
|
|
!formData.role
|
|
) {
|
|
alert("Please fill in all required fields before previewing.");
|
|
return;
|
|
}
|
|
|
|
// Update summary
|
|
document.getElementById("summaryName").textContent = formData.full_name;
|
|
document.getElementById("summaryEmail").textContent = formData.email;
|
|
document.getElementById("summaryUsername").textContent =
|
|
formData.username;
|
|
document.getElementById("summaryRole").textContent =
|
|
formData.role.charAt(0).toUpperCase() + formData.role.slice(1);
|
|
|
|
// Show summary
|
|
creationSummary.style.display = "block";
|
|
setTimeout(() => creationSummary.classList.add("fade-in"), 10);
|
|
|
|
// Scroll to summary
|
|
creationSummary.scrollIntoView({
|
|
behavior: "smooth",
|
|
block: "center",
|
|
});
|
|
});
|
|
|
|
// Form validation
|
|
form.addEventListener("submit", function (e) {
|
|
const fullName = fullNameInput.value.trim();
|
|
const email = emailInput.value.trim();
|
|
const username = usernameInput.value.trim();
|
|
const password = passwordInput.value;
|
|
const role = roleSelect.value;
|
|
|
|
// Comprehensive validation
|
|
if (!fullName || fullName.length < 2) {
|
|
e.preventDefault();
|
|
alert("Full name must be at least 2 characters long.");
|
|
fullNameInput.focus();
|
|
return;
|
|
}
|
|
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
if (!email || !emailRegex.test(email)) {
|
|
e.preventDefault();
|
|
alert("Please enter a valid email address.");
|
|
emailInput.focus();
|
|
return;
|
|
}
|
|
|
|
if (!username || username.length < 3) {
|
|
e.preventDefault();
|
|
alert("Username must be at least 3 characters long.");
|
|
usernameInput.focus();
|
|
return;
|
|
}
|
|
|
|
if (!password || password.length < 6) {
|
|
e.preventDefault();
|
|
alert("Password must be at least 6 characters long.");
|
|
passwordInput.focus();
|
|
return;
|
|
}
|
|
|
|
if (!role) {
|
|
e.preventDefault();
|
|
alert("Please select a user role.");
|
|
roleSelect.focus();
|
|
return;
|
|
}
|
|
|
|
// Show loading state
|
|
const submitBtn = form.querySelector('button[type="submit"]');
|
|
submitBtn.innerHTML =
|
|
'<i class="fas fa-spinner fa-spin"></i> Creating User...';
|
|
submitBtn.disabled = true;
|
|
});
|
|
|
|
// Real-time email validation
|
|
emailInput.addEventListener("blur", function () {
|
|
const email = this.value.trim();
|
|
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
const formGroup = this.closest(".form-group");
|
|
|
|
if (email && !emailRegex.test(email)) {
|
|
formGroup.classList.add("has-error");
|
|
if (!formGroup.querySelector(".error-message")) {
|
|
const errorMsg = document.createElement("div");
|
|
errorMsg.className = "error-message";
|
|
errorMsg.textContent = "Please enter a valid email address";
|
|
formGroup.appendChild(errorMsg);
|
|
}
|
|
} else {
|
|
formGroup.classList.remove("has-error");
|
|
const errorMsg = formGroup.querySelector(".error-message");
|
|
if (errorMsg) {
|
|
errorMsg.remove();
|
|
}
|
|
}
|
|
});
|
|
|
|
// Character counters
|
|
function addCharacterCounter(input, maxLength) {
|
|
const formGroup = input.closest(".form-group");
|
|
const counter = document.createElement("div");
|
|
counter.className = "character-counter";
|
|
|
|
function updateCounter() {
|
|
const currentLength = input.value.length;
|
|
counter.textContent = `${currentLength}${
|
|
maxLength ? "/" + maxLength : ""
|
|
} characters`;
|
|
|
|
if (maxLength && currentLength > maxLength * 0.8) {
|
|
counter.classList.add("warning");
|
|
} else {
|
|
counter.classList.remove("warning");
|
|
}
|
|
}
|
|
|
|
input.addEventListener("input", updateCounter);
|
|
formGroup.appendChild(counter);
|
|
updateCounter();
|
|
}
|
|
|
|
// Add character counters
|
|
addCharacterCounter(fullNameInput, 100);
|
|
addCharacterCounter(usernameInput, 50);
|
|
|
|
// Auto-capitalize full name
|
|
fullNameInput.addEventListener("input", function () {
|
|
this.value = this.value.replace(/\b\w/g, (l) => l.toUpperCase());
|
|
});
|
|
});
|
|
</script>
|
|
|
|
<style>
|
|
/* Additional styles for create user form */
|
|
.form-row {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1.5rem;
|
|
margin-bottom: 1.5rem;
|
|
}
|
|
|
|
.info-panel {
|
|
margin: 1.5rem 0;
|
|
padding: 1.5rem;
|
|
background: linear-gradient(135deg, #eff6ff, #dbeafe);
|
|
border: 1px solid #3b82f6;
|
|
border-radius: var(--radius-xl);
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.info-panel.fade-in {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.info-header {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
font-weight: 600;
|
|
color: #1e40af;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.permissions-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 1.5rem;
|
|
}
|
|
|
|
.permissions-column h5 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
margin-bottom: 0.75rem;
|
|
font-size: 0.875rem;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.permissions-column ul {
|
|
list-style: none;
|
|
padding: 0;
|
|
margin: 0;
|
|
}
|
|
|
|
.permissions-column li {
|
|
padding: 0.5rem 0;
|
|
border-bottom: 1px solid rgba(59, 130, 246, 0.1);
|
|
font-size: 0.875rem;
|
|
color: #374151;
|
|
}
|
|
|
|
.permissions-column li:last-child {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.text-success {
|
|
color: #059669 !important;
|
|
}
|
|
|
|
.text-danger {
|
|
color: #dc2626 !important;
|
|
}
|
|
|
|
.creation-summary {
|
|
margin: 2rem 0;
|
|
padding: 1.5rem;
|
|
background: linear-gradient(135deg, #f0fdf4, #dcfce7);
|
|
border: 2px solid #22c55e;
|
|
border-radius: var(--radius-xl);
|
|
opacity: 0;
|
|
transform: translateY(20px);
|
|
transition: all 0.3s ease;
|
|
}
|
|
|
|
.creation-summary.fade-in {
|
|
opacity: 1;
|
|
transform: translateY(0);
|
|
}
|
|
|
|
.creation-summary h3 {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 0.5rem;
|
|
color: #15803d;
|
|
margin-bottom: 1rem;
|
|
}
|
|
|
|
.summary-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(2, 1fr);
|
|
gap: 1rem;
|
|
}
|
|
|
|
.summary-item {
|
|
padding: 0.75rem;
|
|
background: white;
|
|
border-radius: var(--radius);
|
|
font-size: 0.875rem;
|
|
}
|
|
|
|
.summary-item strong {
|
|
color: #374151;
|
|
margin-right: 0.5rem;
|
|
}
|
|
|
|
.summary-item span {
|
|
color: #059669;
|
|
font-weight: 500;
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.form-row {
|
|
grid-template-columns: 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.permissions-grid {
|
|
grid-template-columns: 1fr;
|
|
gap: 1rem;
|
|
}
|
|
|
|
.summary-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
</style>
|
|
{% endblock %}
|