Add authentication #17
5 changed files with 764 additions and 613 deletions
|
@ -7,12 +7,410 @@ import { Icon } from "astro-icon/components";
|
||||||
<h2 class="text-2xl font-bold">Events</h2>
|
<h2 class="text-2xl font-bold">Events</h2>
|
||||||
<p class="opacity-70">View and manage your IEEE UCSD events</p>
|
<p class="opacity-70">View and manage your IEEE UCSD events</p>
|
||||||
</div>
|
</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
|
<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-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform"
|
||||||
>
|
>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h3 class="card-title">Upcoming Events</h3>
|
<h3 class="card-title mb-4">Upcoming Events</h3>
|
||||||
<!-- Events content will go here -->
|
<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>
|
||||||
</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>
|
||||||
|
|
4
src/config/dashboard.yaml
Normal file
4
src/config/dashboard.yaml
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
officerRoles:
|
||||||
|
- "IEEE Officer"
|
||||||
|
- "IEEE Executive"
|
||||||
|
- "IEEE Administrator"
|
|
@ -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"
|
|
|
@ -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
|
|
|
@ -1,7 +1,5 @@
|
||||||
---
|
---
|
||||||
import yaml from "js-yaml";
|
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 { Icon } from "astro-icon/components";
|
||||||
import ProfileSection from "../components/dashboard/ProfileSection.astro";
|
import ProfileSection from "../components/dashboard/ProfileSection.astro";
|
||||||
import EventsSection from "../components/dashboard/EventsSection.astro";
|
import EventsSection from "../components/dashboard/EventsSection.astro";
|
||||||
|
@ -9,8 +7,6 @@ import ReimbursementSection from "../components/dashboard/ReimbursementSection.a
|
||||||
import SettingsSection from "../components/dashboard/SettingsSection.astro";
|
import SettingsSection from "../components/dashboard/SettingsSection.astro";
|
||||||
|
|
||||||
const title = "Dashboard";
|
const title = "Dashboard";
|
||||||
const config = yaml.load(profileConfig) as any;
|
|
||||||
const text = yaml.load(textConfig) as any;
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<!doctype html>
|
<!doctype html>
|
||||||
|
@ -39,10 +35,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
|
|
||||||
<!-- User Profile -->
|
<!-- User Profile -->
|
||||||
<div class="p-6 border-b border-base-200">
|
<div class="p-6 border-b border-base-200">
|
||||||
<div
|
<div class="flex items-center gap-4" id="userProfileSummary">
|
||||||
class="flex items-center gap-4"
|
|
||||||
id="userProfileSummary"
|
|
||||||
>
|
|
||||||
<div class="avatar flex items-center justify-center">
|
<div class="avatar flex items-center justify-center">
|
||||||
<div
|
<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"
|
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"
|
||||||
|
@ -54,9 +47,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<h3 class="font-medium text-lg" id="userName">
|
<h3 class="font-medium text-lg" id="userName">Loading...</h3>
|
||||||
Loading...
|
|
||||||
</h3>
|
|
||||||
<div
|
<div
|
||||||
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
|
class="badge badge-outline mt-1 border-[#06659d] text-[#06659d]"
|
||||||
id="userRole"
|
id="userRole"
|
||||||
|
@ -90,10 +81,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
||||||
data-section="events"
|
data-section="events"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon name="heroicons:calendar" class="h-5 w-5" />
|
||||||
name="heroicons:calendar"
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
Events
|
Events
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -102,10 +90,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
||||||
data-section="reimbursement"
|
data-section="reimbursement"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon name="heroicons:credit-card" class="h-5 w-5" />
|
||||||
name="heroicons:credit-card"
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
Reimbursement
|
Reimbursement
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -118,10 +103,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
class="dashboard-nav-btn gap-4 transition-all duration-200 outline-none focus:outline-none hover:bg-opacity-5"
|
||||||
data-section="settings"
|
data-section="settings"
|
||||||
>
|
>
|
||||||
<Icon
|
<Icon name="heroicons:cog-6-tooth" class="h-5 w-5" />
|
||||||
name="heroicons:cog-6-tooth"
|
|
||||||
class="h-5 w-5"
|
|
||||||
/>
|
|
||||||
Settings
|
Settings
|
||||||
</button>
|
</button>
|
||||||
</li>
|
</li>
|
||||||
|
@ -147,10 +129,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
<header class="bg-base-100 p-4 shadow-md lg:hidden">
|
<header class="bg-base-100 p-4 shadow-md lg:hidden">
|
||||||
<div class="flex items-center justify-between">
|
<div class="flex items-center justify-between">
|
||||||
<div class="flex items-center gap-2">
|
<div class="flex items-center gap-2">
|
||||||
<button
|
<button id="mobileSidebarToggle" class="btn btn-square btn-ghost">
|
||||||
id="mobileSidebarToggle"
|
|
||||||
class="btn btn-square btn-ghost"
|
|
||||||
>
|
|
||||||
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
<Icon name="heroicons:bars-3" class="h-6 w-6" />
|
||||||
</button>
|
</button>
|
||||||
<h1 class="text-xl font-bold">IEEE UCSD</h1>
|
<h1 class="text-xl font-bold">IEEE UCSD</h1>
|
||||||
|
@ -162,11 +141,8 @@ const text = yaml.load(textConfig) as any;
|
||||||
<div class="p-6 max-w-[1600px] mx-auto">
|
<div class="p-6 max-w-[1600px] mx-auto">
|
||||||
<!-- Loading State -->
|
<!-- Loading State -->
|
||||||
<div id="pageLoadingState" class="w-full">
|
<div id="pageLoadingState" class="w-full">
|
||||||
<div
|
<div class="flex flex-col items-center justify-center p-8">
|
||||||
class="flex flex-col items-center justify-center p-8"
|
<div class="loading loading-spinner loading-lg"></div>
|
||||||
>
|
|
||||||
<div class="loading loading-spinner loading-lg">
|
|
||||||
</div>
|
|
||||||
<p class="mt-4 opacity-70">Loading dashboard...</p>
|
<p class="mt-4 opacity-70">Loading dashboard...</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -210,12 +186,10 @@ const text = yaml.load(textConfig) as any;
|
||||||
Sign in to Access Dashboard
|
Sign in to Access Dashboard
|
||||||
</h2>
|
</h2>
|
||||||
<p class="opacity-70 mb-6">
|
<p class="opacity-70 mb-6">
|
||||||
Please sign in with your IEEE UCSD account
|
Please sign in with your IEEE UCSD account to access the
|
||||||
to access the dashboard.
|
dashboard.
|
||||||
</p>
|
</p>
|
||||||
<button
|
<button class="login-button btn btn-primary btn-lg gap-2">
|
||||||
class="login-button btn btn-primary btn-lg gap-2"
|
|
||||||
>
|
|
||||||
<svg
|
<svg
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
class="h-5 w-5"
|
class="h-5 w-5"
|
||||||
|
@ -259,7 +233,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
const pageLoadingState = document.getElementById("pageLoadingState");
|
const pageLoadingState = document.getElementById("pageLoadingState");
|
||||||
const pageErrorState = document.getElementById("pageErrorState");
|
const pageErrorState = document.getElementById("pageErrorState");
|
||||||
const notAuthenticatedState = document.getElementById(
|
const notAuthenticatedState = document.getElementById(
|
||||||
"notAuthenticatedState"
|
"notAuthenticatedState",
|
||||||
);
|
);
|
||||||
const mainContent = document.getElementById("mainContent");
|
const mainContent = document.getElementById("mainContent");
|
||||||
const sidebar = document.querySelector("aside");
|
const sidebar = document.querySelector("aside");
|
||||||
|
@ -321,7 +295,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
button.addEventListener("click", () => {
|
button.addEventListener("click", () => {
|
||||||
// Remove active class from all buttons
|
// Remove active class from all buttons
|
||||||
navButtons.forEach((btn) =>
|
navButtons.forEach((btn) =>
|
||||||
btn.classList.remove("active", "bg-base-200")
|
btn.classList.remove("active", "bg-base-200"),
|
||||||
);
|
);
|
||||||
// Add active class to clicked button
|
// Add active class to clicked button
|
||||||
button.classList.add("active", "bg-base-200");
|
button.classList.add("active", "bg-base-200");
|
||||||
|
@ -345,8 +319,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
try {
|
try {
|
||||||
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||||
if (pageErrorState) pageErrorState.classList.add("hidden");
|
if (pageErrorState) pageErrorState.classList.add("hidden");
|
||||||
if (notAuthenticatedState)
|
if (notAuthenticatedState) notAuthenticatedState.classList.add("hidden");
|
||||||
notAuthenticatedState.classList.add("hidden");
|
|
||||||
if (mainContent) mainContent.classList.add("hidden");
|
if (mainContent) mainContent.classList.add("hidden");
|
||||||
|
|
||||||
// Check authentication
|
// Check authentication
|
||||||
|
@ -381,8 +354,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
.querySelector(".login-button")
|
.querySelector(".login-button")
|
||||||
?.addEventListener("click", async () => {
|
?.addEventListener("click", async () => {
|
||||||
try {
|
try {
|
||||||
if (pageLoadingState)
|
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
|
||||||
pageLoadingState.classList.remove("hidden");
|
|
||||||
if (notAuthenticatedState)
|
if (notAuthenticatedState)
|
||||||
notAuthenticatedState.classList.add("hidden");
|
notAuthenticatedState.classList.add("hidden");
|
||||||
await auth.login();
|
await auth.login();
|
||||||
|
@ -415,7 +387,7 @@ const text = yaml.load(textConfig) as any;
|
||||||
"fixed",
|
"fixed",
|
||||||
"lg:relative",
|
"lg:relative",
|
||||||
"h-full",
|
"h-full",
|
||||||
"z-50"
|
"z-50",
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
Loading…
Reference in a new issue