new profile look!

This commit is contained in:
chark1es 2025-02-01 01:20:57 -08:00
parent 7cd50bea6c
commit eea944fcd2
4 changed files with 1008 additions and 1068 deletions

File diff suppressed because it is too large Load diff

View file

@ -1,225 +1,261 @@
<div> <div>
<!-- Loading Skeleton (shown by default) --> <!-- Loading Skeleton -->
<div id="loadingSkeleton" class="card bg-base-200 shadow-xl"> <div id="loadingSkeleton" class="card bg-base-100 shadow-xl">
<div class="card-body p-0"> <div class="card-body p-6">
<div class="px-6 pt-6"> <!-- Avatar and Name Section -->
<!-- Title --> <div class="flex flex-col items-center mb-6">
<h2 class="skeleton h-8 w-40 card-title"></h2> <div class="skeleton w-24 h-24 rounded-full mb-4"></div>
<div class="skeleton h-8 w-48"></div>
<div class="skeleton h-4 w-32 mt-2"></div>
</div> </div>
<!-- Content -->
<div class="px-6 pb-6">
<div class="space-y-3">
<!-- Name -->
<div class="space-y-1">
<div class="skeleton h-3 w-16 opacity-70"></div>
<div class="skeleton h-[1.75rem] w-48"></div>
</div>
<div class="divider my-0.5"></div>
<!-- Email --> <!-- Member Status -->
<div class="space-y-1"> <div class="flex justify-center mb-6">
<div class="skeleton h-3 w-16 opacity-70"></div> <div class="skeleton h-6 w-32"></div>
<div class="skeleton h-[1.75rem] w-64"></div> </div>
</div>
<div class="divider my-0.5"></div>
<!-- Member Status --> <!-- Stats Grid -->
<div class="space-y-1"> <div class="stats stats-vertical shadow bg-base-200 mb-6">
<div class="skeleton h-3 w-24 opacity-70"></div> <div class="stat px-6 py-2">
<div class="skeleton h-[1.75rem] w-32"></div> <div class="skeleton h-4 w-20 mb-1"></div>
</div> <div class="skeleton h-8 w-24"></div>
<div class="divider my-0.5"></div> </div>
<div class="stat px-6 py-2">
<div class="skeleton h-4 w-24 mb-1"></div>
<div class="skeleton h-8 w-32"></div>
</div>
</div>
<!-- Member ID --> <!-- Member Details -->
<div class="space-y-1"> <div class="space-y-4">
<div class="skeleton h-3 w-20 opacity-70"></div> <div class="space-y-2">
<div class="flex items-center gap-2"> <div class="skeleton h-4 w-24"></div>
<div class="skeleton h-8 flex-1"></div> <div class="skeleton h-10 w-full"></div>
<div class="skeleton h-8 w-16"></div> </div>
</div> <div class="space-y-2">
</div> <div class="skeleton h-4 w-20"></div>
<div class="divider my-0.5"></div> <div class="skeleton h-10 w-full"></div>
<!-- Last Login -->
<div class="space-y-1">
<div class="skeleton h-3 w-20 opacity-70"></div>
<div class="skeleton h-[1.25rem] w-32"></div>
</div>
<div class="divider my-0.5"></div>
<!-- Event Check-in -->
<div class="space-y-2">
<div class="skeleton h-3 w-24 opacity-70"></div>
<div class="space-y-2">
<div class="flex items-center gap-2">
<div class="skeleton h-8 flex-1"></div>
<div class="skeleton h-8 w-24"></div>
</div>
<div class="skeleton h-3 w-48 opacity-70"></div>
</div>
</div>
<div class="divider my-0.5"></div>
<!-- Resume -->
<div class="space-y-2">
<div class="skeleton h-3 w-16 opacity-70"></div>
<div class="space-y-2">
<div class="flex items-center gap-2">
<div class="skeleton h-5 flex-1"></div>
<div class="skeleton h-5 w-24"></div>
</div>
<div class="skeleton h-8 w-full"></div>
<div class="skeleton h-3 w-48 opacity-70"></div>
</div>
</div>
<div class="divider my-0.5"></div>
<!-- Auth Buttons -->
<div class="pt-2">
<div class="skeleton h-10 w-full"></div>
</div>
</div> </div>
</div> </div>
</div> </div>
</div> </div>
<!-- Actual Content (hidden by default) --> <!-- Actual Content -->
<div id="userInfo" class="card bg-base-200 shadow-xl opacity-0 hidden"> <div id="userInfo" class="card bg-base-100 shadow-xl opacity-0 hidden">
<div class="card-body p-0"> <div class="card-body p-6">
<div class="px-6 pt-6"> <!-- Avatar and Name Section -->
<h2 class="card-title text-2xl">User Profile</h2> <div class="flex flex-col items-center mb-6">
</div> <div class="avatar online placeholder mb-4">
<div class="px-6 pb-6"> <div
<div class="space-y-3"> class="bg-gradient-to-br from-primary to-secondary text-primary-content rounded-full w-24 ring ring-primary ring-offset-base-100 ring-offset-2"
<div class="space-y-1"> >
<label class="text-sm opacity-70">Name</label> <span id="userInitials" class="text-3xl"></span>
<p id="userName" class="h-[1.75rem] font-medium">
Not signed in
</p>
</div>
<div class="divider my-0.5"></div>
<div class="space-y-1">
<label class="text-sm opacity-70">Email</label>
<p id="userEmail" class="h-[1.75rem] font-medium">
Not signed in
</p>
</div>
<div class="divider my-0.5"></div>
<div class="space-y-1">
<label class="text-sm opacity-70">Member Status</label>
<div class="flex h-[1.75rem] items-center">
<div id="memberStatus" class="badge badge-neutral">
Not verified
</div>
</div>
</div>
<div id="officerViewToggle" class="hidden">
<label
class="flex items-center justify-between w-full px-1 bg-base-200 rounded-lg"
>
<span class="text-sm">Officer View</span>
<input
type="checkbox"
class="toggle toggle-primary"
/>
</label>
</div>
<div id="sponsorViewToggle" class="hidden">
<label
class="flex items-center justify-between w-full px-1 bg-base-200 rounded-lg"
>
<span class="text-sm">Sponsor View</span>
<input
type="checkbox"
class="toggle toggle-warning"
/>
</label>
</div>
<div class="divider my-0.5"></div>
<div class="space-y-1">
<label class="text-sm opacity-70">IEEE Member ID</label>
<div class="flex items-center gap-2 h-8">
<input
type="text"
id="memberIdInput"
placeholder="Enter your IEEE Member ID"
class="input input-bordered w-full h-8 min-h-[2rem] disabled:bg-base-300 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed"
disabled
/>
<button
id="saveMemberId"
class="btn h-8 min-h-[2rem] disabled:bg-base-300 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed enabled:btn-primary hover:enabled:bg-primary-focus"
disabled>Save</button
>
</div>
<p id="memberIdStatus" class="text-xs mt-1 opacity-70">
</p>
</div>
<div class="divider my-0.5"></div>
<div class="space-y-1">
<label class="text-sm opacity-70">Last Login</label>
<p
id="lastLogin"
class="text-sm h-[1.25rem] opacity-80"
>
Never
</p>
</div>
<div class="divider my-0.5"></div>
<div class="space-y-2">
<label class="text-sm opacity-70">Resume</label>
<div id="resumeSection" class="space-y-2">
<div class="flex items-center gap-2 h-[1.25rem]">
<p
id="resumeName"
class="text-sm truncate flex-1"
>
No resume uploaded
</p>
<div id="resumeActions" class="flex gap-2">
<a
id="resumeDownload"
href="#"
target="_blank"
class="btn btn-ghost btn-xs">View</a
>
<button
id="deleteResume"
class="btn btn-ghost btn-xs text-error"
>Delete</button
>
</div>
</div>
<div id="uploadSection">
<input
type="file"
id="resumeUpload"
accept=".pdf,.doc,.docx"
class="file-input file-input-bordered file-input-sm w-full disabled:bg-base-300 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed"
disabled
/>
<p
id="uploadStatus"
class="text-xs mt-1 opacity-70"
>
</p>
</div>
</div>
</div>
<div class="divider my-0.5"></div>
<div class="pt-2">
<button
id="contentLoginButton"
class="login-button btn btn-primary w-full"
>Sign in with IEEEUCSD SSO</button
>
<button
id="contentLogoutButton"
class="logout-button btn btn-error w-full"
>Sign Out</button
>
</div> </div>
</div> </div>
<div class="text-center">
<h2 id="userName" class="text-2xl font-bold">
Not signed in
</h2>
<p id="userEmail" class="text-base-content/70">
Not signed in
</p>
</div>
</div>
<!-- Member Status -->
<div class="flex justify-center mb-6">
<div id="memberStatus" class="badge badge-lg gap-2">
<span class="loading loading-ring loading-xs"></span>
Not verified
</div>
</div>
<!-- Stats Grid -->
<div class="stats stats-vertical shadow bg-base-200 mb-6">
<div class="stat px-6 py-2">
<div class="stat-title text-xs">Last Login</div>
<div id="lastLogin" class="stat-value text-lg">Never</div>
</div>
<div class="stat px-6 py-2">
<div class="stat-title text-xs">Member Since</div>
<div class="stat-value text-lg" id="memberSince">-</div>
</div>
</div>
<!-- View Toggles -->
<div class="space-y-2 mb-6">
<div id="officerViewToggle" class="hidden">
<label
class="flex cursor-pointer gap-2 justify-between items-center bg-gradient-to-r from-primary/10 to-primary/5 rounded-lg px-4 py-2 hover:from-primary/20 hover:to-primary/10 transition-all"
>
<span class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M9 6a3 3 0 11-6 0 3 3 0 016 0zM17 6a3 3 0 11-6 0 3 3 0 016 0zM12.93 17c.046-.327.07-.66.07-1a6.97 6.97 0 00-1.5-4.33A5 5 0 0119 16v1h-6.07zM6 11a5 5 0 015 5v1H1v-1a5 5 0 015-5z"
></path>
</svg>
<span>Officer View</span>
</span>
<input type="checkbox" class="toggle toggle-primary" />
</label>
</div>
<div id="sponsorViewToggle" class="hidden">
<label
class="flex cursor-pointer gap-2 justify-between items-center bg-gradient-to-r from-warning/10 to-warning/5 rounded-lg px-4 py-2 hover:from-warning/20 hover:to-warning/10 transition-all"
>
<span class="flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
<span>Sponsor View</span>
</span>
<input type="checkbox" class="toggle toggle-warning" />
</label>
</div>
</div>
<!-- Member Details -->
<div class="space-y-4">
<!-- IEEE Member ID -->
<div class="form-control">
<label class="label">
<span class="label-text flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 2a1 1 0 00-1 1v1a1 1 0 002 0V3a1 1 0 00-1-1zM4 4h3a3 3 0 006 0h3a2 2 0 012 2v9a2 2 0 01-2 2H4a2 2 0 01-2-2V6a2 2 0 012-2zm2.5 7a1.5 1.5 0 100-3 1.5 1.5 0 000 3zm2.45 4a2.5 2.5 0 10-4.9 0h4.9zM12 9a1 1 0 100 2h3a1 1 0 100-2h-3zm-1 4a1 1 0 011-1h2a1 1 0 110 2h-2a1 1 0 01-1-1z"
clip-rule="evenodd"></path>
</svg>
IEEE Member ID
</span>
</label>
<div class="join w-full">
<input
type="text"
id="memberIdInput"
placeholder="Enter your IEEE Member ID"
class="join-item input input-bordered flex-1 disabled:bg-base-200 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed"
disabled
/>
<button
id="saveMemberId"
class="join-item btn disabled:bg-base-200 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed enabled:btn-primary hover:enabled:bg-primary-focus"
disabled>Save</button
>
</div>
<label class="label">
<span id="memberIdStatus" class="label-text-alt"></span>
</label>
</div>
<!-- Resume Upload -->
<div class="form-control">
<label class="label">
<span class="label-text flex items-center gap-2">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-4 w-4"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z"
clip-rule="evenodd"></path>
</svg>
Resume
</span>
</label>
<div id="resumeSection" class="space-y-2">
<div class="flex items-center gap-2">
<p id="resumeName" class="text-sm truncate flex-1">
No resume uploaded
</p>
<div id="resumeActions" class="flex gap-2">
<a
id="resumeDownload"
href="#"
target="_blank"
class="btn btn-ghost btn-xs">View</a
>
<button
id="deleteResume"
class="btn btn-ghost btn-xs text-error"
>Delete</button
>
</div>
</div>
<div id="uploadSection">
<input
type="file"
id="resumeUpload"
accept=".pdf,.doc,.docx"
class="file-input file-input-bordered file-input-sm w-full disabled:bg-base-200 disabled:border-2 disabled:border-opacity-50 disabled:cursor-not-allowed"
disabled
/>
<label class="label">
<span id="uploadStatus" class="label-text-alt"
></span>
</label>
</div>
</div>
</div>
</div>
<!-- Auth Buttons -->
<div class="pt-4">
<button
id="contentLoginButton"
class="login-button btn btn-primary w-full 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>
<button
id="contentLogoutButton"
class="logout-button btn btn-error w-full 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 00-1 1v12a1 1 0 102 0V4a1 1 0 00-1-1zm10.293 9.293a1 1 0 001.414 1.414l3-3a1 1 0 000-1.414l-3-3a1 1 0 10-1.414 1.414L14.586 9H7a1 1 0 100 2h7.586l-1.293 1.293z"
clip-rule="evenodd"></path>
</svg>
Sign Out
</button>
</div> </div>
</div> </div>
</div> </div>
@ -228,7 +264,7 @@
<!-- Profile Editor Dialog --> <!-- Profile Editor Dialog -->
<dialog id="profileEditor" class="modal"> <dialog id="profileEditor" class="modal">
<div class="modal-box"> <div class="modal-box">
<h3 class="font-bold text-lg mb-4">Edit Profile</h3> <h3 class="font-bold text-2xl mb-6">Edit Profile</h3>
<form class="space-y-4" onsubmit="return false" novalidate> <form class="space-y-4" onsubmit="return false" novalidate>
<div class="form-control"> <div class="form-control">
<label class="label"> <label class="label">
@ -356,7 +392,7 @@
display: none; display: none;
} }
#userInfo { #userInfo {
transition: opacity 0.3s ease-in-out; transition: all 0.3s ease-in-out;
} }
.modal { .modal {
background: rgba(0, 0, 0, 0.5); background: rgba(0, 0, 0, 0.5);
@ -369,6 +405,82 @@
<script> <script>
import { StoreAuth } from "./StoreAuth"; import { StoreAuth } from "./StoreAuth";
import { EventCheckIn } from "./EventCheckIn"; import { EventCheckIn } from "./EventCheckIn";
new StoreAuth();
new EventCheckIn(); // Initialize auth and event check-in
document.addEventListener("DOMContentLoaded", () => {
try {
new StoreAuth();
new EventCheckIn();
// Add error handling for failed initialization
window.addEventListener("unhandledrejection", (event) => {
console.error("Profile loading error:", event.reason);
const userInfo = document.getElementById("userInfo");
const loadingSkeleton =
document.getElementById("loadingSkeleton");
const errorMessage = document.createElement("div");
errorMessage.className = "alert alert-error";
errorMessage.innerHTML = `
<div class="flex items-center gap-2">
<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" />
</svg>
<span>Failed to load profile. Please refresh the page.</span>
</div>
`;
if (loadingSkeleton) loadingSkeleton.style.display = "none";
if (userInfo) {
userInfo.innerHTML = "";
userInfo.appendChild(errorMessage);
userInfo.classList.remove("hidden");
userInfo.style.opacity = "1";
}
});
} catch (error) {
console.error("Failed to initialize profile:", error);
}
});
// Add user initials generation
const userNameElement = document.getElementById("userName");
const userInitialsElement = document.getElementById("userInitials");
const memberSinceElement = document.getElementById("memberSince");
if (userNameElement && userInitialsElement && memberSinceElement) {
const observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (
mutation.type === "characterData" ||
mutation.type === "childList"
) {
const name = userNameElement.textContent || "";
if (name && name !== "Not signed in") {
const initials = name
.split(" ")
.map((n) => n[0])
.join("")
.toUpperCase()
.slice(0, 2);
userInitialsElement.textContent = initials;
} else {
userInitialsElement.textContent = "?";
}
}
});
});
observer.observe(userNameElement, {
characterData: true,
childList: true,
subtree: true,
});
}
// Set member since date (you'll need to modify StoreAuth.ts to include this)
if (memberSinceElement) {
const created = new Date(); // Replace with actual user creation date
memberSinceElement.textContent = created.toLocaleDateString();
}
</script> </script>

