Add authentication #17

Manually merged
Webmaster merged 225 commits from auth into main 2025-03-08 10:37:06 +00:00
5 changed files with 340 additions and 122 deletions
Showing only changes of commit 18b3110a22 - Show all commits

View file

@ -299,10 +299,32 @@ export class StoreAuth {
? new Date(user.last_login).toLocaleString()
: this.config.ui.messages.auth.never;
// Show/hide view toggles
// Show/hide view toggles and update view visibility
officerViewToggle.style.display = isOfficer ? "block" : "none";
sponsorViewToggle.style.display = isSponsor ? "block" : "none";
// If not an officer, ensure default view is shown and officer view is hidden
if (!isOfficer) {
const defaultView = document.getElementById("defaultView");
const officerView = document.getElementById("officerView");
const mainTabs = document.querySelector(".tabs.tabs-boxed");
const officerContent = document.getElementById("officerContent");
if (defaultView && officerView && mainTabs && officerContent) {
// Show default view and its tabs
defaultView.classList.remove("hidden");
mainTabs.classList.remove("hidden");
// Hide officer view
officerView.classList.add("hidden");
officerContent.classList.add("hidden");
// Also uncheck the toggle if it exists
const officerViewCheckbox = officerViewToggle.querySelector('input[type="checkbox"]') as HTMLInputElement;
if (officerViewCheckbox) {
officerViewCheckbox.checked = false;
}
}
}
// After everything is updated, show the content
loadingSkeleton.style.display = "none";
userInfo.classList.remove("hidden");

View file

@ -319,6 +319,46 @@
const auth = new StoreAuth();
new EventCheckIn();
// Add officer view toggle handler
const officerViewToggle =
document.getElementById("officerViewToggle");
const officerViewCheckbox = officerViewToggle?.querySelector(
'input[type="checkbox"]'
) as HTMLInputElement;
if (officerViewCheckbox) {
officerViewCheckbox.addEventListener("change", () => {
const defaultView = document.getElementById("defaultView");
const officerView = document.getElementById("officerView");
const mainTabs = document.querySelector(".tabs.tabs-boxed");
const officerContent =
document.getElementById("officerContent");
if (
defaultView &&
officerView &&
mainTabs &&
officerContent
) {
if (officerViewCheckbox.checked) {
// Hide default view and its tabs
defaultView.classList.add("hidden");
mainTabs.classList.add("hidden");
// Show officer view
officerView.classList.remove("hidden");
officerContent.classList.remove("hidden");
} else {
// Show default view and its tabs
defaultView.classList.remove("hidden");
mainTabs.classList.remove("hidden");
// Hide officer view
officerView.classList.add("hidden");
officerContent.classList.add("hidden");
}
}
});
}
// Add login button event listener
const loginButtons = document.querySelectorAll(".login-button");
loginButtons.forEach((button) => {

View file

@ -4,43 +4,43 @@ import EventManagement from "./EventManagement.astro";
---
<div id="officerContent" class="hidden">
<div class="tabs tabs-boxed mb-6">
<button class="tab tab-active" data-tab="members"
<div class="tabs tabs-boxed mb-6" id="officerViewTabs">
<button class="tab tab-active" data-officer-tab="members"
>Member Management</button
>
<button class="tab" data-tab="events">Event Management</button>
<button class="tab" data-officer-tab="events">Event Management</button>
</div>
<div class="tab-contents">
<div id="membersTab" style="display: block;">
<div id="officerMembersTab" style="display: block;">
<MemberManagement />
</div>
<div id="eventsTab" style="display: none;">
<div id="officerEventsTab" style="display: none;">
<EventManagement />
</div>
</div>
</div>
<script>
const tabs = document.querySelectorAll("[data-tab]");
const membersTab = document.getElementById("membersTab");
const eventsTab = document.getElementById("eventsTab");
const officerTabs = document.querySelectorAll("[data-officer-tab]");
const officerMembersTab = document.getElementById("officerMembersTab");
const officerEventsTab = document.getElementById("officerEventsTab");
if (tabs && membersTab && eventsTab) {
tabs.forEach((tab) => {
if (officerTabs && officerMembersTab && officerEventsTab) {
officerTabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update tab styles
tabs.forEach((t) => t.classList.remove("tab-active"));
officerTabs.forEach((t) => t.classList.remove("tab-active"));
tab.classList.add("tab-active");
// Update content visibility
const tabId = (tab as HTMLElement).dataset.tab;
const tabId = (tab as HTMLElement).dataset.officerTab;
if (tabId === "members") {
membersTab.style.display = "block";
eventsTab.style.display = "none";
officerMembersTab.style.display = "block";
officerEventsTab.style.display = "none";
} else {
membersTab.style.display = "none";
eventsTab.style.display = "block";
officerMembersTab.style.display = "none";
officerEventsTab.style.display = "block";
}
});
});

View file

@ -1,12 +1,132 @@
<div class="space-y-6">
<div class="card bg-base-200 shadow-xl">
<div class="card-body">
<h2 class="card-title text-2xl mb-4">Account Settings</h2>
---
// Import the majors list
import allMajors from "../../data/allUCSDMajors.txt?raw";
const majorsList: string[] = allMajors
.split("\n")
.filter((major: string) => major.trim());
---
<div class="card bg-base-100 shadow-xl">
<div class="card-body p-6">
<!-- Header -->
<div class="flex items-center gap-4 mb-8">
<div class="flex-1">
<h2 class="card-title text-2xl">Profile Settings</h2>
<p class="text-base-content/70 mt-1">
Manage your academic information and preferences
</p>
</div>
<div class="flex-none">
<button class="btn btn-primary" id="saveSettings">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5 mr-2"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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>
Save Changes
</button>
</div>
</div>
<!-- Settings Sections -->
<div class="space-y-8">
<!-- Academic Information Section -->
<div class="bg-base-200 rounded-box p-6">
<div class="flex items-center gap-2 mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10.394 2.08a1 1 0 00-.788 0l-7 3a1 1 0 000 1.84L5.25 8.051a.999.999 0 01.356-.257l4-1.714a1 1 0 11.788 1.838L7.667 9.088l1.94.831a1 1 0 00.787 0l7-3a1 1 0 000-1.838l-7-3zM3.31 9.397L5 10.12v4.102a8.969 8.969 0 00-1.05-.174 1 1 0 01-.89-.89 11.115 11.115 0 01.25-3.762zM9.3 16.573A9.026 9.026 0 007 14.935v-3.957l1.818.78a3 3 0 002.364 0l5.508-2.361a11.026 11.026 0 01.25 3.762 1 1 0 01-.89.89 8.968 8.968 0 00-5.35 2.524 1 1 0 01-1.4 0zM6 18a1 1 0 001-1v-2.065a8.935 8.935 0 00-2-.712V17a1 1 0 001 1z"
></path>
</svg>
<h3 class="text-xl font-medium">Academic Information</h3>
</div>
<div class="grid gap-6">
<!-- Major Selection -->
<div class="form-control">
<label class="label">
<span class="label-text font-medium">Major</span>
<span class="label-text-alt text-base-content/70"
>Select your current major</span
>
</label>
<select
id="majorSelect"
class="select select-bordered w-full"
>
<option value="">Select your major</option>
{
majorsList.map((major: string) => (
<option value={major}>{major}</option>
))
}
</select>
</div>
<!-- Graduation Year -->
<div class="form-control">
<label class="label">
<span class="label-text font-medium"
>Expected Graduation</span
>
<span class="label-text-alt text-base-content/70"
>When do you plan to graduate?</span
>
</label>
<select
id="gradYearSelect"
class="select select-bordered w-full"
>
<option value="">Select graduation year</option>
{
Array.from({ length: 6 }, (_, i) => {
const year = new Date().getFullYear() + i;
return <option value={year}>{year}</option>;
})
}
</select>
</div>
</div>
</div>
<!-- IEEE Information Section -->
<div class="bg-base-200 rounded-box p-6">
<div class="flex items-center gap-2 mb-6">
<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="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>
<h3 class="text-xl font-medium">IEEE Membership</h3>
</div>
<div class="grid gap-6">
<!-- IEEE Member ID -->
<div class="form-control">
<label class="label">
<span class="label-text">IEEE Member ID</span>
<span class="label-text font-medium"
>IEEE Member ID</span
>
<span class="label-text-alt text-base-content/70"
>Your IEEE membership number</span
>
</label>
<input
type="text"
@ -16,50 +136,19 @@
/>
</div>
<!-- Major -->
<div class="form-control mt-4">
<!-- Resume Upload -->
<div class="form-control">
<label class="label">
<span class="label-text">Major</span>
</label>
<select id="majorSelect" class="select select-bordered w-full">
<option disabled selected>Select your major</option>
<option value="ECE"
>Electrical & Computer Engineering</option
<span class="label-text font-medium">Resume</span>
<span class="label-text-alt text-base-content/70"
>Upload your latest resume</span
>
<option value="CSE">Computer Science & Engineering</option>
<option value="MAE"
>Mechanical & Aerospace Engineering</option
>
<option value="BE">Bioengineering</option>
<option value="SE">Structural Engineering</option>
<option value="NANO">Nanoengineering</option>
<option value="CENG">Chemical Engineering</option>
<option value="OTHER">Other</option>
</select>
</div>
<!-- Expected Graduation Year -->
<div class="form-control mt-4">
<label class="label">
<span class="label-text">Expected Graduation Year</span>
</label>
<input
type="number"
id="gradYearInput"
min="2024"
max="2030"
placeholder="Enter graduation year"
class="input input-bordered w-full"
/>
</div>
<!-- Resume -->
<div class="form-control mt-4">
<label class="label">
<span class="label-text">Resume</span>
</label>
<div class="flex flex-col gap-2">
<p id="currentResume" class="text-sm opacity-70">
<p
id="currentResume"
class="text-sm text-base-content/70"
>
No resume uploaded
</p>
<input
@ -69,16 +158,62 @@
class="file-input file-input-bordered file-input-sm w-full"
/>
<label class="label">
<span id="uploadStatus" class="label-text-alt"></span>
<span id="uploadStatus" class="label-text-alt"
></span>
</label>
</div>
</div>
</div>
</div>
<!-- Save Button -->
<div class="flex justify-end">
<button class="btn btn-primary" id="saveSettings">Save Changes</button>
<!-- Notification Preferences Section -->
<div class="bg-base-200 rounded-box p-6">
<div class="flex items-center gap-2 mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-5 w-5"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
d="M10 2a6 6 0 00-6 6v3.586l-.707.707A1 1 0 004 14h12a1 1 0 00.707-1.707L16 11.586V8a6 6 0 00-6-6zM10 18a3 3 0 01-3-3h6a3 3 0 01-3 3z"
></path>
</svg>
<h3 class="text-xl font-medium">
Notification Preferences
</h3>
</div>
<div class="grid gap-4">
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium">Event Reminders</h3>
<p class="text-sm text-base-content/70">
Get notified about upcoming events
</p>
</div>
<input
type="checkbox"
class="toggle toggle-primary"
checked
/>
</div>
<div class="flex items-center justify-between">
<div>
<h3 class="font-medium">Newsletter</h3>
<p class="text-sm text-base-content/70">
Receive our monthly newsletter
</p>
</div>
<input
type="checkbox"
class="toggle toggle-primary"
checked
/>
</div>
</div>
</div>
</div>
</div>
</div>
@ -93,9 +228,9 @@
const majorSelect = document.getElementById(
"majorSelect"
) as HTMLSelectElement;
const gradYearInput = document.getElementById(
"gradYearInput"
) as HTMLInputElement;
const gradYearSelect = document.getElementById(
"gradYearSelect"
) as HTMLSelectElement;
const resumeUpload = document.getElementById(
"resumeUpload"
) as HTMLInputElement;
@ -108,11 +243,10 @@
const authState = auth.getAuthState();
if (authState.isValid && authState.model) {
const user = authState.model;
// Set current values
if (memberIdInput) memberIdInput.value = user.member_id || "";
if (majorSelect) majorSelect.value = user.major || "";
if (gradYearInput) gradYearInput.value = user.graduation_year || "";
if (gradYearSelect)
gradYearSelect.value = user.graduation_year || "";
// Update resume display
if (currentResume && user.resume) {
@ -143,19 +277,53 @@
// Handle save settings
if (saveSettings) {
saveSettings.addEventListener("click", async () => {
const button = saveSettings as HTMLButtonElement;
button.classList.add("loading");
try {
const data = {
member_id: memberIdInput?.value,
major: majorSelect?.value,
graduation_year: gradYearInput?.value,
graduation_year: gradYearSelect?.value,
};
await auth.updateProfileSettings(data);
alert("Settings saved successfully");
loadUserData(); // Reload the data to show updated values
// Show success toast
const toast = document.createElement("div");
toast.className =
"alert alert-success fixed bottom-4 right-4 w-auto z-50";
toast.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="M10 18a8 8 0 100-16 8 8 0 000 16zm3.707-9.293a1 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" />
</svg>
<span>Settings saved successfully!</span>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
loadUserData();
} catch (err) {
console.error("Failed to save settings:", err);
alert("Failed to save settings");
// Show error toast
const toast = document.createElement("div");
toast.className =
"alert alert-error fixed bottom-4 right-4 w-auto z-50";
toast.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="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd" />
</svg>
<span>Failed to save settings. Please try again.</span>
</div>
`;
document.body.appendChild(toast);
setTimeout(() => toast.remove(), 3000);
} finally {
button.classList.remove("loading");
}
});
}

View file

@ -8,16 +8,10 @@ const title = "User Profile";
---
<Layout {title}>
<main class="min-h-screen bg-base-100/50">
<div class="container mx-auto px-4 py-8">
<main class="min-h-screen bg-base-100/50 rounded-[1.5rem]">
<div class="container mx-auto px-6 py-6 mt-10">
<!-- 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
@ -108,7 +102,7 @@ const title = "User Profile";
>
<!-- Left Column - User Info -->
<div class="xl:col-span-4 2xl:col-span-3">
<div class="sticky top-8">
<div class="sticky top-[100px]">
<UserProfile />
</div>
</div>
@ -194,14 +188,12 @@ const title = "User Profile";
</div>
<!-- Main Content Tabs -->
<div class="tabs tabs-boxed mb-6">
<button class="tab tab-active" data-tab="events"
<div class="tabs tabs-boxed mb-6" id="defaultViewTabs">
<button class="tab tab-active" data-default-tab="events"
>Events & Activities</button
>
<button class="tab" data-tab="settings">Settings</button
>
<button class="tab" data-tab="officer" id="officerTab"
>Officer Panel</button
<button class="tab" data-default-tab="settings"
>Settings</button
>
</div>
@ -340,23 +332,22 @@ const title = "User Profile";
}
});
// Handle tab switching
tabs.forEach((tab) => {
// Handle default view tab switching
const defaultViewTabs = document.querySelectorAll("[data-default-tab]");
defaultViewTabs.forEach((tab) => {
tab.addEventListener("click", () => {
// Update tab styles
tabs.forEach((t) => t.classList.remove("tab-active"));
defaultViewTabs.forEach((t) => t.classList.remove("tab-active"));
tab.classList.add("tab-active");
// Update content visibility
const tabId = (tab as HTMLElement).dataset.tab;
const tabId = (tab as HTMLElement).dataset.defaultTab;
const defaultView = document.getElementById("defaultView");
const settingsView = document.getElementById("settingsView");
const officerView = document.getElementById("officerView");
if (defaultView && settingsView && officerView) {
if (defaultView && settingsView) {
defaultView.classList.add("hidden");
settingsView.classList.add("hidden");
officerView.classList.add("hidden");
switch (tabId) {
case "events":
@ -365,9 +356,6 @@ const title = "User Profile";
case "settings":
settingsView.classList.remove("hidden");
break;
case "officer":
officerView.classList.remove("hidden");
break;
}
}
});