From c25575757ea815115ab22900a4639063b32f16bb Mon Sep 17 00:00:00 2001 From: chark1es Date: Sat, 8 Mar 2025 03:31:16 -0800 Subject: [PATCH] add resume --- src/components/dashboard/ProfileSection.astro | 154 +++++ .../dashboard/SettingsSection.astro | 22 + .../SettingsSection/ResumeSettings.tsx | 573 ++++++++++++++++++ src/pages/dashboard.astro | 16 +- src/schemas/pocketbase/schema.ts | 1 + 5 files changed, 759 insertions(+), 7 deletions(-) create mode 100644 src/components/dashboard/SettingsSection/ResumeSettings.tsx diff --git a/src/components/dashboard/ProfileSection.astro b/src/components/dashboard/ProfileSection.astro index 70cc7ff..4b342ca 100644 --- a/src/components/dashboard/ProfileSection.astro +++ b/src/components/dashboard/ProfileSection.astro @@ -10,6 +10,160 @@ import { Stats } from "./ProfileSection/Stats";

Welcome to your IEEE UCSD dashboard

+ + + diff --git a/src/components/dashboard/SettingsSection.astro b/src/components/dashboard/SettingsSection.astro index 231bb7c..4eb9d97 100644 --- a/src/components/dashboard/SettingsSection.astro +++ b/src/components/dashboard/SettingsSection.astro @@ -4,6 +4,7 @@ import UserProfileSettings from "./SettingsSection/UserProfileSettings"; import AccountSecuritySettings from "./SettingsSection/AccountSecuritySettings"; import NotificationSettings from "./SettingsSection/NotificationSettings"; import DisplaySettings from "./SettingsSection/DisplaySettings"; +import ResumeSettings from "./SettingsSection/ResumeSettings"; ---
@@ -31,6 +32,27 @@ import DisplaySettings from "./SettingsSection/DisplaySettings";
+ +
+
+

+
+ +
+ Resume Management +

+

+ Upload and manage your resume for recruiters and career + opportunities +

