move settings around

This commit is contained in:
chark1es 2025-02-01 03:56:10 -08:00
parent eea944fcd2
commit 49e1b00586
4 changed files with 380 additions and 382 deletions

View file

@ -85,41 +85,20 @@ interface AuthElements {
memberStatus: HTMLDivElement;
lastLogin: HTMLParagraphElement;
storeContent: HTMLDivElement;
resumeUpload: HTMLInputElement;
resumeName: HTMLParagraphElement;
resumeDownload: HTMLAnchorElement;
deleteResume: HTMLButtonElement;
uploadStatus: HTMLParagraphElement;
resumeActions: HTMLDivElement;
memberIdInput: HTMLInputElement;
saveMemberId: HTMLButtonElement;
memberIdStatus: HTMLParagraphElement;
officerViewToggle: HTMLDivElement;
officerViewCheckbox: HTMLInputElement;
officerContent: HTMLDivElement;
resumeList: HTMLTableSectionElement;
refreshResumes: HTMLButtonElement;
resumeSearch: HTMLInputElement;
searchResumes: HTMLButtonElement;
profileEditor: HTMLDialogElement;
editorName: HTMLInputElement;
editorEmail: HTMLInputElement;
editorMemberId: HTMLInputElement;
editorPoints: HTMLInputElement;
editorResume: HTMLInputElement;
editorCurrentResume: HTMLParagraphElement;
saveProfileButton: HTMLButtonElement;
sponsorViewToggle: HTMLDivElement;
pdfViewer: HTMLDialogElement;
pdfFrame: HTMLIFrameElement;
pdfTitle: HTMLHeadingElement;
pdfExternalLink: HTMLAnchorElement;
}
export class StoreAuth {
private pb: PocketBase;
private elements: AuthElements & { loadingSkeleton: HTMLDivElement };
private isEditingMemberId: boolean = false;
private cachedUsers: any[] = [];
private config = config;
@ -137,6 +116,43 @@ export class StoreAuth {
};
}
// Public method to handle login
public async handleLogin() {
try {
const authMethods = await this.pb.collection("users").listAuthMethods();
const oidcProvider = authMethods.oauth2?.providers?.find(
(p: { name: string }) => p.name === this.config.api.oauth2.providerName
);
if (!oidcProvider) {
throw new Error("OIDC provider not found");
}
localStorage.setItem("provider", JSON.stringify(oidcProvider));
const redirectUrl = window.location.origin + this.config.api.oauth2.redirectPath;
const authUrl = oidcProvider.authURL + encodeURIComponent(redirectUrl);
window.location.href = authUrl;
} catch (err) {
console.error("Authentication error:", err);
this.elements.userEmail.textContent = this.config.ui.messages.auth.loginError;
this.elements.userName.textContent = "Error";
throw err;
}
}
// Public method to update profile settings
public async updateProfileSettings(data: {
major?: string;
graduation_year?: string | number;
}) {
const user = this.pb.authStore.model;
if (!user?.id) {
throw new Error("User ID not found");
}
return await this.pb.collection("users").update(user.id, data);
}
private getElements(): AuthElements & { loadingSkeleton: HTMLDivElement } {
// Get all required elements
const loginButton = document.getElementById("contentLoginButton") as HTMLButtonElement;
@ -148,47 +164,27 @@ export class StoreAuth {
const memberStatus = document.getElementById("memberStatus") as HTMLDivElement;
const lastLogin = document.getElementById("lastLogin") as HTMLParagraphElement;
const storeContent = document.getElementById("storeContent") as HTMLDivElement;
const resumeUpload = document.getElementById("resumeUpload") as HTMLInputElement;
const resumeName = document.getElementById("resumeName") as HTMLParagraphElement;
const resumeDownload = document.getElementById("resumeDownload") as HTMLAnchorElement;
const deleteResume = document.getElementById("deleteResume") as HTMLButtonElement;
const uploadStatus = document.getElementById("uploadStatus") as HTMLParagraphElement;
const resumeActions = document.getElementById("resumeActions") as HTMLDivElement;
const memberIdInput = document.getElementById("memberIdInput") as HTMLInputElement;
const saveMemberId = document.getElementById("saveMemberId") as HTMLButtonElement;
const memberIdStatus = document.getElementById("memberIdStatus") as HTMLParagraphElement;
const officerViewToggle = document.getElementById("officerViewToggle") as HTMLDivElement;
const officerViewCheckbox = officerViewToggle?.querySelector('input[type="checkbox"]') as HTMLInputElement;
const officerContent = document.getElementById("officerContent") as HTMLDivElement;
const resumeList = document.getElementById("resumeList") as HTMLTableSectionElement;
const refreshResumes = document.getElementById("refreshResumes") as HTMLButtonElement;
const resumeSearch = document.getElementById("resumeSearch") as HTMLInputElement;
const searchResumes = document.getElementById("searchResumes") as HTMLButtonElement;
const profileEditor = document.getElementById("profileEditor") as HTMLDialogElement;
const editorName = document.getElementById("editorName") as HTMLInputElement;
const editorEmail = document.getElementById("editorEmail") as HTMLInputElement;
const editorMemberId = document.getElementById("editorMemberId") as HTMLInputElement;
const editorPoints = document.getElementById("editorPoints") as HTMLInputElement;
const editorResume = document.getElementById("editorResume") as HTMLInputElement;
const editorCurrentResume = document.getElementById("editorCurrentResume") as HTMLParagraphElement;
const saveProfileButton = document.getElementById("saveProfileButton") as HTMLButtonElement;
const sponsorViewToggle = document.getElementById("sponsorViewToggle") as HTMLDivElement;
const pdfViewer = document.getElementById("pdfViewer") as HTMLDialogElement;
const pdfFrame = document.getElementById("pdfFrame") as HTMLIFrameElement;
const pdfTitle = document.getElementById("pdfTitle") as HTMLHeadingElement;
const pdfExternalLink = document.getElementById("pdfExternalLink") as HTMLAnchorElement;
// Add CSS for loading state transitions
const style = document.createElement("style");
style.textContent = `
.loading-state {
opacity: 0.5;
transition: opacity 0.3s ease-in-out;
}
.content-ready {
opacity: 1;
}
`;
.loading-state {
opacity: 0.5;
transition: opacity 0.3s ease-in-out;
}
.content-ready {
opacity: 1;
}
`;
document.head.appendChild(style);
return {
@ -201,35 +197,15 @@ export class StoreAuth {
memberStatus,
lastLogin,
storeContent,
resumeUpload,
resumeName,
resumeDownload,
deleteResume,
uploadStatus,
resumeActions,
memberIdInput,
saveMemberId,
memberIdStatus,
officerViewToggle,
officerViewCheckbox,
officerContent,
resumeList,
refreshResumes,
resumeSearch,
searchResumes,
profileEditor,
editorName,
editorEmail,
editorMemberId,
editorPoints,
editorResume,
editorCurrentResume,
saveProfileButton,
sponsorViewToggle,
pdfViewer,
pdfFrame,
pdfTitle,
pdfExternalLink
sponsorViewToggle
};
}
@ -241,20 +217,6 @@ export class StoreAuth {
this.elements.loginButton.addEventListener("click", () => this.handleLogin());
this.elements.logoutButton.addEventListener("click", () => this.handleLogout());
// Resume upload event listener
this.elements.resumeUpload.addEventListener("change", (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
this.handleResumeUpload(file);
}
});
// Resume delete event listener
this.elements.deleteResume.addEventListener("click", () => this.handleResumeDelete());
// Member ID save event listener
this.elements.saveMemberId.addEventListener("click", () => this.handleMemberIdButton());
// Listen for auth state changes
this.pb.authStore.onChange(() => {
console.log("Auth state changed. IsValid:", this.pb.authStore.isValid);
@ -276,11 +238,6 @@ export class StoreAuth {
e.preventDefault();
this.handleProfileSave();
});
// Add resume view event listener
document.addEventListener('viewResume', ((e: CustomEvent) => {
this.handleResumeView(e.detail.url, e.detail.fileName);
}) as EventListener);
}
private async updateUI() {
@ -292,12 +249,6 @@ export class StoreAuth {
userEmail,
memberStatus,
lastLogin,
memberIdInput,
saveMemberId,
resumeUpload,
resumeName,
resumeDownload,
resumeActions,
loadingSkeleton,
officerViewToggle,
sponsorViewToggle,
@ -343,36 +294,11 @@ export class StoreAuth {
memberStatus.classList.add("badge-neutral");
}
// Update member ID input and state
memberIdInput.value = user.member_id || "";
memberIdInput.disabled = false;
saveMemberId.disabled = false;
// Update last login
lastLogin.textContent = user.last_login
? new Date(user.last_login).toLocaleString()
: this.config.ui.messages.auth.never;
// Update resume section
resumeUpload.disabled = false;
if (user.resume && (!Array.isArray(user.resume) || user.resume.length > 0)) {
const resumeUrl = user.resume.toString();
const fileName = this.getFileNameFromUrl(resumeUrl);
resumeName.textContent = fileName;
const fullUrl = this.pb.files.getURL(user, resumeUrl);
resumeDownload.href = "#";
resumeDownload.onclick = (e) => {
e.preventDefault();
this.handleResumeView(fullUrl, fileName);
};
resumeActions.style.display = "flex";
} else {
resumeName.textContent = "No resume uploaded";
resumeDownload.href = "#";
resumeDownload.onclick = null;
resumeActions.style.display = "none";
}
// Show/hide view toggles
officerViewToggle.style.display = isOfficer ? "block" : "none";
sponsorViewToggle.style.display = isSponsor ? "block" : "none";
@ -395,21 +321,6 @@ export class StoreAuth {
memberStatus.classList.add("badge-neutral");
lastLogin.textContent = this.config.ui.messages.auth.never;
// Disable inputs
memberIdInput.disabled = true;
saveMemberId.disabled = true;
resumeUpload.disabled = true;
// Reset member ID
memberIdInput.value = "";
this.isEditingMemberId = false;
// Reset resume section
resumeName.textContent = "No resume uploaded";
resumeDownload.href = "#";
resumeDownload.onclick = null;
resumeActions.style.display = "none";
// Hide view toggles
officerViewToggle.style.display = "none";
sponsorViewToggle.style.display = "none";
@ -423,148 +334,18 @@ export class StoreAuth {
}
}
private async handleLogin() {
try {
const authMethods = await this.pb.collection("users").listAuthMethods();
const oidcProvider = authMethods.oauth2?.providers?.find(
(p: { name: string }) => p.name === this.config.api.oauth2.providerName
);
if (!oidcProvider) {
throw new Error("OIDC provider not found");
}
localStorage.setItem("provider", JSON.stringify(oidcProvider));
const redirectUrl = window.location.origin + this.config.api.oauth2.redirectPath;
const authUrl = oidcProvider.authURL + encodeURIComponent(redirectUrl);
window.location.href = authUrl;
} catch (err) {
console.error("Authentication error:", err);
this.elements.userEmail.textContent = this.config.ui.messages.auth.loginError;
this.elements.userName.textContent = "Error";
}
}
private handleLogout() {
this.pb.authStore.clear();
this.cachedUsers = [];
this.isEditingMemberId = false;
this.updateUI();
}
private getFileNameFromUrl(url: string): string {
try {
const urlObj = new URL(url);
const pathParts = urlObj.pathname.split("/");
return decodeURIComponent(pathParts[pathParts.length - 1]);
} catch (e) {
return url.split("/").pop() || "Unknown File";
}
}
private handleResumeView(url: string, fileName: string) {
const { pdfViewer, pdfFrame, pdfTitle, pdfExternalLink } = this.elements;
pdfFrame.src = url;
pdfTitle.textContent = fileName;
pdfExternalLink.href = url;
pdfViewer.showModal();
}
private async handleResumeUpload(file: File) {
const { uploadStatus } = this.elements;
try {
const user = this.pb.authStore.model;
if (!user?.id) {
throw new Error("User ID not found");
}
const formData = new FormData();
formData.append("resume", file);
await this.pb.collection("users").update(user.id, formData);
uploadStatus.textContent = this.config.ui.messages.resume.success;
this.updateUI();
setTimeout(() => {
uploadStatus.textContent = "";
}, this.config.ui.messages.resume.messageTimeout);
} catch (err) {
console.error("Resume upload error:", err);
uploadStatus.textContent = this.config.ui.messages.resume.error;
}
}
private async handleResumeDelete() {
const { uploadStatus } = this.elements;
try {
const user = this.pb.authStore.model;
if (!user?.id) {
throw new Error("User ID not found");
}
await this.pb.collection("users").update(user.id, {
resume: null
});
uploadStatus.textContent = this.config.ui.messages.resume.deleteSuccess;
this.updateUI();
setTimeout(() => {
uploadStatus.textContent = "";
}, this.config.ui.messages.resume.messageTimeout);
} catch (err) {
console.error("Resume deletion error:", err);
uploadStatus.textContent = this.config.ui.messages.resume.deleteError;
}
}
private async handleMemberIdButton() {
const user = this.pb.authStore.model;
if (user?.member_id && !this.isEditingMemberId) {
this.isEditingMemberId = true;
this.updateUI();
} else {
await this.handleMemberIdSave();
}
}
private async handleMemberIdSave() {
const { memberIdInput, memberIdStatus } = this.elements;
const memberId = memberIdInput.value.trim();
try {
memberIdStatus.textContent = this.config.ui.messages.memberId.saving;
const user = this.pb.authStore.model;
if (!user?.id) {
throw new Error("User ID not found");
}
await this.pb.collection("users").update(user.id, {
member_id: memberId
});
memberIdStatus.textContent = this.config.ui.messages.memberId.success;
this.isEditingMemberId = false;
this.updateUI();
setTimeout(() => {
memberIdStatus.textContent = "";
}, this.config.ui.messages.memberId.messageTimeout);
} catch (err) {
console.error("IEEE Member ID save error:", err);
memberIdStatus.textContent = this.config.ui.messages.memberId.error;
}
}
private async handleProfileSave() {
const {
profileEditor,
editorName,
editorEmail,
editorMemberId,
editorPoints,
editorResume,
saveProfileButton,
} = this.elements;
const userId = saveProfileButton.dataset.userId;
@ -575,23 +356,11 @@ export class StoreAuth {
}
try {
// First get the current user data to check existing resume
const currentUser = await this.pb.collection("users").getOne(userId);
const formData = new FormData();
formData.append("name", editorName.value);
formData.append("email", editorEmail.value);
formData.append("member_id", editorMemberId.value);
formData.append("points", editorPoints.value);
// Only append resume if a new file is selected
if (editorResume.files && editorResume.files.length > 0) {
formData.append("resume", editorResume.files[0]);
} else if (currentUser.resume) {
// If no new file but there's an existing resume, keep it
formData.append("resume", currentUser.resume);
}
await this.pb.collection("users").update(userId, formData);
profileEditor.close();
this.updateUI();

View file

@ -127,104 +127,11 @@
</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"
class="login-button btn btn-primary w-full gap-2 hidden"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -241,7 +148,7 @@
</button>
<button
id="contentLogoutButton"
class="logout-button btn btn-error w-full gap-2"
class="logout-button btn btn-error w-full gap-2 hidden"
>
<svg
xmlns="http://www.w3.org/2000/svg"
@ -409,15 +316,55 @@
// Initialize auth and event check-in
document.addEventListener("DOMContentLoaded", () => {
try {
new StoreAuth();
const auth = new StoreAuth();
new EventCheckIn();
// Add login button event listener
const loginButtons = document.querySelectorAll(".login-button");
loginButtons.forEach((button) => {
button.addEventListener("click", () => {
const loadingSkeleton =
document.getElementById("loadingSkeleton");
const userInfo = document.getElementById("userInfo");
// Show loading state
if (loadingSkeleton)
loadingSkeleton.style.display = "block";
if (userInfo) userInfo.classList.add("hidden");
// Call the handleLogin method
auth.handleLogin().catch((error) => {
console.error("Login error:", error);
// Show error message
if (loadingSkeleton)
loadingSkeleton.style.display = "none";
if (userInfo) {
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 sign in. Please try again.</span>
</div>
`;
userInfo.innerHTML = "";
userInfo.appendChild(errorMessage);
userInfo.classList.remove("hidden");
userInfo.style.opacity = "1";
}
});
});
});
// 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 memberDetails = document.getElementById("memberDetails");
const errorMessage = document.createElement("div");
errorMessage.className = "alert alert-error";
@ -432,12 +379,32 @@
if (loadingSkeleton) loadingSkeleton.style.display = "none";
if (userInfo) {
userInfo.innerHTML = "";
if (memberDetails) memberDetails.style.display = "none";
userInfo.appendChild(errorMessage);
userInfo.classList.remove("hidden");
userInfo.style.opacity = "1";
}
});
// Check auth state and update UI accordingly
const authState = auth.getAuthState();
if (!authState.isValid || !authState.model) {
const userInfo = document.getElementById("userInfo");
const loadingSkeleton =
document.getElementById("loadingSkeleton");
const memberDetails = document.getElementById("memberDetails");
const contentLoginButton =
document.getElementById("contentLoginButton");
if (loadingSkeleton) loadingSkeleton.style.display = "none";
if (memberDetails) memberDetails.style.display = "none";
if (contentLoginButton)
contentLoginButton.classList.remove("hidden");
if (userInfo) {
userInfo.classList.remove("hidden");
userInfo.style.opacity = "1";
}
}
} catch (error) {
console.error("Failed to initialize profile:", error);
}
@ -478,7 +445,7 @@
});
}
// Set member since date (you'll need to modify StoreAuth.ts to include this)
// Set member since date
if (memberSinceElement) {
const created = new Date(); // Replace with actual user creation date
memberSinceElement.textContent = created.toLocaleDateString();

View file

@ -0,0 +1,172 @@
<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>
<!-- IEEE Member ID -->
<div class="form-control">
<label class="label">
<span class="label-text">IEEE Member ID</span>
</label>
<input
type="text"
id="memberIdInput"
placeholder="Enter your IEEE Member ID"
class="input input-bordered w-full"
/>
</div>
<!-- Major -->
<div class="form-control mt-4">
<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
>
<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">
No resume uploaded
</p>
<input
type="file"
id="resumeUpload"
accept=".pdf,.doc,.docx"
class="file-input file-input-bordered file-input-sm w-full"
/>
<label class="label">
<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>
</div>
</div>
<script>
import { StoreAuth } from "../auth/StoreAuth";
const auth = new StoreAuth();
// Get form elements
const memberIdInput = document.getElementById(
"memberIdInput"
) as HTMLInputElement;
const majorSelect = document.getElementById(
"majorSelect"
) as HTMLSelectElement;
const gradYearInput = document.getElementById(
"gradYearInput"
) as HTMLInputElement;
const resumeUpload = document.getElementById(
"resumeUpload"
) as HTMLInputElement;
const currentResume = document.getElementById("currentResume");
const uploadStatus = document.getElementById("uploadStatus");
const saveSettings = document.getElementById("saveSettings");
// Load current user data
const loadUserData = () => {
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 || "";
// Update resume display
if (currentResume && user.resume) {
const fileName = user.resume.toString().split("/").pop();
currentResume.textContent = fileName || "Resume uploaded";
}
}
};
// Handle resume upload
if (resumeUpload && uploadStatus) {
resumeUpload.addEventListener("change", async (e) => {
const file = (e.target as HTMLInputElement).files?.[0];
if (file) {
uploadStatus.textContent = "Uploading...";
try {
await auth.handleResumeUpload(file);
uploadStatus.textContent = "Resume uploaded successfully";
if (currentResume) currentResume.textContent = file.name;
} catch (err) {
console.error("Resume upload error:", err);
uploadStatus.textContent = "Failed to upload resume";
}
}
});
}
// Handle save settings
if (saveSettings) {
saveSettings.addEventListener("click", async () => {
try {
const data = {
member_id: memberIdInput?.value,
major: majorSelect?.value,
graduation_year: gradYearInput?.value,
};
await auth.updateProfileSettings(data);
alert("Settings saved successfully");
loadUserData(); // Reload the data to show updated values
} catch (err) {
console.error("Failed to save settings:", err);
alert("Failed to save settings");
}
});
}
// Load initial data
loadUserData();
// Update when auth state changes
window.addEventListener("storage", (e) => {
if (e.key === "pocketbase_auth") {
loadUserData();
}
});
</script>

