new profile look!
This commit is contained in:
parent
7cd50bea6c
commit
eea944fcd2
4 changed files with 1008 additions and 1068 deletions
File diff suppressed because it is too large
Load diff
|
@ -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>
|
||||||
|
|
|
@ -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 = `
|
||||||
|
|
|
@ -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>
|
||||||
|
|
Loading…
Reference in a new issue