fixed events check in

This commit is contained in:
chark1es 2025-02-10 15:33:04 -08:00
parent 0540caca8c
commit 2a1614edbc
5 changed files with 764 additions and 613 deletions

View file

@ -3,16 +3,414 @@ import { Icon } from "astro-icon/components";
---
<div id="eventsSection" class="dashboard-section hidden">
<div class="mb-6">
<h2 class="text-2xl font-bold">Events</h2>
<p class="opacity-70">View and manage your IEEE UCSD events</p>
</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"
>
<div class="card-body">
<h3 class="card-title">Upcoming Events</h3>
<!-- Events content will go here -->
<div class="mb-6">
<h2 class="text-2xl font-bold">Events</h2>
<p class="opacity-70">View and manage your IEEE UCSD events</p>
</div>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-6">
<!-- Event Check-in Card -->
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body">
<h3 class="card-title text-lg mb-4">Event Check-in</h3>
<div class="form-control w-full">
<label class="label">
<span class="label-text">Enter event code to check in</span>
</label>
<div class="flex gap-2">
<input
type="text"
placeholder="Enter code"
class="input input-bordered flex-1"
/>
<button class="btn btn-primary min-w-[90px]">Check In</button>
</div>
</div>
</div>
</div>
<!-- Event Registration Card -->
<div class="card bg-base-100 shadow-xl border border-base-200">
<div class="card-body">
<h3 class="card-title text-lg mb-4">Event Registration</h3>
<div class="form-control w-full">
<label class="label">
<span class="label-text">Select an event to register</span>
</label>
<div class="flex gap-2">
<select class="select select-bordered flex-1">
<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="btn btn-primary">Register</button>
</div>
</div>
</div>
</div>
</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"
>
<div class="card-body">
<h3 class="card-title mb-4">Upcoming Events</h3>
<div
id="eventsContainer"
class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6"
>
<!-- Events will be dynamically inserted here -->
</div>
</div>
</div>
</div>
<script>
import { Get } from "../pocketbase/Get";
import { Authentication } from "../pocketbase/Authentication";
import { Update } from "../pocketbase/Update";
import { SendLog } from "../pocketbase/SendLog";
// Toast management system
const createToast = (
message: string,
type: "success" | "error" | "warning" = "success",
) => {
let toastContainer = document.querySelector(".toast-container");
if (!toastContainer) {
toastContainer = document.createElement("div");
toastContainer.className = "toast-container fixed bottom-4 right-4 z-50";
document.body.appendChild(toastContainer);
}
const existingToasts = document.querySelectorAll(".toast-container .toast");
if (existingToasts.length >= 2) {
const oldestToast = existingToasts[0];
oldestToast.classList.add("toast-exit");
setTimeout(() => oldestToast.remove(), 150);
}
// Update positions of existing toasts
existingToasts.forEach((t) => {
const toast = t as HTMLElement;
const currentIndex = parseInt(toast.getAttribute("data-index") || "0");
toast.setAttribute("data-index", (currentIndex + 1).toString());
});
const toast = document.createElement("div");
toast.className = "toast translate-x-full";
toast.setAttribute("data-index", "0");
toast.innerHTML = `
<div class="alert alert-${type} shadow-lg min-w-[300px]">
<span>${message}</span>
</div>
`;
toastContainer.appendChild(toast);
// Force a reflow to ensure the animation triggers
toast.offsetHeight;
// Add the transition class and remove transform
toast.classList.add("transition-all", "duration-300", "ease-out");
requestAnimationFrame(() => {
toast.classList.remove("translate-x-full");
});
// Setup exit animation
setTimeout(() => {
toast.classList.add("toast-exit");
setTimeout(() => toast.remove(), 150);
}, 3000);
};
// Add styles to the document
const style = document.createElement("style");
style.textContent = `
.toast-container {
display: flex;
flex-direction: column;
pointer-events: none;
}
.toast {
pointer-events: auto;
transform: translateX(0);
transition: all 0.15s cubic-bezier(0.4, 0, 0.2, 1);
position: relative;
}
.toast-exit {
transform: translateX(100%);
opacity: 0;
}
.toast.translate-x-full {
transform: translateX(100%);
}
.toast-container .toast {
transform: translateY(calc((1 - attr(data-index number)) * -0.25rem));
}
.toast-container .toast[data-index="0"] {
transform: translateY(0);
}
.toast-container .toast[data-index="1"] {
transform: translateY(-0.025rem);
}
`;
document.head.appendChild(style);
interface Event {
id: string;
event_id: string;
event_name: string;
event_code: string;
location: string;
files: string[];
points_to_reward: number;
attendees: string[];
start_date: string;
end_date: string;
description: string;
}
async function handleEventCheckIn(eventCode: string) {
try {
const get = Get.getInstance();
const auth = Authentication.getInstance();
const update = Update.getInstance();
const logger = SendLog.getInstance();
const currentUser = auth.getCurrentUser();
if (!currentUser) {
throw new Error("You must be logged in to check in to events");
}
// Find the event with the given code
const events = await get.getFirst<Event>(
"events",
`event_code = "${eventCode}"`,
);
if (!events) {
throw new Error("Invalid event code");
}
const event = events;
// Check if user is already checked in
if (event.attendees.includes(currentUser.id)) {
throw new Error("You have already checked in to this event");
}
// Check if the event hasn't ended yet
const eventEndDate = new Date(event.end_date);
if (eventEndDate < new Date()) {
throw new Error("This event has already ended");
}
// Add user to attendees
const updatedAttendees = [...event.attendees, currentUser.id];
await update.updateField(
"events",
event.id,
"attendees",
updatedAttendees,
);
// Award points to user if available
if (event.points_to_reward > 0) {
const userPoints = currentUser.points || 0;
await update.updateField(
"users",
currentUser.id,
"points",
userPoints + event.points_to_reward,
);
// Log the points award
await logger.send(
"update",
"event check-in",
`Awarded ${event.points_to_reward} points for checking in to ${event.event_name}`,
);
}
// Show success message with points if awarded
createToast(
`Successfully checked in to ${event.event_name}${
event.points_to_reward > 0
? ` (+${event.points_to_reward} points!)`
: ""
}`,
"success",
);
} catch (error: any) {
// Show error message
createToast(error?.message || "Failed to check in to event", "error");
}
}
// Add event listener to check-in button
document.addEventListener("DOMContentLoaded", () => {
const checkInForm = document.querySelector(".form-control");
const checkInInput = checkInForm?.querySelector("input");
const checkInButton = checkInForm?.querySelector("button");
if (checkInForm && checkInInput && checkInButton) {
checkInButton.addEventListener("click", async () => {
const eventCode = checkInInput.value.trim();
if (!eventCode) {
createToast("Please enter an event code", "warning");
return;
}
checkInButton.classList.add("btn-disabled");
const loadingSpinner = document.createElement("span");
loadingSpinner.className = "loading loading-spinner loading-xs";
const originalText = checkInButton.textContent;
checkInButton.textContent = "";
checkInButton.appendChild(loadingSpinner);
await handleEventCheckIn(eventCode);
checkInButton.classList.remove("btn-disabled");
checkInButton.removeChild(loadingSpinner);
checkInButton.textContent = originalText;
checkInInput.value = "";
});
// Allow Enter key to submit
checkInInput.addEventListener("keypress", async (e) => {
if (e.key === "Enter") {
e.preventDefault();
checkInButton.click();
}
});
}
});
async function loadEvents() {
try {
// Show skeletons first
const eventsContainer = document.getElementById("eventsContainer");
if (!eventsContainer) return;
// Add 6 skeleton cards initially
for (let i = 0; i < 6; i++) {
const skeletonCard = document.createElement("div");
skeletonCard.className = "card bg-base-200 shadow-lg animate-pulse";
skeletonCard.innerHTML = `
<div class="card-body p-5">
<div class="flex flex-col h-full">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1">
<div class="skeleton h-6 w-3/4 mb-2"></div>
<div class="flex items-center gap-2">
<div class="skeleton h-5 w-16"></div>
<div class="skeleton h-5 w-20"></div>
</div>
</div>
<div class="flex flex-col items-end">
<div class="skeleton h-5 w-24 mb-1"></div>
<div class="skeleton h-4 w-16"></div>
</div>
</div>
<div class="skeleton h-4 w-full mb-3"></div>
<div class="flex items-center gap-2">
<div class="skeleton h-4 w-4"></div>
<div class="skeleton h-4 w-1/2"></div>
</div>
</div>
</div>
`;
eventsContainer.appendChild(skeletonCard);
}
const get = Get.getInstance();
const events = await get.getAll<Event>(
"events",
undefined,
"-start_date",
); // Sort by start date descending
// Clear skeletons
eventsContainer.innerHTML = "";
events.forEach((event) => {
const startDate = new Date(event.start_date);
const endDate = new Date(event.end_date);
const card = document.createElement("div");
card.className =
"card bg-base-200 shadow-lg hover:shadow-xl transition-all duration-300 relative overflow-hidden";
card.innerHTML = `
<div class="card-body p-5">
<div class="flex flex-col h-full">
<div class="flex items-start justify-between gap-3 mb-2">
<div class="flex-1">
<h3 class="card-title text-lg font-semibold mb-1 line-clamp-2">${event.event_name}</h3>
<div class="flex items-center gap-2 text-sm text-base-content/70">
<div class="badge badge-primary badge-sm">${event.points_to_reward} pts</div>
<span>•</span>
<span class="badge badge-sm badge-outline">${event.event_code}</span>
</div>
</div>
<div class="text-right shrink-0 text-base-content/80">
<div class="text-sm font-medium">
${startDate.toLocaleDateString(
"en-US",
{
weekday: "short",
month: "short",
day: "numeric",
},
)}
</div>
<div class="text-xs mt-0.5 opacity-75">
${startDate.toLocaleTimeString(
"en-US",
{
hour: "numeric",
minute: "2-digit",
},
)}
</div>
</div>
</div>
<div class="text-sm text-base-content/70 mb-3 line-clamp-2">
${event.description || "No description available"}
</div>
<div class="space-y-3 flex-1">
<div class="flex items-start gap-2.5 text-base-content/80">
<Icon name="mdi:map-marker" class="w-4 h-4 text-primary shrink-0 mt-0.5" />
<span class="text-sm leading-tight">${event.location}</span>
</div>
</div>
</div>
</div>
`;
eventsContainer.appendChild(card);
});
} catch (error) {
console.error("Failed to load events:", error);
// You might want to show an error message to the user here
}
}
// Load events when the section becomes visible
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
loadEvents();
observer.disconnect(); // Only load once
}
});
});
const eventsSection = document.getElementById("eventsSection");
if (eventsSection) {
observer.observe(eventsSection);
}
</script>

