add resume
This commit is contained in:
parent
eb5c994f2d
commit
c25575757e
5 changed files with 759 additions and 7 deletions
|
@ -10,6 +10,160 @@ import { Stats } from "./ProfileSection/Stats";
|
|||
<p class="opacity-70">Welcome to your IEEE UCSD dashboard</p>
|
||||
</div>
|
||||
|
||||
<!-- Resume Alert Notification -->
|
||||
<div id="resumeAlert" class="hidden">
|
||||
<div
|
||||
class="bg-error/10 border-l-4 border-error p-4 rounded-lg mb-6 shadow-md"
|
||||
>
|
||||
<div class="flex items-start justify-between">
|
||||
<div class="flex items-start space-x-3">
|
||||
<div class="flex-shrink-0 mt-0.5">
|
||||
<div class="p-1.5 bg-error/20 rounded-full">
|
||||
<Icon
|
||||
name="heroicons:document-text"
|
||||
class="h-5 w-5 text-error"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<h3 class="font-semibold text-white mb-1">
|
||||
Resume Required
|
||||
</h3>
|
||||
<p class="text-sm text-base-content/80">
|
||||
Your resume is missing. Upload your resume to
|
||||
increase visibility to recruiters and access
|
||||
exclusive career opportunities.
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ml-4 flex-shrink-0">
|
||||
<a
|
||||
href="#settings-section"
|
||||
class="inline-flex items-center px-4 py-2 border border-transparent text-sm font-medium rounded-md shadow-sm text-white bg-error hover:bg-error-focus focus:outline-none focus:ring-2 focus:ring-offset-2 focus:ring-error transition-colors duration-200"
|
||||
id="uploadResumeBtn"
|
||||
>
|
||||
<Icon
|
||||
name="heroicons:arrow-up-tray"
|
||||
class="h-4 w-4 mr-1.5"
|
||||
/>
|
||||
Upload Now
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Function to check if user has a resume and show/hide the alert
|
||||
const checkResumeStatus = async () => {
|
||||
try {
|
||||
const resumeAlert = document.getElementById("resumeAlert");
|
||||
if (!resumeAlert) return;
|
||||
|
||||
// Get the current user from PocketBase
|
||||
const { Authentication } = await import(
|
||||
"../../scripts/pocketbase/Authentication"
|
||||
);
|
||||
const { Get } = await import(
|
||||
"../../scripts/pocketbase/Get"
|
||||
);
|
||||
|
||||
const auth = Authentication.getInstance();
|
||||
const get = Get.getInstance();
|
||||
|
||||
if (!auth.isAuthenticated()) {
|
||||
resumeAlert.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
const user = auth.getCurrentUser();
|
||||
if (!user) {
|
||||
resumeAlert.classList.add("hidden");
|
||||
return;
|
||||
}
|
||||
|
||||
// Fetch user data to check if resume exists
|
||||
const userData = await get.getOne("users", user.id);
|
||||
|
||||
if (userData && userData.resume) {
|
||||
// User has a resume, hide the alert
|
||||
resumeAlert.classList.add("hidden");
|
||||
} else {
|
||||
// User doesn't have a resume, show the alert
|
||||
resumeAlert.classList.remove("hidden");
|
||||
}
|
||||
} catch (error) {
|
||||
console.error("Error checking resume status:", error);
|
||||
}
|
||||
};
|
||||
|
||||
// Check resume status when the page loads
|
||||
document.addEventListener("DOMContentLoaded", checkResumeStatus);
|
||||
|
||||
// Listen for resume upload events
|
||||
window.addEventListener("resumeUploaded", (event: Event) => {
|
||||
const resumeAlert = document.getElementById("resumeAlert");
|
||||
if (!resumeAlert) return;
|
||||
|
||||
// Cast to CustomEvent to access detail property
|
||||
const customEvent = event as CustomEvent<{
|
||||
hasResume: boolean;
|
||||
}>;
|
||||
const { hasResume } = customEvent.detail;
|
||||
|
||||
if (hasResume) {
|
||||
resumeAlert.classList.add("hidden");
|
||||
} else {
|
||||
resumeAlert.classList.remove("hidden");
|
||||
}
|
||||
});
|
||||
|
||||
// Handle the "Upload Now" button click
|
||||
document
|
||||
.getElementById("uploadResumeBtn")
|
||||
?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// Find the settings button in the sidebar and click it
|
||||
const settingsButton = document.querySelector(
|
||||
'[data-section="settings"]'
|
||||
) as HTMLElement;
|
||||
if (settingsButton) {
|
||||
settingsButton.click();
|
||||
|
||||
// Scroll to the resume section after a short delay
|
||||
setTimeout(() => {
|
||||
// Find the resume management section by ID or a more reliable selector
|
||||
const resumeSection = document.getElementById(
|
||||
"resume-management-section"
|
||||
);
|
||||
|
||||
if (resumeSection) {
|
||||
resumeSection.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
});
|
||||
} else {
|
||||
// Fallback: try to find by heading text
|
||||
const headings =
|
||||
document.querySelectorAll("h3.card-title");
|
||||
for (const heading of headings) {
|
||||
if (
|
||||
heading.textContent?.includes(
|
||||
"Resume Management"
|
||||
)
|
||||
) {
|
||||
heading.scrollIntoView({
|
||||
behavior: "smooth",
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 300);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
</div>
|
||||
|
||||
<Stats client:load />
|
||||
|
||||
<!-- Dashboard Content -->
|
||||
|
|
|
@ -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";
|
||||
---
|
||||
|
||||
<div id="settings-section" class="">
|
||||
|
@ -31,6 +32,27 @@ import DisplaySettings from "./SettingsSection/DisplaySettings";
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Resume Settings Card -->
|
||||
<div
|
||||
id="resume-management-section"
|
||||
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"
|
||||
>
|
||||
<div class="card-body">
|
||||
<h3 class="card-title flex items-center gap-3">
|
||||
<div class="badge badge-primary p-3">
|
||||
<Icon name="heroicons:document-text" class="h-5 w-5" />
|
||||
</div>
|
||||
Resume Management
|
||||
</h3>
|
||||
<p class="text-sm opacity-70 mb-4">
|
||||
Upload and manage your resume for recruiters and career
|
||||
opportunities
|
||||
</p>
|
||||
<div class="divider"></div>
|
||||
<ResumeSettings client:load />
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Account Security Settings Card -->
|
||||
<div
|
||||
class="card bg-base-100 shadow-xl border border-base-200 hover:border-primary transition-all duration-300 hover:-translate-y-1 transform mb-6"
|
||||
|
|
573
src/components/dashboard/SettingsSection/ResumeSettings.tsx
Normal file
573
src/components/dashboard/SettingsSection/ResumeSettings.tsx
Normal file
|
@ -0,0 +1,573 @@
|
|||
import { useState, useEffect, useRef } from 'react';
|
||||
import { Authentication } from '../../../scripts/pocketbase/Authentication';
|
||||
import { FileManager } from '../../../scripts/pocketbase/FileManager';
|
||||
import { Update } from '../../../scripts/pocketbase/Update';
|
||||
import { Get } from '../../../scripts/pocketbase/Get';
|
||||
import { Collections, type User } from '../../../schemas/pocketbase/schema';
|
||||
import { toast } from 'react-hot-toast';
|
||||
import FilePreview from '../universal/FilePreview';
|
||||
|
||||
export default function ResumeSettings() {
|
||||
const auth = Authentication.getInstance();
|
||||
const fileManager = FileManager.getInstance();
|
||||
const update = Update.getInstance();
|
||||
const get = Get.getInstance();
|
||||
const [user, setUser] = useState<User | null>(null);
|
||||
const [loading, setLoading] = useState(true);
|
||||
const [uploading, setUploading] = useState(false);
|
||||
const [resumeUrl, setResumeUrl] = useState<string | null>(null);
|
||||
const [resumeFilename, setResumeFilename] = useState<string | null>(null);
|
||||
const [resumeFile, setResumeFile] = useState<File | null>(null);
|
||||
const fileInputRef = useRef<HTMLInputElement>(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<User>('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<HTMLInputElement>) => {
|
||||
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<User>(
|
||||
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<User>(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<User>(
|
||||
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<User>(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<User>(
|
||||
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 (
|
||||
<div className="space-y-6">
|
||||
{loading ? (
|
||||
<div className="flex justify-center items-center py-8">
|
||||
<span className="loading loading-spinner loading-lg"></span>
|
||||
</div>
|
||||
) : (
|
||||
<>
|
||||
{/* Resume Upload Section */}
|
||||
<div className="space-y-4">
|
||||
<div className="flex flex-col sm:flex-row sm:items-center justify-between gap-4">
|
||||
<div>
|
||||
<h3 className="text-lg font-medium">Resume</h3>
|
||||
<p className="text-sm opacity-70">
|
||||
Upload your resume for recruiters and career opportunities
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{!resumeUrl && (
|
||||
<div className="flex items-center gap-2">
|
||||
<input
|
||||
type="file"
|
||||
ref={fileInputRef}
|
||||
onChange={handleFileChange}
|
||||
accept=".pdf,.docx"
|
||||
className="hidden"
|
||||
/>
|
||||
<button
|
||||
onClick={() => fileInputRef.current?.click()}
|
||||
className="btn btn-primary btn-sm gap-2"
|
||||
disabled={uploading}
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M3 17a1 1 0 011-1h12a1 1 0 110 2H4a1 1 0 01-1-1zM6.293 6.707a1 1 0 010-1.414l3-3a1 1 0 011.414 0l3 3a1 1 0 01-1.414 1.414L11 5.414V13a1 1 0 11-2 0V5.414L7.707 6.707a1 1 0 01-1.414 0z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Select Resume
|
||||
</button>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* File selected but not uploaded yet */}
|
||||
{resumeFile && !uploading && (
|
||||
<div className="bg-base-200 p-4 rounded-lg">
|
||||
<div className="flex items-center justify-between">
|
||||
<div className="flex items-center gap-3">
|
||||
<div className="bg-primary/10 p-2 rounded-lg">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-6 w-6 text-primary" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="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-2V4zm2 6a1 1 0 011-1h6a1 1 0 110 2H7a1 1 0 01-1-1zm1 3a1 1 0 100 2h6a1 1 0 100-2H7z" clipRule="evenodd" />
|
||||
</svg>
|
||||
</div>
|
||||
<div>
|
||||
<p className="font-medium truncate max-w-xs">{resumeFile.name}</p>
|
||||
<p className="text-xs opacity-70">{(resumeFile.size / 1024 / 1024).toFixed(2)} MB</p>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleUpload}
|
||||
className="btn btn-primary btn-sm"
|
||||
disabled={uploading}
|
||||
>
|
||||
Upload
|
||||
</button>
|
||||
<button
|
||||
onClick={() => {
|
||||
setResumeFile(null);
|
||||
if (fileInputRef.current) {
|
||||
fileInputRef.current.value = '';
|
||||
}
|
||||
}}
|
||||
className="btn btn-ghost btn-sm"
|
||||
disabled={uploading}
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Uploading state */}
|
||||
{uploading && (
|
||||
<div className="bg-base-200 p-4 rounded-lg">
|
||||
<div className="flex items-center gap-3">
|
||||
<span className="loading loading-spinner loading-sm"></span>
|
||||
<span>Processing your resume...</span>
|
||||
</div>
|
||||
<progress className="progress progress-primary w-full mt-2"></progress>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Resume preview */}
|
||||
{resumeUrl && resumeFilename && !resumeFile && !uploading && (
|
||||
<div className="space-y-4">
|
||||
<div className="flex justify-between items-center">
|
||||
<h4 className="font-medium">Current Resume</h4>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={handleReplace}
|
||||
className="btn btn-sm btn-outline gap-1"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M4 2a1 1 0 011 1v2.101a7.002 7.002 0 0111.601 2.566 1 1 0 11-1.885.666A5.002 5.002 0 005.999 7H9a1 1 0 010 2H4a1 1 0 01-1-1V3a1 1 0 011-1zm.008 9.057a1 1 0 011.276.61A5.002 5.002 0 0014.001 13H11a1 1 0 110-2h5a1 1 0 011 1v5a1 1 0 11-2 0v-2.101a7.002 7.002 0 01-11.601-2.566 1 1 0 01.61-1.276z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Replace
|
||||
</button>
|
||||
<button
|
||||
onClick={handleDelete}
|
||||
className="btn btn-sm btn-error btn-outline gap-1"
|
||||
>
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-4 w-4" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M9 2a1 1 0 00-.894.553L7.382 4H4a1 1 0 000 2v10a2 2 0 002 2h8a2 2 0 002-2V6a1 1 0 100-2h-3.382l-.724-1.447A1 1 0 0011 2H9zM7 8a1 1 0 012 0v6a1 1 0 11-2 0V8zm5-1a1 1 0 00-1 1v6a1 1 0 102 0V8a1 1 0 00-1-1z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Delete
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="border border-base-300 rounded-lg overflow-hidden">
|
||||
<FilePreview
|
||||
url={resumeUrl}
|
||||
filename={resumeFilename}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{/* Resume upload guidelines */}
|
||||
<div className="bg-base-200/50 p-4 rounded-lg mt-4">
|
||||
<h4 className="font-medium mb-2 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-info" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="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 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Resume Guidelines
|
||||
</h4>
|
||||
<ul className="list-disc list-inside text-sm opacity-70 space-y-1">
|
||||
<li>Accepted formats: PDF, DOCX</li>
|
||||
<li>Maximum file size: 50MB</li>
|
||||
<li>Keep your resume up-to-date for better opportunities</li>
|
||||
<li>Highlight your skills, experience, and education</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
{/* Resume Benefits */}
|
||||
<div className="bg-primary/10 p-4 rounded-lg mt-4">
|
||||
<h4 className="font-medium mb-2 flex items-center gap-2">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-primary" viewBox="0 0 20 20" fill="currentColor">
|
||||
<path fillRule="evenodd" d="M6.267 3.455a3.066 3.066 0 001.745-.723 3.066 3.066 0 013.976 0 3.066 3.066 0 001.745.723 3.066 3.066 0 012.812 2.812c.051.643.304 1.254.723 1.745a3.066 3.066 0 010 3.976 3.066 3.066 0 00-.723 1.745 3.066 3.066 0 01-2.812 2.812 3.066 3.066 0 00-1.745.723 3.066 3.066 0 01-3.976 0 3.066 3.066 0 00-1.745-.723 3.066 3.066 0 01-2.812-2.812 3.066 3.066 0 00-.723-1.745 3.066 3.066 0 010-3.976 3.066 3.066 0 00.723-1.745 3.066 3.066 0 012.812-2.812zm7.44 5.252a1 1 0 00-1.414-1.414L9 10.586 7.707 9.293a1 1 0 00-1.414 1.414l2 2a1 1 0 001.414 0l4-4z" clipRule="evenodd" />
|
||||
</svg>
|
||||
Why Upload Your Resume?
|
||||
</h4>
|
||||
<ul className="list-disc list-inside text-sm space-y-1">
|
||||
<li className="text-primary-focus">Increase visibility to recruiters and industry partners</li>
|
||||
<li className="text-primary-focus">Get matched with relevant job opportunities</li>
|
||||
<li className="text-primary-focus">Simplify application process for IEEE UCSD events</li>
|
||||
<li className="text-primary-focus">Access personalized career resources and recommendations</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
|
@ -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) {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue