diff --git a/src/components/auth/StoreAuth.ts b/src/components/auth/StoreAuth.ts index 2cf3e20..d06876d 100644 --- a/src/components/auth/StoreAuth.ts +++ b/src/components/auth/StoreAuth.ts @@ -120,7 +120,7 @@ export class StoreAuth { private pb: PocketBase; private elements: AuthElements & { loadingSkeleton: HTMLDivElement }; private isEditingMemberId: boolean = false; - private cachedUsers: any[] = []; // Store users data + private cachedUsers: any[] = []; private config = config; constructor() { @@ -129,177 +129,73 @@ export class StoreAuth { this.init(); } + // Public method to get auth state + public getAuthState() { + return { + isValid: this.pb.authStore.isValid, + model: this.pb.authStore.model + }; + } + private getElements(): AuthElements & { loadingSkeleton: HTMLDivElement } { - // Fun typescript fixes - const loginButton = document.getElementById( - "contentLoginButton", - ) as HTMLButtonElement; - const logoutButton = document.getElementById( - "contentLogoutButton", - ) as HTMLButtonElement; + // Get all required elements + const loginButton = document.getElementById("contentLoginButton") as HTMLButtonElement; + const logoutButton = document.getElementById("contentLogoutButton") as HTMLButtonElement; const userInfo = document.getElementById("userInfo") as HTMLDivElement; - const loadingSkeleton = document.getElementById( - "loadingSkeleton", - ) as HTMLDivElement; - - // 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; - } - `; - document.head.appendChild(style); - - const userName = document.getElementById( - "userName", - ) as HTMLParagraphElement; - const userEmail = document.getElementById( - "userEmail", - ) as HTMLParagraphElement; - 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 loadingSkeleton = document.getElementById("loadingSkeleton") as HTMLDivElement; + const userName = document.getElementById("userName") as HTMLParagraphElement; + const userEmail = document.getElementById("userEmail") as HTMLParagraphElement; + 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; - if ( - !loginButton || - !logoutButton || - !userInfo || - !storeContent || - !userName || - !userEmail || - !memberStatus || - !lastLogin || - !resumeUpload || - !resumeName || - !loadingSkeleton || - !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 - ) { - throw new Error("Required DOM elements not found"); - } + // 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; + } + `; + document.head.appendChild(style); return { loginButton, logoutButton, userInfo, + loadingSkeleton, userName, userEmail, memberStatus, @@ -307,7 +203,6 @@ export class StoreAuth { storeContent, resumeUpload, resumeName, - loadingSkeleton, resumeDownload, deleteResume, uploadStatus, @@ -334,28 +229,58 @@ export class StoreAuth { pdfViewer, pdfFrame, pdfTitle, - pdfExternalLink, + pdfExternalLink }; } - private updateMemberIdState() { - const { memberIdInput, saveMemberId } = this.elements; - const user = this.pb.authStore.model; + private async init() { + // Initial UI update with loading state + await this.updateUI(); - if (user?.member_id && !this.isEditingMemberId) { - // Has member ID and not editing - show update button and disable input - memberIdInput.disabled = true; - memberIdInput.value = user.member_id; - saveMemberId.textContent = "Update"; - saveMemberId.classList.remove("enabled:btn-primary"); - saveMemberId.classList.add("enabled:btn-ghost", "enabled:btn-outline"); - } else { - // No member ID or editing - show save button and enable input - memberIdInput.disabled = false; - saveMemberId.textContent = "Save"; - saveMemberId.classList.remove("enabled:btn-ghost", "enabled:btn-outline"); - saveMemberId.classList.add("enabled:btn-primary"); - } + // Setup event listeners + 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); + this.updateUI(); + }); + + // Profile editor event listeners + const { profileEditor, saveProfileButton } = this.elements; + + // Close dialog when clicking outside + profileEditor.addEventListener("click", (e) => { + if (e.target === profileEditor) { + profileEditor.close(); + } + }); + + // Save profile button + saveProfileButton.addEventListener("click", (e) => { + 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() { @@ -367,16 +292,14 @@ export class StoreAuth { userEmail, memberStatus, lastLogin, - storeContent, - resumeName, - resumeDownload, - resumeActions, memberIdInput, saveMemberId, resumeUpload, + resumeName, + resumeDownload, + resumeActions, loadingSkeleton, officerViewToggle, - officerContent, sponsorViewToggle, } = this.elements; @@ -392,7 +315,6 @@ export class StoreAuth { // Show logout buttons for authenticated users allLogoutButtons.forEach(btn => btn.classList.remove("hidden")); - // Update all the user information first const user = this.pb.authStore.model; const isSponsor = user.member_type === this.config.roles.sponsor.name; const isOfficer = [ @@ -403,101 +325,37 @@ export class StoreAuth { userName.textContent = user.name || this.config.ui.messages.auth.notProvided; userEmail.textContent = user.email || this.config.ui.messages.auth.notAvailable; - // Hide member ID and resume sections for sponsors - const memberIdSection = memberIdInput.closest('.space-y-1') as HTMLElement; - const resumeSection = resumeUpload.closest('.space-y-2')?.parentElement as HTMLElement; - const memberIdDivider = memberIdSection?.nextElementSibling as HTMLElement; - const resumeDivider = resumeSection?.nextElementSibling as HTMLElement; - - if (isSponsor) { - // Hide member ID and resume sections for sponsors - if (memberIdSection) memberIdSection.style.display = 'none'; - if (memberIdDivider) memberIdDivider.style.display = 'none'; - if (resumeSection) resumeSection.style.display = 'none'; - if (resumeDivider) resumeDivider.style.display = 'none'; - } else { - // Show and enable member ID input and save button for non-sponsors - if (memberIdSection) memberIdSection.style.display = ''; - if (memberIdDivider) memberIdDivider.style.display = ''; - if (resumeSection) resumeSection.style.display = ''; - if (resumeDivider) resumeDivider.style.display = ''; + // Update member status badge + if (user.member_type) { + memberStatus.textContent = user.member_type; + memberStatus.classList.remove("badge-neutral"); - memberIdInput.disabled = false; - saveMemberId.disabled = false; - resumeUpload.disabled = false; - } - - // Update member status - if (user.verified) { - // Check and update member_type if not set - if (!user.member_type) { - try { - const isIeeeOfficer = user.email?.toLowerCase().endsWith(this.config.autoDetection.officer.emailDomain) || false; - const newMemberType = isIeeeOfficer - ? this.config.roles.officer.name - : this.config.roles.member.name; - - await this.pb.collection("users").update(user.id, { - member_type: newMemberType, - }); - - user.member_type = newMemberType; - } catch (err) { - console.error("Failed to update member type:", err); - } - } - - memberStatus.textContent = user.member_type || this.config.roles.member.name; - memberStatus.classList.remove( - "badge-neutral", - "badge-success", - "badge-warning", - "badge-info", - "badge-error", - ); - - // Set color based on member type - const role = Object.values(this.config.roles).find(r => r.name === user.member_type); - if (role) { - memberStatus.classList.add(role.badge); + if (isOfficer) { + memberStatus.classList.add("badge-primary"); + } else if (isSponsor) { + memberStatus.classList.add("badge-warning"); } else { - memberStatus.classList.add(this.config.roles.member.badge); - } - - // Handle view toggles visibility - officerViewToggle.style.display = isOfficer ? "block" : "none"; - sponsorViewToggle.style.display = isSponsor ? "block" : "none"; - - // If user is an officer or sponsor, preload the table data - if (isOfficer || isSponsor) { - await this.fetchUserResumes(); + memberStatus.classList.add("badge-info"); } } else { memberStatus.textContent = this.config.ui.messages.auth.notVerified; - memberStatus.classList.remove( - "badge-info", - "badge-warning", - "badge-success", - "badge-error", - ); + memberStatus.classList.remove("badge-info", "badge-warning", "badge-primary"); memberStatus.classList.add("badge-neutral"); } // Update member ID input and state memberIdInput.value = user.member_id || ""; - this.updateMemberIdState(); + memberIdInput.disabled = false; + saveMemberId.disabled = false; // Update last login - const lastLoginDate = user.last_login + lastLogin.textContent = user.last_login ? new Date(user.last_login).toLocaleString() : this.config.ui.messages.auth.never; - lastLogin.textContent = lastLoginDate; // Update resume section - if ( - user.resume && - (!Array.isArray(user.resume) || user.resume.length > 0) - ) { + 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; @@ -515,44 +373,36 @@ export class StoreAuth { resumeActions.style.display = "none"; } + // Show/hide view toggles + officerViewToggle.style.display = isOfficer ? "block" : "none"; + sponsorViewToggle.style.display = isSponsor ? "block" : "none"; + // After everything is updated, show the content loadingSkeleton.style.display = "none"; userInfo.classList.remove("hidden"); - // Use a small delay to ensure the transition works setTimeout(() => { userInfo.style.opacity = "1"; }, 50); - - officerViewToggle.style.display = isOfficer ? "block" : "none"; - sponsorViewToggle.style.display = isSponsor ? "block" : "none"; } else { // Show login buttons for unauthenticated users allLoginButtons.forEach(btn => btn.classList.remove("hidden")); - // Update for logged out state + // Reset all fields to default state userName.textContent = this.config.ui.messages.auth.notSignedIn; userEmail.textContent = this.config.ui.messages.auth.notSignedIn; memberStatus.textContent = this.config.ui.messages.auth.notVerified; - memberStatus.classList.remove( - "badge-info", - "badge-warning", - "badge-success", - "badge-error", - ); + memberStatus.classList.remove("badge-info", "badge-warning", "badge-primary"); memberStatus.classList.add("badge-neutral"); lastLogin.textContent = this.config.ui.messages.auth.never; - // Disable member ID input and save button + // Disable inputs memberIdInput.disabled = true; saveMemberId.disabled = true; - - // Disable resume upload resumeUpload.disabled = true; // Reset member ID memberIdInput.value = ""; this.isEditingMemberId = false; - this.updateMemberIdState(); // Reset resume section resumeName.textContent = "No resume uploaded"; @@ -560,19 +410,48 @@ export class StoreAuth { resumeDownload.onclick = null; resumeActions.style.display = "none"; - // After everything is updated, show the content + // Hide view toggles + officerViewToggle.style.display = "none"; + sponsorViewToggle.style.display = "none"; + + // Show content loadingSkeleton.style.display = "none"; userInfo.classList.remove("hidden"); - // Use a small delay to ensure the transition works setTimeout(() => { userInfo.style.opacity = "1"; }, 50); - - officerViewToggle.style.display = "none"; - sponsorViewToggle.style.display = "none"; } } + 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); @@ -583,15 +462,68 @@ export class StoreAuth { } } + 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) { - // If we have a member ID and we're not editing, switch to edit mode this.isEditingMemberId = true; - this.updateMemberIdState(); + this.updateUI(); } else { - // If we're editing or don't have a member ID, try to save await this.handleMemberIdSave(); } } @@ -609,317 +541,22 @@ export class StoreAuth { } await this.pb.collection("users").update(user.id, { - member_id: memberId, + member_id: memberId }); memberIdStatus.textContent = this.config.ui.messages.memberId.success; this.isEditingMemberId = false; this.updateUI(); - // Clear the status message after a delay setTimeout(() => { memberIdStatus.textContent = ""; }, this.config.ui.messages.memberId.messageTimeout); - } catch (err: any) { + } catch (err) { console.error("IEEE Member ID save error:", err); memberIdStatus.textContent = this.config.ui.messages.memberId.error; } } - private async handleResumeUpload(file: File) { - const { uploadStatus } = this.elements; - - // Check file type and size - if (!this.config.resume.allowedTypes.some(type => file.name.toLowerCase().endsWith(type))) { - uploadStatus.textContent = `File type not allowed. Allowed types: ${this.config.resume.allowedTypes.join(", ")}`; - return; - } - - if (file.size > this.config.resume.maxSize) { - uploadStatus.textContent = `File too large. Maximum size: ${this.config.resume.maxSize / 1024 / 1024}MB`; - return; - } - - try { - uploadStatus.textContent = this.config.ui.messages.resume.uploading; - - const formData = new FormData(); - formData.append("resume", file); - - const user = this.pb.authStore.model; - if (!user?.id) { - throw new Error("User ID not found"); - } - - // Get current user data first - const currentUser = await this.pb.collection("users").getOne(user.id); - - // Keep existing data - formData.append("name", currentUser.name || ""); - formData.append("email", currentUser.email || ""); - formData.append("member_id", currentUser.member_id || ""); - formData.append("points", currentUser.points?.toString() || "0"); - - await this.pb.collection("users").update(user.id, formData); - - uploadStatus.textContent = this.config.ui.messages.resume.success; - this.updateUI(); - - // Clear the file input - this.elements.resumeUpload.value = ""; - - // Clear the status message after a delay - setTimeout(() => { - uploadStatus.textContent = ""; - }, this.config.ui.messages.resume.messageTimeout); - } catch (err: any) { - console.error("Resume upload error:", err); - uploadStatus.textContent = this.config.ui.messages.resume.error; - } - } - - private async handleResumeDelete() { - const { uploadStatus } = this.elements; - - try { - uploadStatus.textContent = this.config.ui.messages.resume.deleting; - - 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(); - - // Clear the status message after a delay - setTimeout(() => { - uploadStatus.textContent = ""; - }, this.config.ui.messages.resume.messageTimeout); - } catch (err: any) { - console.error("Resume deletion error:", err); - uploadStatus.textContent = this.config.ui.messages.resume.deleteError; - } - } - - private async handleLogin() { - console.log("Starting OAuth2 authentication..."); - 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"); - } - - // Store provider info for the redirect page - localStorage.setItem("provider", JSON.stringify(oidcProvider)); - - // Redirect to the authorization URL - const redirectUrl = window.location.origin + this.config.api.oauth2.redirectPath; - const authUrl = oidcProvider.authURL + encodeURIComponent(redirectUrl); - window.location.href = authUrl; - } catch (err: any) { - console.error("Authentication error:", err); - this.elements.userEmail.textContent = this.config.ui.messages.auth.loginError; - this.elements.userName.textContent = "Error"; - } - } - - private handleLogout() { - // Clear auth store - this.pb.authStore.clear(); - - // Clear cached users - this.cachedUsers = []; - - // Reset member ID editing state - this.isEditingMemberId = false; - - // Show all sections that might have been hidden - const memberIdSection = this.elements.memberIdInput.closest('.space-y-1') as HTMLElement; - const resumeSection = this.elements.resumeUpload.closest('.space-y-2')?.parentElement as HTMLElement; - const memberIdDivider = memberIdSection?.nextElementSibling as HTMLElement; - const resumeDivider = resumeSection?.nextElementSibling as HTMLElement; - - // Show all sections - if (memberIdSection) memberIdSection.style.display = ''; - if (memberIdDivider) memberIdDivider.style.display = ''; - if (resumeSection) resumeSection.style.display = ''; - if (resumeDivider) resumeDivider.style.display = ''; - - // Update UI - this.updateUI(); - } - - private async fetchUserResumes(searchQuery: string = "") { - try { - // Only fetch from API if we don't have cached data - if (this.cachedUsers.length === 0) { - const records = await this.pb.collection("users").getList(1, this.config.ui.defaults.pageSize, { - sort: this.config.ui.defaults.sortField, - fields: "id,name,email,member_id,resume,points,collectionId,collectionName", - expand: "resume", - }); - this.cachedUsers = records.items; - } - - // Filter cached data based on search query - let filteredUsers = this.cachedUsers; - if (searchQuery) { - const terms = searchQuery.toLowerCase().split(" ").filter(term => term.length > 0); - if (terms.length > 0) { - filteredUsers = this.cachedUsers.filter(user => { - return terms.every(term => - (user.name?.toLowerCase().includes(term) || - user.email?.toLowerCase().includes(term) || - user.member_id?.toLowerCase().includes(term)) - ); - }); - } - } - - const { resumeList } = this.elements; - const fragment = document.createDocumentFragment(); - const isSponsor = this.pb.authStore.model?.member_type === this.config.roles.sponsor.name; - - if (filteredUsers.length === 0) { - const row = document.createElement("tr"); - row.innerHTML = ` - - ${searchQuery ? "No users found matching your search." : "No users found."} - - `; - fragment.appendChild(row); - } else { - filteredUsers.forEach((user) => { - const row = document.createElement("tr"); - const resumeUrl = user.resume && user.resume !== "" - ? this.pb.files.getURL(user, user.resume.toString()) - : null; - const fileName = resumeUrl ? this.getFileNameFromUrl(user.resume.toString()) : null; - - // Create edit button only if not a sponsor - const editButton = !isSponsor ? ` - - ` : ''; - - // Create view resume link - const viewResumeLink = resumeUrl - ? `View Resume` - : 'No resume'; - - row.innerHTML = ` - - -
-
${user.name || "N/A"}
-
${user.email || "N/A"}
-
ID: ${user.member_id || "N/A"}
-
Points: ${user.points || 0}
-
- ${viewResumeLink} - ${editButton} -
-
- - - - - ${user.email || "N/A"} - ${user.member_id || "N/A"} - ${user.points || 0} - - ${viewResumeLink} - - - ${editButton} - - `; - - fragment.appendChild(row); - }); - } - - resumeList.innerHTML = ""; - resumeList.appendChild(fragment); - - // Setup edit profile event listeners only if not a sponsor - if (!isSponsor) { - const editButtons = resumeList.querySelectorAll(".edit-profile"); - editButtons.forEach((button) => { - button.addEventListener("click", () => { - const userId = (button as HTMLButtonElement).dataset.userId; - if (userId) { - this.handleProfileEdit(userId); - } - }); - }); - } - } catch (err) { - console.error("Failed to fetch user resumes:", err); - const { resumeList } = this.elements; - resumeList.innerHTML = ` - - - Failed to fetch resumes. Please try again. - - - `; - } - } - - private async handleProfileEdit(userId: string) { - try { - const user = await this.pb.collection("users").getOne(userId); - const { - profileEditor, - editorName, - editorEmail, - editorMemberId, - editorPoints, - editorCurrentResume, - saveProfileButton, - } = this.elements; - - // Populate the form - editorName.value = user.name || ""; - editorEmail.value = user.email || ""; - editorMemberId.value = user.member_id || ""; - editorPoints.value = user.points?.toString() || "0"; - - // Update resume display - if (user.resume) { - const resumeUrl = this.pb.files.getURL(user, user.resume.toString()); - const fileName = this.getFileNameFromUrl(resumeUrl); - editorCurrentResume.textContent = `Current resume: ${fileName}`; - editorCurrentResume.classList.remove("opacity-70"); - } else { - editorCurrentResume.textContent = "No resume uploaded"; - editorCurrentResume.classList.add("opacity-70"); - } - - // Store the user ID for saving - saveProfileButton.dataset.userId = userId; - - // Show the dialog - profileEditor.showModal(); - } catch (err) { - console.error("Failed to load user for editing:", err); - } - } - private async handleProfileSave() { const { profileEditor, @@ -955,151 +592,11 @@ export class StoreAuth { formData.append("resume", currentUser.resume); } - // Log the form data for debugging - console.log("Form data being sent:", { - name: editorName.value, - email: editorEmail.value, - member_id: editorMemberId.value, - points: editorPoints.value, - hasNewResume: editorResume.files && editorResume.files.length > 0, - hasExistingResume: !!currentUser.resume, - }); - - const updatedUser = await this.pb - .collection("users") - .update(userId, formData); - console.log("Update response:", updatedUser); - - // Close the dialog and refresh the table + await this.pb.collection("users").update(userId, formData); profileEditor.close(); - this.fetchUserResumes(); + this.updateUI(); } catch (err) { console.error("Failed to save user profile:", err); } } - - 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 init() { - // Initial UI update with loading state - this.updateUI().catch(console.error); - - // Setup event listeners - 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(), - ); - - // Search functionality - const handleSearch = () => { - const searchQuery = this.elements.resumeSearch.value.trim(); - this.fetchUserResumes(searchQuery); - }; - - // Real-time search - this.elements.resumeSearch.addEventListener("input", handleSearch); - - // Search button click handler - this.elements.searchResumes.addEventListener("click", handleSearch); - - // Officer view toggle event listener - this.elements.officerViewCheckbox.addEventListener("change", (e) => { - const isChecked = (e.target as HTMLInputElement).checked; - const storeItemsContainer = document.getElementById("storeItemsGrid"); - const { officerContent } = this.elements; - - if (storeItemsContainer) { - storeItemsContainer.style.display = isChecked ? "none" : "grid"; - } - officerContent.style.display = isChecked ? "block" : "none"; - - // Uncheck sponsor view if officer view is checked - if (isChecked) { - const sponsorCheckbox = this.elements.sponsorViewToggle.querySelector('input[type="checkbox"]') as HTMLInputElement; - if (sponsorCheckbox) { - sponsorCheckbox.checked = false; - } - } - }); - - // Sponsor view toggle event listener - const sponsorCheckbox = this.elements.sponsorViewToggle.querySelector('input[type="checkbox"]') as HTMLInputElement; - if (sponsorCheckbox) { - sponsorCheckbox.addEventListener("change", (e) => { - const isChecked = (e.target as HTMLInputElement).checked; - const storeItemsContainer = document.getElementById("storeItemsGrid"); - const { officerContent } = this.elements; - - if (storeItemsContainer) { - storeItemsContainer.style.display = isChecked ? "none" : "grid"; - } - officerContent.style.display = isChecked ? "block" : "none"; - - // Uncheck officer view if sponsor view is checked - if (isChecked) { - this.elements.officerViewCheckbox.checked = false; - } - }); - } - - // Refresh resumes button event listener - this.elements.refreshResumes.addEventListener("click", () => { - this.elements.resumeSearch.value = ""; // Clear search when refreshing - this.cachedUsers = []; // Clear the cache to force a new fetch - this.fetchUserResumes(); - }); - - // Listen for auth state changes - this.pb.authStore.onChange(async (token) => { - console.log("Auth state changed. IsValid:", this.pb.authStore.isValid); - this.updateUI(); - }); - - // Profile editor event listeners - const { profileEditor, saveProfileButton } = this.elements; - - // Close dialog when clicking outside - profileEditor.addEventListener("click", (e) => { - if (e.target === profileEditor) { - profileEditor.close(); - } - }); - - // Save profile button - saveProfileButton.addEventListener("click", (e) => { - e.preventDefault(); - this.handleProfileSave(); - }); - - // Add resume view event listener - document.addEventListener('viewResume', ((e: CustomEvent) => { - this.handleResumeView(e.detail.url, e.detail.fileName); - }) as EventListener); - } -} +} \ No newline at end of file diff --git a/src/components/auth/UserProfile.astro b/src/components/auth/UserProfile.astro index 880c0dc..20d6492 100644 --- a/src/components/auth/UserProfile.astro +++ b/src/components/auth/UserProfile.astro @@ -1,225 +1,261 @@
- -
-
-
- -

+ +
+
+ +
+
+
+
- -
-
- -
-
-
-
-
- -
-
-
-
-
+ +
+
+
- -
-
-
-
-
+ +
+
+
+
+
+
+
+
+
+
- -
-
-
-
-
-
-
-
- - -
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
- - -
-
-
-
-
-
-
-
-
-
-
-
- - -
-
-
+ +
+
+
+
+
+
+
+
- -