parent
2eaf5a230e
commit
43c1fc074a
1 changed files with 455 additions and 547 deletions
|
@ -3,6 +3,12 @@ import yaml from "js-yaml";
|
||||||
import { Icon } from "astro-icon/components";
|
import { Icon } from "astro-icon/components";
|
||||||
import fs from "node:fs";
|
import fs from "node:fs";
|
||||||
import path from "node:path";
|
import path from "node:path";
|
||||||
|
import { Authentication } from "../scripts/pocketbase/Authentication";
|
||||||
|
import { Get } from "../scripts/pocketbase/Get";
|
||||||
|
import { SendLog } from "../scripts/pocketbase/SendLog";
|
||||||
|
import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
|
||||||
|
import { OfficerTypes } from "../schemas/pocketbase/schema";
|
||||||
|
import { initAuthSync } from "../scripts/database/initAuthSync";
|
||||||
import ToastProvider from "../components/dashboard/universal/ToastProvider";
|
import ToastProvider from "../components/dashboard/universal/ToastProvider";
|
||||||
import FirstTimeLoginManager from "../components/dashboard/universal/FirstTimeLoginManager";
|
import FirstTimeLoginManager from "../components/dashboard/universal/FirstTimeLoginManager";
|
||||||
|
|
||||||
|
@ -51,21 +57,11 @@ const components = Object.fromEntries(
|
||||||
logtoApiEndpoint={logtoApiEndpoint}
|
logtoApiEndpoint={logtoApiEndpoint}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="flex h-screen overflow-hidden">
|
<div class="flex h-screen">
|
||||||
<!-- Sidebar -->
|
<!-- Sidebar -->
|
||||||
<aside
|
<aside
|
||||||
id="dashboardSidebar"
|
class="bg-base-100 w-80 flex flex-col shadow-xl border-r border-base-200 transition-all duration-300 fixed xl:relative h-full z-50 -translate-x-full xl:translate-x-0"
|
||||||
class="bg-base-100 w-80 flex flex-col shadow-xl border-r border-base-200 transition-all duration-300 fixed lg:static h-full z-50 transform -translate-x-full lg:translate-x-0 overflow-y-auto"
|
|
||||||
>
|
>
|
||||||
<!-- Add close button for mobile -->
|
|
||||||
<button
|
|
||||||
id="closeSidebarBtn"
|
|
||||||
class="absolute top-4 right-4 btn btn-sm btn-ghost lg:hidden"
|
|
||||||
aria-label="Close menu"
|
|
||||||
>
|
|
||||||
<Icon name="heroicons:x-mark" class="h-5 w-5" />
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Logo -->
|
<!-- Logo -->
|
||||||
<div class="p-6 border-b border-base-200">
|
<div class="p-6 border-b border-base-200">
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
|
@ -157,6 +153,9 @@ const components = Object.fromEntries(
|
||||||
<!-- Navigation -->
|
<!-- Navigation -->
|
||||||
<nav
|
<nav
|
||||||
class="flex-1 overflow-y-auto scrollbar-hide py-6 flex flex-col"
|
class="flex-1 overflow-y-auto scrollbar-hide py-6 flex flex-col"
|
||||||
|
>
|
||||||
|
<ul
|
||||||
|
class="menu gap-2 px-4 text-base-content/80 flex-1 flex flex-col"
|
||||||
>
|
>
|
||||||
<!-- Loading Skeleton -->
|
<!-- Loading Skeleton -->
|
||||||
<div id="menuLoadingSkeleton">
|
<div id="menuLoadingSkeleton">
|
||||||
|
@ -183,7 +182,10 @@ const components = Object.fromEntries(
|
||||||
<div id="actualMenu" class="hidden">
|
<div id="actualMenu" class="hidden">
|
||||||
{
|
{
|
||||||
Object.entries(dashboardConfig.categories).map(
|
Object.entries(dashboardConfig.categories).map(
|
||||||
([categoryKey, category]: [string, any]) => (
|
([categoryKey, category]: [
|
||||||
|
string,
|
||||||
|
any,
|
||||||
|
]) => (
|
||||||
<>
|
<>
|
||||||
<li
|
<li
|
||||||
class={`menu-title font-medium opacity-70 ${
|
class={`menu-title font-medium opacity-70 ${
|
||||||
|
@ -201,7 +203,8 @@ const components = Object.fromEntries(
|
||||||
{category.sections.map(
|
{category.sections.map(
|
||||||
(sectionKey: string) => {
|
(sectionKey: string) => {
|
||||||
const section =
|
const section =
|
||||||
dashboardConfig.sections[
|
dashboardConfig
|
||||||
|
.sections[
|
||||||
sectionKey
|
sectionKey
|
||||||
];
|
];
|
||||||
return (
|
return (
|
||||||
|
@ -254,31 +257,23 @@ const components = Object.fromEntries(
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
</div>
|
</div>
|
||||||
|
</ul>
|
||||||
</nav>
|
</nav>
|
||||||
</aside>
|
</aside>
|
||||||
|
|
||||||
<!-- Sidebar Overlay - Only visible on mobile when sidebar is open -->
|
|
||||||
<div
|
|
||||||
id="sidebarOverlay"
|
|
||||||
class="fixed inset-0 bg-black bg-opacity-50 z-40 lg:hidden hidden"
|
|
||||||
aria-hidden="true"
|
|
||||||
>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Main Content -->
|
<!-- Main Content -->
|
||||||
<main
|
<main
|
||||||
class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200 w-full"
|
class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200 w-full xl:w-[calc(100%-20rem)]"
|
||||||
>
|
>
|
||||||
<!-- Mobile Header -->
|
<!-- Mobile Header -->
|
||||||
<header
|
<header
|
||||||
class="bg-base-100 p-4 shadow-md lg:hidden sticky top-0 z-40"
|
class="bg-base-100 p-4 shadow-md xl:hidden sticky top-0 z-40"
|
||||||
>
|
>
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button
|
||||||
id="mobileSidebarToggle"
|
id="mobileSidebarToggle"
|
||||||
class="btn btn-square btn-ghost"
|
class="btn btn-square btn-ghost"
|
||||||
aria-label="Open menu"
|
|
||||||
>
|
>
|
||||||
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
|
@ -435,418 +430,50 @@ const components = Object.fromEntries(
|
||||||
const userName = document.getElementById("userName");
|
const userName = document.getElementById("userName");
|
||||||
const userRole = document.getElementById("userRole");
|
const userRole = document.getElementById("userRole");
|
||||||
|
|
||||||
// Centralized sidebar management
|
// Function to update section visibility based on role
|
||||||
class SidebarManager {
|
const updateSectionVisibility = (officerStatus: OfficerStatus) => {
|
||||||
private sidebar: HTMLElement | null = null;
|
// Special handling for sponsor role
|
||||||
private overlay: HTMLElement | null = null;
|
if (officerStatus === "sponsor") {
|
||||||
private toggleBtn: HTMLElement | null = null;
|
|
||||||
private closeBtn: HTMLElement | null = null;
|
|
||||||
private breakpoint = 1024; // lg breakpoint
|
|
||||||
private isInitialized = false;
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
private setupEventListeners(): void {
|
|
||||||
// Toggle button (mobile only)
|
|
||||||
this.toggleBtn?.addEventListener("click", () =>
|
|
||||||
this.openSidebar()
|
|
||||||
);
|
|
||||||
|
|
||||||
// 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<typeof setTimeout>;
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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
|
// Hide all sections first
|
||||||
document
|
document
|
||||||
.querySelectorAll(".dashboard-section")
|
.querySelectorAll("[data-role-required]")
|
||||||
.forEach((section) => {
|
.forEach((element) => {
|
||||||
section.classList.add("hidden");
|
element.classList.add("hidden");
|
||||||
});
|
});
|
||||||
|
|
||||||
// Show appropriate default section based on role
|
// Only show sponsor sections
|
||||||
// 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"]'
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
document
|
||||||
.querySelector(".login-button")
|
.querySelectorAll('[data-role-required="sponsor"]')
|
||||||
?.addEventListener("click", async () => {
|
.forEach((element) => {
|
||||||
try {
|
element.classList.remove("hidden");
|
||||||
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");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For non-sponsor roles, handle normally
|
||||||
|
document
|
||||||
|
.querySelectorAll("[data-role-required]")
|
||||||
|
.forEach((element) => {
|
||||||
|
const requiredRole = element.getAttribute(
|
||||||
|
"data-role-required"
|
||||||
|
) as OfficerStatus;
|
||||||
|
|
||||||
|
// Skip elements that don't have a role requirement
|
||||||
|
if (!requiredRole || requiredRole === "none") {
|
||||||
|
element.classList.remove("hidden");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if user has permission for this role
|
||||||
|
const hasPermission = hasAccess(
|
||||||
|
officerStatus,
|
||||||
|
requiredRole
|
||||||
|
);
|
||||||
|
|
||||||
|
// Only show elements if user has permission
|
||||||
|
element.classList.toggle("hidden", !hasPermission);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
// Function to delete all cookies (to handle Logto logout)
|
// Function to delete all cookies (to handle Logto logout)
|
||||||
const deleteAllCookies = () => {
|
const deleteAllCookies = () => {
|
||||||
|
@ -1022,48 +649,60 @@ const components = Object.fromEntries(
|
||||||
if (modal) (modal as HTMLDialogElement).showModal();
|
if (modal) (modal as HTMLDialogElement).showModal();
|
||||||
};
|
};
|
||||||
|
|
||||||
// Function to update section visibility based on role
|
// Handle navigation
|
||||||
const updateSectionVisibility = (officerStatus: OfficerStatus) => {
|
const handleNavigation = () => {
|
||||||
// Special handling for sponsor role
|
const navButtons =
|
||||||
if (officerStatus === "sponsor") {
|
document.querySelectorAll(".dashboard-nav-btn");
|
||||||
// Hide all sections first
|
const sections =
|
||||||
document
|
document.querySelectorAll(".dashboard-section");
|
||||||
.querySelectorAll("[data-role-required]")
|
const mainContentDiv = document.getElementById("mainContent");
|
||||||
.forEach((element) => {
|
|
||||||
element.classList.add("hidden");
|
|
||||||
});
|
|
||||||
|
|
||||||
// Only show sponsor sections
|
// Ensure mainContent is visible
|
||||||
document
|
if (mainContentDiv) {
|
||||||
.querySelectorAll('[data-role-required="sponsor"]')
|
mainContentDiv.classList.remove("hidden");
|
||||||
.forEach((element) => {
|
}
|
||||||
element.classList.remove("hidden");
|
|
||||||
});
|
navButtons.forEach((button) => {
|
||||||
|
button.addEventListener("click", () => {
|
||||||
|
const sectionKey = button.getAttribute("data-section");
|
||||||
|
|
||||||
|
// Handle logout button
|
||||||
|
if (sectionKey === "logout") {
|
||||||
|
showLogoutConfirmation();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// For non-sponsor roles, handle normally
|
// Remove active class from all buttons
|
||||||
document
|
navButtons.forEach((btn) => {
|
||||||
.querySelectorAll("[data-role-required]")
|
btn.classList.remove("active", "bg-base-200");
|
||||||
.forEach((element) => {
|
});
|
||||||
const requiredRole = element.getAttribute(
|
|
||||||
"data-role-required"
|
|
||||||
) as OfficerStatus;
|
|
||||||
|
|
||||||
// Skip elements that don't have a role requirement
|
// Add active class to clicked button
|
||||||
if (!requiredRole || requiredRole === "none") {
|
button.classList.add("active", "bg-base-200");
|
||||||
element.classList.remove("hidden");
|
|
||||||
return;
|
// 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
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if user has permission for this role
|
// Close mobile sidebar if needed
|
||||||
const hasPermission = hasAccess(
|
if (window.innerWidth < 1024 && sidebar) {
|
||||||
officerStatus,
|
sidebar.classList.add("-translate-x-full");
|
||||||
requiredRole
|
document.body.classList.remove("overflow-hidden");
|
||||||
);
|
const overlay =
|
||||||
|
document.getElementById("sidebarOverlay");
|
||||||
// Only show elements if user has permission
|
overlay?.remove();
|
||||||
element.classList.toggle("hidden", !hasPermission);
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1187,6 +826,275 @@ const components = Object.fromEntries(
|
||||||
updateSectionVisibility("" as OfficerStatus);
|
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");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in a new issue