Fix toast hydration

This commit is contained in:
chark1es 2025-03-01 04:37:33 -08:00
parent eb77c00540
commit ef3e8f38d6
9 changed files with 855 additions and 931 deletions

View file

@ -2,6 +2,9 @@
import FilePreview from "./universal/FilePreview"; import FilePreview from "./universal/FilePreview";
import EventCheckIn from "./EventsSection/EventCheckIn"; import EventCheckIn from "./EventsSection/EventCheckIn";
import EventLoad from "./EventsSection/EventLoad"; import EventLoad from "./EventsSection/EventLoad";
import { Icon } from "astro-icon/components";
import { Get } from "../../scripts/pocketbase/Get";
import { toast } from "react-hot-toast";
--- ---
<div id="" class=""> <div id="" class="">
@ -28,7 +31,8 @@ import EventLoad from "./EventsSection/EventLoad";
<div <div
class="absolute inset-0 bg-base-100 opacity-0 group-hover:opacity-90 transition-opacity duration-300 flex items-center justify-center z-10" class="absolute inset-0 bg-base-100 opacity-0 group-hover:opacity-90 transition-opacity duration-300 flex items-center justify-center z-10"
> >
<span class="text-base-content font-medium text-sm sm:text-base" <span
class="text-base-content font-medium text-sm sm:text-base"
>Coming Soon</span >Coming Soon</span
> >
</div> </div>
@ -48,8 +52,12 @@ import EventLoad from "./EventsSection/EventLoad";
disabled disabled
> >
<option disabled selected>Pick an event</option> <option disabled selected>Pick an event</option>
<option>Technical Workshop - Web Development</option> <option
<option>Professional Development Workshop</option> >Technical Workshop - Web Development</option
>
<option
>Professional Development Workshop</option
>
<option>Social Event - Game Night</option> <option>Social Event - Game Night</option>
</select> </select>
<button <button
@ -89,8 +97,9 @@ import EventLoad from "./EventsSection/EventLoad";
class="btn btn-circle btn-ghost btn-sm sm:btn-md" class="btn btn-circle btn-ghost btn-sm sm:btn-md"
onclick="window.closeEventDetailsModal()" onclick="window.closeEventDetailsModal()"
> >
<iconify-icon icon="heroicons:x-mark" className="h-4 w-4 sm:h-6 sm:w-6" <iconify-icon
></iconify-icon> icon="heroicons:x-mark"
className="h-4 w-4 sm:h-6 sm:w-6"></iconify-icon>
</button> </button>
</div> </div>
@ -124,7 +133,8 @@ import EventLoad from "./EventsSection/EventLoad";
id="previewLoadingSpinner" id="previewLoadingSpinner"
class="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50 hidden" class="absolute inset-0 flex items-center justify-center bg-base-200 bg-opacity-50 hidden"
> >
<span class="loading loading-spinner loading-md sm:loading-lg"></span> <span class="loading loading-spinner loading-md sm:loading-lg"
></span>
</div> </div>
<div id="previewContent" class="w-full"> <div id="previewContent" class="w-full">
<FilePreview client:load isModal={true} /> <FilePreview client:load isModal={true} />
@ -137,109 +147,26 @@ import EventLoad from "./EventsSection/EventLoad";
</dialog> </dialog>
<script> <script>
import { toast } from "react-hot-toast";
import JSZip from "jszip"; import JSZip from "jszip";
// Toast management system
const createToast = (
message: string,
type: "success" | "error" | "warning" = "success",
) => {
let toastContainer = document.querySelector(".toast-container");
if (!toastContainer) {
toastContainer = document.createElement("div");
toastContainer.className = "toast-container fixed bottom-4 right-4 z-50";
document.body.appendChild(toastContainer);
}
const existingToasts = document.querySelectorAll(".toast-container .toast");
if (existingToasts.length >= 2) {
const oldestToast = existingToasts[0];
oldestToast.classList.add("toast-exit");
setTimeout(() => oldestToast.remove(), 150);
}
// Update positions of existing toasts
existingToasts.forEach((t) => {
const toast = t as HTMLElement;
const currentIndex = parseInt(toast.getAttribute("data-index") || "0");
toast.setAttribute("data-index", (currentIndex + 1).toString());
});
const toast = document.createElement("div");
toast.className = "toast translate-x-full";
toast.setAttribute("data-index", "0");
// Update alert styling based on type
const alertClass =
type === "success"
? "alert-success bg-success text-success-content"
: type === "error"
? "alert-error bg-error text-error-content"
: "alert-warning bg-warning text-warning-content";
toast.innerHTML = `
<div class="alert ${alertClass} shadow-lg min-w-[300px]">
<div class="flex items-center gap-2">
${
type === "success"
? '<iconify-icon icon="heroicons:check-circle" class="stroke-current shrink-0 h-6 w-6"></iconify-icon>'
: type === "error"
? '<iconify-icon icon="heroicons:x-circle" class="stroke-current shrink-0 h-6 w-6"></iconify-icon>'
: '<iconify-icon icon="heroicons:exclamation-triangle" class="stroke-current shrink-0 w-6"></iconify-icon>'
}
<span>${message}</span>
</div>
</div>
`;
toastContainer.appendChild(toast);
// Force a reflow to ensure the animation triggers
toast.offsetHeight;
// Add the transition class and remove transform
toast.classList.add("transition-all", "duration-300", "ease-out");
requestAnimationFrame(() => {
toast.classList.remove("translate-x-full");
});
// Setup exit animation
setTimeout(() => {
toast.classList.add("toast-exit");
setTimeout(() => toast.remove(), 150);
}, 3000);
};
// Add styles to the document // Add styles to the document
const style = document.createElement("style"); const style = document.createElement("style");
style.textContent = ` style.textContent = `
.toast-container { /* Custom styles for the event details modal */
display: flex; .event-details-grid {
flex-direction: column; display: grid;
pointer-events: none; grid-template-columns: 1fr 1fr;
gap: 1rem;
} }
.toast {
pointer-events: auto; @media (max-width: 640px) {
transform: translateX(0); .event-details-grid {
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1); grid-template-columns: 1fr;
position: relative;
} }
.toast-exit {
transform: translateX(100%);
opacity: 0;
}
.toast.translate-x-full {
transform: translateX(100%);
}
.toast-container .toast {
transform: translateY(calc((1 - attr(data-index number)) * -0.25rem));
}
.toast-container .toast[data-index="0"] {
transform: translateY(0);
}
.toast-container .toast[data-index="1"] {
transform: translateY(-0.025rem);
} }
/* Remove custom toast styles since we're using react-hot-toast */
`; `;
document.head.appendChild(style); document.head.appendChild(style);
@ -269,7 +196,7 @@ import EventLoad from "./EventsSection/EventLoad";
window.previewFileEvents = function (url: string, filename: string) { window.previewFileEvents = function (url: string, filename: string) {
console.log("previewFileEvents called with:", { url, filename }); console.log("previewFileEvents called with:", { url, filename });
const modal = document.getElementById( const modal = document.getElementById(
"filePreviewModal", "filePreviewModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName"); const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent"); const previewContent = document.getElementById("previewContent");
@ -286,7 +213,7 @@ import EventLoad from "./EventsSection/EventLoad";
window.dispatchEvent( window.dispatchEvent(
new CustomEvent("filePreviewStateChange", { new CustomEvent("filePreviewStateChange", {
detail: { url, filename }, detail: { url, filename },
}), })
); );
} }
}; };
@ -295,7 +222,7 @@ import EventLoad from "./EventsSection/EventLoad";
window.closeFilePreviewEvents = function () { window.closeFilePreviewEvents = function () {
console.log("closeFilePreviewEvents called"); console.log("closeFilePreviewEvents called");
const modal = document.getElementById( const modal = document.getElementById(
"filePreviewModal", "filePreviewModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName"); const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent"); const previewContent = document.getElementById("previewContent");
@ -306,7 +233,7 @@ import EventLoad from "./EventsSection/EventLoad";
window.dispatchEvent( window.dispatchEvent(
new CustomEvent("filePreviewStateChange", { new CustomEvent("filePreviewStateChange", {
detail: { url: "", filename: "" }, detail: { url: "", filename: "" },
}), })
); );
// Reset the UI // Reset the UI
@ -329,10 +256,10 @@ import EventLoad from "./EventsSection/EventLoad";
// Update the openDetailsModal function to use the events-specific preview // Update the openDetailsModal function to use the events-specific preview
window.openDetailsModal = function (event: any) { window.openDetailsModal = function (event: any) {
const modal = document.getElementById( const modal = document.getElementById(
"eventDetailsModal", "eventDetailsModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const filesContent = document.getElementById( const filesContent = document.getElementById(
"filesContent", "filesContent"
) as HTMLDivElement; ) as HTMLDivElement;
// Check if event has ended // Check if event has ended
@ -340,10 +267,14 @@ import EventLoad from "./EventsSection/EventLoad";
const now = new Date(); const now = new Date();
if (eventEndDate > now) { if (eventEndDate > now) {
createToast( toast("Files are only available after the event has ended.", {
"Files are only available after the event has ended.", icon: "⚠️",
"warning", style: {
); borderRadius: "10px",
background: "#FFC107",
color: "#000",
},
});
return; return;
} }
@ -352,7 +283,11 @@ import EventLoad from "./EventsSection/EventLoad";
if (filesContent) filesContent.classList.remove("hidden"); if (filesContent) filesContent.classList.remove("hidden");
// Populate files content // Populate files content
if (event.files && Array.isArray(event.files) && event.files.length > 0) { if (
event.files &&
Array.isArray(event.files) &&
event.files.length > 0
) {
const baseUrl = "https://pocketbase.ieeeucsd.org"; const baseUrl = "https://pocketbase.ieeeucsd.org";
const collectionId = "events"; const collectionId = "events";
const recordId = event.id; const recordId = event.id;
@ -409,7 +344,7 @@ import EventLoad from "./EventsSection/EventLoad";
// Add downloadAllFiles function // Add downloadAllFiles function
window.downloadAllFiles = async function () { window.downloadAllFiles = async function () {
const downloadBtn = document.getElementById( const downloadBtn = document.getElementById(
"downloadAllBtn", "downloadAllBtn"
) as HTMLButtonElement; ) as HTMLButtonElement;
if (!downloadBtn) return; if (!downloadBtn) return;
const originalBtnContent = downloadBtn.innerHTML; const originalBtnContent = downloadBtn.innerHTML;
@ -460,12 +395,11 @@ import EventLoad from "./EventsSection/EventLoad";
URL.revokeObjectURL(downloadUrl); URL.revokeObjectURL(downloadUrl);
// Show success message // Show success message
createToast("Files downloaded successfully!", "success"); toast.success("Files downloaded successfully!");
} catch (error: any) { } catch (error: any) {
console.error("Failed to download files:", error); console.error("Failed to download files:", error);
createToast( toast.error(
error?.message || "Failed to download files. Please try again.", error?.message || "Failed to download files. Please try again."
"error",
); );
} finally { } finally {
// Reset button state // Reset button state
@ -477,7 +411,7 @@ import EventLoad from "./EventsSection/EventLoad";
// Close event details modal // Close event details modal
window.closeEventDetailsModal = function () { window.closeEventDetailsModal = function () {
const modal = document.getElementById( const modal = document.getElementById(
"eventDetailsModal", "eventDetailsModal"
) as HTMLDialogElement; ) as HTMLDialogElement;
const filesContent = document.getElementById("filesContent"); const filesContent = document.getElementById("filesContent");

View file

@ -6,6 +6,7 @@ import { SendLog } from "../../../scripts/pocketbase/SendLog";
import { DataSyncService } from "../../../scripts/database/DataSyncService"; import { DataSyncService } from "../../../scripts/database/DataSyncService";
import { Collections } from "../../../schemas/pocketbase/schema"; import { Collections } from "../../../schemas/pocketbase/schema";
import { Icon } from "@iconify/react"; import { Icon } from "@iconify/react";
import toast from "react-hot-toast";
import type { Event, AttendeeEntry } from "../../../schemas/pocketbase"; import type { Event, AttendeeEntry } from "../../../schemas/pocketbase";
// Extended Event interface with additional properties needed for this component // Extended Event interface with additional properties needed for this component
@ -17,77 +18,6 @@ interface ExtendedEvent extends Event {
// When fetching events, UTC dates are converted to local time. // When fetching events, UTC dates are converted to local time.
// When saving events, local dates are converted back to UTC. // When saving events, local dates are converted back to UTC.
// Toast management system
const createToast = (
message: string,
type: "success" | "error" | "warning" = "success"
) => {
let toastContainer = document.querySelector(".toast-container");
if (!toastContainer) {
toastContainer = document.createElement("div");
toastContainer.className = "toast-container fixed bottom-4 right-4 z-50";
document.body.appendChild(toastContainer);
}
const existingToasts = document.querySelectorAll(".toast-container .toast");
if (existingToasts.length >= 2) {
const oldestToast = existingToasts[0];
oldestToast.classList.add("toast-exit");
setTimeout(() => oldestToast.remove(), 150);
}
// Update positions of existing toasts
existingToasts.forEach((t) => {
const toast = t as HTMLElement;
const currentIndex = parseInt(toast.getAttribute("data-index") || "0");
toast.setAttribute("data-index", (currentIndex + 1).toString());
});
const toast = document.createElement("div");
toast.className = "toast translate-x-full";
toast.setAttribute("data-index", "0");
// Update alert styling based on type
const alertClass =
type === "success"
? "alert-success bg-success text-success-content"
: type === "error"
? "alert-error bg-error text-error-content"
: "alert-warning bg-warning text-warning-content";
const iconName = type === "success"
? "heroicons:check-circle"
: type === "error"
? "heroicons:x-circle"
: "heroicons:exclamation-triangle";
toast.innerHTML = `
<div class="alert ${alertClass} shadow-lg min-w-[300px]">
<div class="flex items-center gap-2">
<iconify-icon icon="${iconName}" width="20" height="20"></iconify-icon>
<span>${message}</span>
</div>
</div>
`;
toastContainer.appendChild(toast);
// Force a reflow to ensure the animation triggers
toast.offsetHeight;
// Add the transition class and remove transform
toast.classList.add("transition-all", "duration-300", "ease-out");
requestAnimationFrame(() => {
toast.classList.remove("translate-x-full");
});
// Setup exit animation
setTimeout(() => {
toast.classList.add("toast-exit");
setTimeout(() => toast.remove(), 150);
}, 3000);
};
const EventCheckIn = () => { const EventCheckIn = () => {
const [currentCheckInEvent, setCurrentCheckInEvent] = useState<Event | null>(null); const [currentCheckInEvent, setCurrentCheckInEvent] = useState<Event | null>(null);
const [isLoading, setIsLoading] = useState(false); const [isLoading, setIsLoading] = useState(false);
@ -101,7 +31,7 @@ const EventCheckIn = () => {
const currentUser = auth.getCurrentUser(); const currentUser = auth.getCurrentUser();
if (!currentUser) { if (!currentUser) {
createToast("You must be logged in to check in to events", "error"); toast.error("You must be logged in to check in to events");
return; return;
} }
@ -151,7 +81,7 @@ const EventCheckIn = () => {
await completeCheckIn(event, null); await completeCheckIn(event, null);
} }
} catch (error: any) { } catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error"); toast.error(error?.message || "Failed to check in to event");
} }
} }
@ -171,7 +101,7 @@ const EventCheckIn = () => {
const userId = auth.getUserId(); const userId = auth.getUserId();
if (!userId) { if (!userId) {
createToast("You must be logged in to check in to an event", "error"); toast.error("You must be logged in to check in to an event");
return; return;
} }
@ -184,7 +114,14 @@ const EventCheckIn = () => {
); );
if (isAlreadyCheckedIn) { if (isAlreadyCheckedIn) {
createToast("You are already checked in to this event", "warning"); toast("You are already checked in to this event", {
icon: '⚠️',
style: {
borderRadius: '10px',
background: '#FFC107',
color: '#000',
},
});
return; return;
} }
@ -243,12 +180,11 @@ const EventCheckIn = () => {
} }
// Show success message with points if awarded // Show success message with points if awarded
createToast( toast.success(
`Successfully checked in to ${event.event_name}${event.points_to_reward > 0 `Successfully checked in to ${event.event_name}${event.points_to_reward > 0
? ` (+${event.points_to_reward} points!)` ? ` (+${event.points_to_reward} points!)`
: "" : ""
}`, }`
"success"
); );
// Log the check-in // Log the check-in
@ -265,7 +201,7 @@ const EventCheckIn = () => {
setFoodInput(""); setFoodInput("");
} }
} catch (error: any) { } catch (error: any) {
createToast(error?.message || "Failed to check in to event", "error"); toast.error(error?.message || "Failed to check in to event");
} }
} }
@ -296,7 +232,14 @@ const EventCheckIn = () => {
input.value = ""; input.value = "";
}); });
} else { } else {
createToast("Please enter an event code", "warning"); toast("Please enter an event code", {
icon: '⚠️',
style: {
borderRadius: '10px',
background: '#FFC107',
color: '#000',
},
});
} }
}}> }}>
<div className="flex flex-col sm:flex-row gap-2"> <div className="flex flex-col sm:flex-row gap-2">

