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

@ -23,12 +23,13 @@ import EventLoad from "./EventsSection/EventLoad";
<!-- Event Registration Card --> <!-- Event Registration Card -->
<div class="w-full"> <div class="w-full">
<div <div
class="card bg-base-100 shadow-xl border border-base-200 opacity-50 cursor-not-allowed relative group h-full" class="card bg-card shadow-xl border border-border opacity-50 cursor-not-allowed relative group h-full"
> >
<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-card 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-card-foreground font-medium text-sm sm:text-base"
>Coming Soon</span >Coming Soon</span
> >
</div> </div>
@ -36,24 +37,28 @@ import EventLoad from "./EventsSection/EventLoad";
<h3 class="card-title text-base sm:text-lg mb-3 sm:mb-4"> <h3 class="card-title text-base sm:text-lg mb-3 sm:mb-4">
Event Registration Event Registration
</h3> </h3>
<div class="form-control w-full"> <div class="w-full">
<label class="label"> <label class="block text-sm sm:text-base mb-2">
<span class="label-text text-sm sm:text-base" <span class="text-sm sm:text-base"
>Select an event to register</span >Select an event to register</span
> >
</label> </label>
<div class="flex flex-col sm:flex-row gap-2"> <div class="flex flex-col sm:flex-row gap-2">
<select <select
class="select select-bordered flex-1 text-sm sm:text-base h-10 min-h-[2.5rem] w-full" 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 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
class="btn btn-primary text-sm sm:text-base h-10 min-h-[2.5rem] w-full sm:w-[100px]" 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 disabled>Register</button
> >
</div> </div>
@ -89,8 +94,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 +130,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} />
@ -203,7 +210,7 @@ import EventLoad from "./EventsSection/EventLoad";
if (!filename || typeof filename !== "string") { if (!filename || typeof filename !== "string") {
console.error( console.error(
"Invalid filename provided to previewFileEvents:", "Invalid filename provided to previewFileEvents:",
filename, filename
); );
toast.error("Cannot preview file: Invalid filename"); toast.error("Cannot preview file: Invalid filename");
return; return;
@ -211,7 +218,10 @@ import EventLoad from "./EventsSection/EventLoad";
// Ensure URL is properly formatted // Ensure URL is properly formatted
if (!url.startsWith("http")) { if (!url.startsWith("http")) {
console.warn("URL doesn't start with http, attempting to fix:", url); console.warn(
"URL doesn't start with http, attempting to fix:",
url
);
if (url.startsWith("/")) { if (url.startsWith("/")) {
url = `https://pocketbase.ieeeucsd.org${url}`; url = `https://pocketbase.ieeeucsd.org${url}`;
} else { } else {
@ -221,7 +231,7 @@ import EventLoad from "./EventsSection/EventLoad";
} }
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");
@ -260,7 +270,7 @@ import EventLoad from "./EventsSection/EventLoad";
background: "#FFC107", background: "#FFC107",
color: "#000", color: "#000",
}, },
}, }
); );
} }
}) })
@ -276,7 +286,7 @@ import EventLoad from "./EventsSection/EventLoad";
window.dispatchEvent( window.dispatchEvent(
new CustomEvent("filePreviewStateChange", { new CustomEvent("filePreviewStateChange", {
detail: { url, filename }, detail: { url, filename },
}), })
); );
}); });
@ -296,7 +306,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");
@ -314,7 +324,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
@ -346,10 +356,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
@ -373,7 +383,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;
@ -432,7 +446,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;
@ -487,7 +501,7 @@ import EventLoad from "./EventsSection/EventLoad";
} catch (error: any) { } catch (error: any) {
console.error("Failed to download files:", error); console.error("Failed to download files:", error);
toast.error( toast.error(
error?.message || "Failed to download files. Please try again.", error?.message || "Failed to download files. Please try again."
); );
} finally { } finally {
// Reset button state // Reset button state
@ -499,7 +513,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

@ -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,10 +2,11 @@
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" />
@ -16,19 +17,19 @@ import InView from "../components/core/InView.astro";
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")) {
localStorage.setItem("theme", "dark");
document.documentElement.setAttribute("data-theme", "dark"); document.documentElement.setAttribute("data-theme", "dark");
} else {
// Apply saved theme
const savedTheme = localStorage.getItem("theme");
document.documentElement.setAttribute("data-theme", savedTheme);
}
</script> </script>
</head> </head>
<InView /> <InView />
<body class="w-full h-full m-0 bg-ieee-black"> <body class="w-full h-full m-0 bg-ieee-black">
<script>
// Initialize theme from IndexedDB
import { initTheme } from "../scripts/database/initTheme";
initTheme().catch((err) =>
console.error("Error initializing theme:", err)
);
</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 />

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");