Update add new roles

This commit is contained in:
2025-08-10 21:34:24 -04:00
parent d91aa10a37
commit 413d1dac73
6 changed files with 1538 additions and 3404 deletions
+173 -386
View File
@@ -1,50 +1,60 @@
{% 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>
{% extends "base_authenticated.html" %} {% block title %}Create New User - QR
Code Management{% endblock %} {% block content %}
<div class="create-user-page">
<div class="page-header">
<div class="header-content">
<h1>
<i class="fas fa-user-plus"></i>
Create New User
</h1>
<p>Add a new user to the system with appropriate role permissions</p>
</div>
</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 class="create-user-container">
<form
id="createUserForm"
method="POST"
action="{{ url_for('create_user') }}"
>
<div class="form-section">
<h3>
<i class="fas fa-user"></i>
User Information
</h3>
<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 full name"
/>
<small class="form-help">First and last name</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@example.com"
/>
<small class="form-help">Must be unique in the system</small>
</div>
</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>
@@ -55,12 +65,11 @@ endblock %} {% block content %}
id="username"
name="username"
required
placeholder="Enter username"
pattern="[a-zA-Z0-9_]+"
title="Username can only contain letters, numbers, and underscores"
placeholder="Enter username"
/>
<small class="form-help"
>Must be unique. Letters, numbers, and underscores only</small
>Letters, numbers, and underscores only</small
>
</div>
@@ -72,10 +81,12 @@ endblock %} {% block content %}
<select id="role" name="role" required>
<option value="">Select Role</option>
<option value="staff">Staff User</option>
<option value="payroll">Payroll Specialist</option>
<option value="project_manager">Project Manager</option>
<option value="admin">Administrator</option>
</select>
<small class="form-help"
>Staff can manage QR codes; Admin has full system access</small
>Determines user permissions and system access level</small
>
</div>
</div>
@@ -166,7 +177,7 @@ endblock %} {% block content %}
const passwordInput = document.getElementById("password");
const passwordStrength = document.getElementById("passwordStrength");
// Role information
// UPDATED: Role information with new roles
const rolePermissions = {
staff: {
title: "Staff User Permissions",
@@ -183,6 +194,38 @@ endblock %} {% block content %}
"Cannot access admin settings",
],
},
payroll: {
title: "Payroll Specialist 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",
"Same permissions as Staff (additional features coming soon)",
],
restrictions: [
"Cannot delete QR codes",
"Cannot manage other users",
"Cannot access admin settings",
],
},
project_manager: {
title: "Project Manager 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",
"Same permissions as Staff (additional features coming soon)",
],
restrictions: [
"Cannot delete QR codes",
"Cannot manage other users",
"Cannot access admin settings",
],
},
admin: {
title: "Administrator Permissions",
permissions: [
@@ -197,394 +240,138 @@ endblock %} {% block content %}
},
};
// Show role information when role is selected
// Role selection handler
roleSelect.addEventListener("change", function () {
const selectedRole = this.value;
if (selectedRole && rolePermissions[selectedRole]) {
const permissions = rolePermissions[selectedRole];
const roleData = 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>
`;
let permissionsHtml = `
<h4>${roleData.title}</h4>
<div class="permissions-section">
<h5><i class="fas fa-check-circle text-success"></i> Permissions</h5>
<ul class="permissions-list">
`;
roleData.permissions.forEach((permission) => {
permissionsHtml += `<li><i class="fas fa-check"></i> ${permission}</li>`;
});
permissionsHtml += `
</ul>
<h5><i class="fas fa-times-circle text-warning"></i> Restrictions</h5>
<ul class="restrictions-list">
`;
roleData.restrictions.forEach((restriction) => {
permissionsHtml += `<li><i class="fas fa-times"></i> ${restriction}</li>`;
});
permissionsHtml += `</ul></div>`;
roleContent.innerHTML = permissionsHtml;
roleInfo.style.display = "block";
setTimeout(() => roleInfo.classList.add("fade-in"), 10);
setTimeout(() => roleInfo.classList.add("fade-in"), 100);
} else {
roleInfo.style.display = "none";
roleInfo.classList.remove("fade-in");
}
});
// Password strength checker
// Password strength indicator
passwordInput.addEventListener("input", function () {
const password = this.value;
let strength = 0;
let feedback = [];
const strength = calculatePasswordStrength(password);
// 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;
}
passwordStrength.className = `password-strength ${strength.class}`;
passwordStrength.textContent = strength.text;
});
// 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,
};
const fullName = fullNameInput.value.trim();
const email = emailInput.value.trim();
const username = usernameInput.value.trim();
const role = roleSelect.value;
// Validate required fields
if (
!formData.full_name ||
!formData.email ||
!formData.username ||
!formData.role
) {
if (!fullName || !email || !username || !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);
document.getElementById("summaryName").textContent = fullName;
document.getElementById("summaryEmail").textContent = email;
document.getElementById("summaryUsername").textContent = username;
// Get role display name
const roleDisplayNames = {
staff: "Staff User",
payroll: "Payroll Specialist",
project_manager: "Project Manager",
admin: "Administrator",
};
document.getElementById("summaryRole").textContent =
roleDisplayNames[role] || role;
// Show summary
creationSummary.style.display = "block";
setTimeout(() => creationSummary.classList.add("fade-in"), 10);
setTimeout(() => creationSummary.classList.add("fade-in"), 100);
// Scroll to summary
creationSummary.scrollIntoView({
behavior: "smooth",
block: "center",
});
creationSummary.scrollIntoView({ behavior: "smooth" });
});
// 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) {
if (password.length < 6) {
e.preventDefault();
alert("Password must be at least 6 characters long.");
passwordInput.focus();
return;
}
if (!role) {
if (!roleSelect.value) {
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());
// Username validation
usernameInput.addEventListener("input", function () {
this.value = this.value.replace(/[^a-zA-Z0-9_]/g, "");
});
});
function calculatePasswordStrength(password) {
if (password.length === 0) {
return { class: "", text: "" };
}
let score = 0;
// Length
if (password.length >= 6) score += 1;
if (password.length >= 8) score += 1;
if (password.length >= 12) score += 1;
// Character variety
if (/[a-z]/.test(password)) score += 1;
if (/[A-Z]/.test(password)) score += 1;
if (/[0-9]/.test(password)) score += 1;
if (/[^a-zA-Z0-9]/.test(password)) score += 1;
if (score < 3) {
return { class: "weak", text: "Weak password" };
} else if (score < 5) {
return { class: "medium", text: "Medium strength" };
} else {
return { class: "strong", text: "Strong password" };
}
}
</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 %}