View file

@ -4,7 +4,10 @@ import { Get } from "../../scripts/pocketbase/Get";
import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm"; import EventRequestForm from "./Officer_EventRequestForm/EventRequestForm";
import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests"; import UserEventRequests from "./Officer_EventRequestForm/UserEventRequests";
import { Collections } from "../../schemas/pocketbase/schema"; import { Collections } from "../../schemas/pocketbase/schema";
import { Toaster } from "react-hot-toast"; import { Icon } from "astro-icon/components";
import { Create } from "../../scripts/pocketbase/Create";
import { Update } from "../../scripts/pocketbase/Update";
import { toast } from "react-hot-toast";
// Import the EventRequest type from UserEventRequests to ensure consistency // Import the EventRequest type from UserEventRequests to ensure consistency
import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests"; import type { EventRequest as UserEventRequest } from "./Officer_EventRequestForm/UserEventRequests";
@ -118,8 +121,9 @@ if (auth.isAuthenticated()) {
</div> </div>
</div> </div>
<!-- Toast container for notifications --> <div class="dashboard-section hidden" id="eventRequestFormSection">
<Toaster client:load position="bottom-right" /> <!-- ... existing code ... -->
</div>
<script> <script>
// Import the DataSyncService for client-side use // Import the DataSyncService for client-side use

View file

@ -1,10 +1,11 @@
--- ---
import { Authentication } from "../../scripts/pocketbase/Authentication"; import { Authentication } from "../../scripts/pocketbase/Authentication";
import { Get } from "../../scripts/pocketbase/Get"; import { Get } from "../../scripts/pocketbase/Get";
import { Toaster } from "react-hot-toast"; import { toast } from "react-hot-toast";
import EventRequestManagementTable from "./Officer_EventRequestManagement/EventRequestManagementTable"; import EventRequestManagementTable from "./Officer_EventRequestManagement/EventRequestManagementTable";
import type { EventRequest } from "../../schemas/pocketbase"; import type { EventRequest } from "../../schemas/pocketbase";
import { Collections } from "../../schemas/pocketbase/schema"; import { Collections } from "../../schemas/pocketbase/schema";
import { Icon } from "astro-icon/components";
// Get instances // Get instances
const get = Get.getInstance(); const get = Get.getInstance();
@ -152,9 +153,6 @@ try {
</div> </div>
) )
} }
<!-- Toast container for notifications -->
<Toaster client:load position="bottom-right" />
</div> </div>
<script> <script>