View file

@ -8,48 +8,44 @@
<h2 class="card-title text-2xl">Events</h2> <h2 class="card-title text-2xl">Events</h2>
</div> </div>
<!-- Event Check-in --> <!-- Event Check-in Section -->
<div <div class="card bg-base-200 shadow-xl mb-8">
id="eventCheckInSkeleton" <div class="card-body p-6">
class="card bg-base-100 shadow-sm mb-4 animate-pulse" <h2 class="card-title text-2xl mb-4">Quick Check-in</h2>
>
<div class="card-body p-4">
<div class="h-6 bg-base-300 rounded w-2/3 mb-4"></div>
<div class="flex items-center gap-2">
<div class="h-8 bg-base-300 rounded flex-1"></div>
<div class="h-8 bg-base-300 rounded w-24"></div>
</div>
</div>
</div>
<div <!-- Event Check-in Skeleton -->
id="eventCheckInContent" <div id="eventCheckInSkeleton" class="animate-pulse">
class="card bg-base-100 shadow-sm mb-4 hidden" <div class="h-6 bg-base-300 rounded w-2/3 mb-4">
> </div>
<div class="card-body p-4"> <div class="flex items-center gap-2">
<h3 class="font-medium text-lg mb-2"> <div class="h-8 bg-base-300 rounded flex-1">
Enter your event code to check in </div>
</h3> <div class="h-8 bg-base-300 rounded w-24"></div>
<div id="eventCheckInSection" class="space-y-2"> </div>
<div class="flex items-center gap-2"> </div>
<input
type="text" <!-- Event Check-in Content -->
id="eventCodeInput" <div id="eventCheckInContent" class="hidden">
placeholder="Enter event code" <div id="eventCheckInSection" class="space-y-2">
class="input input-bordered input-sm flex-1" <div class="flex items-center gap-2">
value="" <input
/> type="text"
<button id="eventCodeInput"
id="checkInButton" placeholder="Enter event code"
class="btn btn-sm btn-primary" class="input input-bordered flex-1"
>Check In</button value=""
> />
<button
id="checkInButton"
class="btn btn-primary">Check In</button
>
</div>
<p
id="checkInStatus"
class="text-sm mt-1 opacity-70"
>
</p>
</div> </div>
<p
id="checkInStatus"
class="text-xs mt-1 opacity-70"
>
</p>
</div> </div>
</div> </div>
</div> </div>
@ -210,6 +206,7 @@
"eventCheckInSkeleton" "eventCheckInSkeleton"
); );
const eventCheckInContent = document.getElementById("eventCheckInContent"); const eventCheckInContent = document.getElementById("eventCheckInContent");
const pastEventsCount = document.getElementById("pastEventsCount");
// Function to show content and hide skeleton // Function to show content and hide skeleton
function showEventCheckIn() { function showEventCheckIn() {
@ -325,6 +322,7 @@
const hasFiles = const hasFiles =
event.files && Array.isArray(event.files) && event.files.length > 0; event.files && Array.isArray(event.files) && event.files.length > 0;
const isPastEvent = new Date(event.end_date) < new Date(); const isPastEvent = new Date(event.end_date) < new Date();
const { status, badge } = getEventStatus(event, isAttended);
// Only show files button for past events // Only show files button for past events
const filesButton = const filesButton =
@ -343,30 +341,33 @@
: ""; : "";
return ` return `
<div class="card bg-base-100 shadow-sm"> <div class="card bg-base-100 shadow-sm hover:shadow-md transition-shadow">
<div class="card-body p-4"> <div class="card-body p-4">
<div class="flex justify-between items-start"> <div class="flex justify-between items-start gap-4">
<div> <div class="flex-1 min-w-0">
<h3 class="font-medium text-lg">${event.event_name}</h3> <div class="flex items-center gap-2 mb-2">
<h3 class="font-medium text-lg truncate">${event.event_name}</h3>
<div class="badge ${badge} gap-1">
${getStatusIcon(status)}
${status}
</div>
</div>
<div class="text-sm opacity-70 space-y-1"> <div class="text-sm opacity-70 space-y-1">
<p>Starts: ${formatDate(event.start_date)}</p> <p>Starts: ${formatDate(event.start_date)}</p>
<p>Ends: ${formatDate(event.end_date)}</p> <p>Ends: ${formatDate(event.end_date)}</p>
${event.location ? `<p class="text-xs">📍 ${event.location}</p>` : ""} ${
event.location
? `<p class="flex items-center gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M5.05 4.05a7 7 0 119.9 9.9L10 18.9l-4.95-4.95a7 7 0 010-9.9zM10 11a2 2 0 100-4 2 2 0 000 4z" clip-rule="evenodd" />
</svg>
${event.location}
</p>`
: ""
}
</div> </div>
${hasFiles && isPastEvent ? `<div class="mt-2">${filesButton}</div>` : ""} ${hasFiles && isPastEvent ? `<div class="mt-3">${filesButton}</div>` : ""}
</div> </div>
${
isAttended
? `
<div class="badge badge-success gap-1">
<svg xmlns="http://www.w3.org/2000/svg" class="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
<path fill-rule="evenodd" d="M16.707 5.293a1 1 0 010 1.414l-8 8a1 1 0 01-1.414 0l-4-4a1 1 0 011.414-1.414L8 12.586l7.293-7.293a1 1 0 011.414 0z" clip-rule="evenodd" />
</svg>
Attended
</div>
`
: ""
}
</div> </div>
</div> </div>
</div> </div>
@ -427,14 +428,12 @@
} }
}); });
// Sort upcoming events by start date and limit to 2 // Sort upcoming events by start date
const nextTwoEvents = upcomingEvents const sortedUpcomingEvents = upcomingEvents.sort(
.sort( (a, b) =>
(a, b) => new Date(a.start_date).getTime() -
new Date(a.start_date).getTime() - new Date(b.start_date).getTime()
new Date(b.start_date).getTime() );
)
.slice(0, 2);
// Sort past events by date descending (most recent first) // Sort past events by date descending (most recent first)
const sortedPastEvents = pastEvents.sort( const sortedPastEvents = pastEvents.sort(
@ -443,50 +442,34 @@
new Date(a.end_date).getTime() new Date(a.end_date).getTime()
); );
// Function to render section // Update past events count
function renderSection( if (pastEventsCount) {
title: string, pastEventsCount.textContent =
events: Event[], sortedPastEvents.length.toString();
showDivider: boolean = true
): string {
if (events.length === 0) return "";
return `
<div class="space-y-4">
<h3 class="text-lg font-medium text-base-content/70">${title}</h3>
<div class="space-y-4">
${events.map((event) => renderEventCard(event, attendedEvents)).join("")}
</div>
${showDivider ? '<div class="divider"></div>' : ""}
</div>
`;
} }
// Update main events list (left column) // Function to render section
eventsList.innerHTML = ` function renderSection(events: Event[]): string {
${renderSection("Upcoming Events", nextTwoEvents, nextTwoEvents.length > 0)} if (events.length === 0) {
${renderSection("Currently Happening", currentEvents, false)} return `
`;
// Update past events list (right column)
pastEventsList.innerHTML =
sortedPastEvents.length > 0
? sortedPastEvents
.map((event) =>
renderEventCard(event, attendedEvents)
)
.join("")
: `<div class="text-center py-8 opacity-70">
<p>No past events found</p>
</div>`;
// If no events at all
if (events.items.length === 0) {
eventsList.innerHTML = `
<div class="text-center py-8 opacity-70"> <div class="text-center py-8 opacity-70">
<p>No events found</p> <p>No events found</p>
</div> </div>
`; `;
}
return events
.map((event) => renderEventCard(event, attendedEvents))
.join("");
} }
// Update main events list (current & upcoming)
eventsList.innerHTML = renderSection([
...currentEvents,
...sortedUpcomingEvents,
]);
// Update past events list
pastEventsList.innerHTML = renderSection(sortedPastEvents);
} catch (err) { } catch (err) {
console.error("Failed to render events:", err); console.error("Failed to render events:", err);
const errorMessage = ` const errorMessage = `

View file

@ -7,21 +7,219 @@ const title = "User Profile";
--- ---
<Layout {title}> <Layout {title}>
<main class="mx-auto pb-12 md:pt-[5vh] pt-[5vw] min-h-screen"> <main class="min-h-screen bg-base-100/50">
<h1 class="text-4xl font-bold mb-12">Profile Management</h1> <div class="container mx-auto px-4 py-8">
<div class="grid grid-cols-1 lg:grid-cols-4 gap-8"> <!-- Header Section with Breadcrumbs -->
<!-- Left Column - User Info --> <div class="mb-8">
<div class="lg:col-span-2 2xl:col-span-1 h-fit"> <div class="text-sm breadcrumbs opacity-70 mb-2">
<UserProfile /> <ul>
<li><a href="/">Home</a></li>
<li>Profile</li>
</ul>
</div>
<h1 class="text-4xl font-bold">Profile Dashboard</h1>
<p class="text-base-content/70 mt-2">
Manage your IEEE UCSD membership and activities
</p>
</div> </div>
<!-- Right Column - Store Items --> <!-- Loading State -->
<div id="storeContent" class="lg:col-span-2 2xl:col-span-3"> <div id="pageLoadingState" class="w-full">
<div id="defaultView"> <div class="flex flex-col items-center justify-center p-8">
<DefaultProfileView /> <div class="loading loading-spinner loading-lg"></div>
<p class="mt-4 text-base-content/70">
Loading your profile...
</p>
</div> </div>
<div id="officerView" class="hidden"> </div>
<OfficerProfileView />
<!-- Error State -->
<div id="pageErrorState" class="hidden w-full">
<div class="alert alert-error">
<div class="flex items-center gap-2">
<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 profile data. Please try refreshing
the page.</span
>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div
id="mainContent"
class="hidden grid grid-cols-1 xl:grid-cols-12 gap-8"
>
<!-- Left Column - User Info -->
<div class="xl:col-span-4 2xl:col-span-3">
<div class="sticky top-8">
<UserProfile />
</div>
</div>
<!-- Right Column - Events & Activities -->
<div class="xl:col-span-8 2xl:col-span-9">
<!-- Quick Actions -->
<div class="flex flex-wrap gap-4 mb-8">
<button
class="btn btn-primary gap-2"
id="quickCheckInBtn"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M5 3a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2V5a2 2 0 00-2-2H5zM5 11a2 2 0 00-2 2v2a2 2 0 002 2h2a2 2 0 002-2v-2a2 2 0 00-2-2H5zM11 5a2 2 0 012-2h2a2 2 0 012 2v2a2 2 0 01-2 2h-2a2 2 0 01-2-2V5zM14 11a1 1 0 011 1v1h1a1 1 0 110 2h-1v1a1 1 0 11-2 0v-1h-1a1 1 0 110-2h1v-1a1 1 0 011-1z"
></path>
</svg>
Quick Check-in
</button>
<button
class="btn btn-ghost gap-2"
id="viewCalendarBtn"
>
<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="M6 2a1 1 0 00-1 1v1H4a2 2 0 00-2 2v10a2 2 0 002 2h12a2 2 0 002-2V6a2 2 0 00-2-2h-1V3a1 1 0 10-2 0v1H7V3a1 1 0 00-1-1zm0 5a1 1 0 000 2h8a1 1 0 100-2H6z"
clip-rule="evenodd"></path>
</svg>
View Calendar
</button>
<button
class="btn btn-ghost gap-2"
id="uploadResumeBtn"
>
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M4 4a2 2 0 012-2h4.586A2 2 0 0112 2.586L15.414 6A2 2 0 0116 7.414V16a2 2 0 01-2 2H6a2 2 0 01-2-2V4z"
></path>
</svg>
Upload Resume
</button>
</div>
<!-- Stats Overview -->
<div class="stats shadow w-full mb-8 bg-base-100">
<div class="stat">
<div class="stat-figure text-primary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M13 6a3 3 0 11-6 0 3 3 0 016 0zM18 8a2 2 0 11-4 0 2 2 0 014 0zM14 15a4 4 0 00-8 0v3h8v-3zM6 8a2 2 0 11-4 0 2 2 0 014 0zM16 18v-3a5.972 5.972 0 00-.75-2.906A3.005 3.005 0 0119 15v3h-3zM4.75 12.094A5.973 5.973 0 004 15v3H1v-3a3 3 0 013.75-2.906z"
></path>
</svg>
</div>
<div class="stat-title">Events Attended</div>
<div
class="stat-value text-primary"
id="eventsAttendedValue"
>
-
</div>
<div class="stat-desc">Since joining</div>
</div>
<div class="stat">
<div class="stat-figure text-secondary">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Loyalty Points</div>
<div
class="stat-value text-secondary"
id="loyaltyPointsValue"
>
-
</div>
<div class="stat-desc" id="loyaltyPointsChange">
Loading...
</div>
</div>
<div class="stat">
<div class="stat-figure text-accent">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-8 w-8"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M5 3a2 2 0 00-2 2v10a2 2 0 002 2h10a2 2 0 002-2V5a2 2 0 00-2-2H5zm9 4a1 1 0 10-2 0v6a1 1 0 102 0V7zm-3 2a1 1 0 10-2 0v4a1 1 0 102 0V9zm-3 3a1 1 0 10-2 0v1a1 1 0 102 0v-1z"
clip-rule="evenodd"></path>
</svg>
</div>
<div class="stat-title">Activity Level</div>
<div
class="stat-value text-accent"
id="activityLevelValue"
>
-
</div>
<div class="stat-desc" id="activityLevelDesc">
Loading...
</div>
</div>
</div>
<!-- Main Content Tabs -->
<div class="tabs tabs-boxed mb-6">
<button class="tab tab-active" data-tab="events"
>Events & Activities</button
>
<button class="tab" data-tab="settings">Settings</button
>
<button class="tab" data-tab="officer" id="officerTab"
>Officer Panel</button
>
</div>
<!-- Content Areas -->
<div id="defaultView">
<DefaultProfileView />
</div>
<div id="officerView" class="hidden">
<OfficerProfileView />
</div>
</div> </div>
</div> </div>
</div> </div>
@ -30,21 +228,171 @@ const title = "User Profile";
<script> <script>
import { StoreAuth } from "../components/auth/StoreAuth"; import { StoreAuth } from "../components/auth/StoreAuth";
new StoreAuth(); const auth = new StoreAuth();
// Handle view toggling // Initialize page state
const officerViewToggle = document.getElementById("officerViewToggle"); const pageLoadingState = document.getElementById("pageLoadingState");
const officerViewCheckbox = officerViewToggle?.querySelector( const pageErrorState = document.getElementById("pageErrorState");
'input[type="checkbox"]' const mainContent = document.getElementById("mainContent");
); const tabs = document.querySelectorAll(".tab");
const defaultView = document.getElementById("defaultView"); const defaultView = document.getElementById("defaultView");
const officerView = document.getElementById("officerView"); const officerView = document.getElementById("officerView");
const officerTab = document.getElementById("officerTab");
if (officerViewCheckbox && defaultView && officerView) { // Stats elements
officerViewCheckbox.addEventListener("change", (e) => { const eventsAttendedValue = document.getElementById("eventsAttendedValue");
const isChecked = (e.target as HTMLInputElement).checked; const loyaltyPointsValue = document.getElementById("loyaltyPointsValue");
defaultView.style.display = isChecked ? "none" : "block"; const loyaltyPointsChange = document.getElementById("loyaltyPointsChange");
officerView.style.display = isChecked ? "block" : "none"; const activityLevelValue = document.getElementById("activityLevelValue");
const activityLevelDesc = document.getElementById("activityLevelDesc");
// Quick action buttons
const quickCheckInBtn = document.getElementById("quickCheckInBtn");
const viewCalendarBtn = document.getElementById("viewCalendarBtn");
const uploadResumeBtn = document.getElementById("uploadResumeBtn");
// Hide officer tab by default
if (officerTab) {
officerTab.style.display = "none";
}
// Show officer tab if user is an officer
const showOfficerTab = () => {
if (officerTab) {
const authState = auth.getAuthState();
const isOfficer =
authState.model?.member_type === "officer" ||
authState.model?.member_type === "administrator";
officerTab.style.display = isOfficer ? "inline-flex" : "none";
}
};
// Initialize page
const initializePage = async () => {
try {
// Show loading state
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (mainContent) mainContent.classList.add("hidden");
// Check auth state
const authState = auth.getAuthState();
if (!authState.isValid || !authState.model) {
throw new Error("User not authenticated");
}
const user = authState.model;
// Update stats
if (eventsAttendedValue) {
const eventsAttended = user.events_attended?.length || 0;
eventsAttendedValue.textContent = eventsAttended.toString();
}
if (loyaltyPointsValue && loyaltyPointsChange) {
const points = user.points || 0;
loyaltyPointsValue.textContent = points.toString();
// Calculate points change (example logic)
const pointsChange = 10; // Replace with actual calculation
loyaltyPointsChange.textContent =
pointsChange > 0
? `↗︎ ${pointsChange} points (30 days)`
: `↘︎ ${Math.abs(pointsChange)} points (30 days)`;
}
if (activityLevelValue && activityLevelDesc) {
// Calculate activity level based on events and points
const eventsAttended = user.events_attended?.length || 0;
const points = user.points || 0;
let activityLevel = "Low";
let description = "Occasional Member";
if (eventsAttended > 5 || points > 50) {
activityLevel = "High";
description = "Active Member";
} else if (eventsAttended > 2 || points > 20) {
activityLevel = "Medium";
description = "Regular Member";
}
activityLevelValue.textContent = activityLevel;
activityLevelDesc.textContent = description;
}
// Show officer tab if applicable
showOfficerTab();
// Hide loading state and show content
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (mainContent) mainContent.classList.remove("hidden");
} catch (error) {
console.error("Failed to initialize page:", error);
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
if (mainContent) mainContent.classList.add("hidden");
}
};
// Check on load and auth changes
initializePage();
window.addEventListener("storage", (e) => {
if (e.key === "pocketbase_auth") {
initializePage();
}
});
// Handle tab switching
tabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update tab styles
tabs.forEach((t) => t.classList.remove("tab-active"));
tab.classList.add("tab-active");
// Update content visibility
const tabId = (tab as HTMLElement).dataset.tab;
if (tabId === "events") {
defaultView!.style.display = "block";
officerView!.style.display = "none";
} else if (tabId === "officer") {
defaultView!.style.display = "none";
officerView!.style.display = "block";
}
});
});
// Quick action button handlers
if (quickCheckInBtn) {
quickCheckInBtn.addEventListener("click", () => {
const eventCheckInInput = document.getElementById(
"eventCheckInInput"
) as HTMLInputElement;
if (eventCheckInInput) {
eventCheckInInput.focus();
}
});
}
if (viewCalendarBtn) {
viewCalendarBtn.addEventListener("click", () => {
// Implement calendar view logic
console.log("Calendar view not implemented yet");
});
}
if (uploadResumeBtn) {
uploadResumeBtn.addEventListener("click", () => {
const resumeUpload = document.getElementById(
"resumeUpload"
) as HTMLInputElement;
if (resumeUpload) {
resumeUpload.click();
}
}); });
} }
</script> </script>
<style>
.hidden {
display: none !important;
}
</style>