View file

@ -0,0 +1,4 @@
officerRoles:
- "IEEE Officer"
- "IEEE Executive"
- "IEEE Administrator"

View file

@ -1,130 +0,0 @@
roles:
administrator:
name: IEEE Administrator
badge: badge-warning
permissions: [view, edit, manage]
officer:
name: IEEE Officer
badge: badge-info
permissions: [view, edit]
sponsor:
name: IEEE Sponsor
badge: badge-warning
permissions: [view]
member:
name: Regular Member
badge: badge-neutral
permissions: [self]
resume:
allowedTypes: [.pdf, .doc, .docx]
maxSize: 5242880 # 5MB in bytes
viewer:
width: w-11/12
maxWidth: max-w-5xl
height: h-[80vh]
ui:
transitions:
fadeDelay: 50
messages:
memberId:
saving: Saving member ID...
success: IEEE Member ID saved successfully!
error: Failed to save IEEE Member ID. Please try again.
messageTimeout: 3000
resume:
uploading: Uploading resume...
success: Resume uploaded successfully!
error: Failed to upload resume. Please try again.
deleting: Deleting resume...
deleteSuccess: Resume deleted successfully!
deleteError: Failed to delete resume. Please try again.
messageTimeout: 3000
event:
saving: Saving event...
success: Event saved successfully!
error: Failed to save event. Please try again.
deleting: Deleting event...
deleteSuccess: Event deleted successfully!
deleteError: Failed to delete event. Please try again.
messageTimeout: 3000
checkIn:
checking: Checking event code...
success: Successfully checked in to event!
error: Failed to check in. Please try again.
invalid: Invalid event code. Please try again.
expired: This event is not currently active.
alreadyCheckedIn: You have already checked in to this event.
messageTimeout: 3000
auth:
loginError: Failed to start authentication
notSignedIn: Not signed in
notVerified: Not verified
notProvided: Not provided
notAvailable: Not available
never: Never
tables:
events:
title: Event Management
editor_title: Event Details
columns:
event_name: Event Name
event_id: Event ID
event_code: Event Code
start_date: Start Date
end_date: End Date
points_to_reward: Points to Reward
location: Location
registered_users: Registered
actions: Actions
form:
event_id:
label: Event ID
placeholder: Enter unique event ID
event_name:
label: Event Name
placeholder: Enter event name
event_code:
label: Event Code
placeholder: Enter check-in code
start_date:
date_label: Start Date for check-in
time_label: Start Time for check-in
date_placeholder: Select start date for check-in
time_placeholder: Select start time for check-in
end_date:
date_label: End Date for check-in
time_label: End Time for check-in
date_placeholder: Select end date for check-in
time_placeholder: Select end time for check-in
points_to_reward:
label: Points to Reward
placeholder: Enter points value
location:
label: Location
placeholder: Enter event location
files:
label: Event Files
help_text: Upload event-related files (PDF, DOC, DOCX, TXT, JPG, JPEG, PNG)
buttons:
save: Save
cancel: Cancel
edit: Edit
delete: Delete
defaults:
pageSize: 50
sortField: -updated
autoDetection:
officer:
emailDomain: "@ieeeucsd.org"

