Initial Codes
This commit is contained in:
@@ -0,0 +1,590 @@
|
||||
{% 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 %}
|
||||
Reference in New Issue
Block a user