View file

@ -3,6 +3,7 @@ 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";
import UserSettings from "../components/profile/UserSettings.astro";
const title = "User Profile";
---
@ -56,6 +57,50 @@ const title = "User Profile";
</div>
</div>
<!-- Not Authenticated State -->
<div id="notAuthenticatedState" class="hidden w-full">
<div class="card bg-base-100 shadow-xl">
<div class="card-body items-center text-center">
<div class="mb-6">
<svg
xmlns="http://www.w3.org/2000/svg"
class="h-16 w-16 text-base-content/30"
viewBox="0 0 20 20"
fill="currentColor"
>
<path
fill-rule="evenodd"
d="M10 9a3 3 0 100-6 3 3 0 000 6zm-7 9a7 7 0 1114 0H3z"
clip-rule="evenodd"></path>
</svg>
</div>
<h2 class="card-title text-2xl mb-2">
Sign in to Access Your Profile
</h2>
<p class="text-base-content/70 mb-6">
Please sign in with your IEEE UCSD account to view
and manage your profile.
</p>
<button
class="login-button btn btn-primary btn-lg 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>
</div>
</div>
</div>
<!-- Main Content Grid -->
<div
id="mainContent"
@ -217,6 +262,9 @@ const title = "User Profile";
<div id="defaultView">
<DefaultProfileView />
</div>
<div id="settingsView" class="hidden">
<UserSettings />
</div>
<div id="officerView" class="hidden">
<OfficerProfileView />
</div>
@ -233,6 +281,9 @@ const title = "User Profile";
// Initialize page state
const pageLoadingState = document.getElementById("pageLoadingState");
const pageErrorState = document.getElementById("pageErrorState");
const notAuthenticatedState = document.getElementById(
"notAuthenticatedState"
);
const mainContent = document.getElementById("mainContent");
const tabs = document.querySelectorAll(".tab");
const defaultView = document.getElementById("defaultView");
@ -273,12 +324,18 @@ const title = "User Profile";
// Show loading state
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (pageErrorState) pageErrorState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.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");
// Show not authenticated state
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.remove("hidden");
return;
}
const user = authState.model;
@ -350,12 +407,26 @@ const title = "User Profile";
// 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";
const defaultView = document.getElementById("defaultView");
const settingsView = document.getElementById("settingsView");
const officerView = document.getElementById("officerView");
if (defaultView && settingsView && officerView) {
defaultView.classList.add("hidden");
settingsView.classList.add("hidden");
officerView.classList.add("hidden");
switch (tabId) {
case "events":
defaultView.classList.remove("hidden");
break;
case "settings":
settingsView.classList.remove("hidden");
break;
case "officer":
officerView.classList.remove("hidden");
break;
}
}
});
});
@ -389,6 +460,25 @@ const title = "User Profile";
}
});
}
// Add login button event listener
const loginButtons = document.querySelectorAll(".login-button");
loginButtons.forEach((button) => {
button.addEventListener("click", () => {
// Show loading state while authentication is in progress
if (pageLoadingState) pageLoadingState.classList.remove("hidden");
if (notAuthenticatedState)
notAuthenticatedState.classList.add("hidden");
// Call the handleLogin method from StoreAuth
auth.handleLogin().catch((error) => {
console.error("Login error:", error);
// Show error state if login fails
if (pageLoadingState) pageLoadingState.classList.add("hidden");
if (pageErrorState) pageErrorState.classList.remove("hidden");
});
});
});
</script>
<style>