@@ -430,51 +435,419 @@ const components = Object.fromEntries(
const userName = document.getElementById("userName");
const userRole = document.getElementById("userRole");
- // Function to update section visibility based on role
- const updateSectionVisibility = (officerStatus: OfficerStatus) => {
- // Special handling for sponsor role
- if (officerStatus === "sponsor") {
- // Hide all sections first
- document
- .querySelectorAll("[data-role-required]")
- .forEach((element) => {
- element.classList.add("hidden");
- });
+ // Centralized sidebar management
+ class SidebarManager {
+ private sidebar: HTMLElement | null = null;
+ private overlay: HTMLElement | null = null;
+ private toggleBtn: HTMLElement | null = null;
+ private closeBtn: HTMLElement | null = null;
+ private breakpoint = 1024; // lg breakpoint
+ private isInitialized = false;
- // Only show sponsor sections
- document
- .querySelectorAll('[data-role-required="sponsor"]')
- .forEach((element) => {
- element.classList.remove("hidden");
- });
- return;
+ constructor() {
+ // Initialize elements
+ this.sidebar = document.getElementById("dashboardSidebar");
+ this.overlay = document.getElementById("sidebarOverlay");
+ this.toggleBtn = document.getElementById(
+ "mobileSidebarToggle"
+ );
+ this.closeBtn = document.getElementById("closeSidebarBtn");
+
+ if (!this.sidebar || !this.overlay) {
+ console.error("Sidebar elements not found");
+ return;
+ }
+
+ this.setupEventListeners();
+ this.checkViewportSize();
+ this.isInitialized = true;
}
- // For non-sponsor roles, handle normally
- document
- .querySelectorAll("[data-role-required]")
- .forEach((element) => {
- const requiredRole = element.getAttribute(
- "data-role-required"
- ) as OfficerStatus;
+ private setupEventListeners(): void {
+ // Toggle button (mobile only)
+ this.toggleBtn?.addEventListener("click", () =>
+ this.openSidebar()
+ );
- // Skip elements that don't have a role requirement
- if (!requiredRole || requiredRole === "none") {
- element.classList.remove("hidden");
+ // Close button (mobile only)
+ this.closeBtn?.addEventListener("click", () =>
+ this.closeSidebar()
+ );
+
+ // Overlay click to close
+ this.overlay?.addEventListener("click", () =>
+ this.closeSidebar()
+ );
+
+ // ESC key to close
+ document.addEventListener("keydown", (e) => {
+ if (e.key === "Escape") this.closeSidebar();
+ });
+
+ // Handle resize events with debounce
+ let resizeTimer: ReturnType;
+ window.addEventListener("resize", () => {
+ clearTimeout(resizeTimer);
+ resizeTimer = setTimeout(() => {
+ this.checkViewportSize();
+ }, 100);
+ });
+
+ // Handle navigation item clicks
+ document
+ .querySelectorAll(".dashboard-nav-btn")
+ .forEach((btn) => {
+ btn.addEventListener("click", (e) => {
+ const target = e.currentTarget as HTMLElement;
+ const sectionKey =
+ target.getAttribute("data-section");
+
+ // Skip for logout button
+ if (sectionKey === "logout") return;
+
+ // Close sidebar on mobile when navigation happens
+ if (window.innerWidth < this.breakpoint) {
+ this.closeSidebar();
+ }
+ });
+ });
+ }
+
+ private checkViewportSize(): void {
+ if (!this.sidebar) return;
+
+ const isMobile = window.innerWidth < this.breakpoint;
+
+ if (isMobile) {
+ // On mobile, ensure sidebar starts closed
+ this.closeSidebar();
+ } else {
+ // On desktop, ensure sidebar is always visible
+ this.sidebar.classList.remove("-translate-x-full");
+ this.sidebar.classList.add("translate-x-0");
+ document.body.classList.remove("overflow-hidden");
+ this.hideOverlay();
+ }
+ }
+
+ private openSidebar(): void {
+ if (!this.sidebar) return;
+
+ // Show sidebar
+ this.sidebar.classList.remove("-translate-x-full");
+ this.sidebar.classList.add("translate-x-0");
+
+ // Prevent body scrolling
+ document.body.classList.add("overflow-hidden");
+
+ // Show overlay
+ this.showOverlay();
+ }
+
+ private closeSidebar(): void {
+ if (!this.sidebar || window.innerWidth >= this.breakpoint)
+ return;
+
+ // Hide sidebar
+ this.sidebar.classList.add("-translate-x-full");
+ this.sidebar.classList.remove("translate-x-0");
+
+ // Restore body scrolling
+ document.body.classList.remove("overflow-hidden");
+
+ // Hide overlay
+ this.hideOverlay();
+ }
+
+ private showOverlay(): void {
+ this.overlay?.classList.remove("hidden");
+ }
+
+ private hideOverlay(): void {
+ this.overlay?.classList.add("hidden");
+ }
+
+ // Public API
+ public toggle(): void {
+ if (!this.sidebar) return;
+
+ if (this.sidebar.classList.contains("-translate-x-full")) {
+ this.openSidebar();
+ } else {
+ this.closeSidebar();
+ }
+ }
+ }
+
+ // Handle navigation
+ const handleNavigation = () => {
+ const navButtons =
+ document.querySelectorAll(".dashboard-nav-btn");
+ const sections =
+ document.querySelectorAll(".dashboard-section");
+ const mainContentDiv = document.getElementById("mainContent");
+
+ // Ensure mainContent is visible
+ if (mainContentDiv) {
+ mainContentDiv.classList.remove("hidden");
+ }
+
+ navButtons.forEach((button) => {
+ button.addEventListener("click", () => {
+ const sectionKey = button.getAttribute("data-section");
+
+ // Handle logout button
+ if (sectionKey === "logout") {
+ showLogoutConfirmation();
return;
}
- // Check if user has permission for this role
- const hasPermission = hasAccess(
- officerStatus,
- requiredRole
+ // Remove active class from all buttons
+ navButtons.forEach((btn) => {
+ btn.classList.remove("active", "bg-base-200");
+ });
+
+ // Add active class to clicked button
+ button.classList.add("active", "bg-base-200");
+
+ // Hide all sections
+ sections.forEach((section) => {
+ section.classList.add("hidden");
+ });
+
+ // Show selected section
+ const sectionId = `${sectionKey}Section`;
+ const targetSection =
+ document.getElementById(sectionId);
+ if (targetSection) {
+ targetSection.classList.remove("hidden");
+ }
+ });
+ });
+ };
+
+ // Function to initialize the page
+ const initializePage = async () => {
+ try {
+ // Define a temporary toast function that does nothing for unauthenticated users
+ const originalToast = window.toast;
+
+ // Check if user is authenticated
+ if (!auth.isAuthenticated()) {
+ // Temporarily override toast function to prevent notifications for unauthenticated users
+ window.toast = () => {};
+
+ // Initialize auth sync for IndexedDB (but toast notifications will be suppressed)
+ await initAuthSync();
+
+ // Restore original toast function
+ window.toast = originalToast;
+
+ // console.log("User not authenticated");
+ if (pageLoadingState)
+ pageLoadingState.classList.add("hidden");
+ if (notAuthenticatedState)
+ notAuthenticatedState.classList.remove("hidden");
+ return;
+ }
+
+ // Initialize auth sync for IndexedDB (for authenticated users)
+ await initAuthSync();
+
+ if (pageLoadingState)
+ pageLoadingState.classList.remove("hidden");
+ if (pageErrorState) pageErrorState.classList.add("hidden");
+ if (notAuthenticatedState)
+ notAuthenticatedState.classList.add("hidden");
+
+ // Show loading states
+ const userProfileSkeleton = document.getElementById(
+ "userProfileSkeleton"
+ );
+ const userProfileSignedOut = document.getElementById(
+ "userProfileSignedOut"
+ );
+ const userProfileSummary =
+ document.getElementById("userProfileSummary");
+ const menuLoadingSkeleton = document.getElementById(
+ "menuLoadingSkeleton"
+ );
+ const actualMenu = document.getElementById("actualMenu");
+
+ if (userProfileSkeleton)
+ userProfileSkeleton.classList.remove("hidden");
+ if (userProfileSummary)
+ userProfileSummary.classList.add("hidden");
+ if (userProfileSignedOut)
+ userProfileSignedOut.classList.add("hidden");
+ if (menuLoadingSkeleton)
+ menuLoadingSkeleton.classList.remove("hidden");
+ if (actualMenu) actualMenu.classList.add("hidden");
+
+ const user = auth.getCurrentUser();
+ await updateUserProfile(user);
+
+ // Show actual profile and hide skeleton
+ if (userProfileSkeleton)
+ userProfileSkeleton.classList.add("hidden");
+ if (userProfileSummary)
+ userProfileSummary.classList.remove("hidden");
+
+ // Hide all sections first
+ document
+ .querySelectorAll(".dashboard-section")
+ .forEach((section) => {
+ section.classList.add("hidden");
+ });
+
+ // Show appropriate default section based on role
+ // Get the officer record for this user if it exists
+ let officerStatus: OfficerStatus = "none";
+
+ try {
+ const officerRecords = await get.getList(
+ "officers",
+ 1,
+ 50,
+ `user="${user.id}"`,
+ "",
+ {
+ fields: ["id", "type", "role"],
+ }
);
- // Only show elements if user has permission
- element.classList.toggle("hidden", !hasPermission);
- });
+ if (officerRecords && officerRecords.items.length > 0) {
+ const officerType = officerRecords.items[0].type;
+ // We can also get the role here if needed for display elsewhere
+ const officerRole = officerRecords.items[0].role;
+
+ // Map the officer type to our OfficerStatus
+ switch (officerType) {
+ case OfficerTypes.ADMINISTRATOR:
+ officerStatus = "administrator";
+ break;
+ case OfficerTypes.EXECUTIVE:
+ officerStatus = "executive";
+ break;
+ case OfficerTypes.GENERAL:
+ officerStatus = "general";
+ break;
+ case OfficerTypes.HONORARY:
+ officerStatus = "honorary";
+ break;
+ case OfficerTypes.PAST:
+ officerStatus = "past";
+ break;
+ default:
+ officerStatus = "none";
+ }
+ } else {
+ // Check if user is a sponsor by querying the sponsors collection
+ const sponsorRecords = await get.getList(
+ "sponsors",
+ 1,
+ 1,
+ `user="${user.id}"`,
+ "",
+ {
+ fields: ["id", "company"],
+ }
+ );
+
+ if (
+ sponsorRecords &&
+ sponsorRecords.items.length > 0
+ ) {
+ officerStatus = "sponsor";
+ } else {
+ officerStatus = "none";
+ }
+ }
+ } catch (error) {
+ console.error(
+ "Error determining officer status:",
+ error
+ );
+ officerStatus = "none";
+ }
+
+ let defaultSection;
+ let defaultButton;
+
+ // Set default section based on role
+ // Only sponsors get a different default view
+ if (officerStatus === "sponsor") {
+ // For sponsors, show the sponsor dashboard
+ defaultSection = document.getElementById(
+ "sponsorDashboardSection"
+ );
+ defaultButton = document.querySelector(
+ '[data-section="sponsorDashboard"]'
+ );
+ } else {
+ // For all other users (including administrators), show the profile section
+ defaultSection =
+ document.getElementById("profileSection");
+ defaultButton = document.querySelector(
+ '[data-section="profile"]'
+ );
+ }
+
+ if (defaultSection) {
+ defaultSection.classList.remove("hidden");
+ }
+ if (defaultButton) {
+ defaultButton.classList.add("active", "bg-base-200");
+ }
+
+ // Initialize navigation
+ handleNavigation();
+
+ // Show actual menu and hide skeleton
+ if (menuLoadingSkeleton)
+ menuLoadingSkeleton.classList.add("hidden");
+ if (actualMenu) actualMenu.classList.remove("hidden");
+
+ // Show main content and hide loading
+ if (mainContent) mainContent.classList.remove("hidden");
+ if (pageLoadingState)
+ pageLoadingState.classList.add("hidden");
+ } catch (error) {
+ console.error("Error initializing dashboard:", error);
+ if (pageLoadingState)
+ pageLoadingState.classList.add("hidden");
+ if (pageErrorState)
+ pageErrorState.classList.remove("hidden");
+ }
};
+ // Initialize when DOM is loaded
+ document.addEventListener("DOMContentLoaded", () => {
+ initializePage();
+
+ // Initialize sidebar manager
+ new SidebarManager();
+ });
+
+ // Handle login button click
+ document
+ .querySelector(".login-button")
+ ?.addEventListener("click", async () => {
+ try {
+ if (pageLoadingState)
+ pageLoadingState.classList.remove("hidden");
+ if (notAuthenticatedState)
+ notAuthenticatedState.classList.add("hidden");
+ await auth.login();
+ } catch (error) {
+ console.error("Login error:", error);
+ if (pageLoadingState)
+ pageLoadingState.classList.add("hidden");
+ if (pageErrorState)
+ pageErrorState.classList.remove("hidden");
+ }
+ });
+
// Function to delete all cookies (to handle Logto logout)
const deleteAllCookies = () => {
// Get all cookies
@@ -649,61 +1022,49 @@ const components = Object.fromEntries(
if (modal) (modal as HTMLDialogElement).showModal();
};
- // Handle navigation
- const handleNavigation = () => {
- const navButtons =
- document.querySelectorAll(".dashboard-nav-btn");
- const sections =
- document.querySelectorAll(".dashboard-section");
- const mainContentDiv = document.getElementById("mainContent");
+ // Function to update section visibility based on role
+ const updateSectionVisibility = (officerStatus: OfficerStatus) => {
+ // Special handling for sponsor role
+ if (officerStatus === "sponsor") {
+ // Hide all sections first
+ document
+ .querySelectorAll("[data-role-required]")
+ .forEach((element) => {
+ element.classList.add("hidden");
+ });
- // Ensure mainContent is visible
- if (mainContentDiv) {
- mainContentDiv.classList.remove("hidden");
+ // Only show sponsor sections
+ document
+ .querySelectorAll('[data-role-required="sponsor"]')
+ .forEach((element) => {
+ element.classList.remove("hidden");
+ });
+ return;
}
- navButtons.forEach((button) => {
- button.addEventListener("click", () => {
- const sectionKey = button.getAttribute("data-section");
+ // For non-sponsor roles, handle normally
+ document
+ .querySelectorAll("[data-role-required]")
+ .forEach((element) => {
+ const requiredRole = element.getAttribute(
+ "data-role-required"
+ ) as OfficerStatus;
- // Handle logout button
- if (sectionKey === "logout") {
- showLogoutConfirmation();
+ // Skip elements that don't have a role requirement
+ if (!requiredRole || requiredRole === "none") {
+ element.classList.remove("hidden");
return;
}
- // Remove active class from all buttons
- navButtons.forEach((btn) => {
- btn.classList.remove("active", "bg-base-200");
- });
+ // Check if user has permission for this role
+ const hasPermission = hasAccess(
+ officerStatus,
+ requiredRole
+ );
- // Add active class to clicked button
- button.classList.add("active", "bg-base-200");
-
- // Hide all sections
- sections.forEach((section) => {
- section.classList.add("hidden");
- });
-
- // Show selected section
- const sectionId = `${sectionKey}Section`;
- const targetSection =
- document.getElementById(sectionId);
- if (targetSection) {
- targetSection.classList.remove("hidden");
- // console.log(`Showing section: ${sectionId}`); // Debug log
- }
-
- // Close mobile sidebar if needed
- if (window.innerWidth < 1024 && sidebar) {
- sidebar.classList.add("-translate-x-full");
- document.body.classList.remove("overflow-hidden");
- const overlay =
- document.getElementById("sidebarOverlay");
- overlay?.remove();
- }
+ // Only show elements if user has permission
+ element.classList.toggle("hidden", !hasPermission);
});
- });
};
// Display user profile information and handle role-based access
@@ -826,275 +1187,6 @@ const components = Object.fromEntries(
updateSectionVisibility("" as OfficerStatus);
}
};
-
- // Mobile sidebar toggle
- const mobileSidebarToggle = document.getElementById(
- "mobileSidebarToggle"
- );
- if (mobileSidebarToggle && sidebar) {
- const toggleSidebar = () => {
- const isOpen =
- !sidebar.classList.contains("-translate-x-full");
-
- if (isOpen) {
- sidebar.classList.add("-translate-x-full");
- document.body.classList.remove("overflow-hidden");
- const overlay =
- document.getElementById("sidebarOverlay");
- overlay?.remove();
- } else {
- sidebar.classList.remove("-translate-x-full");
- document.body.classList.add("overflow-hidden");
- const overlay = document.createElement("div");
- overlay.id = "sidebarOverlay";
- overlay.className =
- "fixed inset-0 bg-black bg-opacity-50 z-40 xl:hidden";
- overlay.addEventListener("click", toggleSidebar);
- document.body.appendChild(overlay);
- }
- };
-
- mobileSidebarToggle.addEventListener("click", toggleSidebar);
- }
-
- // Function to initialize the page
- const initializePage = async () => {
- try {
- // Define a temporary toast function that does nothing for unauthenticated users
- const originalToast = window.toast;
-
- // Check if user is authenticated
- if (!auth.isAuthenticated()) {
- // Temporarily override toast function to prevent notifications for unauthenticated users
- window.toast = () => {};
-
- // Initialize auth sync for IndexedDB (but toast notifications will be suppressed)
- await initAuthSync();
-
- // Restore original toast function
- window.toast = originalToast;
-
- // console.log("User not authenticated");
- if (pageLoadingState)
- pageLoadingState.classList.add("hidden");
- if (notAuthenticatedState)
- notAuthenticatedState.classList.remove("hidden");
- return;
- }
-
- // Initialize auth sync for IndexedDB (for authenticated users)
- await initAuthSync();
-
- if (pageLoadingState)
- pageLoadingState.classList.remove("hidden");
- if (pageErrorState) pageErrorState.classList.add("hidden");
- if (notAuthenticatedState)
- notAuthenticatedState.classList.add("hidden");
-
- // Show loading states
- const userProfileSkeleton = document.getElementById(
- "userProfileSkeleton"
- );
- const userProfileSignedOut = document.getElementById(
- "userProfileSignedOut"
- );
- const userProfileSummary =
- document.getElementById("userProfileSummary");
- const menuLoadingSkeleton = document.getElementById(
- "menuLoadingSkeleton"
- );
- const actualMenu = document.getElementById("actualMenu");
-
- if (userProfileSkeleton)
- userProfileSkeleton.classList.remove("hidden");
- if (userProfileSummary)
- userProfileSummary.classList.add("hidden");
- if (userProfileSignedOut)
- userProfileSignedOut.classList.add("hidden");
- if (menuLoadingSkeleton)
- menuLoadingSkeleton.classList.remove("hidden");
- if (actualMenu) actualMenu.classList.add("hidden");
-
- const user = auth.getCurrentUser();
- await updateUserProfile(user);
-
- // Show actual profile and hide skeleton
- if (userProfileSkeleton)
- userProfileSkeleton.classList.add("hidden");
- if (userProfileSummary)
- userProfileSummary.classList.remove("hidden");
-
- // Hide all sections first
- document
- .querySelectorAll(".dashboard-section")
- .forEach((section) => {
- section.classList.add("hidden");
- });
-
- // Show appropriate default section based on role
- // Get the officer record for this user if it exists
- let officerStatus: OfficerStatus = "none";
-
- try {
- const officerRecords = await get.getList(
- "officers",
- 1,
- 50,
- `user="${user.id}"`,
- "",
- {
- fields: ["id", "type", "role"],
- }
- );
-
- if (officerRecords && officerRecords.items.length > 0) {
- const officerType = officerRecords.items[0].type;
- // We can also get the role here if needed for display elsewhere
- const officerRole = officerRecords.items[0].role;
-
- // Map the officer type to our OfficerStatus
- switch (officerType) {
- case OfficerTypes.ADMINISTRATOR:
- officerStatus = "administrator";
- break;
- case OfficerTypes.EXECUTIVE:
- officerStatus = "executive";
- break;
- case OfficerTypes.GENERAL:
- officerStatus = "general";
- break;
- case OfficerTypes.HONORARY:
- officerStatus = "honorary";
- break;
- case OfficerTypes.PAST:
- officerStatus = "past";
- break;
- default:
- officerStatus = "none";
- }
- } else {
- // Check if user is a sponsor by querying the sponsors collection
- const sponsorRecords = await get.getList(
- "sponsors",
- 1,
- 1,
- `user="${user.id}"`,
- "",
- {
- fields: ["id", "company"],
- }
- );
-
- if (
- sponsorRecords &&
- sponsorRecords.items.length > 0
- ) {
- officerStatus = "sponsor";
- } else {
- officerStatus = "none";
- }
- }
- } catch (error) {
- console.error(
- "Error determining officer status:",
- error
- );
- officerStatus = "none";
- }
-
- let defaultSection;
- let defaultButton;
-
- // Set default section based on role
- // Only sponsors get a different default view
- if (officerStatus === "sponsor") {
- // For sponsors, show the sponsor dashboard
- defaultSection = document.getElementById(
- "sponsorDashboardSection"
- );
- defaultButton = document.querySelector(
- '[data-section="sponsorDashboard"]'
- );
- } else {
- // For all other users (including administrators), show the profile section
- defaultSection =
- document.getElementById("profileSection");
- defaultButton = document.querySelector(
- '[data-section="profile"]'
- );
-
- // Log the default section for debugging
- // console.log(`Setting default section to profile for user with role: ${officerStatus}`);
- }
-
- if (defaultSection) {
- defaultSection.classList.remove("hidden");
- }
- if (defaultButton) {
- defaultButton.classList.add("active", "bg-base-200");
- }
-
- // Initialize navigation
- handleNavigation();
-
- // Show actual menu and hide skeleton
- if (menuLoadingSkeleton)
- menuLoadingSkeleton.classList.add("hidden");
- if (actualMenu) actualMenu.classList.remove("hidden");
-
- // Show main content and hide loading
- if (mainContent) mainContent.classList.remove("hidden");
- if (pageLoadingState)
- pageLoadingState.classList.add("hidden");
- } catch (error) {
- console.error("Error initializing dashboard:", error);
- if (pageLoadingState)
- pageLoadingState.classList.add("hidden");
- if (pageErrorState)
- pageErrorState.classList.remove("hidden");
- }
- };
-
- // Initialize when DOM is loaded
- document.addEventListener("DOMContentLoaded", initializePage);
-
- // Handle login button click
- document
- .querySelector(".login-button")
- ?.addEventListener("click", async () => {
- try {
- if (pageLoadingState)
- pageLoadingState.classList.remove("hidden");
- if (notAuthenticatedState)
- notAuthenticatedState.classList.add("hidden");
- await auth.login();
- } catch (error) {
- console.error("Login error:", error);
- if (pageLoadingState)
- pageLoadingState.classList.add("hidden");
- if (pageErrorState)
- pageErrorState.classList.remove("hidden");
- }
- });
-
- // Handle responsive sidebar
- if (sidebar) {
- if (window.innerWidth < 1024) {
- sidebar.classList.add("-translate-x-full");
- }
-
- window.addEventListener("resize", () => {
- if (window.innerWidth >= 1024) {
- const overlay =
- document.getElementById("sidebarOverlay");
- if (overlay) {
- overlay.remove();
- document.body.classList.remove("overflow-hidden");
- }
- sidebar.classList.remove("-translate-x-full");
- }
- });
- }