View file

@ -1,93 +0,0 @@
ui:
transitions:
fadeDelay: 50
messages:
memberId:
saving: Saving member ID...
success: IEEE Member ID saved successfully!
error: Failed to save IEEE Member ID. Please try again.
messageTimeout: 3000
resume:
uploading: Uploading resume...
success: Resume uploaded successfully!
error: Failed to upload resume. Please try again.
deleting: Deleting resume...
deleteSuccess: Resume deleted successfully!
deleteError: Failed to delete resume. Please try again.
messageTimeout: 3000
event:
saving: Saving event...
success: Event saved successfully!
error: Failed to save event. Please try again.
deleting: Deleting event...
deleteSuccess: Event deleted successfully!
deleteError: Failed to delete event. Please try again.
messageTimeout: 3000
checkIn:
checking: Checking event code...
success: Successfully checked in to event!
error: Failed to check in. Please try again.
invalid: Invalid event code. Please try again.
expired: This event is not currently active.
alreadyCheckedIn: You have already checked in to this event.
messageTimeout: 3000
auth:
loginError: Failed to start authentication
notSignedIn: Not signed in
notVerified: Not verified
notProvided: Not provided
notAvailable: Not available
never: Never
tables:
events:
title: Event Management
editor_title: Event Details
columns:
event_name: Event Name
event_id: Event ID
event_code: Event Code
start_date: Start Date
end_date: End Date
points_to_reward: Points to Reward
location: Location
registered_users: Registered
actions: Actions
form:
event_id:
label: Event ID
placeholder: Enter unique event ID
event_name:
label: Event Name
placeholder: Enter event name
event_code:
label: Event Code
placeholder: Enter check-in code
start_date:
date_label: Start Date for check-in
time_label: Start Time for check-in
date_placeholder: Select start date for check-in
time_placeholder: Select start time for check-in
end_date:
date_label: End Date for check-in
time_label: End Time for check-in
date_placeholder: Select end date for check-in
time_placeholder: Select end time for check-in
points_to_reward:
label: Points to Reward
placeholder: Enter points value
location:
label: Location
placeholder: Enter event location
files:
label: Event Files
help_text: Upload event-related files (PDF, DOC, DOCX, TXT, JPG, JPEG, PNG)
buttons:
save: Save
cancel: Cancel
edit: Edit
delete: Delete

