398 lines
17 KiB
Text
398 lines
17 KiB
Text
---
|
|
import Layout from "../layouts/Layout.astro";
|
|
import UserProfile from "../components/auth/UserProfile.astro";
|
|
import DefaultProfileView from "../components/profile/DefaultProfileView.astro";
|
|
import OfficerProfileView from "../components/profile/OfficerView.astro";
|
|
const title = "User Profile";
|
|
---
|
|
|
|
<Layout {title}>
|
|
<main class="min-h-screen bg-base-100/50">
|
|
<div class="container mx-auto px-4 py-8">
|
|
<!-- Header Section with Breadcrumbs -->
|
|
<div class="mb-8">
|
|
<div class="text-sm breadcrumbs opacity-70 mb-2">
|
|
<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>
|
|
|
|
<!-- Loading State -->
|
|
<div id="pageLoadingState" class="w-full">
|
|
<div class="flex flex-col items-center justify-center p-8">
|
|
<div class="loading loading-spinner loading-lg"></div>
|
|
<p class="mt-4 text-base-content/70">
|
|
Loading your profile...
|
|
</p>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 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>
|
|
</main>
|
|
</Layout>
|
|
|
|
<script>
|
|
import { StoreAuth } from "../components/auth/StoreAuth";
|
|
const auth = new StoreAuth();
|
|
|
|
// Initialize page state
|
|
const pageLoadingState = document.getElementById("pageLoadingState");
|
|
const pageErrorState = document.getElementById("pageErrorState");
|
|
const mainContent = document.getElementById("mainContent");
|
|
const tabs = document.querySelectorAll(".tab");
|
|
const defaultView = document.getElementById("defaultView");
|
|
const officerView = document.getElementById("officerView");
|
|
const officerTab = document.getElementById("officerTab");
|
|
|
|
// Stats elements
|
|
const eventsAttendedValue = document.getElementById("eventsAttendedValue");
|
|
const loyaltyPointsValue = document.getElementById("loyaltyPointsValue");
|
|
const loyaltyPointsChange = document.getElementById("loyaltyPointsChange");
|
|
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>
|
|
|
|
<style>
|
|
.hidden {
|
|
display: none !important;
|
|
}
|
|
</style>
|