+
+ +
+
+
(null); + const [loading, setLoading] = useState(true); + const [uploading, setUploading] = useState(false); + const [resumeUrl, setResumeUrl] = useState(null); + const [resumeFilename, setResumeFilename] = useState(null); + const [resumeFile, setResumeFile] = useState(null); + const fileInputRef = useRef(null); + + // Fetch user data on component mount + useEffect(() => { + const fetchUserData = async () => { + try { + setLoading(true); + const currentUser = auth.getCurrentUser(); + if (!currentUser) { + throw new Error('User not authenticated'); + } + + const userData = await get.getOne('users', currentUser.id); + setUser(userData); + + // Check if user has a resume + if (userData.resume) { + const resumeFile = userData.resume; + const fileUrl = fileManager.getFileUrl('users', userData.id, resumeFile); + setResumeUrl(fileUrl); + setResumeFilename(resumeFile); + } + } catch (error) { + console.error('Error fetching user data:', error); + toast.error('Failed to load user data'); + } finally { + setLoading(false); + } + }; + + fetchUserData(); + }, []); + + const handleFileChange = (e: React.ChangeEvent) => { + const files = e.target.files; + if (files && files.length > 0) { + const file = files[0]; + + // Validate file type (PDF or DOCX) + const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; + const fileExtension = file.name.split('.').pop()?.toLowerCase(); + + // Check both MIME type and file extension + if (!allowedTypes.includes(file.type) && + !(fileExtension === 'pdf' || fileExtension === 'docx')) { + toast.error('Please upload a PDF or DOCX file'); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + return; + } + + // Validate file size (max 50MB) + const maxSize = 50 * 1024 * 1024; // 50MB in bytes + if (file.size > maxSize) { + const sizeMB = (file.size / (1024 * 1024)).toFixed(2); + toast.error(`File too large (${sizeMB}MB). Maximum size is 50MB.`); + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + return; + } + + setResumeFile(file); + setResumeFilename(file.name); + toast.success(`File "${file.name}" selected. Click Upload to confirm.`); + } + }; + + const handleUpload = async () => { + if (!resumeFile || !user) { + toast.error('No file selected or user not authenticated'); + return; + } + + try { + setUploading(true); + + // Create a FormData object for the file upload + const formData = new FormData(); + formData.append('resume', resumeFile); + + // Show progress toast + const loadingToast = toast.loading('Uploading resume...'); + + // Log the file being uploaded for debugging + console.log('Uploading file:', { + name: resumeFile.name, + size: resumeFile.size, + type: resumeFile.type + }); + + let updatedUserData: User; + + try { + // Use the FileManager to upload the file directly + console.log('Using FileManager to upload resume file'); + + // Upload the file using the FileManager's uploadFile method + const result = await fileManager.uploadFile( + Collections.USERS, + user.id, + 'resume', + resumeFile, + false // Don't append, replace existing file + ); + + // Verify the file was uploaded by checking the response + if (!result || !result.resume) { + throw new Error('Resume was not properly saved to the user record'); + } + + console.log('Resume upload successful:', result.resume); + + // Store the updated user data + updatedUserData = result; + + // Fetch the updated user record to ensure we have the latest data + const refreshedUser = await get.getOne(Collections.USERS, user.id); + console.log('Refreshed user data:', refreshedUser); + + // Double check that the resume field is populated + if (!refreshedUser.resume) { + console.warn('Resume field is missing in the refreshed user data'); + } + } catch (uploadError) { + console.error('Error in file upload process:', uploadError); + toast.dismiss(loadingToast); + toast.error('Failed to upload resume: ' + (uploadError instanceof Error ? uploadError.message : 'Unknown error')); + setUploading(false); + return; + } + + // Get the URL of the uploaded file + if (!updatedUserData.resume) { + throw new Error('Resume filename is missing in the updated user data'); + } + + const fileUrl = fileManager.getFileUrl('users', user.id, updatedUserData.resume); + + // Update state with the new resume information + setResumeUrl(fileUrl); + setResumeFilename(updatedUserData.resume); + setResumeFile(null); + + // Reset file input + if (fileInputRef.current) { + fileInputRef.current.value = ''; + } + + // Dismiss loading toast and show success message + toast.dismiss(loadingToast); + toast.success('Resume uploaded successfully'); + + // Log the successful upload + console.log('Resume uploaded successfully:', updatedUserData.resume); + + // Dispatch a custom event to notify the dashboard about the resume upload + const event = new CustomEvent('resumeUploaded', { + detail: { hasResume: true } + }); + window.dispatchEvent(event); + + } catch (error) { + console.error('Error uploading resume:', error); + + // Provide more specific error messages based on the error type + if (error instanceof Error) { + if (error.message.includes('size')) { + toast.error('File size exceeds the maximum allowed limit (50MB)'); + } else if (error.message.includes('type')) { + toast.error('Invalid file type. Please upload a PDF or DOCX file'); + } else if (error.message.includes('network')) { + toast.error('Network error. Please check your connection and try again'); + } else if (error.message.includes('permission')) { + toast.error('Permission denied. You may not have the right permissions'); + } else { + toast.error(`Upload failed: ${error.message}`); + } + } else { + toast.error('Failed to upload resume. Please try again later'); + } + } finally { + setUploading(false); + } + }; + + const handleDelete = async () => { + if (!user) { + toast.error('User not authenticated'); + return; + } + + // Ask for confirmation before deleting + if (!confirm('Are you sure you want to delete your resume? This action cannot be undone.')) { + return; + } + + try { + setUploading(true); + + // Show progress toast + const loadingToast = toast.loading('Deleting resume...'); + + // Log the deletion attempt + console.log('Attempting to delete resume for user:', user.id); + + // Create a FormData with empty resume field to remove the file + const formData = new FormData(); + formData.append('resume', ''); + + try { + console.log('Using FileManager to delete resume file'); + + // Use the FileManager's deleteFile method to remove the file + const result = await fileManager.deleteFile( + Collections.USERS, + user.id, + 'resume' + ); + + // Verify the file was deleted + if (result.resume) { + console.warn('Resume field still exists after deletion attempt:', result.resume); + toast.dismiss(loadingToast); + toast.error('Failed to completely remove the resume. Please try again.'); + setUploading(false); + return; + } + + console.log('Resume deletion successful for user:', user.id); + + // Fetch the updated user record to ensure we have the latest data + const refreshedUser = await get.getOne(Collections.USERS, user.id); + console.log('Refreshed user data after deletion:', refreshedUser); + + // Double check that the resume field is empty + if (refreshedUser.resume) { + console.warn('Resume field is still present in the refreshed user data:', refreshedUser.resume); + } + } catch (deleteError) { + console.error('Error in file deletion process:', deleteError); + toast.dismiss(loadingToast); + toast.error('Failed to delete resume: ' + (deleteError instanceof Error ? deleteError.message : 'Unknown error')); + setUploading(false); + return; + } + + // Update state to reflect the deletion + setResumeUrl(null); + setResumeFilename(null); + + // Dismiss loading toast and show success message + toast.dismiss(loadingToast); + toast.success('Resume deleted successfully'); + + // Log the successful deletion + console.log('Resume deleted successfully for user:', user.id); + + // Dispatch a custom event to notify the dashboard about the resume deletion + const event = new CustomEvent('resumeUploaded', { + detail: { hasResume: false } + }); + window.dispatchEvent(event); + + } catch (error) { + console.error('Error deleting resume:', error); + + // Provide more specific error messages based on the error type + if (error instanceof Error) { + if (error.message.includes('permission')) { + toast.error('Permission denied. You may not have the right permissions'); + } else if (error.message.includes('network')) { + toast.error('Network error. Please check your connection and try again'); + } else { + toast.error(`Deletion failed: ${error.message}`); + } + } else { + toast.error('Failed to delete resume. Please try again later'); + } + } finally { + setUploading(false); + } + }; + + const handleReplace = async () => { + if (!user) { + toast.error('User not authenticated'); + return; + } + + // Create a file input element + const fileInput = document.createElement('input'); + fileInput.type = 'file'; + fileInput.accept = '.pdf,.docx'; + fileInput.style.display = 'none'; + document.body.appendChild(fileInput); + + // Trigger click to open file dialog + fileInput.click(); + + // Handle file selection + fileInput.onchange = async (e) => { + const input = e.target as HTMLInputElement; + if (!input.files || input.files.length === 0) { + document.body.removeChild(fileInput); + return; + } + + const file = input.files[0]; + + // Validate file type (PDF or DOCX) + const allowedTypes = ['application/pdf', 'application/vnd.openxmlformats-officedocument.wordprocessingml.document']; + const fileExtension = file.name.split('.').pop()?.toLowerCase(); + + if (!allowedTypes.includes(file.type) && + !(fileExtension === 'pdf' || fileExtension === 'docx')) { + toast.error('Please upload a PDF or DOCX file'); + document.body.removeChild(fileInput); + return; + } + + // Validate file size (max 50MB) + const maxSize = 50 * 1024 * 1024; // 50MB in bytes + if (file.size > maxSize) { + const sizeMB = (file.size / (1024 * 1024)).toFixed(2); + toast.error(`File too large (${sizeMB}MB). Maximum size is 50MB.`); + document.body.removeChild(fileInput); + return; + } + + try { + setUploading(true); + + // Show progress toast + const loadingToast = toast.loading('Replacing resume...'); + + // Log the file being uploaded for debugging + console.log('Replacing resume with file:', { + name: file.name, + size: file.size, + type: file.type + }); + + // Create a FormData object for the file upload + const formData = new FormData(); + formData.append('resume', file); + + // Use the update service to directly update the user record + const result = await update.updateFields( + Collections.USERS, + user.id, + formData + ); + + // Verify the file was uploaded + if (!result || !result.resume) { + throw new Error('Resume was not properly saved to the user record'); + } + + // Get the URL of the uploaded file + const fileUrl = fileManager.getFileUrl('users', user.id, result.resume); + + // Update state with the new resume information + setResumeUrl(fileUrl); + setResumeFilename(result.resume); + setResumeFile(null); + + // Dismiss loading toast and show success message + toast.dismiss(loadingToast); + toast.success('Resume replaced successfully'); + + // Dispatch a custom event to notify the dashboard about the resume upload + const event = new CustomEvent('resumeUploaded', { + detail: { hasResume: true } + }); + window.dispatchEvent(event); + + } catch (error) { + console.error('Error replacing resume:', error); + toast.error('Failed to replace resume: ' + (error instanceof Error ? error.message : 'Unknown error')); + } finally { + setUploading(false); + document.body.removeChild(fileInput); + } + }; + }; + + return ( +
+ {loading ? ( +
+ +
+ ) : ( + <> + {/* Resume Upload Section */} +
+
+
+

Resume

+

+ Upload your resume for recruiters and career opportunities +

+
+ + {!resumeUrl && ( +
+ + +
+ )} +
+ + {/* File selected but not uploaded yet */} + {resumeFile && !uploading && ( +
+
+
+
+ + + +
+
+

{resumeFile.name}

+

{(resumeFile.size / 1024 / 1024).toFixed(2)} MB

+
+
+
+ + +
+
+
+ )} + + {/* Uploading state */} + {uploading && ( +
+
+ + Processing your resume... +
+ +
+ )} + + {/* Resume preview */} + {resumeUrl && resumeFilename && !resumeFile && !uploading && ( +
+
+

Current Resume

+
+ + +
+
+ +
+ +
+
+ )} + + {/* Resume upload guidelines */} +
+

+ + + + Resume Guidelines +

+
    +
  • Accepted formats: PDF, DOCX
  • +
  • Maximum file size: 50MB
  • +
  • Keep your resume up-to-date for better opportunities
  • +
  • Highlight your skills, experience, and education
  • +
+
+ + {/* Resume Benefits */} +
+

+ + + + Why Upload Your Resume? +

+
    +
  • Increase visibility to recruiters and industry partners
  • +
  • Get matched with relevant job opportunities
  • +
  • Simplify application process for IEEE UCSD events
  • +
  • Access personalized career resources and recommendations
  • +
+
+
+ + )} +
+ ); +} diff --git a/src/pages/dashboard.astro b/src/pages/dashboard.astro index 13187de..68ac770 100644 --- a/src/pages/dashboard.astro +++ b/src/pages/dashboard.astro @@ -786,26 +786,28 @@ console.log("Available components:", Object.keys(components)); // Debug log let defaultSection; let defaultButton; + // Set default section based on role + // Only sponsors get a different default view if (officerStatus === "sponsor") { + // For sponsors, show the sponsor dashboard defaultSection = document.getElementById( "sponsorDashboardSection" ); defaultButton = document.querySelector( '[data-section="sponsorDashboard"]' ); - } else if (officerStatus === "administrator") { - defaultSection = document.getElementById( - "adminDashboardSection" - ); - defaultButton = document.querySelector( - '[data-section="adminDashboard"]' - ); } else { + // For all other users (including administrators), show the profile section defaultSection = document.getElementById("profileSection"); defaultButton = document.querySelector( '[data-section="profile"]' ); + + // Log the default section for debugging + console.log( + `Setting default section to profile for user with role: ${officerStatus}` + ); } if (defaultSection) { diff --git a/src/schemas/pocketbase/schema.ts b/src/schemas/pocketbase/schema.ts index 94b6937..891297a 100644 --- a/src/schemas/pocketbase/schema.ts +++ b/src/schemas/pocketbase/schema.ts @@ -35,6 +35,7 @@ export interface User extends BaseRecord { notification_preferences?: string; // JSON string of notification settings display_preferences?: string; // JSON string of display settings (theme, font size, etc.) accessibility_settings?: string; // JSON string of accessibility settings (color blind mode, reduced motion) + resume?: string; } /**