change theme

This commit is contained in:
chark1es 2025-03-28 23:15:02 -07:00
parent e27a78a15b
commit a86140a6b5
10 changed files with 619 additions and 571 deletions

View file

@ -5,144 +5,151 @@ import EventLoad from "./EventsSection/EventLoad";
--- ---
<div id="" class=""> <div id="" class="">
<div class="mb-4 sm:mb-6 px-4 sm:px-6"> <div class="mb-4 sm:mb-6 px-4 sm:px-6">
<h2 class="text-xl sm:text-2xl font-bold">Events</h2> <h2 class="text-xl sm:text-2xl font-bold">Events</h2>
<p class="opacity-70 text-sm sm:text-base"> <p class="opacity-70 text-sm sm:text-base">
View and manage your IEEE UCSD events View and manage your IEEE UCSD events
</p> </p>
</div>
<div
class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 mb-4 sm:mb-6 px-4 sm:px-6"
>
<!-- Event Check-in Card -->
<div class="w-full">
<EventCheckIn client:load />
</div> </div>
<!-- Event Registration Card --> <div
<div class="w-full"> class="grid grid-cols-1 md:grid-cols-2 gap-4 sm:gap-6 mb-4 sm:mb-6 px-4 sm:px-6"
<div >
class="card bg-base-100 shadow-xl border border-base-200 opacity-50 cursor-not-allowed relative group h-full" <!-- Event Check-in Card -->
> <div class="w-full">
<div <EventCheckIn client:load />
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"
>Coming Soon</span
>
</div> </div>
<div class="card-body p-4 sm:p-6">
<h3 class="card-title text-base sm:text-lg mb-3 sm:mb-4"> <!-- Event Registration Card -->
Event Registration <div class="w-full">
</h3> <div
<div class="form-control w-full"> class="card bg-card shadow-xl border border-border opacity-50 cursor-not-allowed relative group h-full"
<label class="label"> >
<span class="label-text text-sm sm:text-base" <div
>Select an event to register</span class="absolute inset-0 bg-card opacity-0 group-hover:opacity-90 transition-opacity duration-300 flex items-center justify-center z-10"
> >
</label> <span
<div class="flex flex-col sm:flex-row gap-2"> class="text-card-foreground font-medium text-sm sm:text-base"
<select >Coming Soon</span
class="select select-bordered flex-1 text-sm sm:text-base h-10 min-h-[2.5rem] w-full" >
disabled </div>
> <div class="card-body p-4 sm:p-6">
<option disabled selected>Pick an event</option> <h3 class="card-title text-base sm:text-lg mb-3 sm:mb-4">
<option>Technical Workshop - Web Development</option> Event Registration
<option>Professional Development Workshop</option> </h3>
<option>Social Event - Game Night</option> <div class="w-full">
</select> <label class="block text-sm sm:text-base mb-2">
<button <span class="text-sm sm:text-base"
class="btn btn-primary text-sm sm:text-base h-10 min-h-[2.5rem] w-full sm:w-[100px]" >Select an event to register</span
disabled>Register</button >
> </label>
<div class="flex flex-col sm:flex-row gap-2">
<select
class="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 flex-1 text-sm sm:text-base min-h-[2.5rem]"
disabled
>
<option disabled selected>Pick an event</option>
<option
>Technical Workshop - Web Development</option
>
<option
>Professional Development Workshop</option
>
<option>Social Event - Game Night</option>
</select>
<button
class="inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 min-h-[2.5rem] w-full sm:w-[100px] px-4 py-2"
disabled>Register</button
>
</div>
</div>
</div>
</div> </div>
</div>
</div> </div>
</div>
</div> </div>
</div>
<EventLoad client:load /> <EventLoad client:load />
</div> </div>
<!-- Event Details Modal --> <!-- Event Details Modal -->
<dialog id="eventDetailsModal" class="modal"> <dialog id="eventDetailsModal" class="modal">
<div class="modal-box max-w-[90vw] sm:max-w-4xl p-4 sm:p-6"> <div class="modal-box max-w-[90vw] sm:max-w-4xl p-4 sm:p-6">
<div class="flex justify-between items-center mb-3 sm:mb-4"> <div class="flex justify-between items-center mb-3 sm:mb-4">
<div class="flex items-center gap-2 sm:gap-3"> <div class="flex items-center gap-2 sm:gap-3">
<h3 class="font-bold text-base sm:text-lg" id="modalTitle"> <h3 class="font-bold text-base sm:text-lg" id="modalTitle">
Event Files Event Files
</h3> </h3>
<button <button
id="downloadAllBtn" id="downloadAllBtn"
class="btn btn-primary btn-sm gap-1 text-xs sm:text-sm" class="btn btn-primary btn-sm gap-1 text-xs sm:text-sm"
onclick="window.downloadAllFiles()" onclick="window.downloadAllFiles()"
> >
<iconify-icon <iconify-icon
icon="heroicons:arrow-down-tray-20-solid" icon="heroicons:arrow-down-tray-20-solid"
className="h-3 w-3 sm:h-4 sm:w-4"></iconify-icon> className="h-3 w-3 sm:h-4 sm:w-4"></iconify-icon>
Download All Download All
</button> </button>
</div> </div>
<button <button
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"
</button> className="h-4 w-4 sm:h-6 sm:w-6"></iconify-icon>
</div> </button>
</div>
<div id="filesContent" class="space-y-3 sm:space-y-4"> <div id="filesContent" class="space-y-3 sm:space-y-4">
<!-- Files list will be populated here --> <!-- Files list will be populated here -->
</div>
</div> </div>
</div> <form method="dialog" class="modal-backdrop">
<form method="dialog" class="modal-backdrop"> <button onclick="window.closeEventDetailsModal()">close</button>
<button onclick="window.closeEventDetailsModal()">close</button> </form>
</form>
</dialog> </dialog>
<!-- Universal File Preview Modal --> <!-- Universal File Preview Modal -->
<dialog id="filePreviewModal" class="modal"> <dialog id="filePreviewModal" class="modal">
<div class="modal-box max-w-[90vw] sm:max-w-4xl p-4 sm:p-6"> <div class="modal-box max-w-[90vw] sm:max-w-4xl p-4 sm:p-6">
<div class="flex justify-between items-center mb-3 sm:mb-4"> <div class="flex justify-between items-center mb-3 sm:mb-4">
<div class="flex items-center gap-2 sm:gap-3"> <div class="flex items-center gap-2 sm:gap-3">
<button <button
class="btn btn-ghost btn-sm text-xs sm:text-sm" class="btn btn-ghost btn-sm text-xs sm:text-sm"
onclick="window.closeFilePreviewEvents()">Close</button onclick="window.closeFilePreviewEvents()">Close</button
> >
<h3 <h3
class="font-bold text-base sm:text-lg truncate" class="font-bold text-base sm:text-lg truncate"
id="previewFileName" id="previewFileName"
> >
</h3> </h3>
</div> </div>
</div>
<div class="relative" id="previewContainer">
<div
id="previewLoadingSpinner"
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>
</div>
<div id="previewContent" class="w-full">
<FilePreview client:load isModal={true} />
</div>
</div>
</div> </div>
<div class="relative" id="previewContainer"> <form method="dialog" class="modal-backdrop">
<div <button onclick="window.closeFilePreviewEvents()">close</button>
id="previewLoadingSpinner" </form>
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>
</div>
<div id="previewContent" class="w-full">
<FilePreview client:load isModal={true} />
</div>
</div>
</div>
<form method="dialog" class="modal-backdrop">
<button onclick="window.closeFilePreviewEvents()">close</button>
</form>
</dialog> </dialog>
<script> <script>
import { toast } from "react-hot-toast"; import { toast } from "react-hot-toast";
import JSZip from "jszip"; import JSZip from "jszip";
// Add styles to the document // Add styles to the document
const style = document.createElement("style"); const style = document.createElement("style");
style.textContent = ` style.textContent = `
/* Custom styles for the event details modal */ /* Custom styles for the event details modal */
.event-details-grid { .event-details-grid {
display: grid; display: grid;
@ -158,227 +165,234 @@ import EventLoad from "./EventsSection/EventLoad";
/* Remove custom toast styles since we're using react-hot-toast */ /* Remove custom toast styles since we're using react-hot-toast */
`; `;
document.head.appendChild(style); document.head.appendChild(style);
// Add helper functions for file preview // Add helper functions for file preview
function getFileType(filename: string): string { function getFileType(filename: string): string {
const extension = filename.split(".").pop()?.toLowerCase(); const extension = filename.split(".").pop()?.toLowerCase();
const mimeTypes: { [key: string]: string } = { const mimeTypes: { [key: string]: string } = {
pdf: "application/pdf", pdf: "application/pdf",
jpg: "image/jpeg", jpg: "image/jpeg",
jpeg: "image/jpeg", jpeg: "image/jpeg",
png: "image/png", png: "image/png",
gif: "image/gif", gif: "image/gif",
mp4: "video/mp4", mp4: "video/mp4",
mp3: "audio/mpeg", mp3: "audio/mpeg",
txt: "text/plain", txt: "text/plain",
doc: "application/msword", doc: "application/msword",
docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document", docx: "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
xls: "application/vnd.ms-excel", xls: "application/vnd.ms-excel",
xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", xlsx: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
json: "application/json", json: "application/json",
};
return mimeTypes[extension || ""] || "application/octet-stream";
}
// Universal file preview function for events section
window.previewFileEvents = function (url: string, filename: string) {
// console.log("previewFileEvents called with:", { url, filename });
// console.log("URL type:", typeof url, "URL length:", url?.length || 0);
// console.log(
// "Filename type:",
// typeof filename,
// "Filename length:",
// filename?.length || 0
// );
// Validate inputs
if (!url || typeof url !== "string") {
console.error("Invalid URL provided to previewFileEvents:", url);
toast.error("Cannot preview file: Invalid URL");
return;
}
if (!filename || typeof filename !== "string") {
console.error(
"Invalid filename provided to previewFileEvents:",
filename
);
toast.error("Cannot preview file: Invalid filename");
return;
}
// Ensure URL is properly formatted
if (!url.startsWith("http")) {
console.warn(
"URL doesn't start with http, attempting to fix:",
url
);
if (url.startsWith("/")) {
url = `https://pocketbase.ieeeucsd.org${url}`;
} else {
url = `https://pocketbase.ieeeucsd.org/${url}`;
}
// console.log("Fixed URL:", url);
}
const modal = document.getElementById(
"filePreviewModal"
) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent");
const loadingSpinner = document.getElementById("previewLoadingSpinner");
if (modal && previewFileName && previewContent) {
// console.log("Found all required elements");
// Show loading spinner
if (loadingSpinner) {
loadingSpinner.classList.remove("hidden");
}
// Update the filename display
previewFileName.textContent = filename;
// Show the modal
modal.showModal();
// Test the URL with a fetch before dispatching the event
fetch(url, { method: "HEAD" })
.then((response) => {
// console.log(
// "URL test response:",
// response.status,
// response.ok
// );
if (!response.ok) {
console.warn("URL might not be accessible:", url);
toast(
"File might not be accessible. Attempting to preview anyway.",
{
icon: "⚠️",
style: {
borderRadius: "10px",
background: "#FFC107",
color: "#000",
},
}
);
}
})
.catch((err) => {
console.error("Error testing URL:", err);
})
.finally(() => {
// Dispatch state change event to update the FilePreview component
// console.log(
// "Dispatching filePreviewStateChange event with:",
// { url, filename }
// );
window.dispatchEvent(
new CustomEvent("filePreviewStateChange", {
detail: { url, filename },
})
);
});
// Hide loading spinner after a short delay
setTimeout(() => {
if (loadingSpinner) {
loadingSpinner.classList.add("hidden");
}
}, 1000); // Increased delay to allow for URL testing
} else {
console.error("Missing required elements for file preview");
toast.error("Could not initialize file preview");
}
}; };
return mimeTypes[extension || ""] || "application/octet-stream"; // Close file preview for events section
} window.closeFilePreviewEvents = function () {
// console.log("closeFilePreviewEvents called");
const modal = document.getElementById(
"filePreviewModal"
) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent");
const loadingSpinner = document.getElementById("previewLoadingSpinner");
// Universal file preview function for events section if (loadingSpinner) {
window.previewFileEvents = function (url: string, filename: string) { loadingSpinner.classList.add("hidden");
// console.log("previewFileEvents called with:", { url, filename }); }
// console.log("URL type:", typeof url, "URL length:", url?.length || 0);
// console.log(
// "Filename type:",
// typeof filename,
// "Filename length:",
// filename?.length || 0
// );
// Validate inputs if (modal && previewFileName && previewContent) {
if (!url || typeof url !== "string") { // console.log("Resetting preview and closing modal");
console.error("Invalid URL provided to previewFileEvents:", url);
toast.error("Cannot preview file: Invalid URL");
return;
}
if (!filename || typeof filename !== "string") { // First reset the preview state by dispatching an event with empty values
console.error( // This ensures the FilePreview component clears its internal state
"Invalid filename provided to previewFileEvents:", window.dispatchEvent(
filename, new CustomEvent("filePreviewStateChange", {
); detail: { url: "", filename: "" },
toast.error("Cannot preview file: Invalid filename"); })
return; );
}
// Ensure URL is properly formatted // Reset the UI
if (!url.startsWith("http")) { previewFileName.textContent = "";
console.warn("URL doesn't start with http, attempting to fix:", url);
if (url.startsWith("/")) {
url = `https://pocketbase.ieeeucsd.org${url}`;
} else {
url = `https://pocketbase.ieeeucsd.org/${url}`;
}
// console.log("Fixed URL:", url);
}
const modal = document.getElementById( // Close the modal
"filePreviewModal", modal.close();
) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent");
const loadingSpinner = document.getElementById("previewLoadingSpinner");
if (modal && previewFileName && previewContent) { // console.log("File preview modal closed and state reset");
// console.log("Found all required elements"); } else {
console.error("Could not find elements to close file preview");
}
};
// Show loading spinner // Update the showFilePreview function for events section
if (loadingSpinner) { window.showFilePreviewEvents = function (file: {
loadingSpinner.classList.remove("hidden"); url: string;
} name: string;
}) {
// console.log("showFilePreviewEvents called with:", file);
if (!file || !file.url || !file.name) {
console.error("Invalid file data:", file);
toast.error("Could not preview file: missing file information");
return;
}
window.previewFileEvents(file.url, file.name);
};
// Update the filename display // Update the openDetailsModal function to use the events-specific preview
previewFileName.textContent = filename; window.openDetailsModal = function (event: any) {
const modal = document.getElementById(
"eventDetailsModal"
) as HTMLDialogElement;
const filesContent = document.getElementById(
"filesContent"
) as HTMLDivElement;
// Show the modal // Check if event has ended
modal.showModal(); const eventEndDate = new Date(event.end_date);
const now = new Date();
// Test the URL with a fetch before dispatching the event if (eventEndDate > now) {
fetch(url, { method: "HEAD" }) toast("Files are only available after the event has ended.", {
.then((response) => {
// console.log(
// "URL test response:",
// response.status,
// response.ok
// );
if (!response.ok) {
console.warn("URL might not be accessible:", url);
toast(
"File might not be accessible. Attempting to preview anyway.",
{
icon: "⚠️", icon: "⚠️",
style: { style: {
borderRadius: "10px", borderRadius: "10px",
background: "#FFC107", background: "#FFC107",
color: "#000", color: "#000",
}, },
}, });
); return;
}
})
.catch((err) => {
console.error("Error testing URL:", err);
})
.finally(() => {
// Dispatch state change event to update the FilePreview component
// console.log(
// "Dispatching filePreviewStateChange event with:",
// { url, filename }
// );
window.dispatchEvent(
new CustomEvent("filePreviewStateChange", {
detail: { url, filename },
}),
);
});
// Hide loading spinner after a short delay
setTimeout(() => {
if (loadingSpinner) {
loadingSpinner.classList.add("hidden");
} }
}, 1000); // Increased delay to allow for URL testing
} else {
console.error("Missing required elements for file preview");
toast.error("Could not initialize file preview");
}
};
// Close file preview for events section // Reset state
window.closeFilePreviewEvents = function () { window.currentEventId = event.id;
// console.log("closeFilePreviewEvents called"); if (filesContent) filesContent.classList.remove("hidden");
const modal = document.getElementById(
"filePreviewModal",
) as HTMLDialogElement;
const previewFileName = document.getElementById("previewFileName");
const previewContent = document.getElementById("previewContent");
const loadingSpinner = document.getElementById("previewLoadingSpinner");
if (loadingSpinner) { // Populate files content
loadingSpinner.classList.add("hidden"); if (
} event.files &&
Array.isArray(event.files) &&
event.files.length > 0
) {
const baseUrl = "https://pocketbase.ieeeucsd.org";
const collectionId = "events";
const recordId = event.id;
if (modal && previewFileName && previewContent) { filesContent.innerHTML = `
// console.log("Resetting preview and closing modal");
// First reset the preview state by dispatching an event with empty values
// This ensures the FilePreview component clears its internal state
window.dispatchEvent(
new CustomEvent("filePreviewStateChange", {
detail: { url: "", filename: "" },
}),
);
// Reset the UI
previewFileName.textContent = "";
// Close the modal
modal.close();
// console.log("File preview modal closed and state reset");
} else {
console.error("Could not find elements to close file preview");
}
};
// Update the showFilePreview function for events section
window.showFilePreviewEvents = function (file: {
url: string;
name: string;
}) {
// console.log("showFilePreviewEvents called with:", file);
if (!file || !file.url || !file.name) {
console.error("Invalid file data:", file);
toast.error("Could not preview file: missing file information");
return;
}
window.previewFileEvents(file.url, file.name);
};
// Update the openDetailsModal function to use the events-specific preview
window.openDetailsModal = function (event: any) {
const modal = document.getElementById(
"eventDetailsModal",
) as HTMLDialogElement;
const filesContent = document.getElementById(
"filesContent",
) as HTMLDivElement;
// Check if event has ended
const eventEndDate = new Date(event.end_date);
const now = new Date();
if (eventEndDate > now) {
toast("Files are only available after the event has ended.", {
icon: "⚠️",
style: {
borderRadius: "10px",
background: "#FFC107",
color: "#000",
},
});
return;
}
// Reset state
window.currentEventId = event.id;
if (filesContent) filesContent.classList.remove("hidden");
// Populate files content
if (event.files && Array.isArray(event.files) && event.files.length > 0) {
const baseUrl = "https://pocketbase.ieeeucsd.org";
const collectionId = "events";
const recordId = event.id;
filesContent.innerHTML = `
<div class="overflow-x-auto"> <div class="overflow-x-auto">
<table class="table table-zebra w-full"> <table class="table table-zebra w-full">
<thead> <thead>
@ -389,16 +403,16 @@ import EventLoad from "./EventsSection/EventLoad";
</thead> </thead>
<tbody> <tbody>
${event.files ${event.files
.map((file: string) => { .map((file: string) => {
// Ensure the file URL is properly formatted // Ensure the file URL is properly formatted
const fileUrl = `${baseUrl}/api/files/${collectionId}/${recordId}/${file}`; const fileUrl = `${baseUrl}/api/files/${collectionId}/${recordId}/${file}`;
const fileType = getFileType(file); const fileType = getFileType(file);
// Properly escape the data for the onclick handler // Properly escape the data for the onclick handler
const fileData = { const fileData = {
url: fileUrl, url: fileUrl,
name: file, name: file,
}; };
return ` return `
<tr> <tr>
<td>${file}</td> <td>${file}</td>
<td class="text-right"> <td class="text-right">
@ -411,123 +425,123 @@ import EventLoad from "./EventsSection/EventLoad";
</td> </td>
</tr> </tr>
`; `;
}) })
.join("")} .join("")}
</tbody> </tbody>
</table> </table>
</div> </div>
`; `;
} else { } else {
filesContent.innerHTML = ` filesContent.innerHTML = `
<div class="text-center py-8 text-base-content/70"> <div class="text-center py-8 text-base-content/70">
<iconify-icon icon="heroicons:document-duplicate" className="h-12 w-12 mx-auto mb-4 opacity-50" /> <iconify-icon icon="heroicons:document-duplicate" className="h-12 w-12 mx-auto mb-4 opacity-50" />
<p>No files attached to this event</p> <p>No files attached to this event</p>
</div> </div>
`; `;
}
modal.showModal();
};
// Add downloadAllFiles function
window.downloadAllFiles = async function () {
const downloadBtn = document.getElementById(
"downloadAllBtn",
) as HTMLButtonElement;
if (!downloadBtn) return;
const originalBtnContent = downloadBtn.innerHTML;
try {
// Show loading state
downloadBtn.innerHTML =
'<span class="loading loading-spinner loading-xs"></span> Preparing...';
downloadBtn.disabled = true;
const zip = new JSZip();
// Get current event files
const baseUrl = "https://pocketbase.ieeeucsd.org";
const collectionId = "events";
const recordId = window.currentEventId;
// Get the current event from the window object
const eventDataId = `event_${window.currentEventId}`;
const event = window[eventDataId];
if (!event || !event.files || event.files.length === 0) {
throw new Error("No files available to download");
}
// Download each file and add to zip
const filePromises = event.files.map(async (filename: string) => {
const fileUrl = `${baseUrl}/api/files/${collectionId}/${recordId}/${filename}`;
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error(`Failed to download ${filename}`);
} }
const blob = await response.blob();
zip.file(filename, blob);
});
await Promise.all(filePromises); modal.showModal();
};
// Generate and download zip // Add downloadAllFiles function
const zipBlob = await zip.generateAsync({ type: "blob" }); window.downloadAllFiles = async function () {
const downloadUrl = URL.createObjectURL(zipBlob); const downloadBtn = document.getElementById(
const link = document.createElement("a"); "downloadAllBtn"
link.href = downloadUrl; ) as HTMLButtonElement;
link.download = `${event.event_name}_files.zip`; if (!downloadBtn) return;
document.body.appendChild(link); const originalBtnContent = downloadBtn.innerHTML;
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
// Show success message try {
toast.success("Files downloaded successfully!"); // Show loading state
} catch (error: any) { downloadBtn.innerHTML =
console.error("Failed to download files:", error); '<span class="loading loading-spinner loading-xs"></span> Preparing...';
toast.error( downloadBtn.disabled = true;
error?.message || "Failed to download files. Please try again.",
);
} finally {
// Reset button state
downloadBtn.innerHTML = originalBtnContent;
downloadBtn.disabled = false;
}
};
// Close event details modal const zip = new JSZip();
window.closeEventDetailsModal = function () {
const modal = document.getElementById(
"eventDetailsModal",
) as HTMLDialogElement;
const filesContent = document.getElementById("filesContent");
if (modal) { // Get current event files
// Reset the files content const baseUrl = "https://pocketbase.ieeeucsd.org";
if (filesContent) { const collectionId = "events";
filesContent.innerHTML = ""; const recordId = window.currentEventId;
}
// Reset any other state if needed // Get the current event from the window object
window.currentEventId = ""; const eventDataId = `event_${window.currentEventId}`;
const event = window[eventDataId];
// Close the modal if (!event || !event.files || event.files.length === 0) {
modal.close(); throw new Error("No files available to download");
} }
};
// Make helper functions available globally // Download each file and add to zip
window.showFilePreview = window.showFilePreviewEvents; const filePromises = event.files.map(async (filename: string) => {
window.handlePreviewError = function () { const fileUrl = `${baseUrl}/api/files/${collectionId}/${recordId}/${filename}`;
const previewContent = document.getElementById("previewContent"); const response = await fetch(fileUrl);
if (previewContent) { if (!response.ok) {
previewContent.innerHTML = ` throw new Error(`Failed to download ${filename}`);
}
const blob = await response.blob();
zip.file(filename, blob);
});
await Promise.all(filePromises);
// Generate and download zip
const zipBlob = await zip.generateAsync({ type: "blob" });
const downloadUrl = URL.createObjectURL(zipBlob);
const link = document.createElement("a");
link.href = downloadUrl;
link.download = `${event.event_name}_files.zip`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(downloadUrl);
// Show success message
toast.success("Files downloaded successfully!");
} catch (error: any) {
console.error("Failed to download files:", error);
toast.error(
error?.message || "Failed to download files. Please try again."
);
} finally {
// Reset button state
downloadBtn.innerHTML = originalBtnContent;
downloadBtn.disabled = false;
}
};
// Close event details modal
window.closeEventDetailsModal = function () {
const modal = document.getElementById(
"eventDetailsModal"
) as HTMLDialogElement;
const filesContent = document.getElementById("filesContent");
if (modal) {
// Reset the files content
if (filesContent) {
filesContent.innerHTML = "";
}
// Reset any other state if needed
window.currentEventId = "";
// Close the modal
modal.close();
}
};
// Make helper functions available globally
window.showFilePreview = window.showFilePreviewEvents;
window.handlePreviewError = function () {
const previewContent = document.getElementById("previewContent");
if (previewContent) {
previewContent.innerHTML = `
<div class="alert alert-error"> <div class="alert alert-error">
<Icon icon="heroicons:x-circle" className="h-6 w-6" /> <Icon icon="heroicons:x-circle" className="h-6 w-6" />
<span>Failed to load file preview</span> <span>Failed to load file preview</span>
</div> </div>
`; `;
} }
}; };
</script> </script>

View file

@ -359,12 +359,12 @@ const EventCheckIn = () => {
return ( return (
<> <>
<div className="card bg-base-100 shadow-xl border border-base-200 h-full"> <div className="card bg-card shadow-xl border border-border h-full">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Event Check-in</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Event Check-in</h3>
<div className="form-control w-full"> <div className="w-full">
<label className="label"> <label className="block text-sm sm:text-base mb-2">
<span className="label-text text-sm sm:text-base">Enter event code to check in</span> <span className="text-sm sm:text-base">Enter event code to check in</span>
</label> </label>
<form onSubmit={(e) => { <form onSubmit={(e) => {
e.preventDefault(); e.preventDefault();

View file

@ -103,28 +103,28 @@ const EventLoad = () => {
}, []); }, []);
const createSkeletonCard = () => ( const createSkeletonCard = () => (
<div className="card bg-base-200 shadow-lg animate-pulse"> <div className="card bg-base-200 dark:bg-gray-800/90 shadow-lg animate-pulse border border-base-300 dark:border-gray-700">
<div className="card-body p-5"> <div className="card-body p-5">
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex items-start justify-between gap-3 mb-2"> <div className="flex items-start justify-between gap-3 mb-2">
<div className="flex-1"> <div className="flex-1">
<div className="skeleton h-6 w-3/4 mb-2"></div> <div className="skeleton h-6 w-3/4 mb-2 bg-base-300 dark:bg-gray-700"></div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="skeleton h-5 w-16"></div> <div className="skeleton h-5 w-16 bg-base-300 dark:bg-gray-700"></div>
<div className="skeleton h-5 w-20"></div> <div className="skeleton h-5 w-20 bg-base-300 dark:bg-gray-700"></div>
</div> </div>
</div> </div>
<div className="flex flex-col items-end"> <div className="flex flex-col items-end">
<div className="skeleton h-5 w-24 mb-1"></div> <div className="skeleton h-5 w-24 mb-1 bg-base-300 dark:bg-gray-700"></div>
<div className="skeleton h-4 w-16"></div> <div className="skeleton h-4 w-16 bg-base-300 dark:bg-gray-700"></div>
</div> </div>
</div> </div>
<div className="skeleton h-4 w-full mb-3"></div> <div className="skeleton h-4 w-full mb-3 bg-base-300 dark:bg-gray-700"></div>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<div className="skeleton h-4 w-4"></div> <div className="skeleton h-4 w-4 bg-base-300 dark:bg-gray-700"></div>
<div className="skeleton h-4 w-1/2"></div> <div className="skeleton h-4 w-1/2 bg-base-300 dark:bg-gray-700"></div>
</div> </div>
</div> </div>
</div> </div>
@ -178,15 +178,15 @@ const EventLoad = () => {
const isPastEvent = endDate < now; const isPastEvent = endDate < now;
return ( return (
<div key={event.id} className="card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-300 relative overflow-hidden"> <div id={`event-card-${event.id}`} key={event.id} className="card bg-base-200 dark:bg-gray-800/90 shadow-lg hover:shadow-xl transition-all duration-300 relative overflow-hidden border border-base-300 dark:border-gray-700">
<div className="card-body p-3 sm:p-4"> <div className="card-body p-3 sm:p-4">
<div className="flex flex-col h-full"> <div className="flex flex-col h-full">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
<div className="flex-1"> <div className="flex-1">
<h3 className="card-title text-base sm:text-lg font-semibold mb-1 line-clamp-2">{event.event_name}</h3> <h3 className="card-title text-base sm:text-lg font-semibold mb-1 line-clamp-2 text-gray-800 dark:text-gray-100">{event.event_name}</h3>
<div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm text-base-content/70"> <div className="flex flex-wrap items-center gap-2 text-xs sm:text-sm text-gray-600 dark:text-gray-300">
<div className="badge badge-primary badge-sm">{event.points_to_reward} pts</div> <div className="badge badge-primary badge-sm">{event.points_to_reward} pts</div>
<div className="text-xs sm:text-sm opacity-75"> <div className="text-xs sm:text-sm text-gray-600 dark:text-gray-300">
{startDate.toLocaleDateString("en-US", { {startDate.toLocaleDateString("en-US", {
weekday: "short", weekday: "short",
month: "short", month: "short",
@ -202,7 +202,7 @@ const EventLoad = () => {
</div> </div>
</div> </div>
<div className="text-xs sm:text-sm text-base-content/70 my-2 line-clamp-2"> <div className="text-xs sm:text-sm text-gray-600 dark:text-gray-300 my-2 line-clamp-2">
{event.event_description || "No description available"} {event.event_description || "No description available"}
</div> </div>
@ -210,14 +210,14 @@ const EventLoad = () => {
{event.files && event.files.length > 0 && ( {event.files && event.files.length > 0 && (
<button <button
onClick={() => window.openDetailsModal(event as ExtendedEvent)} onClick={() => window.openDetailsModal(event as ExtendedEvent)}
className="btn btn-ghost btn-sm text-xs sm:text-sm gap-1 h-8 min-h-0 px-2" className="btn btn-ghost btn-sm text-xs sm:text-sm gap-1 h-8 min-h-0 px-2 text-gray-700 dark:text-gray-300"
> >
<Icon icon="heroicons:document-duplicate" className="h-3 w-3 sm:h-4 sm:w-4" /> <Icon icon="heroicons:document-duplicate" className="h-3 w-3 sm:h-4 sm:w-4" />
Files ({event.files.length}) Files ({event.files.length})
</button> </button>
)} )}
{isPastEvent && ( {isPastEvent && (
<div className={`badge ${hasAttended ? 'badge-success' : 'badge-ghost'} text-xs gap-1`}> <div className={`badge ${hasAttended ? 'badge-success' : 'badge-ghost'} text-xs gap-1 attended-badge ${!hasAttended ? 'hidden' : ''}`}>
<Icon <Icon
icon={hasAttended ? "heroicons:check-circle" : "heroicons:x-circle"} icon={hasAttended ? "heroicons:check-circle" : "heroicons:x-circle"}
className="h-3 w-3" className="h-3 w-3"
@ -225,7 +225,7 @@ const EventLoad = () => {
{hasAttended ? 'Attended' : 'Not Attended'} {hasAttended ? 'Attended' : 'Not Attended'}
</div> </div>
)} )}
<div className="text-xs sm:text-sm opacity-75 ml-auto"> <div className="text-xs sm:text-sm text-gray-600 dark:text-gray-400 ml-auto">
{event.location} {event.location}
</div> </div>
</div> </div>
@ -480,9 +480,9 @@ const EventLoad = () => {
return ( return (
<> <>
{/* Ongoing Events */} {/* Ongoing Events */}
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Ongoing Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Ongoing Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<div key={`ongoing-skeleton-${i}`}>{createSkeletonCard()}</div> <div key={`ongoing-skeleton-${i}`}>{createSkeletonCard()}</div>
@ -492,9 +492,9 @@ const EventLoad = () => {
</div> </div>
{/* Upcoming Events */} {/* Upcoming Events */}
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Upcoming Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Upcoming Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<div key={`upcoming-skeleton-${i}`}>{createSkeletonCard()}</div> <div key={`upcoming-skeleton-${i}`}>{createSkeletonCard()}</div>
@ -504,9 +504,9 @@ const EventLoad = () => {
</div> </div>
{/* Past Events */} {/* Past Events */}
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Past Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Past Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{[...Array(3)].map((_, i) => ( {[...Array(3)].map((_, i) => (
<div key={`past-skeleton-${i}`}>{createSkeletonCard()}</div> <div key={`past-skeleton-${i}`}>{createSkeletonCard()}</div>
@ -525,14 +525,14 @@ const EventLoad = () => {
<> <>
{/* No Events Message */} {/* No Events Message */}
{noEvents && ( {noEvents && (
<div className="card bg-base-100 shadow-xl border border-base-200 mx-4 sm:mx-6 p-8"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 mx-4 sm:mx-6 p-8">
<div className="text-center"> <div className="text-center">
<Icon icon="heroicons:calendar" className="w-16 h-16 mx-auto text-base-content/30 mb-4" /> <Icon icon="heroicons:calendar" className="w-16 h-16 mx-auto text-gray-400 dark:text-gray-600 mb-4" />
<h3 className="text-xl font-bold mb-2">No Events Found</h3> <h3 className="text-xl font-bold mb-2 text-gray-800 dark:text-gray-100">No Events Found</h3>
<p className="text-base-content/70 mb-4"> <p className="text-gray-600 dark:text-gray-300 mb-4">
There are currently no events to display. This could be due to: There are currently no events to display. This could be due to:
</p> </p>
<ul className="list-disc text-left max-w-md mx-auto text-base-content/70 mb-6"> <ul className="list-disc text-left max-w-md mx-auto text-gray-600 dark:text-gray-300 mb-6">
<li className="mb-1">No events have been published yet</li> <li className="mb-1">No events have been published yet</li>
<li className="mb-1">There might be a connection issue with the event database</li> <li className="mb-1">There might be a connection issue with the event database</li>
<li className="mb-1">The events data might be temporarily unavailable</li> <li className="mb-1">The events data might be temporarily unavailable</li>
@ -560,9 +560,9 @@ const EventLoad = () => {
{/* Ongoing Events */} {/* Ongoing Events */}
{events.ongoing.length > 0 && ( {events.ongoing.length > 0 && (
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Ongoing Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Ongoing Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{events.ongoing.map(renderEventCard)} {events.ongoing.map(renderEventCard)}
</div> </div>
@ -572,9 +572,9 @@ const EventLoad = () => {
{/* Upcoming Events */} {/* Upcoming Events */}
{events.upcoming.length > 0 && ( {events.upcoming.length > 0 && (
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-4 sm:mb-6 mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Upcoming Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Upcoming Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{events.upcoming.map(renderEventCard)} {events.upcoming.map(renderEventCard)}
</div> </div>
@ -584,9 +584,9 @@ const EventLoad = () => {
{/* Past Events */} {/* Past Events */}
{events.past.length > 0 && ( {events.past.length > 0 && (
<div className="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mx-4 sm:mx-6"> <div className="card bg-base-100 dark:bg-gray-900/90 shadow-xl border border-base-200 dark:border-gray-700 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mx-4 sm:mx-6">
<div className="card-body p-4 sm:p-6"> <div className="card-body p-4 sm:p-6">
<h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4">Past Events</h3> <h3 className="card-title text-base sm:text-lg mb-3 sm:mb-4 text-gray-800 dark:text-gray-100">Past Events</h3>
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6"> <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4 sm:gap-6">
{events.past.map(renderEventCard)} {events.past.map(renderEventCard)}
</div> </div>

View file

@ -125,9 +125,9 @@ export default function LeaderboardStats() {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
{[1, 2, 3, 4].map((i) => ( {[1, 2, 3, 4].map((i) => (
<div key={i} className="h-24 bg-gray-100/50 dark:bg-gray-800/50 animate-pulse rounded-xl"> <div key={i} className="h-24 bg-base-200 dark:bg-gray-800/50 animate-pulse rounded-xl">
<div className="h-4 w-24 bg-gray-200 dark:bg-gray-700 rounded mb-2 mt-4 mx-4"></div> <div className="h-4 w-24 bg-base-300 dark:bg-gray-700 rounded mb-2 mt-4 mx-4"></div>
<div className="h-8 w-16 bg-gray-200 dark:bg-gray-700 rounded mx-4"></div> <div className="h-8 w-16 bg-base-300 dark:bg-gray-700 rounded mx-4"></div>
</div> </div>
))} ))}
</div> </div>
@ -136,27 +136,27 @@ export default function LeaderboardStats() {
return ( return (
<div className="grid grid-cols-1 md:grid-cols-4 gap-4"> <div className="grid grid-cols-1 md:grid-cols-4 gap-4">
<div className="p-6 bg-white/90 dark:bg-gray-800/90 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700"> <div className="p-6 bg-base-100 dark:bg-gray-800/90 rounded-xl shadow-sm border border-base-200 dark:border-gray-700">
<div className="text-sm font-medium text-gray-600 dark:text-gray-300">Total Members</div> <div className="text-sm font-medium text-gray-700 dark:text-gray-300">Total Members</div>
<div className="mt-2 text-3xl font-bold text-gray-800 dark:text-white">{stats.totalUsers}</div> <div className="mt-2 text-3xl font-bold text-gray-800 dark:text-white">{stats.totalUsers}</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">In the leaderboard</div> <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">In the leaderboard</div>
</div> </div>
<div className="p-6 bg-white/90 dark:bg-gray-800/90 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700"> <div className="p-6 bg-base-100 dark:bg-gray-800/90 rounded-xl shadow-sm border border-base-200 dark:border-gray-700">
<div className="text-sm font-medium text-gray-600 dark:text-gray-300">Total Points</div> <div className="text-sm font-medium text-gray-700 dark:text-gray-300">Total Points</div>
<div className="mt-2 text-3xl font-bold text-gray-800 dark:text-white">{stats.totalPoints}</div> <div className="mt-2 text-3xl font-bold text-gray-800 dark:text-white">{stats.totalPoints}</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">Earned by all members</div> <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">Earned by all members</div>
</div> </div>
<div className="p-6 bg-white/90 dark:bg-gray-800/90 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700"> <div className="p-6 bg-base-100 dark:bg-gray-800/90 rounded-xl shadow-sm border border-base-200 dark:border-gray-700">
<div className="text-sm font-medium text-gray-600 dark:text-gray-300">Top Score</div> <div className="text-sm font-medium text-gray-700 dark:text-gray-300">Top Score</div>
<div className="mt-2 text-3xl font-bold text-indigo-600 dark:text-indigo-400">{stats.topScore}</div> <div className="mt-2 text-3xl font-bold text-primary dark:text-primary">{stats.topScore}</div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400">Highest individual points</div> <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">Highest individual points</div>
</div> </div>
<div className="p-6 bg-white/90 dark:bg-gray-800/90 rounded-xl shadow-sm border border-gray-100 dark:border-gray-700"> <div className="p-6 bg-base-100 dark:bg-gray-800/90 rounded-xl shadow-sm border border-base-200 dark:border-gray-700">
<div className="text-sm font-medium text-gray-600 dark:text-gray-300">Your Score</div> <div className="text-sm font-medium text-gray-700 dark:text-gray-300">Your Score</div>
<div className="mt-2 text-3xl font-bold text-indigo-600 dark:text-indigo-400"> <div className="mt-2 text-3xl font-bold text-primary dark:text-primary">
{isAuthenticated ? stats.yourPoints : '-'} {isAuthenticated ? stats.yourPoints : '-'}
</div> </div>
<div className="mt-1 text-xs text-gray-500 dark:text-gray-400"> <div className="mt-1 text-xs text-gray-500 dark:text-gray-400">

View file

@ -184,37 +184,37 @@ export default function LeaderboardTable() {
<input <input
type="text" type="text"
placeholder="Search by name or major..." placeholder="Search by name or major..."
className="w-full pl-10 pr-4 py-2 border border-gray-300 dark:border-gray-700 rounded-lg className="w-full pl-10 pr-4 py-2 border border-base-300 dark:border-gray-700 rounded-lg
bg-white/90 dark:bg-gray-800/90 text-gray-700 dark:text-gray-200 focus:outline-none bg-base-100 dark:bg-gray-800/90 text-gray-700 dark:text-gray-200 focus:outline-none
focus:ring-2 focus:ring-indigo-500 focus:border-transparent" focus:ring-2 focus:ring-primary focus:border-transparent shadow-sm"
value={searchQuery} value={searchQuery}
onChange={(e) => setSearchQuery(e.target.value)} onChange={(e) => setSearchQuery(e.target.value)}
/> />
</div> </div>
{/* Leaderboard table */} {/* Leaderboard table */}
<div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700"> <div className="overflow-x-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
<table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700"> <table className="min-w-full divide-y divide-gray-200 dark:divide-gray-700">
<thead className="bg-gray-50/80 dark:bg-gray-800/80"> <thead className="bg-base-200 dark:bg-gray-800/80">
<tr> <tr>
<th scope="col" className="w-16 px-6 py-3 text-center text-xs font-medium text-gray-600 dark:text-gray-300 uppercase tracking-wider"> <th scope="col" className="w-16 px-6 py-3 text-center text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
Rank Rank
</th> </th>
<th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-600 dark:text-gray-300 uppercase tracking-wider"> <th scope="col" className="px-6 py-3 text-left text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
User User
</th> </th>
<th scope="col" className="w-24 px-6 py-3 text-center text-xs font-medium text-gray-600 dark:text-gray-300 uppercase tracking-wider"> <th scope="col" className="w-24 px-6 py-3 text-center text-xs font-medium text-gray-700 dark:text-gray-300 uppercase tracking-wider">
Points Points
</th> </th>
</tr> </tr>
</thead> </thead>
<tbody className="bg-white/90 dark:bg-gray-900/90 divide-y divide-gray-200 dark:divide-gray-800"> <tbody className="bg-base-100 dark:bg-gray-900/90 divide-y divide-gray-200 dark:divide-gray-800">
{currentUsers.map((user, index) => { {currentUsers.map((user, index) => {
const actualRank = user.points > 0 ? indexOfFirstUser + index + 1 : null; const actualRank = user.points > 0 ? indexOfFirstUser + index + 1 : null;
const isCurrentUser = user.id === currentUserId; const isCurrentUser = user.id === currentUserId;
return ( return (
<tr key={user.id} className={isCurrentUser ? 'bg-indigo-50 dark:bg-indigo-900/20' : ''}> <tr key={user.id} className={isCurrentUser ? 'bg-primary/10 dark:bg-primary/20' : ''}>
<td className="px-6 py-4 whitespace-nowrap text-center"> <td className="px-6 py-4 whitespace-nowrap text-center">
{actualRank ? ( {actualRank ? (
actualRank <= 3 ? ( actualRank <= 3 ? (
@ -233,7 +233,7 @@ export default function LeaderboardTable() {
<td className="px-6 py-4 whitespace-nowrap"> <td className="px-6 py-4 whitespace-nowrap">
<div className="flex items-center"> <div className="flex items-center">
<div className="flex-shrink-0 h-10 w-10"> <div className="flex-shrink-0 h-10 w-10">
<div className="w-10 h-10 rounded-full bg-gray-200 dark:bg-gray-700 flex items-center justify-center overflow-hidden relative"> <div className="w-10 h-10 rounded-full bg-base-300 dark:bg-gray-700 flex items-center justify-center overflow-hidden relative">
{user.avatar ? ( {user.avatar ? (
<img className="h-10 w-10 rounded-full" src={user.avatar} alt={user.name} /> <img className="h-10 w-10 rounded-full" src={user.avatar} alt={user.name} />
) : ( ) : (
@ -255,7 +255,7 @@ export default function LeaderboardTable() {
</div> </div>
</div> </div>
</td> </td>
<td className="px-6 py-4 whitespace-nowrap text-center font-bold text-indigo-600 dark:text-indigo-400"> <td className="px-6 py-4 whitespace-nowrap text-center font-bold text-primary dark:text-primary">
{user.points} {user.points}
</td> </td>
</tr> </tr>
@ -270,8 +270,8 @@ export default function LeaderboardTable() {
<div className="flex justify-center mt-6"> <div className="flex justify-center mt-6">
<nav className="flex items-center"> <nav className="flex items-center">
<button <button
className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-gray-300 dark:border-gray-700 className="relative inline-flex items-center px-2 py-2 rounded-l-md border border-base-300 dark:border-gray-700
bg-white/90 dark:bg-gray-800/90 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700" bg-base-100 dark:bg-gray-800/90 text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-base-200 dark:hover:bg-gray-700 shadow-sm"
onClick={() => paginate(Math.max(1, currentPage - 1))} onClick={() => paginate(Math.max(1, currentPage - 1))}
disabled={currentPage === 1} disabled={currentPage === 1}
> >
@ -284,11 +284,11 @@ export default function LeaderboardTable() {
{Array.from({ length: totalPages }, (_, i) => ( {Array.from({ length: totalPages }, (_, i) => (
<button <button
key={i + 1} key={i + 1}
className={`relative inline-flex items-center px-4 py-2 border border-gray-300 dark:border-gray-700 className={`relative inline-flex items-center px-4 py-2 border border-base-300 dark:border-gray-700
bg-white/90 dark:bg-gray-800/90 text-sm font-medium ${currentPage === i + 1 bg-base-100 dark:bg-gray-800/90 text-sm font-medium ${currentPage === i + 1
? 'text-indigo-600 dark:text-indigo-400 border-indigo-500 dark:border-indigo-500 z-10' ? 'text-primary dark:text-primary border-primary dark:border-primary z-10 font-bold'
: 'text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700' : 'text-gray-700 dark:text-gray-300 hover:bg-base-200 dark:hover:bg-gray-700'
}`} } shadow-sm`}
onClick={() => paginate(i + 1)} onClick={() => paginate(i + 1)}
> >
{i + 1} {i + 1}
@ -296,8 +296,8 @@ export default function LeaderboardTable() {
))} ))}
<button <button
className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-gray-300 dark:border-gray-700 className="relative inline-flex items-center px-2 py-2 rounded-r-md border border-base-300 dark:border-gray-700
bg-white/90 dark:bg-gray-800/90 text-sm font-medium text-gray-500 dark:text-gray-400 hover:bg-gray-50 dark:hover:bg-gray-700" bg-base-100 dark:bg-gray-800/90 text-sm font-medium text-gray-600 dark:text-gray-400 hover:bg-base-200 dark:hover:bg-gray-700 shadow-sm"
onClick={() => paginate(Math.min(totalPages, currentPage + 1))} onClick={() => paginate(Math.min(totalPages, currentPage + 1))}
disabled={currentPage === totalPages} disabled={currentPage === totalPages}
> >
@ -312,9 +312,9 @@ export default function LeaderboardTable() {
{/* Show current user rank if not in current page */} {/* Show current user rank if not in current page */}
{isAuthenticated && currentUserRank && !currentUsers.some(user => user.id === currentUserId) && ( {isAuthenticated && currentUserRank && !currentUsers.some(user => user.id === currentUserId) && (
<div className="mt-4 p-3 bg-gray-50/80 dark:bg-gray-800/80 border border-gray-200 dark:border-gray-700 rounded-lg"> <div className="mt-4 p-3 bg-base-200 dark:bg-gray-800/80 border border-base-300 dark:border-gray-700 rounded-lg shadow-sm">
<p className="text-center text-sm text-gray-700 dark:text-gray-300"> <p className="text-center text-sm text-gray-700 dark:text-gray-300">
Your rank: <span className="font-bold text-indigo-600 dark:text-indigo-400">#{currentUserRank}</span> Your rank: <span className="font-bold text-primary dark:text-primary">#{currentUserRank}</span>
</p> </p>
</div> </div>
)} )}
@ -323,7 +323,7 @@ export default function LeaderboardTable() {
{isAuthenticated && currentUserId && {isAuthenticated && currentUserId &&
!currentUserRank && !currentUserRank &&
currentUsers.some(user => user.id === currentUserId) && ( currentUsers.some(user => user.id === currentUserId) && (
<div className="mt-4 p-3 bg-gray-50/80 dark:bg-gray-800/80 border border-gray-200 dark:border-gray-700 rounded-lg"> <div className="mt-4 p-3 bg-base-200 dark:bg-gray-800/80 border border-base-300 dark:border-gray-700 rounded-lg shadow-sm">
<p className="text-center text-sm text-gray-700 dark:text-gray-300"> <p className="text-center text-sm text-gray-700 dark:text-gray-300">
Participate in events to earn points and get ranked! Participate in events to earn points and get ranked!
</p> </p>

View file

@ -215,11 +215,13 @@ const safeLogtoApiEndpoint = logtoApiEndpoint || "";
<!-- Display Settings Card --> <!-- Display Settings Card -->
<div <div
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform" class="card bg-card shadow-xl border border-border hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
> >
<div class="card-body"> <div class="card-body">
<h3 class="card-title flex items-center gap-3"> <h3 class="card-title flex items-center gap-3">
<div class="badge badge-primary p-3"> <div
class="inline-flex items-center justify-center p-3 rounded-full bg-primary text-primary-foreground"
>
<Icon name="heroicons:computer-desktop" class="h-5 w-5" /> <Icon name="heroicons:computer-desktop" class="h-5 w-5" />
</div> </div>
Display Settings Display Settings
@ -227,14 +229,18 @@ const safeLogtoApiEndpoint = logtoApiEndpoint || "";
<p class="text-sm opacity-70 mb-4"> <p class="text-sm opacity-70 mb-4">
Customize your dashboard appearance and display preferences Customize your dashboard appearance and display preferences
</p> </p>
<div class="divider"></div> <div class="h-px w-full bg-border my-4"></div>
<div class="alert alert-warning mb-4"> <div
class="flex p-4 mb-4 text-sm rounded-lg bg-warning/20 text-warning-foreground"
role="alert"
>
<svg <svg
xmlns="http://www.w3.org/2000/svg" xmlns="http://www.w3.org/2000/svg"
class="stroke-current shrink-0 h-6 w-6" class="flex-shrink-0 w-5 h-5 mr-3"
fill="none" fill="none"
viewBox="0 0 24 24" viewBox="0 0 24 24"
stroke="currentColor"
><path ><path
stroke-linecap="round" stroke-linecap="round"
stroke-linejoin="round" stroke-linejoin="round"

View file

@ -258,17 +258,17 @@ export default function DisplaySettings() {
{/* Theme Settings */} {/* Theme Settings */}
<div> <div>
<h4 className="font-semibold text-lg mb-2">Theme</h4> <h4 className="font-semibold text-lg mb-2">Theme</h4>
<div className="form-control w-full max-w-xs"> <div className="w-full max-w-xs">
<select <select
value={theme} value={theme}
onChange={handleThemeChange} onChange={handleThemeChange}
className="select select-bordered" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
> >
<option value="light">Light</option> <option value="light">Light</option>
<option value="dark">Dark</option> <option value="dark">Dark</option>
</select> </select>
<label className="label"> <label className="mt-1 block">
<span className="label-text-alt">Select your preferred theme</span> <span className="text-xs text-muted-foreground">Select your preferred theme</span>
</label> </label>
</div> </div>
</div> </div>
@ -276,19 +276,19 @@ export default function DisplaySettings() {
{/* Font Size Settings */} {/* Font Size Settings */}
<div> <div>
<h4 className="font-semibold text-lg mb-2">Font Size</h4> <h4 className="font-semibold text-lg mb-2">Font Size</h4>
<div className="form-control w-full max-w-xs"> <div className="w-full max-w-xs">
<select <select
value={fontSize} value={fontSize}
onChange={handleFontSizeChange} onChange={handleFontSizeChange}
className="select select-bordered" className="flex h-10 w-full rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2"
> >
<option value="small">Small</option> <option value="small">Small</option>
<option value="medium">Medium</option> <option value="medium">Medium</option>
<option value="large">Large</option> <option value="large">Large</option>
<option value="extra-large">Extra Large</option> <option value="extra-large">Extra Large</option>
</select> </select>
<label className="label"> <label className="mt-1 block">
<span className="label-text-alt">Select your preferred font size</span> <span className="text-xs text-muted-foreground">Select your preferred font size</span>
</label> </label>
</div> </div>
</div> </div>
@ -297,54 +297,64 @@ export default function DisplaySettings() {
<div> <div>
<h4 className="font-semibold text-lg mb-2">Accessibility</h4> <h4 className="font-semibold text-lg mb-2">Accessibility</h4>
<div className="form-control"> <div className="flex items-center space-x-4 mb-4">
<label className="cursor-pointer label justify-start gap-4"> <label className="relative inline-flex items-center cursor-pointer">
<input <input
type="checkbox" type="checkbox"
checked={colorBlindMode} checked={colorBlindMode}
onChange={handleColorBlindModeChange} onChange={handleColorBlindModeChange}
className="toggle toggle-primary" className="sr-only peer"
/> />
<div> <div className="w-11 h-6 bg-muted rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
<span className="label-text font-medium">Color Blind Mode</span>
<p className="text-xs opacity-70">Enhances color contrast and uses color-blind friendly palettes</p>
</div>
</label> </label>
<div>
<span className="font-medium">Color Blind Mode</span>
<p className="text-xs text-muted-foreground">Enhances color contrast and uses color-blind friendly palettes</p>
</div>
</div> </div>
<div className="form-control mt-2"> <div className="flex items-center space-x-4">
<label className="cursor-pointer label justify-start gap-4"> <label className="relative inline-flex items-center cursor-pointer">
<input <input
type="checkbox" type="checkbox"
checked={reducedMotion} checked={reducedMotion}
onChange={handleReducedMotionChange} onChange={handleReducedMotionChange}
className="toggle toggle-primary" className="sr-only peer"
/> />
<div> <div className="w-11 h-6 bg-muted rounded-full peer peer-checked:after:translate-x-full rtl:peer-checked:after:-translate-x-full peer-checked:after:border-white after:content-[''] after:absolute after:top-[2px] after:start-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-5 after:w-5 after:transition-all peer-checked:bg-primary"></div>
<span className="label-text font-medium">Reduced Motion</span>
<p className="text-xs opacity-70">Minimizes animations and transitions</p>
</div>
</label> </label>
<div>
<span className="font-medium">Reduced Motion</span>
<p className="text-xs text-muted-foreground">Minimizes animations and transitions</p>
</div>
</div> </div>
</div> </div>
<p className="text-sm text-info"> <p className="text-sm text-blue-500 dark:text-blue-400 mt-4">
These settings are saved to your browser using IndexedDB and your IEEE UCSD account. They will be applied whenever you log in. These settings are saved to your browser using IndexedDB and your IEEE UCSD account. They will be applied whenever you log in.
</p> </p>
<div className="form-control"> <div className="mt-4">
<div className="flex flex-col gap-2"> <div className="flex flex-col gap-2">
{hasChanges && ( {hasChanges && (
<p className="text-sm text-warning"> <p className="text-sm text-amber-600 dark:text-amber-400">
You have unsaved changes. Click "Save Settings" to apply them. You have unsaved changes. Click "Save Settings" to apply them.
</p> </p>
)} )}
<button <button
type="submit" type="submit"
className={`btn btn-primary ${saving ? 'loading' : ''}`} className={`inline-flex items-center justify-center rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 bg-primary text-primary-foreground hover:bg-primary/90 h-10 px-4 py-2 ${saving ? 'opacity-70' : ''}`}
disabled={saving || !hasChanges} disabled={saving || !hasChanges}
> >
{saving ? 'Saving...' : 'Save Settings'} {saving ? (
<>
<svg className="animate-spin -ml-1 mr-2 h-4 w-4 text-white" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
Saving...
</>
) : 'Save Settings'}
</button> </button>
</div> </div>
</div> </div>

View file

@ -84,15 +84,20 @@ export default function ThemeToggle() {
}; };
return ( return (
<div className="dropdown dropdown-end"> <div className="relative">
<button <button
onClick={handleToggle} onClick={handleToggle}
className={`btn btn-circle btn-sm ${isLoading ? 'loading' : ''}`} className={`inline-flex items-center justify-center rounded-full w-8 h-8 text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50 hover:bg-accent hover:text-accent-foreground ${isLoading ? 'opacity-70' : ''}`}
aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`} aria-label={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme`}
title={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme (Light mode is experimental)`} title={`Switch to ${theme === 'light' ? 'dark' : 'light'} theme (Light mode is experimental)`}
disabled={isLoading} disabled={isLoading}
> >
{!isLoading && ( {isLoading ? (
<svg className="animate-spin h-4 w-4" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
<circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4"></circle>
<path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"></path>
</svg>
) : (
theme === 'light' ? ( theme === 'light' ? (
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" /> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M20.354 15.354A9 9 0 018.646 3.646 9.003 9.003 0 0012 21a9.003 9.003 0 008.354-5.646z" />
@ -104,9 +109,9 @@ export default function ThemeToggle() {
) )
)} )}
</button> </button>
<div className="dropdown-content z-[1] menu p-2 shadow bg-base-100 rounded-box w-52 mt-2 text-xs"> <div className="absolute right-0 z-10 mt-2 w-52 origin-top-right rounded-md bg-card shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none hidden group-hover:block">
<div className="p-2"> <div className="p-3 text-xs">
<p className="font-bold text-warning mb-1">Warning:</p> <p className="font-bold text-amber-600 dark:text-amber-400 mb-1">Warning:</p>
<p>Light mode is experimental and not fully supported yet. Some UI elements may not display correctly.</p> <p>Light mode is experimental and not fully supported yet. Some UI elements may not display correctly.</p>
</div> </div>
</div> </div>

View file

@ -2,41 +2,42 @@
import Navbar from "../components/core/Navbar.astro"; import Navbar from "../components/core/Navbar.astro";
import Footer from "../components/core/Footer.astro"; import Footer from "../components/core/Footer.astro";
import InView from "../components/core/InView.astro"; import InView from "../components/core/InView.astro";
import { initTheme } from "../scripts/database/initTheme";
--- ---
<!doctype html> <!doctype html>
<html lang="en" data-theme="dark" class="w-full h-full m-0 bg-ieee-black"> <html lang="en" class="w-full h-full m-0 bg-ieee-black">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width" /> <meta name="viewport" content="width=device-width" />
<link rel="icon" type="image/svg+xml" href="/favicon.svg" /> <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
<meta name="generator" content={Astro.generator} /> <meta name="generator" content={Astro.generator} />
<title>IEEEUCSD</title> <title>IEEEUCSD</title>
<script <script
src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js" src="https://code.iconify.design/iconify-icon/1.0.7/iconify-icon.min.js"
></script> ></script>
<script is:inline> <script is:inline>
// Set default theme to dark if not already set // Set a default theme until IndexedDB loads
if (!localStorage.getItem("theme")) { document.documentElement.setAttribute("data-theme", "dark");
localStorage.setItem("theme", "dark"); </script>
document.documentElement.setAttribute("data-theme", "dark"); </head>
} else { <InView />
// Apply saved theme <body class="w-full h-full m-0 bg-ieee-black">
const savedTheme = localStorage.getItem("theme"); <script>
document.documentElement.setAttribute("data-theme", savedTheme); // Initialize theme from IndexedDB
} import { initTheme } from "../scripts/database/initTheme";
</script> initTheme().catch((err) =>
</head> console.error("Error initializing theme:", err)
<InView /> );
<body class="w-full h-full m-0 bg-ieee-black"> </script>
<div class="text-white min-h-screen"> <div class="text-white min-h-screen">
<header class="sticky top-0 w-full z-[999]"> <header class="sticky top-0 w-full z-[999]">
<Navbar /> <Navbar />
</header> </header>
<main class="w-[95%] mx-auto"> <main class="w-[95%] mx-auto">
<slot /> <slot />
</main> </main>
<Footer /> <Footer />
</div> </div>
</body> </body>
</html> </html>

View file

@ -9,8 +9,10 @@ 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 { initTheme } from "../scripts/database/initTheme";
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";
import ThemeToggle from "../components/dashboard/universal/ThemeToggle";
const title = "Dashboard"; const title = "Dashboard";
@ -40,7 +42,7 @@ const components = Object.fromEntries(
--- ---
<!doctype html> <!doctype html>
<html lang="en" data-theme="dark"> <html lang="en">
<head> <head>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
@ -49,6 +51,10 @@ const components = Object.fromEntries(
<script <script
src="https://code.iconify.design/iconify-icon/2.3.0/iconify-icon.min.js" src="https://code.iconify.design/iconify-icon/2.3.0/iconify-icon.min.js"
></script> ></script>
<script is:inline>
// Set a default theme until IndexedDB loads
document.documentElement.setAttribute("data-theme", "dark");
</script>
</head> </head>
<body class="bg-base-200"> <body class="bg-base-200">
<!-- First Time Login Manager - This handles the onboarding popup for new users --> <!-- First Time Login Manager - This handles the onboarding popup for new users -->
@ -387,6 +393,7 @@ const components = Object.fromEntries(
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 { initTheme } from "../scripts/database/initTheme";
const auth = Authentication.getInstance(); const auth = Authentication.getInstance();
const get = Get.getInstance(); const get = Get.getInstance();
@ -397,6 +404,11 @@ const components = Object.fromEntries(
window.toast = () => {}; window.toast = () => {};
} }
// Initialize theme from IndexedDB
initTheme().catch((err) =>
console.error("Error initializing theme:", err)
);
// Initialize page state // Initialize page state
const pageLoadingState = const pageLoadingState =
document.getElementById("pageLoadingState"); document.getElementById("pageLoadingState");