Update add new roles
This commit is contained in:
+173
-386
@@ -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 %}
|
||||
|
||||
Reference in New Issue
Block a user