/* Settings Module JavaScript */
(function () {
"use strict";
var STORAGE_KEYS = {
LOCALE: "gb-locale",
DATE_FORMAT: "gb-date-format",
TIME_FORMAT: "gb-time-format",
THEME: "gb-theme",
COMPACT_MODE: "gb-compact-mode",
ANIMATIONS: "gb-animations",
};
var API_ENDPOINTS = {
USERS_LIST: "/users/list",
USERS_CREATE: "/users/create",
USERS_UPDATE: "/users/:user_id/update",
USERS_DELETE: "/users/:user_id/delete",
USERS_PROFILE: "/users/:user_id/profile",
USERS_ASSIGN_ORG: "/users/:user_id/organization",
USERS_MEMBERSHIPS: "/users/:user_id/memberships",
GROUPS_LIST: "/groups/list",
GROUPS_CREATE: "/groups/create",
GROUPS_UPDATE: "/groups/:group_id/update",
GROUPS_DELETE: "/groups/:group_id/delete",
GROUPS_MEMBERS: "/groups/:group_id/members",
GROUPS_ADD_MEMBER: "/groups/:group_id/members/add",
GROUPS_REMOVE_MEMBER: "/groups/:group_id/members/remove",
ORGS_LIST: "/organizations/list",
CURRENT_USER: "/api/auth/me",
LOGOUT: "/api/auth/logout",
};
var currentUser = null;
var currentOrgId = null;
var usersData = [];
var groupsData = [];
var organizationsData = [];
function getAuthToken() {
return localStorage.getItem("gb-access-token");
}
function apiUrl(endpoint) {
return "/api/directory" + endpoint;
}
function init() {
bindNavigation();
bindToggles();
bindThemeSelector();
bindAvatarUpload();
bindFormValidation();
initLanguageSettings();
loadSavedSettings();
initUserManagement();
initGroupManagement();
loadCurrentUser();
}
function loadCurrentUser() {
var token = getAuthToken();
if (!token) return;
fetch(API_ENDPOINTS.CURRENT_USER, {
headers: { Authorization: "Bearer " + token },
})
.then(function (r) { return r.ok ? r.json() : null; })
.then(function (user) {
if (user) {
currentUser = user;
currentOrgId = user.organization_id;
updateUserDisplay();
checkAdminAccess();
}
})
.catch(function (e) { console.warn("Failed to load user:", e); });
}
function updateUserDisplay() {
if (!currentUser) return;
var displayNameEl = document.getElementById("user-display-name");
var emailEl = document.getElementById("user-email");
if (displayNameEl) {
displayNameEl.textContent = currentUser.display_name || currentUser.username || "";
}
if (emailEl) {
emailEl.textContent = currentUser.email || "";
}
}
function getInitials(name) {
if (!name) return "??";
var parts = name.split(" ");
if (parts.length >= 2) return (parts[0][0] + parts[1][0]).toUpperCase();
return name.substring(0, 2).toUpperCase();
}
function checkAdminAccess() {
if (!currentUser || !currentUser.roles) return;
var isAdmin = currentUser.roles.some(function (r) {
var rl = r.toLowerCase();
return rl.indexOf("admin") !== -1 || rl.indexOf("super") !== -1;
});
var adminSections = document.querySelectorAll('[data-admin-only="true"], .admin-only');
var adminNavItems = document.querySelectorAll('.nav-item[href="#users"], .nav-item[href="#groups"]');
adminSections.forEach(function (s) { s.style.display = isAdmin ? "" : "none"; });
adminNavItems.forEach(function (i) { i.style.display = isAdmin ? "" : "none"; });
if (isAdmin) {
loadUsers();
loadGroups();
}
}
function bindNavigation() {
document.querySelectorAll(".settings-nav-item").forEach(function (item) {
item.addEventListener("click", function (e) {
e.preventDefault();
document.querySelectorAll(".settings-nav-item").forEach(function (i) { i.classList.remove("active"); });
this.classList.add("active");
});
});
}
function bindToggles() {
document.querySelectorAll(".toggle-switch input").forEach(function (toggle) {
toggle.addEventListener("change", function () {
saveSetting(this.dataset.setting, this.checked);
});
});
}
function bindThemeSelector() {
document.querySelectorAll(".theme-option input").forEach(function (option) {
option.addEventListener("change", function () {
document.body.setAttribute("data-theme", this.value);
saveSetting("theme", this.value);
});
});
}
function bindAvatarUpload() {
var avatarInput = document.getElementById("avatar-input");
if (avatarInput) {
avatarInput.addEventListener("change", function (e) {
var file = e.target.files[0];
if (file) {
var reader = new FileReader();
reader.onload = function (ev) {
var preview = document.querySelector(".avatar-preview img");
if (preview) preview.src = ev.target.result;
};
reader.readAsDataURL(file);
}
});
}
}
function bindFormValidation() {
document.querySelectorAll(".settings-form").forEach(function (form) {
form.addEventListener("submit", function (e) {
var inputs = form.querySelectorAll("[required]");
var valid = true;
inputs.forEach(function (input) {
if (!input.value.trim()) {
valid = false;
input.classList.add("error");
} else {
input.classList.remove("error");
}
});
if (!valid) e.preventDefault();
});
});
}
function initLanguageSettings() {
var languageSelect = document.getElementById("language-select");
var savedLocale = localStorage.getItem(STORAGE_KEYS.LOCALE) || "en";
if (languageSelect) languageSelect.value = savedLocale;
document.querySelectorAll(".language-option").forEach(function (opt) {
opt.classList.toggle("active", opt.dataset.locale === savedLocale);
});
}
function loadSavedSettings() {
var theme = localStorage.getItem(STORAGE_KEYS.THEME);
if (theme) {
document.body.setAttribute("data-theme", theme);
var themeOption = document.querySelector('.theme-option[data-theme="' + theme + '"]');
if (themeOption) themeOption.classList.add("active");
}
var compactMode = localStorage.getItem(STORAGE_KEYS.COMPACT_MODE) === "true";
var compactToggle = document.querySelector('[name="compact_mode"]');
if (compactToggle) {
compactToggle.checked = compactMode;
if (compactMode) document.body.classList.add("compact-mode");
}
var animations = localStorage.getItem(STORAGE_KEYS.ANIMATIONS) !== "false";
var animationsToggle = document.querySelector('[name="animations"]');
if (animationsToggle) {
animationsToggle.checked = animations;
if (!animations) document.body.classList.add("no-animations");
}
}
function saveSetting(key, value) {
try { localStorage.setItem(key, JSON.stringify(value)); }
catch (e) { console.warn("Failed to save setting:", key, e); }
}
function initUserManagement() {
var addUserBtn = document.getElementById("add-user-btn");
if (addUserBtn) addUserBtn.addEventListener("click", openAddUserDialog);
var userSearchInput = document.getElementById("user-search");
if (userSearchInput) {
userSearchInput.addEventListener("input", debounce(function () { loadUsers(this.value); }, 300));
}
var addUserForm = document.getElementById("add-user-form");
if (addUserForm) addUserForm.addEventListener("submit", handleAddUser);
}
function initGroupManagement() {
var addGroupBtn = document.getElementById("add-group-btn");
if (addGroupBtn) addGroupBtn.addEventListener("click", openAddGroupDialog);
var groupSearchInput = document.getElementById("group-search");
if (groupSearchInput) {
groupSearchInput.addEventListener("input", debounce(function () { loadGroups(this.value); }, 300));
}
var addGroupForm = document.getElementById("add-group-form");
if (addGroupForm) addGroupForm.addEventListener("submit", handleAddGroup);
}
function loadUsers(search) {
var container = document.getElementById("users-list");
if (!container) return;
container.innerHTML = '
';
var params = new URLSearchParams();
params.append("per_page", "50");
if (search) params.append("search", search);
if (currentOrgId) params.append("organization_id", currentOrgId);
var token = getAuthToken();
fetch(apiUrl(API_ENDPOINTS.USERS_LIST) + "?" + params.toString(), {
headers: { Authorization: "Bearer " + token },
})
.then(function (r) { if (!r.ok) throw new Error("Failed"); return r.json(); })
.then(function (data) { usersData = data.users || []; renderUsers(container); })
.catch(function () { container.innerHTML = ''; });
}
function renderUsers(container) {
if (usersData.length === 0) {
container.innerHTML = '';
return;
}
var html = '| User | Email | Organization | Roles | Status | Actions |
';
usersData.forEach(function (user) {
var initials = getInitials(user.username || user.email);
var displayName = user.display_name || user.username || user.email;
var isActive = user.state === "active" || user.state === "USER_STATE_ACTIVE";
var roles = (user.roles || []).join(", ") || "user";
var orgId = user.organization_id || "-";
html += "";
html += '' + initials + ' ' + escapeHtml(displayName) + '@' + escapeHtml(user.username || "") + ' | ';
html += "" + escapeHtml(user.email || "") + " | ";
html += "" + escapeHtml(orgId) + " | ";
html += "" + escapeHtml(roles) + " | ";
html += '' + (isActive ? 'Active' : 'Inactive') + ' | ';
html += '';
html += '';
html += '';
html += ' |
';
});
html += "
";
container.innerHTML = html;
}
function loadGroups(search) {
var container = document.getElementById("groups-list");
if (!container) return;
container.innerHTML = '';
var params = new URLSearchParams();
params.append("per_page", "50");
if (search) params.append("search", search);
var token = getAuthToken();
fetch(apiUrl(API_ENDPOINTS.GROUPS_LIST) + "?" + params.toString(), {
headers: { Authorization: "Bearer " + token },
})
.then(function (r) { if (!r.ok) throw new Error("Failed"); return r.json(); })
.then(function (data) { groupsData = data.groups || []; renderGroups(container); })
.catch(function () { container.innerHTML = ''; });
}
function renderGroups(container) {
if (groupsData.length === 0) {
container.innerHTML = '';
return;
}
var html = '';
groupsData.forEach(function (group) {
html += '
';
html += '';
if (group.description) html += '
' + escapeHtml(group.description) + '
';
html += '
';
html += '
';
html += '
';
html += '
';
html += '
';
});
html += '
';
container.innerHTML = html;
}
function openAddUserDialog() {
var dialog = document.getElementById("add-user-dialog");
if (dialog) dialog.showModal();
}
function closeAddUserDialog() {
var dialog = document.getElementById("add-user-dialog");
if (dialog) { dialog.close(); var f = document.getElementById("add-user-form"); if (f) f.reset(); }
}
function openAddGroupDialog() {
var dialog = document.getElementById("add-group-dialog");
if (dialog) dialog.showModal();
}
function closeAddGroupDialog() {
var dialog = document.getElementById("add-group-dialog");
if (dialog) { dialog.close(); var f = document.getElementById("add-group-form"); if (f) f.reset(); }
}
function handleAddUser(e) {
e.preventDefault();
var form = e.target, btn = form.querySelector('button[type="submit"]'), orig = btn.textContent;
btn.disabled = true; btn.textContent = "Creating...";
var userData = {
username: form.username.value,
email: form.email.value,
password: form.password ? form.password.value : null,
first_name: form.first_name.value,
last_name: form.last_name.value,
role: form.role ? form.role.value : "user",
organization_id: currentOrgId || (form.organization_id ? form.organization_id.value : null),
roles: form.roles ? [form.roles.value] : ["user"]
};
fetch(apiUrl(API_ENDPOINTS.USERS_CREATE), {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + getAuthToken() },
body: JSON.stringify(userData)
})
.then(function (r) { if (!r.ok) return r.json().then(function (err) { throw new Error(err.error || "Failed"); }); return r.json(); })
.then(function () { showToast("User created and added to organization", "success"); closeAddUserDialog(); loadUsers(); })
.catch(function (e) { showToast(e.message, "error"); })
.finally(function () { btn.disabled = false; btn.textContent = orig; });
}
function handleAddGroup(e) {
e.preventDefault();
var form = e.target, btn = form.querySelector('button[type="submit"]'), orig = btn.textContent;
btn.disabled = true; btn.textContent = "Creating...";
var groupData = { name: form.name.value, description: form.description ? form.description.value : "" };
fetch(apiUrl(API_ENDPOINTS.GROUPS_CREATE), {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + getAuthToken() },
body: JSON.stringify(groupData)
})
.then(function (r) { if (!r.ok) return r.json().then(function (err) { throw new Error(err.error || "Failed"); }); return r.json(); })
.then(function () { showToast("Group created successfully", "success"); closeAddGroupDialog(); loadGroups(); })
.catch(function (e) { showToast(e.message, "error"); })
.finally(function () { btn.disabled = false; btn.textContent = orig; });
}
function editUser(userId) {
var user = usersData.find(function (u) { return u.id === userId; });
if (!user) return;
var d = document.getElementById("edit-user-dialog");
if (d) d.showModal();
}
function deleteUser(userId) {
if (!confirm("Delete this user?")) return;
fetch(apiUrl(API_ENDPOINTS.USERS_DELETE.replace(":user_id", userId)), {
method: "DELETE",
headers: { Authorization: "Bearer " + getAuthToken() }
})
.then(function (r) { if (!r.ok) throw new Error("Failed"); showToast("User deleted", "success"); loadUsers(); })
.catch(function (e) { showToast(e.message, "error"); });
}
function editGroup(groupId) {
var group = groupsData.find(function (g) { return g.id === groupId; });
if (!group) return;
var d = document.getElementById("edit-group-dialog");
if (d) d.showModal();
}
function deleteGroup(groupId) {
if (!confirm("Delete this group?")) return;
fetch(apiUrl(API_ENDPOINTS.GROUPS_DELETE.replace(":group_id", groupId)), {
method: "DELETE",
headers: { Authorization: "Bearer " + getAuthToken() }
})
.then(function (r) { if (!r.ok) throw new Error("Failed"); showToast("Group deleted", "success"); loadGroups(); })
.catch(function (e) { showToast(e.message, "error"); });
}
function viewGroupMembers(groupId) {
var d = document.getElementById("group-members-dialog");
if (d) d.showModal();
}
function removeGroupMember(groupId, userId) {
if (!confirm("Remove member?")) return;
fetch(apiUrl(API_ENDPOINTS.GROUPS_REMOVE_MEMBER.replace(":group_id", groupId)), {
method: "POST",
headers: { "Content-Type": "application/json", Authorization: "Bearer " + getAuthToken() },
body: JSON.stringify({ user_id: userId })
})
.then(function (r) { if (!r.ok) throw new Error("Failed"); showToast("Member removed", "success"); loadGroups(); })
.catch(function (e) { showToast(e.message, "error"); });
}
function handleLogout() {
var token = getAuthToken();
if (token) fetch(API_ENDPOINTS.LOGOUT, { method: "POST", headers: { Authorization: "Bearer " + token } }).catch(function () {});
localStorage.removeItem("gb-access-token");
localStorage.removeItem("gb-refresh-token");
localStorage.removeItem("gb-token-expires");
localStorage.removeItem("gb-user-data");
window.location.href = "/auth/login";
}
function escapeHtml(text) { if (!text) return ""; var d = document.createElement("div"); d.textContent = text; return d.innerHTML; }
function debounce(func, wait) { var timeout; return function () { var ctx = this, args = arguments; clearTimeout(timeout); timeout = setTimeout(function () { func.apply(ctx, args); }, wait); }; }
function showToast(message, type) {
type = type || "success";
var existing = document.querySelector(".toast");
if (existing) existing.remove();
var toast = document.createElement("div");
toast.className = "toast toast-" + type;
toast.innerHTML = '' + message + '';
document.body.appendChild(toast);
requestAnimationFrame(function () { toast.classList.add("show"); });
setTimeout(function () { toast.classList.remove("show"); setTimeout(function () { toast.remove(); }, 300); }, 3000);
}
window.changeLanguage = function (locale) { localStorage.setItem(STORAGE_KEYS.LOCALE, locale); document.documentElement.lang = locale; showToast("Language changed"); setTimeout(function () { location.reload(); }, 500); };
window.selectLanguage = function (locale, element) { document.querySelectorAll(".language-option").forEach(function (o) { o.classList.remove("active"); }); if (element) element.classList.add("active"); changeLanguage(locale); };
window.changeDateFormat = function (format) { localStorage.setItem(STORAGE_KEYS.DATE_FORMAT, format); showToast("Date format updated"); };
window.changeTimeFormat = function (format) { localStorage.setItem(STORAGE_KEYS.TIME_FORMAT, format); showToast("Time format updated"); };
window.setTheme = function (theme, element) { document.body.setAttribute("data-theme", theme); localStorage.setItem(STORAGE_KEYS.THEME, theme); document.querySelectorAll(".theme-option").forEach(function (o) { o.classList.remove("active"); }); if (element) element.classList.add("active"); showToast("Theme updated"); };
window.toggleCompactMode = function (checkbox) { var enabled = checkbox.checked; localStorage.setItem(STORAGE_KEYS.COMPACT_MODE, enabled); document.body.classList.toggle("compact-mode", enabled); showToast(enabled ? "Compact mode enabled" : "Compact mode disabled"); };
window.toggleAnimations = function (checkbox) { var enabled = checkbox.checked; localStorage.setItem(STORAGE_KEYS.ANIMATIONS, enabled); document.body.classList.toggle("no-animations", !enabled); showToast(enabled ? "Animations enabled" : "Animations disabled"); };
window.showSection = function (sectionId, navElement) { document.querySelectorAll(".settings-section").forEach(function (s) { s.classList.remove("active"); }); document.querySelectorAll(".nav-item").forEach(function (i) { i.classList.remove("active"); }); var section = document.getElementById(sectionId + "-section"); if (section) section.classList.add("active"); if (navElement) navElement.classList.add("active"); history.replaceState(null, "", "#" + sectionId); };
window.showToast = showToast;
window.previewAvatar = function (input) { if (input.files && input.files[0]) { var reader = new FileReader(); reader.onload = function (e) { var avatar = document.getElementById("current-avatar"); if (avatar) avatar.innerHTML = '
'; }; reader.readAsDataURL(input.files[0]); } };
window.removeAvatar = function () { var avatar = document.getElementById("current-avatar"); if (avatar) avatar.innerHTML = "JD"; showToast("Avatar removed"); };
function initFromHash() { var hash = window.location.hash.slice(1); if (hash) { var navItem = document.querySelector('.nav-item[href="#' + hash + '"]'); if (navItem) showSection(hash, navItem); } }
window.SettingsModule = {
init: init, changeLanguage: window.changeLanguage, changeDateFormat: window.changeDateFormat, changeTimeFormat: window.changeTimeFormat, setTheme: window.setTheme, showToast: showToast,
editUser: editUser, deleteUser: deleteUser, editGroup: editGroup, deleteGroup: deleteGroup, viewGroupMembers: viewGroupMembers, removeGroupMember: removeGroupMember, logout: handleLogout
};
if (document.readyState === "loading") {
document.addEventListener("DOMContentLoaded", function () { init(); initFromHash(); });
} else {
init();
initFromHash();
}
})();