// Shared Authentication Utility for Admin Panel // Include this file in all admin pages to handle authentication // Global authentication state window.adminAuth = { user: null, isAuthenticated: false, }; // Load and apply theme on all admin pages function loadAdminTheme() { const savedTheme = localStorage.getItem("adminTheme") || "light"; applyAdminTheme(savedTheme); // Watch for system theme changes if in auto mode if (savedTheme === "auto") { window .matchMedia("(prefers-color-scheme: dark)") .addEventListener("change", (e) => { if (localStorage.getItem("adminTheme") === "auto") { applyAdminTheme("auto"); } }); } } function applyAdminTheme(theme) { const body = document.body; if (theme === "dark") { body.classList.add("dark-mode"); body.classList.remove("light-mode"); } else if (theme === "light") { body.classList.add("light-mode"); body.classList.remove("dark-mode"); } else if (theme === "auto") { // Check system preference const prefersDark = window.matchMedia( "(prefers-color-scheme: dark)" ).matches; if (prefersDark) { body.classList.add("dark-mode"); body.classList.remove("light-mode"); } else { body.classList.add("light-mode"); body.classList.remove("dark-mode"); } } } // Initialize theme immediately (before page loads) loadAdminTheme(); // Check authentication and redirect if needed - attach to window window.checkAuth = async function () { try { const response = await fetch("/api/admin/session", { credentials: "include", headers: { Accept: "application/json", }, }); if (!response.ok) { window.redirectToLogin(); return false; } const data = await response.json(); if (!data.authenticated) { window.redirectToLogin(); return false; } // Store user data window.adminAuth.user = data.user; window.adminAuth.isAuthenticated = true; // Initialize mobile menu after auth check window.initMobileMenu(); return true; } catch (error) { // Only log in development if (window.location.hostname === "localhost") { console.error("Authentication check failed:", error); } window.redirectToLogin(); return false; } }; // Redirect to login page window.redirectToLogin = function () { if (window.location.pathname !== "/admin/login.html") { window.location.href = "/admin/login.html"; } }; // Initialize mobile menu toggle window.initMobileMenu = function () { // Check if mobile menu button exists let menuToggle = document.getElementById("mobileMenuToggle"); if (!menuToggle && window.innerWidth <= 768) { // Create mobile menu button menuToggle = document.createElement("button"); menuToggle.id = "mobileMenuToggle"; menuToggle.className = "mobile-menu-toggle"; menuToggle.setAttribute("aria-label", "Toggle navigation menu"); menuToggle.setAttribute("aria-expanded", "false"); menuToggle.innerHTML = ''; document.body.appendChild(menuToggle); } if (menuToggle) { menuToggle.addEventListener("click", function () { const sidebar = document.querySelector(".sidebar"); if (sidebar) { const isActive = sidebar.classList.toggle("active"); this.setAttribute("aria-expanded", isActive ? "true" : "false"); this.innerHTML = isActive ? '' : ''; } }); // Close sidebar when clicking outside on mobile document.addEventListener("click", function (event) { const sidebar = document.querySelector(".sidebar"); const menuToggle = document.getElementById("mobileMenuToggle"); if (sidebar && menuToggle && window.innerWidth <= 768) { if ( !sidebar.contains(event.target) && event.target !== menuToggle && !menuToggle.contains(event.target) ) { if (sidebar.classList.contains("active")) { sidebar.classList.remove("active"); menuToggle.setAttribute("aria-expanded", "false"); menuToggle.innerHTML = ''; } } } }); // Close menu on link click (mobile) const sidebarLinks = document.querySelectorAll(".sidebar-menu a"); sidebarLinks.forEach((link) => { link.addEventListener("click", function () { if (window.innerWidth <= 768) { const sidebar = document.querySelector(".sidebar"); if (sidebar && sidebar.classList.contains("active")) { sidebar.classList.remove("active"); if (menuToggle) { menuToggle.setAttribute("aria-expanded", "false"); menuToggle.innerHTML = ''; } } } }); }); } // Handle window resize let resizeTimer; window.addEventListener("resize", function () { clearTimeout(resizeTimer); resizeTimer = setTimeout(function () { if (window.innerWidth > 768) { const sidebar = document.querySelector(".sidebar"); if (sidebar) { sidebar.classList.remove("active"); } if (menuToggle) { menuToggle.setAttribute("aria-expanded", "false"); menuToggle.innerHTML = ''; } } }, 250); }); }; // Custom logout confirmation modal window.showLogoutConfirm = function (onConfirm) { // Create modal backdrop const backdrop = document.createElement("div"); backdrop.style.cssText = ` position: fixed; top: 0; left: 0; width: 100%; height: 100%; background: rgba(0, 0, 0, 0.5); z-index: 10000; display: flex; align-items: center; justify-content: center; animation: fadeIn 0.2s ease; `; // Create modal const modal = document.createElement("div"); modal.style.cssText = ` background: white; border-radius: 12px; padding: 30px; max-width: 400px; width: 90%; box-shadow: 0 10px 40px rgba(0, 0, 0, 0.3); animation: slideIn 0.3s ease; `; modal.innerHTML = `

