diff --git a/src/components/auth/StoreAuth.ts b/src/components/auth/StoreAuth.ts index d06876d..825cfa7 100644 --- a/src/components/auth/StoreAuth.ts +++ b/src/components/auth/StoreAuth.ts @@ -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(); diff --git a/src/components/auth/UserProfile.astro b/src/components/auth/UserProfile.astro index 20d6492..66bccc8 100644 --- a/src/components/auth/UserProfile.astro +++ b/src/components/auth/UserProfile.astro @@ -127,104 +127,11 @@ - -
- -
- -
- - -
- -
- - -
- -
-
-

- No resume uploaded -

-
- View - -
-
-
- - -
-
-
-
-
+
+ + + diff --git a/src/pages/profile.astro b/src/pages/profile.astro index 4983c30..d223c8f 100644 --- a/src/pages/profile.astro +++ b/src/pages/profile.astro @@ -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"; + + +
+ @@ -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"); + }); + }); + });