Spaces:
Sleeping
Sleeping
| import React from 'react'; | |
| import { motion, AnimatePresence } from 'framer-motion'; | |
| import { X, Mail, Phone, MapPin, Briefcase, GraduationCap, Award, User, Loader2 } from 'lucide-react'; | |
| import { supabase } from '../supabaseClient'; | |
| const FullProfileOverlay = ({ candidate, onClose }) => { | |
| const [profile, setProfile] = React.useState(null); | |
| const [loading, setLoading] = React.useState(true); | |
| React.useEffect(() => { | |
| const fetchFullProfile = async () => { | |
| if (!candidate?.userId && !candidate?.id) return; | |
| setLoading(true); | |
| try { | |
| const targetId = candidate.userId || candidate.id; | |
| const { data, error } = await supabase | |
| .from('profiles') | |
| .select('*') | |
| .eq('id', targetId) | |
| .single(); | |
| if (error) throw error; | |
| setProfile(data); | |
| } catch (error) { | |
| console.error("Error fetching full profile:", error); | |
| setProfile(candidate); | |
| } finally { | |
| setLoading(false); | |
| } | |
| }; | |
| fetchFullProfile(); | |
| }, [candidate]); | |
| if (!candidate) return null; | |
| if (loading) { | |
| return ( | |
| <div style={{ position: 'fixed', inset: 0, zIndex: 60, display: 'flex', alignItems: 'center', justifyContent: 'center', backgroundColor: 'rgba(0, 0, 0, 0.7)', backdropFilter: 'blur(8px)' }}> | |
| <motion.div animate={{ rotate: 360 }} transition={{ repeat: Infinity, duration: 1, ease: 'linear' }}> | |
| <Loader2 size={48} color="#EF4444" /> | |
| </motion.div> | |
| </div> | |
| ); | |
| } | |
| const displayData = profile || candidate; | |
| // Helper to safely parse skills (array or CSV string) | |
| const parseSkills = (data) => { | |
| if (Array.isArray(data)) return data; | |
| if (typeof data === 'string') return data.split(',').map(s => s.trim()).filter(Boolean); | |
| return []; | |
| }; | |
| const allSkills = [...new Set([ | |
| ...parseSkills(displayData.skills), | |
| ...parseSkills(displayData.technical_skills) | |
| ])]; | |
| const fullCandidate = { | |
| ...displayData, | |
| name: displayData.full_name || displayData.name || 'No Name Provided', | |
| role: displayData.current_position || displayData.role || 'Applicant', | |
| email: displayData.email || 'No email available', | |
| phone: displayData.phone || 'No phone provided', | |
| location: displayData.location || 'Remote', | |
| avatar: displayData.avatar_url || displayData.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(displayData.full_name || 'User')}`, | |
| about: displayData.summary || displayData.headline || "No summary available.", | |
| skills: allSkills, | |
| education: Array.isArray(displayData.education) ? displayData.education : [], | |
| experience_details: Array.isArray(displayData.work_experience) ? displayData.work_experience : [], | |
| projects: Array.isArray(displayData.projects) ? displayData.projects : [] | |
| }; | |
| return ( | |
| <motion.div | |
| initial={{ opacity: 0 }} | |
| animate={{ opacity: 1 }} | |
| exit={{ opacity: 0 }} | |
| style={{ | |
| position: 'fixed', | |
| inset: 0, | |
| zIndex: 60, // Higher than Drawer (50) | |
| display: 'flex', | |
| alignItems: 'center', | |
| justifyContent: 'center', | |
| backgroundColor: 'rgba(0, 0, 0, 0.7)', | |
| backdropFilter: 'blur(8px)', | |
| padding: '2rem' | |
| }} | |
| > | |
| <motion.div | |
| initial={{ scale: 0.9, opacity: 0, y: 20 }} | |
| animate={{ scale: 1, opacity: 1, y: 0 }} | |
| exit={{ scale: 0.9, opacity: 0, y: 20 }} | |
| transition={{ type: 'spring', damping: 25, stiffness: 300 }} | |
| style={{ | |
| width: '100%', | |
| maxWidth: '900px', | |
| maxHeight: '90vh', | |
| overflowY: 'auto', | |
| backgroundColor: '#0f172a', | |
| backgroundImage: ` | |
| radial-gradient(at 0% 0%, rgba(56, 189, 248, 0.15) 0px, transparent 50%), | |
| radial-gradient(at 100% 100%, rgba(239, 68, 68, 0.15) 0px, transparent 50%), | |
| linear-gradient(135deg, rgba(255,255,255,0.03) 0%, transparent 100%) | |
| `, | |
| borderRadius: '1.5rem', | |
| border: '1px solid rgba(255,255,255,0.1)', | |
| boxShadow: '0 25px 50px -12px rgba(0, 0, 0, 0.5)', | |
| color: 'white', | |
| display: 'flex', | |
| flexDirection: 'column' | |
| }} | |
| > | |
| {/* === Header === */} | |
| <div style={{ | |
| padding: '2rem', | |
| borderBottom: '1px solid rgba(255,255,255,0.1)', | |
| display: 'flex', | |
| justifyContent: 'space-between', | |
| alignItems: 'start', | |
| position: 'sticky', | |
| top: 0, | |
| backgroundColor: 'rgba(15, 23, 42, 0.95)', | |
| backdropFilter: 'blur(4px)', | |
| zIndex: 10 | |
| }}> | |
| <div style={{ display: 'flex', gap: '1.5rem', alignItems: 'center' }}> | |
| <div style={{ | |
| width: '80px', | |
| height: '80px', | |
| borderRadius: '50%', | |
| overflow: 'hidden', | |
| border: '3px solid rgba(255,255,255,0.1)', | |
| boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.1)' | |
| }}> | |
| <img | |
| src={fullCandidate.avatar || `https://ui-avatars.com/api/?name=${encodeURIComponent(fullCandidate.name)}&background=random`} | |
| alt={fullCandidate.name} | |
| style={{ width: '100%', height: '100%', objectFit: 'cover' }} | |
| /> | |
| </div> | |
| <div> | |
| <h2 style={{ fontSize: '2rem', fontWeight: 'bold', margin: 0, lineHeight: 1.2 }}>{fullCandidate.name}</h2> | |
| <p style={{ color: '#94a3b8', fontSize: '1.1rem', margin: '0.25rem 0 0 0' }}>{fullCandidate.role}</p> | |
| <div style={{ display: 'flex', gap: '1rem', marginTop: '0.75rem', fontSize: '0.9rem', color: '#cbd5e1' }}> | |
| <span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}><Mail size={14} /> {fullCandidate.email}</span> | |
| <span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}><Phone size={14} /> {fullCandidate.phone}</span> | |
| <span style={{ display: 'flex', alignItems: 'center', gap: '6px' }}><MapPin size={14} /> {fullCandidate.location}</span> | |
| </div> | |
| </div> | |
| </div> | |
| <button | |
| onClick={onClose} | |
| style={{ | |
| padding: '0.75rem', | |
| borderRadius: '0.75rem', | |
| backgroundColor: 'rgba(255,255,255,0.05)', | |
| border: '1px solid rgba(255,255,255,0.1)', | |
| color: '#94a3b8', | |
| cursor: 'pointer', | |
| transition: 'all 0.2s', | |
| display: 'flex', alignItems: 'center', justifyContent: 'center' | |
| }} | |
| onMouseOver={(e) => { e.currentTarget.style.backgroundColor = 'rgba(239, 68, 68, 0.2)'; e.currentTarget.style.color = 'white'; }} | |
| onMouseOut={(e) => { e.currentTarget.style.backgroundColor = 'rgba(255,255,255,0.05)'; e.currentTarget.style.color = '#94a3b8'; }} | |
| > | |
| <X size={24} /> | |
| </button> | |
| </div> | |
| {/* === Content Body === */} | |
| <div style={{ padding: '2rem', display: 'grid', gridTemplateColumns: 'minmax(300px, 1fr) 2fr', gap: '3rem' }}> | |
| {/* Left Column */} | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}> | |
| {/* About */} | |
| <section> | |
| <h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '1.1rem', fontWeight: '600', color: 'white', marginBottom: '1rem' }}> | |
| <User size={18} color="#38bdf8" /> About | |
| </h3> | |
| <p style={{ color: '#94a3b8', lineHeight: '1.7', fontSize: '0.95rem' }}> | |
| {fullCandidate.about} | |
| </p> | |
| </section> | |
| {/* Skills */} | |
| <section> | |
| <h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '1.1rem', fontWeight: '600', color: 'white', marginBottom: '1rem' }}> | |
| <Award size={18} color="#c084fc" /> Skills | |
| </h3> | |
| <div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}> | |
| {fullCandidate.skills?.map((skill, i) => ( | |
| <span key={i} style={{ | |
| fontSize: '0.85rem', | |
| backgroundColor: 'rgba(56, 189, 248, 0.1)', | |
| color: '#38bdf8', | |
| padding: '0.4rem 0.8rem', | |
| borderRadius: '6px', | |
| border: '1px solid rgba(56, 189, 248, 0.2)' | |
| }}> | |
| {skill} | |
| </span> | |
| ))} | |
| </div> | |
| </section> | |
| {/* Projects Pointers */} | |
| <section> | |
| <h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '1.1rem', fontWeight: '600', color: 'white', marginBottom: '1rem' }}> | |
| <Briefcase size={18} color="#34d399" /> Key Projects | |
| </h3> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '0.75rem' }}> | |
| {fullCandidate.projects?.map((project, i) => ( | |
| <div key={i} style={{ padding: '0.75rem', backgroundColor: 'rgba(255,255,255,0.03)', borderRadius: '0.5rem', border: '1px solid rgba(255,255,255,0.05)' }}> | |
| <div style={{ color: '#e2e8f0', fontSize: '0.95rem', fontWeight: '600' }}> | |
| {typeof project === 'object' ? (project.title || project.name) : project} | |
| </div> | |
| {typeof project === 'object' && project.description && ( | |
| <div style={{ color: '#94a3b8', fontSize: '0.85rem', marginTop: '0.25rem' }}> | |
| {project.description} | |
| </div> | |
| )} | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| </div> | |
| {/* Right Column */} | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '2rem' }}> | |
| {/* Experience Timeline */} | |
| <section> | |
| <h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '1.1rem', fontWeight: '600', color: 'white', marginBottom: '1.5rem' }}> | |
| <Briefcase size={18} color="#fcd34d" /> Experience | |
| </h3> | |
| <div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem', position: 'relative', paddingLeft: '1rem' }}> | |
| {/* Vertical Line */} | |
| <div style={{ position: 'absolute', left: 0, top: '10px', bottom: '10px', width: '2px', backgroundColor: 'rgba(255,255,255,0.1)' }} /> | |
| {fullCandidate.experience_details.map((exp, i) => ( | |
| <div key={i} style={{ position: 'relative', paddingLeft: '1.5rem' }}> | |
| {/* Dot */} | |
| <div style={{ position: 'absolute', left: '-5px', top: '6px', width: '12px', height: '12px', borderRadius: '50%', backgroundColor: '#fcd34d', boxShadow: '0 0 10px rgba(252, 211, 77, 0.5)' }} /> | |
| <h4 style={{ color: 'white', fontWeight: '600', fontSize: '1.05rem', marginBottom: '0.25rem' }}>{exp.role || exp.job_title}</h4> | |
| <div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '0.9rem', color: '#94a3b8', marginBottom: '0.5rem' }}> | |
| <span>{exp.company}</span> | |
| <span>{exp.duration || exp.years}</span> | |
| </div> | |
| <p style={{ color: '#cbd5e1', fontSize: '0.95rem', lineHeight: '1.6' }}>{exp.description}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| {/* Education */} | |
| <section> | |
| <h3 style={{ display: 'flex', alignItems: 'center', gap: '8px', fontSize: '1.1rem', fontWeight: '600', color: 'white', marginBottom: '1rem' }}> | |
| <GraduationCap size={18} color="#f472b6" /> Education | |
| </h3> | |
| <div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}> | |
| {fullCandidate.education.map((edu, i) => ( | |
| <div key={i} style={{ padding: '1rem', backgroundColor: 'rgba(255,255,255,0.03)', borderRadius: '0.75rem', border: '1px solid rgba(255,255,255,0.05)' }}> | |
| <h4 style={{ color: 'white', fontWeight: '600', fontSize: '1rem', marginBottom: '0.25rem' }}>{edu.degree || edu.course}</h4> | |
| <p style={{ color: '#94a3b8', fontSize: '0.9rem' }}>{edu.school || edu.institution}</p> | |
| <p style={{ color: '#64748b', fontSize: '0.85rem', marginTop: '0.5rem' }}>{edu.year}</p> | |
| </div> | |
| ))} | |
| </div> | |
| </section> | |
| </div> | |
| </div> | |
| {/* === Footer === */} | |
| <div style={{ | |
| padding: '1.5rem 2rem', | |
| borderTop: '1px solid rgba(255,255,255,0.1)', | |
| backgroundColor: 'rgba(15, 23, 42, 0.5)', | |
| display: 'flex', | |
| justifyContent: 'flex-end', | |
| gap: '1rem' | |
| }}> | |
| <button | |
| onClick={onClose} | |
| style={{ | |
| padding: '0.75rem 1.5rem', | |
| borderRadius: '0.5rem', | |
| border: '1px solid rgba(255,255,255,0.2)', | |
| backgroundColor: 'transparent', | |
| color: 'white', | |
| fontWeight: '600', | |
| cursor: 'pointer' | |
| }} | |
| > | |
| Close | |
| </button> | |
| </div> | |
| </motion.div> | |
| </motion.div> | |
| ); | |
| }; | |
| export default FullProfileOverlay; | |