Confirm Logout

Are you sure you want to logout?

`; backdrop.appendChild(modal); document.body.appendChild(backdrop); // Add hover effects const cancelBtn = modal.querySelector("#cancelLogout"); const confirmBtn = modal.querySelector("#confirmLogout"); cancelBtn.addEventListener("mouseenter", function () { this.style.background = "#6c757d"; this.style.color = "white"; }); cancelBtn.addEventListener("mouseleave", function () { this.style.background = "white"; this.style.color = "#6c757d"; }); confirmBtn.addEventListener("mouseenter", function () { this.style.transform = "translateY(-2px)"; this.style.boxShadow = "0 4px 12px rgba(220, 53, 69, 0.4)"; }); confirmBtn.addEventListener("mouseleave", function () { this.style.transform = "translateY(0)"; this.style.boxShadow = "0 2px 8px rgba(220, 53, 69, 0.3)"; }); // Handle buttons const closeModal = () => { backdrop.style.animation = "fadeIn 0.2s ease reverse"; setTimeout(() => backdrop.remove(), 200); }; cancelBtn.addEventListener("click", closeModal); backdrop.addEventListener("click", function (e) { if (e.target === backdrop) closeModal(); }); confirmBtn.addEventListener("click", function () { closeModal(); onConfirm(); }); // ESC key to close const escHandler = (e) => { if (e.key === "Escape") { closeModal(); document.removeEventListener("keydown", escHandler); } }; document.addEventListener("keydown", escHandler); }; // Logout function - explicitly attach to window for onclick handlers window.logout = async function (skipConfirm = false) { if (!skipConfirm) { window.showLogoutConfirm(async () => { await performLogout(); }); return; } await performLogout(); }; // CRITICAL: Global function for inline onclick="logout()" handlers // This must be at global scope so inline onclick can find it function logout(skipConfirm = false) { window.logout(skipConfirm); } // Actual logout logic async function performLogout() { try { const response = await fetch("/api/admin/logout", { method: "POST", credentials: "include", }); if (response.ok) { window.adminAuth.user = null; window.adminAuth.isAuthenticated = false; window.location.href = "/admin/login.html"; } else { console.error("Logout failed with status:", response.status); // Still redirect to login even if logout fails window.location.href = "/admin/login.html"; } } catch (error) { console.error("Logout error:", error); // Still redirect to login even if logout fails window.location.href = "/admin/login.html"; } } // Show success notification window.showSuccess = function (message) { const alert = document.createElement("div"); alert.className = "alert alert-success alert-dismissible fade show position-fixed"; alert.style.cssText = "top: 20px; right: 20px; z-index: 9999; min-width: 300px;"; alert.innerHTML = ` ${message} `; document.body.appendChild(alert); setTimeout(() => alert.remove(), 5000); }; // Show error notification window.showError = function (message) { const alert = document.createElement("div"); alert.className = "alert alert-danger alert-dismissible fade show position-fixed"; alert.style.cssText = "top: 20px; right: 20px; z-index: 9999; min-width: 300px;"; alert.innerHTML = ` ${message} `; document.body.appendChild(alert); setTimeout(() => alert.remove(), 5000); }; // Auto-check authentication when this script loads // Only run if we're not on the login page if (window.location.pathname !== "/admin/login.html") { document.addEventListener("DOMContentLoaded", function () { window.checkAuth(); // Attach logout event listeners to all logout buttons const logoutButtons = document.querySelectorAll( '.btn-logout, [data-logout], [onclick*="logout"]' ); logoutButtons.forEach((button) => { // Remove inline onclick if it exists button.removeAttribute("onclick"); // Add proper event listener button.addEventListener("click", function (e) { e.preventDefault(); e.stopPropagation(); window.logout(); }); }); }); } // Fix Bootstrap modal aria-hidden focus warning for all modals - Universal Solution (function () { // Use event delegation on document level to catch all modal hide events document.addEventListener( "hide.bs.modal", function (event) { // Get the modal that's closing const modalElement = event.target; // Blur any focused element inside the modal before it closes const focusedElement = document.activeElement; if (focusedElement && modalElement.contains(focusedElement)) { focusedElement.blur(); } }, true ); // Use capture phase to run before Bootstrap's handlers })();