View file

@ -1,4 +1,4 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect, useRef } from 'react';
import { Icon } from '@iconify/react'; import { Icon } from '@iconify/react';
import { Authentication } from '../../../scripts/pocketbase/Authentication'; import { Authentication } from '../../../scripts/pocketbase/Authentication';
import { DataSyncService } from '../../../scripts/database/DataSyncService'; import { DataSyncService } from '../../../scripts/database/DataSyncService';
@ -7,7 +7,6 @@ import ReceiptForm from './ReceiptForm';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import FilePreview from '../universal/FilePreview'; import FilePreview from '../universal/FilePreview';
import ToastProvider from './ToastProvider';
import type { ItemizedExpense, Reimbursement, Receipt } from '../../../schemas/pocketbase'; import type { ItemizedExpense, Reimbursement, Receipt } from '../../../schemas/pocketbase';
interface ReceiptFormData { interface ReceiptFormData {
@ -310,7 +309,6 @@ export default function ReimbursementForm() {
return ( return (
<> <>
<ToastProvider />
<motion.div <motion.div
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"

View file

@ -6,7 +6,6 @@ import { FileManager } from '../../../scripts/pocketbase/FileManager';
import FilePreview from '../universal/FilePreview'; import FilePreview from '../universal/FilePreview';
import { toast } from 'react-hot-toast'; import { toast } from 'react-hot-toast';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import ToastProvider from './ToastProvider';
import type { ItemizedExpense, Reimbursement, Receipt } from '../../../schemas/pocketbase'; import type { ItemizedExpense, Reimbursement, Receipt } from '../../../schemas/pocketbase';
import { DataSyncService } from '../../../scripts/database/DataSyncService'; import { DataSyncService } from '../../../scripts/database/DataSyncService';
import { Collections } from '../../../schemas/pocketbase/schema'; import { Collections } from '../../../schemas/pocketbase/schema';
@ -355,7 +354,6 @@ export default function ReimbursementList() {
return ( return (
<> <>
<ToastProvider />
<motion.div <motion.div
variants={containerVariants} variants={containerVariants}
initial="hidden" initial="hidden"

View file

@ -1,19 +0,0 @@
import { Toaster } from 'react-hot-toast';
export default function ToastProvider() {
return (
<Toaster
position="top-center"
reverseOrder={false}
toastOptions={{
duration: 5000,
style: {
background: '#333',
color: '#fff',
padding: '16px',
borderRadius: '8px',
},
}}
/>
);
}

View file

@ -0,0 +1,31 @@
import React from 'react';
import { Toaster } from 'react-hot-toast';
// Centralized toast provider to ensure consistent rendering
export default function ToastProvider() {
return (
<Toaster
position="bottom-right"
toastOptions={{
duration: 4000,
style: {
background: '#333',
color: '#fff',
borderRadius: '8px',
padding: '12px',
},
success: {
style: {
background: 'green',
},
},
error: {
style: {
background: 'red',
},
duration: 5000,
},
}}
/>
);
}

View file

@ -9,6 +9,7 @@ import { SendLog } from "../scripts/pocketbase/SendLog";
import { hasAccess, type OfficerStatus } from "../utils/roleAccess"; import { hasAccess, type OfficerStatus } from "../utils/roleAccess";
import { OfficerTypes } from "../schemas/pocketbase/schema"; import { OfficerTypes } from "../schemas/pocketbase/schema";
import { initAuthSync } from "../scripts/database/initAuthSync"; import { initAuthSync } from "../scripts/database/initAuthSync";
import ToastProvider from "../components/dashboard/universal/ToastProvider";
const title = "Dashboard"; const title = "Dashboard";
@ -365,10 +366,11 @@ console.log("Available components:", Object.keys(components)); // Debug log
</div> </div>
</main> </main>
</div> </div>
</body>
</html>
<script> <!-- Centralized Toast Provider -->
<ToastProvider client:load />
<script>
import { Authentication } from "../scripts/pocketbase/Authentication"; import { Authentication } from "../scripts/pocketbase/Authentication";
import { Get } from "../scripts/pocketbase/Get"; import { Get } from "../scripts/pocketbase/Get";
import { SendLog } from "../scripts/pocketbase/SendLog"; import { SendLog } from "../scripts/pocketbase/SendLog";
@ -381,7 +383,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
const logger = SendLog.getInstance(); const logger = SendLog.getInstance();
// Initialize page state // Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState"); const pageLoadingState =
document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState"); const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById( const notAuthenticatedState = document.getElementById(
"notAuthenticatedState" "notAuthenticatedState"
@ -415,7 +418,9 @@ console.log("Available components:", Object.keys(components)); // Debug log
} }
// For non-sponsor roles, handle normally // For non-sponsor roles, handle normally
document.querySelectorAll("[data-role-required]").forEach((element) => { document
.querySelectorAll("[data-role-required]")
.forEach((element) => {
const requiredRole = element.getAttribute( const requiredRole = element.getAttribute(
"data-role-required" "data-role-required"
) as OfficerStatus; ) as OfficerStatus;
@ -427,7 +432,10 @@ console.log("Available components:", Object.keys(components)); // Debug log
} }
// Check if user has permission for this role // Check if user has permission for this role
const hasPermission = hasAccess(officerStatus, requiredRole); const hasPermission = hasAccess(
officerStatus,
requiredRole
);
// Only show elements if user has permission // Only show elements if user has permission
element.classList.toggle("hidden", !hasPermission); element.classList.toggle("hidden", !hasPermission);
@ -436,8 +444,10 @@ console.log("Available components:", Object.keys(components)); // Debug log
// Handle navigation // Handle navigation
const handleNavigation = () => { const handleNavigation = () => {
const navButtons = document.querySelectorAll(".dashboard-nav-btn"); const navButtons =
const sections = document.querySelectorAll(".dashboard-section"); document.querySelectorAll(".dashboard-nav-btn");
const sections =
document.querySelectorAll(".dashboard-section");
const mainContentDiv = document.getElementById("mainContent"); const mainContentDiv = document.getElementById("mainContent");
// Ensure mainContent is visible // Ensure mainContent is visible
@ -471,7 +481,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
// Show selected section // Show selected section
const sectionId = `${sectionKey}Section`; const sectionId = `${sectionKey}Section`;
const targetSection = document.getElementById(sectionId); const targetSection =
document.getElementById(sectionId);
if (targetSection) { if (targetSection) {
targetSection.classList.remove("hidden"); targetSection.classList.remove("hidden");
console.log(`Showing section: ${sectionId}`); // Debug log console.log(`Showing section: ${sectionId}`); // Debug log
@ -481,7 +492,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
if (window.innerWidth < 1024 && sidebar) { if (window.innerWidth < 1024 && sidebar) {
sidebar.classList.add("-translate-x-full"); sidebar.classList.add("-translate-x-full");
document.body.classList.remove("overflow-hidden"); document.body.classList.remove("overflow-hidden");
const overlay = document.getElementById("sidebarOverlay"); const overlay =
document.getElementById("sidebarOverlay");
overlay?.remove(); overlay?.remove();
} }
}); });
@ -581,15 +593,19 @@ console.log("Available components:", Object.keys(components)); // Debug log
}; };
// Mobile sidebar toggle // Mobile sidebar toggle
const mobileSidebarToggle = document.getElementById("mobileSidebarToggle"); const mobileSidebarToggle = document.getElementById(
"mobileSidebarToggle"
);
if (mobileSidebarToggle && sidebar) { if (mobileSidebarToggle && sidebar) {
const toggleSidebar = () => { const toggleSidebar = () => {
const isOpen = !sidebar.classList.contains("-translate-x-full"); const isOpen =
!sidebar.classList.contains("-translate-x-full");
if (isOpen) { if (isOpen) {
sidebar.classList.add("-translate-x-full"); sidebar.classList.add("-translate-x-full");
document.body.classList.remove("overflow-hidden"); document.body.classList.remove("overflow-hidden");
const overlay = document.getElementById("sidebarOverlay"); const overlay =
document.getElementById("sidebarOverlay");
overlay?.remove(); overlay?.remove();
} else { } else {
sidebar.classList.remove("-translate-x-full"); sidebar.classList.remove("-translate-x-full");
@ -615,13 +631,15 @@ console.log("Available components:", Object.keys(components)); // Debug log
// Check if user is authenticated // Check if user is authenticated
if (!auth.isAuthenticated()) { if (!auth.isAuthenticated()) {
console.log("User not authenticated"); console.log("User not authenticated");
if (pageLoadingState) pageLoadingState.classList.add("hidden"); if (pageLoadingState)
pageLoadingState.classList.add("hidden");
if (notAuthenticatedState) if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden"); notAuthenticatedState.classList.remove("hidden");
return; return;
} }
if (pageLoadingState) pageLoadingState.classList.remove("hidden"); if (pageLoadingState)
pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden"); if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState) if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden"); notAuthenticatedState.classList.add("hidden");
@ -642,7 +660,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
if (userProfileSkeleton) if (userProfileSkeleton)
userProfileSkeleton.classList.remove("hidden"); userProfileSkeleton.classList.remove("hidden");
if (userProfileSummary) userProfileSummary.classList.add("hidden"); if (userProfileSummary)
userProfileSummary.classList.add("hidden");
if (userProfileSignedOut) if (userProfileSignedOut)
userProfileSignedOut.classList.add("hidden"); userProfileSignedOut.classList.add("hidden");
if (menuLoadingSkeleton) if (menuLoadingSkeleton)
@ -705,16 +724,23 @@ console.log("Available components:", Object.keys(components)); // Debug log
officerStatus = "none"; officerStatus = "none";
} }
} else { } else {
const extendedUser = await get.getOne("users", user.id, { const extendedUser = await get.getOne(
"users",
user.id,
{
fields: ["member_type"], fields: ["member_type"],
}); }
);
if (extendedUser.member_type === "Sponsor") { if (extendedUser.member_type === "Sponsor") {
officerStatus = "sponsor"; officerStatus = "sponsor";
} }
} }
} catch (error) { } catch (error) {
console.error("Error determining officer status:", error); console.error(
"Error determining officer status:",
error
);
officerStatus = "none"; officerStatus = "none";
} }
@ -736,7 +762,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
'[data-section="adminDashboard"]' '[data-section="adminDashboard"]'
); );
} else { } else {
defaultSection = document.getElementById("profileSection"); defaultSection =
document.getElementById("profileSection");
defaultButton = document.querySelector( defaultButton = document.querySelector(
'[data-section="profile"]' '[data-section="profile"]'
); );
@ -759,11 +786,14 @@ console.log("Available components:", Object.keys(components)); // Debug log
// Show main content and hide loading // Show main content and hide loading
if (mainContent) mainContent.classList.remove("hidden"); if (mainContent) mainContent.classList.remove("hidden");
if (pageLoadingState) pageLoadingState.classList.add("hidden"); if (pageLoadingState)
pageLoadingState.classList.add("hidden");
} catch (error) { } catch (error) {
console.error("Error initializing dashboard:", error); console.error("Error initializing dashboard:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden"); if (pageLoadingState)
if (pageErrorState) pageErrorState.classList.remove("hidden"); pageLoadingState.classList.add("hidden");
if (pageErrorState)
pageErrorState.classList.remove("hidden");
} }
}; };
@ -782,13 +812,17 @@ console.log("Available components:", Object.keys(components)); // Debug log
await auth.login(); await auth.login();
} catch (error) { } catch (error) {
console.error("Login error:", error); console.error("Login error:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden"); if (pageLoadingState)
if (pageErrorState) pageErrorState.classList.remove("hidden"); pageLoadingState.classList.add("hidden");
if (pageErrorState)
pageErrorState.classList.remove("hidden");
} }
}); });
// Handle logout button click // Handle logout button click
document.getElementById("logoutButton")?.addEventListener("click", () => { document
.getElementById("logoutButton")
?.addEventListener("click", () => {
auth.logout(); auth.logout();
window.location.reload(); window.location.reload();
}); });
@ -801,7 +835,8 @@ console.log("Available components:", Object.keys(components)); // Debug log
window.addEventListener("resize", () => { window.addEventListener("resize", () => {
if (window.innerWidth >= 1024) { if (window.innerWidth >= 1024) {
const overlay = document.getElementById("sidebarOverlay"); const overlay =
document.getElementById("sidebarOverlay");
if (overlay) { if (overlay) {
overlay.remove(); overlay.remove();
document.body.classList.remove("overflow-hidden"); document.body.classList.remove("overflow-hidden");
@ -810,4 +845,6 @@ console.log("Available components:", Object.keys(components)); // Debug log
} }
}); });
} }
</script> </script>
</body>
</html>