View file

@ -1,7 +1,5 @@
---
import yaml from "js-yaml";
import profileConfig from "../config/profileConfig.yaml?raw";
import textConfig from "../config/text.yml?raw";
import { Icon } from "astro-icon/components";
import ProfileSection from "../components/dashboard/ProfileSection.astro";
import EventsSection from "../components/dashboard/EventsSection.astro";
@ -9,413 +7,387 @@ import ReimbursementSection from "../components/dashboard/ReimbursementSection.a
import SettingsSection from "../components/dashboard/SettingsSection.astro";
const title = "Dashboard";
const config = yaml.load(profileConfig) as any;
const text = yaml.load(textConfig) as any;
---
<!doctype html>
<html lang="en" data-theme="dark">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title} | IEEE UCSD</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body class="bg-base-200">
<div class="flex h-screen">
<!-- Sidebar -->
<aside
class="bg-base-100 w-80 flex flex-col shadow-xl border-r border-base-200 transition-all duration-300"
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>{title} | IEEE UCSD</title>
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
</head>
<body class="bg-base-200">
<div class="flex h-screen">
<!-- Sidebar -->
<aside
class="bg-base-100 w-80 flex flex-col shadow-xl border-r border-base-200 transition-all duration-300"
>
<!-- Logo -->
<div class="p-6 border-b border-base-200">
<div class="flex items-center justify-center">
<span
class="text-4xl font-bold text-[#06659d] select-none tracking-wide"
>IEEEUCSD</span
>
<!-- Logo -->
<div class="p-6 border-b border-base-200">
<div class="flex items-center justify-center">
<span
class="text-4xl font-bold text-[#06659d] select-none tracking-wide"
>IEEEUCSD</span
>
</div>
</div>
<!-- User Profile -->
<div class="p-6 border-b border-base-200">
<div
class="flex items-center gap-4"
id="userProfileSummary"
>
<div class="avatar flex items-center justify-center">
<div
class="w-12 h-12 rounded-xl bg-[#06659d] text-white ring ring-base-200 ring-offset-base-100 ring-offset-2 inline-flex items-center justify-center"
>
<span
class="text-xl font-semibold select-none inline-flex items-center justify-center w-full h-full"
id="userInitials">?</span
>
</div>
</div>
<div>
<h3 class="font-medium text-lg" id="userName">
Loading...
</h3>
<div
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
id="userRole"
>
Member
</div>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="flex-1 overflow-y-auto scrollbar-hide py-6">
<ul class="menu gap-2 px-4 text-base-content/80">
<li class="menu-title font-medium opacity-70">
<span>Main Menu</span>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 active:bg-base-200 active:font-medium active:text-primary"
data-section="profile"
>
<Icon
name="heroicons:home"
class="h-5 w-5 group-[.active]:text-primary"
/>
Dashboard
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="events"
>
<Icon
name="heroicons:calendar"
class="h-5 w-5"
/>
Events
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="reimbursement"
>
<Icon
name="heroicons:credit-card"
class="h-5 w-5"
/>
Reimbursement
</button>
</li>
<li class="menu-title mt-6 font-medium opacity-70">
<span>Account</span>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="settings"
>
<Icon
name="heroicons:cog-6-tooth"
class="h-5 w-5"
/>
Settings
</button>
</li>
<li>
<button
id="logoutButton"
class="gap-4 text-error hover:bg-error/10 transition-all duration-200 outline-none focus:outline-none"
>
<Icon
name="heroicons:arrow-right-on-rectangle"
class="h-5 w-5"
/>
Logout
</button>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200">
<!-- Mobile Header -->
<header class="bg-base-100 p-4 shadow-md lg:hidden">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<button
id="mobileSidebarToggle"
class="btn btn-square btn-ghost"
>
<Icon name="heroicons:bars-3" class="h-6 w-6" />
</button>
<h1 class="text-xl font-bold">IEEE UCSD</h1>
</div>
</div>
</header>
<!-- Page Content -->
<div class="p-6 max-w-[1600px] mx-auto">
<!-- Loading State -->
<div id="pageLoadingState" class="w-full">
<div
class="flex flex-col items-center justify-center p-8"
>
<div class="loading loading-spinner loading-lg">
</div>
<p class="mt-4 opacity-70">Loading dashboard...</p>
</div>
</div>
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"></path>
</svg>
<span>Failed to load dashboard content</span>
</div>
</div>
<!-- Not Authenticated State -->
<div id="notAuthenticatedState" class="hidden w-full">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 opacity-30"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
</div>
<h2 class="card-title text-2xl mb-2">
Sign in to Access Dashboard
</h2>
<p class="opacity-70 mb-6">
Please sign in with your IEEE UCSD account
to access the dashboard.
</p>
<button
class="login-button btn btn-primary btn-lg gap-2"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
clip-rule="evenodd"></path>
</svg>
Sign in with IEEEUCSD SSO
</button>
</div>
</div>
</div>
<!-- Main Content -->
<div id="mainContent" class="hidden">
<ProfileSection />
<EventsSection />
<ReimbursementSection />
<SettingsSection />
</div>
</div>
</main>
</div>
</div>
</body>
<!-- User Profile -->
<div class="p-6 border-b border-base-200">
<div class="flex items-center gap-4" id="userProfileSummary">
<div class="avatar flex items-center justify-center">
<div
class="w-12 h-12 rounded-xl bg-[#06659d] text-white ring ring-base-200 ring-offset-base-100 ring-offset-2 inline-flex items-center justify-center"
>
<span
class="text-xl font-semibold select-none inline-flex items-center justify-center w-full h-full"
id="userInitials">?</span
>
</div>
</div>
<div>
<h3 class="font-medium text-lg" id="userName">Loading...</h3>
<div
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
id="userRole"
>
Member
</div>
</div>
</div>
</div>
<!-- Navigation -->
<nav class="flex-1 overflow-y-auto scrollbar-hide py-6">
<ul class="menu gap-2 px-4 text-base-content/80">
<li class="menu-title font-medium opacity-70">
<span>Main Menu</span>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5 active:bg-base-200 active:font-medium active:text-primary"
data-section="profile"
>
<Icon
name="heroicons:home"
class="h-5 w-5 group-[.active]:text-primary"
/>
Dashboard
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="events"
>
<Icon name="heroicons:calendar" class="h-5 w-5" />
Events
</button>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="reimbursement"
>
<Icon name="heroicons:credit-card" class="h-5 w-5" />
Reimbursement
</button>
</li>
<li class="menu-title mt-6 font-medium opacity-70">
<span>Account</span>
</li>
<li>
<button
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
data-section="settings"
>
<Icon name="heroicons:cog-6-tooth" class="h-5 w-5" />
Settings
</button>
</li>
<li>
<button
id="logoutButton"
class="gap-4 text-error hover:bg-error/10 transition-all duration-200 outline-none focus:outline-none"
>
<Icon
name="heroicons:arrow-right-on-rectangle"
class="h-5 w-5"
/>
Logout
</button>
</li>
</ul>
</nav>
</aside>
<!-- Main Content -->
<main class="flex-1 overflow-x-hidden overflow-y-auto bg-base-200">
<!-- Mobile Header -->
<header class="bg-base-100 p-4 shadow-md lg:hidden">
<div class="flex items-center justify-between">
<div class="flex items-center gap-2">
<button id="mobileSidebarToggle" class="btn btn-square btn-ghost">
<Icon name="heroicons:bars-3" class="h-6 w-6" />
</button>
<h1 class="text-xl font-bold">IEEE UCSD</h1>
</div>
</div>
</header>
<!-- Page Content -->
<div class="p-6 max-w-[1600px] mx-auto">
<!-- Loading State -->
<div id="pageLoadingState" class="w-full">
<div class="flex flex-col items-center justify-center p-8">
<div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 opacity-70">Loading dashboard...</p>
</div>
</div>
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-6 w-6"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7 4a1 1 0 11-2 0 1 1 0 012 0zm-1-9a1 1 0 00-1 1v4a1 1 0 102 0V6a1 1 0 00-1-1z"
clip-rule="evenodd"></path>
</svg>
<span>Failed to load dashboard content</span>
</div>
</div>
<!-- Not Authenticated State -->
<div id="notAuthenticatedState" class="hidden w-full">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 opacity-30"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
</div>
<h2 class="card-title text-2xl mb-2">
Sign in to Access Dashboard
</h2>
<p class="opacity-70 mb-6">
Please sign in with your IEEE UCSD account to access the
dashboard.
</p>
<button class="login-button btn btn-primary btn-lg gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M3 3a1 1 0 011 1v12a1 1 0 11-2 0V4a1 1 0 011-1zm7.707 3.293a1 1 0 010 1.414L9.414 9H17a1 1 0 110 2H9.414l1.293 1.293a1 1 0 01-1.414 1.414l-3-3a1 1 0 010-1.414l3-3a1 1 0 011.414 0z"
clip-rule="evenodd"></path>
</svg>
Sign in with IEEEUCSD SSO
</button>
</div>
</div>
</div>
<!-- Main Content -->
<div id="mainContent" class="hidden">
<ProfileSection />
<EventsSection />
<ReimbursementSection />
<SettingsSection />
</div>
</div>
</main>
</div>
</body>
</html>
<script>
import { Authentication } from "../components/pocketbase/Authentication";
import { Get } from "../components/pocketbase/Get";
import { SendLog } from "../components/pocketbase/SendLog";
import { Authentication } from "../components/pocketbase/Authentication";
import { Get } from "../components/pocketbase/Get";
import { SendLog } from "../components/pocketbase/SendLog";
const auth = Authentication.getInstance();
const get = Get.getInstance();
const logger = SendLog.getInstance();
const auth = Authentication.getInstance();
const get = Get.getInstance();
const logger = SendLog.getInstance();
// Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById(
"notAuthenticatedState"
);
const mainContent = document.getElementById("mainContent");
const sidebar = document.querySelector("aside");
// Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById(
"notAuthenticatedState",
);
const mainContent = document.getElementById("mainContent");
const sidebar = document.querySelector("aside");
// User profile elements
const userInitials = document.getElementById("userInitials");
const userName = document.getElementById("userName");
const userRole = document.getElementById("userRole");
// User profile elements
const userInitials = document.getElementById("userInitials");
const userName = document.getElementById("userName");
const userRole = document.getElementById("userRole");
// Display user profile information
const updateUserProfile = async (user: any) => {
if (!user) return;
// Display user profile information
const updateUserProfile = async (user: any) => {
if (!user) return;
try {
// Get user information including member type
const extendedUser = await get.getOne("users", user.id, {
fields: ["id", "name", "member_type", "expand.member_type"],
});
try {
// Get user information including member type
const extendedUser = await get.getOne("users", user.id, {
fields: ["id", "name", "member_type", "expand.member_type"],
});
// Update display elements
if (userName) {
userName.textContent = extendedUser.name || "Unknown User";
}
// Update display elements
if (userName) {
userName.textContent = extendedUser.name || "Unknown User";
}
if (userRole) {
userRole.textContent = extendedUser.member_type || "Member";
}
if (userRole) {
userRole.textContent = extendedUser.member_type || "Member";
}
if (userInitials) {
const initials = (extendedUser.name || "U")
.split(" ")
.map((n: string) => n[0])
.join("")
.toUpperCase();
userInitials.textContent = initials;
}
} catch (error) {
console.error("Error fetching user profile:", error);
if (userName) userName.textContent = "Unknown User";
if (userRole) userRole.textContent = "Member";
if (userInitials) userInitials.textContent = "?";
}
};
// Mobile sidebar toggle
const mobileSidebarToggle = document.getElementById("mobileSidebarToggle");
if (mobileSidebarToggle && sidebar) {
mobileSidebarToggle.addEventListener("click", () => {
sidebar.classList.toggle("-translate-x-full");
});
if (userInitials) {
const initials = (extendedUser.name || "U")
.split(" ")
.map((n: string) => n[0])
.join("")
.toUpperCase();
userInitials.textContent = initials;
}
} catch (error) {
console.error("Error fetching user profile:", error);
if (userName) userName.textContent = "Unknown User";
if (userRole) userRole.textContent = "Member";
if (userInitials) userInitials.textContent = "?";
}
};
// Handle navigation
const handleNavigation = () => {
const navButtons = document.querySelectorAll(".dashboard-nav-btn");
const sections = document.querySelectorAll(".dashboard-section");
// Mobile sidebar toggle
const mobileSidebarToggle = document.getElementById("mobileSidebarToggle");
if (mobileSidebarToggle && sidebar) {
mobileSidebarToggle.addEventListener("click", () => {
sidebar.classList.toggle("-translate-x-full");
});
}
navButtons.forEach((button) => {
button.addEventListener("click", () => {
// Remove active class from all buttons
navButtons.forEach((btn) =>
btn.classList.remove("active", "bg-base-200")
);
// Add active class to clicked button
button.classList.add("active", "bg-base-200");
// Handle navigation
const handleNavigation = () => {
const navButtons = document.querySelectorAll(".dashboard-nav-btn");
const sections = document.querySelectorAll(".dashboard-section");
// Hide all sections
sections.forEach((section) => section.classList.add("hidden"));
// Show selected section
const sectionId = `${button.getAttribute("data-section")}Section`;
document.getElementById(sectionId)?.classList.remove("hidden");
navButtons.forEach((button) => {
button.addEventListener("click", () => {
// Remove active class from all buttons
navButtons.forEach((btn) =>
btn.classList.remove("active", "bg-base-200"),
);
// Add active class to clicked button
button.classList.add("active", "bg-base-200");
// Close sidebar on mobile after selection
if (window.innerWidth < 1024 && sidebar) {
sidebar.classList.add("-translate-x-full");
}
});
});
};
// Hide all sections
sections.forEach((section) => section.classList.add("hidden"));
// Show selected section
const sectionId = `${button.getAttribute("data-section")}Section`;
document.getElementById(sectionId)?.classList.remove("hidden");
// Initialize page
const initializePage = async () => {
try {
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Check authentication
if (!auth.isAuthenticated()) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
return;
}
const user = auth.getCurrentUser();
await updateUserProfile(user);
// Initialize navigation
handleNavigation();
// Show main content
if (mainContent) mainContent.classList.remove("hidden");
if (pageLoadingState) pageLoadingState.classList.add("hidden");
} catch (error) {
console.error("Error initializing dashboard:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
// Close sidebar on mobile after selection
if (window.innerWidth < 1024 && sidebar) {
sidebar.classList.add("-translate-x-full");
}
};
});
});
};
// Initialize when DOM is loaded
document.addEventListener("DOMContentLoaded", initializePage);
// Initialize page
const initializePage = async () => {
try {
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState) notAuthenticatedState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Handle login button click
document
.querySelector(".login-button")
?.addEventListener("click", async () => {
try {
if (pageLoadingState)
pageLoadingState.classList.remove("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
await auth.login();
} catch (error) {
console.error("Login error:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
}
});
// Check authentication
if (!auth.isAuthenticated()) {
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
return;
}
// Handle logout button click
document.getElementById("logoutButton")?.addEventListener("click", () => {
auth.logout();
window.location.reload();
const user = auth.getCurrentUser();
await updateUserProfile(user);
// Initialize navigation
handleNavigation();
// Show main content
if (mainContent) mainContent.classList.remove("hidden");
if (pageLoadingState) pageLoadingState.classList.add("hidden");
} catch (error) {
console.error("Error initializing dashboard:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
}
};
// Initialize when DOM is loaded
document.addEventListener("DOMContentLoaded", initializePage);
// Handle login button click
document
.querySelector(".login-button")
?.addEventListener("click", async () => {
try {
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
await auth.login();
} catch (error) {
console.error("Login error:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
}
});
// Handle responsive sidebar
if (sidebar) {
// Hide sidebar by default on mobile
if (window.innerWidth < 1024) {
sidebar.classList.add("-translate-x-full");
}
// Handle logout button click
document.getElementById("logoutButton")?.addEventListener("click", () => {
auth.logout();
window.location.reload();
});
// Add transition class
sidebar.classList.add(
"transition-transform",
"duration-300",
"ease-in-out",
"lg:translate-x-0",
"fixed",
"lg:relative",
"h-full",
"z-50"
);
// Handle responsive sidebar
if (sidebar) {
// Hide sidebar by default on mobile
if (window.innerWidth < 1024) {
sidebar.classList.add("-translate-x-full");
}
// Add transition class
sidebar.classList.add(
"transition-transform",
"duration-300",
"ease-in-out",
"lg:translate-x-0",
"fixed",
"lg:relative",
"h-full",
"z-50",
);
}
</script>