Files
SkyArtShop/website/admin/js/users.js

444 lines
12 KiB
JavaScript
Raw Normal View History

2025-12-14 01:54:40 -06:00
// User Management JavaScript
let usersData = [];
let userModal;
let passwordModal;
const rolePermissions = {
Cashier: ["View Products", "Process Orders", "View Customers"],
Accountant: [
"View Products",
"View Orders",
"View Reports",
"View Financial Data",
],
2026-01-18 02:22:05 -06:00
Sales: ["Manage Products", "Manage Orders", "View Reports"],
2025-12-14 01:54:40 -06:00
Admin: [
"Manage Products",
"Manage Portfolio",
"Manage Blog",
"Manage Pages",
"Manage Users",
"View Reports",
"Full System Access",
"Manage Settings",
],
};
document.addEventListener("DOMContentLoaded", function () {
userModal = new bootstrap.Modal(document.getElementById("userModal"));
passwordModal = new bootstrap.Modal(document.getElementById("passwordModal"));
checkAuth().then((authenticated) => {
if (authenticated) {
loadUsers();
}
});
});
async function loadUsers() {
try {
const response = await fetch("/api/admin/users", {
credentials: "include",
});
const data = await response.json();
if (data.success) {
usersData = data.users;
renderUsers(usersData);
}
} catch (error) {
console.error("Failed to load users:", error);
}
}
function renderUsers(users) {
const tbody = document.getElementById("usersTableBody");
if (users.length === 0) {
tbody.innerHTML = `
<tr><td colspan="8" class="text-center p-4">
<i class="bi bi-inbox" style="font-size: 3rem; color: #ccc;"></i>
<p class="mt-3 text-muted">No users found</p>
<button class="btn btn-primary" onclick="showCreateUser()">
<i class="bi bi-person-plus"></i> Create First User
</button>
</td></tr>`;
return;
}
tbody.innerHTML = users
.map(
(u) => `
<tr>
<td>${u.id}</td>
<td><strong>${escapeHtml(u.name)}</strong></td>
<td>${escapeHtml(u.email)}</td>
<td>@${escapeHtml(u.username)}</td>
<td><span class="role-badge role-${u.role
.toLowerCase()
.replace(" ", "-")}">${u.role}</span></td>
<td><span class="badge ${u.isactive ? "badge-success" : "badge-danger"}">
${u.isactive ? "Active" : "Disabled"}</span></td>
<td>${formatDate(u.createdat)}</td>
<td>
2026-01-01 22:24:30 -06:00
<button class="btn btn-sm btn-info" onclick="editUser('${escapeHtml(
2026-01-18 02:22:05 -06:00
u.id,
2026-01-01 22:24:30 -06:00
)}')" title="Edit User">
2025-12-14 01:54:40 -06:00
<i class="bi bi-pencil"></i>
</button>
2026-01-01 22:24:30 -06:00
<button class="btn btn-sm btn-warning" onclick="showChangePassword('${escapeHtml(
2026-01-18 02:22:05 -06:00
u.id,
2026-01-01 22:24:30 -06:00
)}', '${escapeHtml(u.name)}')" title="Change Password">
2025-12-14 01:54:40 -06:00
<i class="bi bi-key"></i>
</button>
2026-01-01 22:24:30 -06:00
<button class="btn btn-sm btn-danger" onclick="deleteUser('${escapeHtml(
2026-01-18 02:22:05 -06:00
u.id,
2026-01-01 22:24:30 -06:00
)}', '${escapeHtml(u.name)}')" title="Delete User">
2025-12-14 01:54:40 -06:00
<i class="bi bi-trash"></i>
</button>
</td>
2026-01-18 02:22:05 -06:00
</tr>`,
2025-12-14 01:54:40 -06:00
)
.join("");
}
function filterUsers() {
const searchTerm = document.getElementById("searchInput").value.toLowerCase();
const filtered = usersData.filter(
(u) =>
u.name.toLowerCase().includes(searchTerm) ||
u.email.toLowerCase().includes(searchTerm) ||
2026-01-18 02:22:05 -06:00
u.username.toLowerCase().includes(searchTerm),
2025-12-14 01:54:40 -06:00
);
renderUsers(filtered);
}
function showCreateUser() {
document.getElementById("modalTitle").textContent = "Create New User";
document.getElementById("userForm").reset();
document.getElementById("userId").value = "";
document.getElementById("userActive").checked = true;
document.getElementById("userPasswordNeverExpires").checked = false;
document.getElementById("userRole").value = "Cashier";
updatePermissionsPreview();
userModal.show();
}
async function editUser(id) {
try {
const response = await fetch(`/api/admin/users/${id}`, {
credentials: "include",
});
const data = await response.json();
if (data.success) {
const user = data.user;
document.getElementById("modalTitle").textContent = "Edit User";
document.getElementById("userId").value = user.id;
document.getElementById("userName").value = user.name;
document.getElementById("userUsername").value = user.username;
document.getElementById("userEmail").value = user.email;
document.getElementById("userPassword").value = "";
document.getElementById("userPasswordConfirm").value = "";
document.getElementById("userRole").value = user.role;
document.getElementById("userActive").checked = user.isactive;
document.getElementById("userPasswordNeverExpires").checked =
user.passwordneverexpires || false;
updatePermissionsPreview();
userModal.show();
}
} catch (error) {
console.error("Failed to load user:", error);
showError("Failed to load user details");
}
}
async function saveUser() {
const id = document.getElementById("userId").value;
const password = document.getElementById("userPassword").value;
const passwordConfirm = document.getElementById("userPasswordConfirm").value;
// Password validation (only for new users or when changing password)
if (!id || password) {
if (!password) {
showError("Password is required for new users");
return;
}
if (password !== passwordConfirm) {
showError("Passwords do not match");
return;
}
if (password.length < 8) {
showError("Password must be at least 8 characters long");
return;
}
2026-01-18 02:22:05 -06:00
if (!/[A-Z]/.test(password)) {
showError("Password must contain at least one uppercase letter");
return;
}
if (!/[a-z]/.test(password)) {
showError("Password must contain at least one lowercase letter");
return;
}
if (!/[0-9]/.test(password)) {
showError("Password must contain at least one number");
return;
}
2025-12-14 01:54:40 -06:00
}
const formData = {
name: document.getElementById("userName").value,
username: document.getElementById("userUsername").value,
email: document.getElementById("userEmail").value,
role: document.getElementById("userRole").value,
isactive: document.getElementById("userActive").checked,
passwordneverexpires: document.getElementById("userPasswordNeverExpires")
.checked,
};
if (password) {
formData.password = password;
}
if (!formData.name || !formData.username || !formData.email) {
showError("Please fill in all required fields");
return;
}
2026-01-01 22:24:30 -06:00
showLoading(id ? "Updating user..." : "Creating user...");
2025-12-14 01:54:40 -06:00
try {
const url = id ? `/api/admin/users/${id}` : "/api/admin/users";
const method = id ? "PUT" : "POST";
const response = await fetch(url, {
method: method,
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify(formData),
});
const data = await response.json();
2026-01-01 22:24:30 -06:00
hideLoading();
2025-12-14 01:54:40 -06:00
if (data.success) {
showSuccess(
2026-01-18 02:22:05 -06:00
id ? "User updated successfully" : "User created successfully",
2025-12-14 01:54:40 -06:00
);
userModal.hide();
loadUsers();
} else {
showError(data.message || "Failed to save user");
}
} catch (error) {
console.error("Failed to save user:", error);
2026-01-01 22:24:30 -06:00
hideLoading();
2025-12-14 01:54:40 -06:00
showError("Failed to save user");
}
}
function showChangePassword(id, name) {
document.getElementById("passwordUserId").value = id;
document.getElementById("passwordUserName").value = name;
document.getElementById("passwordUserDisplay").textContent = name;
document.getElementById("passwordForm").reset();
passwordModal.show();
}
async function changePassword() {
const id = document.getElementById("passwordUserId").value;
const newPassword = document.getElementById("newPassword").value;
const confirmPassword = document.getElementById("confirmNewPassword").value;
if (!newPassword || !confirmPassword) {
showError("Please enter and confirm the new password");
return;
}
if (newPassword !== confirmPassword) {
showError("Passwords do not match");
return;
}
if (newPassword.length < 8) {
showError("Password must be at least 8 characters long");
return;
}
2026-01-18 02:22:05 -06:00
if (!/[A-Z]/.test(newPassword)) {
showError("Password must contain at least one uppercase letter");
return;
}
if (!/[a-z]/.test(newPassword)) {
showError("Password must contain at least one lowercase letter");
return;
}
if (!/[0-9]/.test(newPassword)) {
showError("Password must contain at least one number");
return;
}
2026-01-01 22:24:30 -06:00
showLoading("Changing password...");
2025-12-14 01:54:40 -06:00
try {
const response = await fetch(`/api/admin/users/${id}/password`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({ password: newPassword }),
});
const data = await response.json();
2026-01-01 22:24:30 -06:00
hideLoading();
2025-12-14 01:54:40 -06:00
if (data.success) {
showSuccess("Password changed successfully");
passwordModal.hide();
} else {
showError(data.message || "Failed to change password");
}
} catch (error) {
console.error("Failed to change password:", error);
2026-01-01 22:24:30 -06:00
hideLoading();
2025-12-14 01:54:40 -06:00
showError("Failed to change password");
}
}
async function deleteUser(id, name) {
2026-01-18 02:22:05 -06:00
showDeleteConfirm(
`Are you sure you want to delete user "${name}"? This action cannot be undone.`,
async () => {
showLoading("Deleting user...");
try {
const response = await fetch(`/api/admin/users/${id}`, {
method: "DELETE",
credentials: "include",
});
const data = await response.json();
hideLoading();
if (data.success) {
showSuccess("User deleted successfully");
loadUsers();
} else {
showError(data.message || "Failed to delete user");
}
} catch (error) {
console.error("Failed to delete user:", error);
hideLoading();
showError("Failed to delete user");
}
},
{ title: "Delete User", confirmText: "Delete User" },
);
2025-12-14 01:54:40 -06:00
}
function updatePermissionsPreview() {
const role = document.getElementById("userRole").value;
const permissions = rolePermissions[role] || [];
const container = document.getElementById("permissionsPreview");
container.innerHTML = permissions
.map(
(perm) => `
<div class="permission-item active">
<i class="bi bi-check-circle-fill" style="color: #10b981; margin-right: 8px;"></i>
<span>${perm}</span>
</div>
2026-01-18 02:22:05 -06:00
`,
2025-12-14 01:54:40 -06:00
)
.join("");
}
function escapeHtml(text) {
const map = {
"&": "&amp;",
"<": "&lt;",
">": "&gt;",
'"': "&quot;",
"'": "&#039;",
};
return text.replace(/[&<>"']/g, (m) => map[m]);
}
function formatDate(dateString) {
return new Date(dateString).toLocaleDateString("en-US", {
year: "numeric",
month: "short",
day: "numeric",
});
}
2026-01-01 22:24:30 -06:00
// Custom notification system
function showNotification(type, title, message) {
const container = document.getElementById("notificationContainer");
const notification = document.createElement("div");
notification.className = `notification notification-${type}`;
const icon =
type === "success"
? '<i class="bi bi-check-circle-fill"></i>'
: '<i class="bi bi-exclamation-circle-fill"></i>';
notification.innerHTML = `
<div class="notification-icon">${icon}</div>
<div class="notification-content">
<div class="notification-title">${title}</div>
<div class="notification-message">${message}</div>
</div>
<button class="notification-close" onclick="closeNotification(this)">
<i class="bi bi-x"></i>
</button>
`;
container.appendChild(notification);
// Auto remove after 3 seconds
setTimeout(() => {
if (notification.parentElement) {
notification.classList.add("removing");
setTimeout(() => {
if (notification.parentElement) {
notification.remove();
}
}, 300);
}
}, 3000);
}
function closeNotification(button) {
const notification = button.closest(".notification");
notification.classList.add("removing");
setTimeout(() => {
notification.remove();
}, 300);
}
2025-12-14 01:54:40 -06:00
function showSuccess(message) {
2026-01-01 22:24:30 -06:00
showNotification("success", "Success!", message);
2025-12-14 01:54:40 -06:00
}
2026-01-01 22:24:30 -06:00
2025-12-14 01:54:40 -06:00
function showError(message) {
2026-01-01 22:24:30 -06:00
showNotification("error", "Error", message);
}
// Loading overlay functions
function showLoading(message = "Saving...") {
const overlay = document.createElement("div");
overlay.className = "loading-overlay";
overlay.id = "loadingOverlay";
overlay.innerHTML = `
<div class="loading-spinner-container">
<div class="loading-spinner-icon"></div>
<div class="loading-spinner-text">${message}</div>
</div>
`;
document.body.appendChild(overlay);
}
function hideLoading() {
const overlay = document.getElementById("loadingOverlay");
if (overlay) {
overlay.remove();
}
2025-12-14 01:54:40 -06:00
}