iris_backend / src /components /ResumeBuilder.jsx
sameer2026's picture
fix: ats resume builder UI and backend extraction handling
9d6cc86
import React, { useState, useRef } from 'react';
import { motion } from 'framer-motion';
import { DownloadIcon, ArrowLeftIcon, PlusIcon, TrashIcon } from './Icons';
export default function ResumeBuilder({ analysisResult, onBack }) {
const printRef = useRef(null);
const missingKeywords = analysisResult?.matches?.filter(m => !m.found) || [];
const resumeData = analysisResult?.resume_data || {};
// Form State - Auto-fill from resume_data if available
const [personalInfo, setPersonalInfo] = useState({
fullName: resumeData.name || '',
email: resumeData.email || '',
phone: resumeData.phone || '',
location: resumeData.location || '',
linkedin: resumeData.linkedin || '',
portfolio: resumeData.portfolio || ''
});
const [summary, setSummary] = useState(resumeData.summary || '');
const [experience, setExperience] = useState(
resumeData.work_experience?.length > 0
? resumeData.work_experience.map((exp, idx) => ({
id: Date.now() + idx,
company: exp.company || '',
role: exp.role || '',
startDate: exp.years || exp.start_date || '',
endDate: exp.end_date || '',
description: Array.isArray(exp.responsibilities) ? exp.responsibilities.map(r => `β€’ ${r}`).join('\n') : (exp.responsibilities || exp.description || '')
}))
: [{ id: 1, company: '', role: '', startDate: '', endDate: '', description: '' }]
);
const [education, setEducation] = useState(
resumeData.education?.length > 0
? resumeData.education.map((edu, idx) => ({
id: Date.now() + idx,
institution: edu.institution || '',
degree: edu.course || edu.degree || '',
startDate: edu.year || edu.start_date || '',
endDate: edu.end_date || '',
location: edu.location || ''
}))
: [{ id: 1, institution: '', degree: '', startDate: '', endDate: '' }]
);
const [projects, setProjects] = useState(
resumeData.projects?.length > 0
? resumeData.projects.map((proj, idx) => ({
id: Date.now() + idx,
name: proj.title || proj.name || '',
description: proj.description || '',
link: proj.link || ''
}))
: [{ id: 1, name: '', description: '', link: '' }]
);
const [certifications, setCertifications] = useState(
resumeData.certifications?.length > 0
? resumeData.certifications.map((cert, idx) => {
if (typeof cert === 'string') {
const parts = cert.split(/ - | – | β€” |: /);
if (parts.length > 1) {
return { id: Date.now() + idx, issuer: parts[0].trim(), name: parts.slice(1).join(' - ').trim(), date: '' };
}
return { id: Date.now() + idx, name: cert, issuer: '', date: '' };
}
return {
id: Date.now() + idx,
name: cert.name || cert.title || '',
issuer: cert.issuer || '',
date: cert.date || ''
};
})
: [{ id: 1, name: '', issuer: '', date: '' }]
);
const initialSkills = [];
if (resumeData.technical_skills) initialSkills.push(...resumeData.technical_skills);
if (resumeData.skills) initialSkills.push(...resumeData.skills);
const [skills, setSkills] = useState(initialSkills.join(', '));
const [themeColor, setThemeColor] = useState('#000000');
// Drag and Drop Handlers
const handleDragStart = (e, keyword) => {
e.dataTransfer.setData("text/plain", keyword);
};
const handleDrop = (e, setter, currentValue) => {
e.preventDefault();
const draggedText = e.dataTransfer.getData("text/plain");
if (draggedText) {
// Append the dropped text
// Determine if we need a comma or space based on context (hacky but functional for this scope)
const separator = currentValue ? (setter === setSkills ? ', ' : ' ') : '';
setter(`${currentValue}${separator}${draggedText}`);
}
};
const handleExperienceDrop = (e, id, currentValue) => {
e.preventDefault();
const draggedText = e.dataTransfer.getData("text/plain");
if (draggedText) {
const separator = currentValue ? ' ' : '';
updateExperience(id, 'description', `${currentValue}${separator}${draggedText}`);
}
};
const handleProjectDrop = (e, id, currentValue) => {
e.preventDefault();
const draggedText = e.dataTransfer.getData("text/plain");
if (draggedText) {
const separator = currentValue ? ' ' : '';
updateProject(id, 'description', `${currentValue}${separator}${draggedText}`);
}
};
const handleDragOver = (e) => {
e.preventDefault(); // Necessary to allow dropping
};
// Handlers
const handlePersonalInfoChange = (e) => {
const { name, value } = e.target;
setPersonalInfo(prev => ({ ...prev, [name]: value }));
};
const addExperience = () => {
setExperience(prev => [...prev, { id: Date.now(), company: '', role: '', startDate: '', endDate: '', description: '' }]);
};
const updateExperience = (id, field, value) => {
setExperience(prev => prev.map(exp => exp.id === id ? { ...exp, [field]: value } : exp));
};
const removeExperience = (id) => {
setExperience(prev => prev.filter(exp => exp.id !== id));
};
const addEducation = () => {
setEducation(prev => [...prev, { id: Date.now(), institution: '', degree: '', startDate: '', endDate: '' }]);
};
const updateEducation = (id, field, value) => {
setEducation(prev => prev.map(edu => edu.id === id ? { ...edu, [field]: value } : edu));
};
const removeEducation = (id) => {
setEducation(prev => prev.filter(edu => edu.id !== id));
};
const addProject = () => setProjects(prev => [...prev, { id: Date.now(), name: '', description: '', link: '' }]);
const updateProject = (id, field, value) => setProjects(prev => prev.map(proj => proj.id === id ? { ...proj, [field]: value } : proj));
const removeProject = (id) => setProjects(prev => prev.filter(proj => proj.id !== id));
const addCertification = () => setCertifications(prev => [...prev, { id: Date.now(), name: '', issuer: '', date: '' }]);
const updateCertification = (id, field, value) => setCertifications(prev => prev.map(cert => cert.id === id ? { ...cert, [field]: value } : cert));
const removeCertification = (id) => setCertifications(prev => prev.filter(cert => cert.id !== id));
const handlePrint = () => {
window.print();
};
const inputStyle = {
width: '100%',
padding: '0.75rem',
borderRadius: '0.5rem',
border: '1px solid rgba(236, 183, 26, 0.46)',
backgroundColor: 'rgba(255,255,255,0.05)',
color: 'white',
marginBottom: '1rem',
fontFamily: 'inherit',
};
const labelStyle = {
display: 'block',
marginBottom: '0.5rem',
color: '#d1d5db',
fontSize: '0.875rem'
};
const sectionHeaderStyle = {
fontSize: '1.25rem',
fontWeight: 'bold',
color: '#FBBF24',
marginBottom: '1rem',
borderBottom: '1px solid rgba(236, 183, 26, 0.3)',
paddingBottom: '0.5rem'
};
const resumeSectionHeaderStyle = {
fontSize: '14px',
fontVariant: 'small-caps',
fontWeight: 'bold',
textTransform: 'uppercase',
borderBottom: `1px solid ${themeColor}`,
color: themeColor,
paddingBottom: '2px',
marginBottom: '8px',
letterSpacing: '1px'
};
return (
<div style={{ display: 'grid', gridTemplateColumns: 'minmax(300px, 1fr) 1.5fr', gap: '2rem', height: 'calc(100vh - 150px)' }}>
{/* Left Panel: Form & Gaps */}
<div style={{ display: 'flex', flexDirection: 'column', gap: '1.5rem', overflowY: 'auto', paddingRight: '1rem' }} className="no-print hide-scrollbar">
{/* Header Actions */}
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
<button
onClick={onBack}
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', background: 'none', border: 'none', color: '#d1d5db', cursor: 'pointer', padding: '0.5rem 0' }}
>
<ArrowLeftIcon /> Back to Analysis
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '1rem' }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', backgroundColor: 'rgba(255,255,255,0.05)', padding: '0.25rem 0.5rem', borderRadius: '0.5rem', border: '1px solid rgba(255,255,255,0.1)' }}>
<label style={{ fontSize: '0.75rem', color: '#d1d5db' }}>Theme Color</label>
<div style={{ position: 'relative', width: '24px', height: '24px', borderRadius: '4px', backgroundColor: themeColor, border: '1px solid rgba(255,255,255,0.2)', overflow: 'hidden' }}>
<input
type="color"
value={themeColor}
onChange={(e) => setThemeColor(e.target.value)}
style={{ position: 'absolute', top: '-10px', left: '-10px', width: '50px', height: '50px', cursor: 'pointer', opacity: 0 }}
title="Change Resume Theme Color"
/>
</div>
</div>
<button
onClick={handlePrint}
style={{ display: 'flex', alignItems: 'center', gap: '0.5rem', backgroundColor: '#FBBF24', color: 'black', border: 'none', padding: '0.5rem 1rem', borderRadius: '0.5rem', fontWeight: 'bold', cursor: 'pointer' }}
>
<DownloadIcon /> Export PDF
</button>
</div>
</div>
{/* Fix Your Gaps Panel */}
{missingKeywords.length > 0 && (
<div style={{
backgroundColor: 'rgba(239, 68, 68, 0.1)',
border: '1px solid rgba(239, 68, 68, 0.3)',
borderRadius: '1rem',
padding: '1.5rem',
position: 'sticky',
top: 0,
zIndex: 10,
backdropFilter: 'blur(8px)', // Optional: makes it look better when scrolling over text
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.5)'
}}>
<h3 style={{ fontSize: '1.125rem', fontWeight: 'bold', color: '#EF4444', marginBottom: '1rem' }}>Fix Your ATS Gaps</h3>
<p style={{ color: '#d1d5db', fontSize: '0.875rem', marginBottom: '1rem' }}>Drag and drop these keywords into your text fields below.</p>
<div style={{ display: 'flex', flexWrap: 'wrap', gap: '0.5rem' }}>
{missingKeywords.map((match, idx) => (
<span
key={idx}
draggable
onDragStart={(e) => handleDragStart(e, match.keyword)}
style={{
backgroundColor: 'rgba(239, 68, 68, 0.2)',
color: '#FCA5A5',
padding: '0.25rem 0.5rem',
borderRadius: '0.25rem',
fontSize: '0.75rem',
border: '1px solid rgba(239, 68, 68, 0.4)',
cursor: 'grab'
}}
>
{match.keyword}
</span>
))}
</div>
</div>
)}
{/* Builder Form */}
<div style={{ backgroundColor: 'rgba(255,255,255,0.02)', border: '1px solid rgba(255,255,255,0.1)', borderRadius: '1rem', padding: '1.5rem' }}>
{/* Personal Info */}
<div style={{ marginBottom: '2rem' }}>
<h3 style={sectionHeaderStyle}>Personal Information</h3>
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div><label style={labelStyle}>Full Name</label><input style={inputStyle} name="fullName" value={personalInfo.fullName} onChange={handlePersonalInfoChange} placeholder="John Doe" /></div>
<div><label style={labelStyle}>Email</label><input style={inputStyle} name="email" value={personalInfo.email} onChange={handlePersonalInfoChange} placeholder="john@example.com" /></div>
<div><label style={labelStyle}>Phone</label><input style={inputStyle} name="phone" value={personalInfo.phone} onChange={handlePersonalInfoChange} placeholder="(555) 123-4567" /></div>
<div><label style={labelStyle}>Location</label><input style={inputStyle} name="location" value={personalInfo.location} onChange={handlePersonalInfoChange} placeholder="City, State" /></div>
<div><label style={labelStyle}>LinkedIn (Optional)</label><input style={inputStyle} name="linkedin" value={personalInfo.linkedin} onChange={handlePersonalInfoChange} placeholder="linkedin.com/in/johndoe" /></div>
<div><label style={labelStyle}>Portfolio (Optional)</label><input style={inputStyle} name="portfolio" value={personalInfo.portfolio} onChange={handlePersonalInfoChange} placeholder="johndoe.com" /></div>
</div>
</div>
{/* Summary */}
<div style={{ marginBottom: '2rem' }}>
<h3 style={sectionHeaderStyle}>Professional Summary</h3>
<textarea
style={{ ...inputStyle, minHeight: '100px', resize: 'vertical' }}
value={summary}
onChange={e => setSummary(e.target.value)}
onDrop={(e) => handleDrop(e, setSummary, summary)}
onDragOver={handleDragOver}
placeholder="Brief overview of your professional background and goals..."
/>
</div>
{/* Experience */}
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Experience</h3>
<button onClick={addExperience} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
<PlusIcon size={14} /> Add Role
</button>
</div>
{experience.map((exp, index) => (
<div key={exp.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
{index > 0 && (
<button onClick={() => removeExperience(exp.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Role">
<TrashIcon size={16} />
</button>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div><label style={labelStyle}>Company</label><input style={inputStyle} value={exp.company} onChange={e => updateExperience(exp.id, 'company', e.target.value)} placeholder="Acme Corp" /></div>
<div><label style={labelStyle}>Job Title</label><input style={inputStyle} value={exp.role} onChange={e => updateExperience(exp.id, 'role', e.target.value)} placeholder="Software Engineer" /></div>
<div><label style={labelStyle}>Start Date</label><input style={inputStyle} value={exp.startDate} onChange={e => updateExperience(exp.id, 'startDate', e.target.value)} placeholder="MMM YYYY" /></div>
<div><label style={labelStyle}>End Date</label><input style={inputStyle} value={exp.endDate} onChange={e => updateExperience(exp.id, 'endDate', e.target.value)} placeholder="MMM YYYY or Present" /></div>
</div>
<label style={labelStyle}>Description (Bullet points recommended)</label>
<textarea
style={{ ...inputStyle, minHeight: '80px', resize: 'vertical', marginBottom: 0 }}
value={exp.description}
onChange={e => updateExperience(exp.id, 'description', e.target.value)}
onDrop={(e) => handleExperienceDrop(e, exp.id, exp.description)}
onDragOver={handleDragOver}
placeholder="β€’ Achieved X by doing Y..."
/>
</div>
))}
</div>
{/* Education */}
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Education</h3>
<button onClick={addEducation} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
<PlusIcon size={14} /> Add Degree
</button>
</div>
{education.map((edu, index) => (
<div key={edu.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
{index > 0 && (
<button onClick={() => removeEducation(edu.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Degree">
<TrashIcon size={16} />
</button>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem' }}>
<div><label style={labelStyle}>Institution</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.institution} onChange={e => updateEducation(edu.id, 'institution', e.target.value)} placeholder="University of State" /></div>
<div><label style={labelStyle}>Degree / Major</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.degree} onChange={e => updateEducation(edu.id, 'degree', e.target.value)} placeholder="B.S. Computer Science" /></div>
<div><label style={labelStyle}>Start Date</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.startDate} onChange={e => updateEducation(edu.id, 'startDate', e.target.value)} placeholder="YYYY" /></div>
<div><label style={labelStyle}>End Date</label><input style={{ ...inputStyle, marginBottom: 0 }} value={edu.endDate} onChange={e => updateEducation(edu.id, 'endDate', e.target.value)} placeholder="YYYY" /></div>
</div>
</div>
))}
</div>
{/* Projects */}
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Projects</h3>
<button onClick={addProject} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
<PlusIcon size={14} /> Add Project
</button>
</div>
{projects.map((proj, index) => (
<div key={proj.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
{index > 0 && (
<button onClick={() => removeProject(proj.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Project">
<TrashIcon size={16} />
</button>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr', gap: '1rem', marginBottom: '1rem' }}>
<div><label style={labelStyle}>Project Name</label><input style={{ ...inputStyle, marginBottom: 0 }} value={proj.name} onChange={e => updateProject(proj.id, 'name', e.target.value)} placeholder="E-commerce App" /></div>
<div><label style={labelStyle}>Link (Optional)</label><input style={{ ...inputStyle, marginBottom: 0 }} value={proj.link} onChange={e => updateProject(proj.id, 'link', e.target.value)} placeholder="github.com/..." /></div>
</div>
<label style={labelStyle}>Description (Bullet points recommended)</label>
<textarea
style={{ ...inputStyle, minHeight: '60px', resize: 'vertical', marginBottom: 0 }}
value={proj.description}
onChange={e => updateProject(proj.id, 'description', e.target.value)}
onDrop={(e) => handleProjectDrop(e, proj.id, proj.description)}
onDragOver={handleDragOver}
placeholder="β€’ Developed using React..."
/>
</div>
))}
</div>
{/* Certifications */}
<div style={{ marginBottom: '2rem' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '1rem' }}>
<h3 style={{ ...sectionHeaderStyle, borderBottom: 'none', marginBottom: 0 }}>Certifications</h3>
<button onClick={addCertification} style={{ display: 'flex', alignItems: 'center', gap: '0.25rem', background: 'none', border: '1px solid #FBBF24', color: '#FBBF24', padding: '0.25rem 0.5rem', borderRadius: '0.25rem', cursor: 'pointer', fontSize: '0.75rem' }}>
<PlusIcon size={14} /> Add Certification
</button>
</div>
{certifications.map((cert, index) => (
<div key={cert.id} style={{ backgroundColor: 'rgba(0,0,0,0.2)', padding: '1rem', borderRadius: '0.5rem', marginBottom: '1rem', position: 'relative' }}>
{index > 0 && (
<button onClick={() => removeCertification(cert.id)} style={{ position: 'absolute', top: '1rem', right: '1rem', background: 'none', border: 'none', color: '#EF4444', cursor: 'pointer' }} title="Remove Certification">
<TrashIcon size={16} />
</button>
)}
<div style={{ display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: '1rem' }}>
<div><label style={labelStyle}>Name</label><input style={{ ...inputStyle, marginBottom: 0 }} value={cert.name} onChange={e => updateCertification(cert.id, 'name', e.target.value)} placeholder="AWS Cloud Practitioner" /></div>
<div><label style={labelStyle}>Issuer</label><input style={{ ...inputStyle, marginBottom: 0 }} value={cert.issuer} onChange={e => updateCertification(cert.id, 'issuer', e.target.value)} placeholder="Amazon Web Services" /></div>
<div><label style={labelStyle}>Date / Year</label><input style={{ ...inputStyle, marginBottom: 0 }} value={cert.date} onChange={e => updateCertification(cert.id, 'date', e.target.value)} placeholder="YYYY" /></div>
</div>
</div>
))}
</div>
{/* Skills */}
<div>
<h3 style={sectionHeaderStyle}>Skills</h3>
<label style={labelStyle}>Comma separated list of skills</label>
<textarea
style={{ ...inputStyle, minHeight: '80px', resize: 'vertical' }}
value={skills}
onChange={e => setSkills(e.target.value)}
onDrop={(e) => handleDrop(e, setSkills, skills)}
onDragOver={handleDragOver}
placeholder="React, Python, SQL, Project Management..."
/>
</div>
</div>
</div>
{/* Right Panel: Live Preview (Printable Area) */}
<div
style={{
backgroundColor: '#52525B', // Darker backdrop for contrast
padding: '2rem',
overflowY: 'auto',
borderRadius: '1rem',
display: 'flex',
justifyContent: 'center'
}}
className="hide-scrollbar" // Wrapper shouldn't have no-print so children can print
>
{/* Actual Paper Element */}
<div
ref={printRef}
className="resume-print-area"
style={{
backgroundColor: 'white',
color: 'black',
width: '100%',
maxWidth: '850px',
minHeight: '1100px', // ~8.5x11 aspect ratio
padding: '40px 50px',
boxShadow: '0 20px 25px -5px rgba(0, 0, 0, 0.5)',
fontFamily: "'Times New Roman', Times, serif",
lineHeight: '1.4',
boxSizing: 'border-box'
}}
>
{/* Header */}
<div style={{ textAlign: 'center', marginBottom: '20px' }}>
<h1 style={{ fontSize: '36px', fontWeight: 'normal', margin: '0 0 10px 0', letterSpacing: '1px', color: themeColor }}>
{personalInfo.fullName || 'Your Name'}
</h1>
<div style={{ display: 'flex', flexWrap: 'wrap', justifyContent: 'center', gap: '16px', fontSize: '13px' }}>
{personalInfo.phone && (
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M6.62 10.79a15.053 15.053 0 0 0 6.59 6.59l2.2-2.2a1 1 0 0 1 1.11-.27c1.12.37 2.33.57 3.58.57a1 1 0 0 1 1 1V20a1 1 0 0 1-1 1A19 19 0 0 1 3 4a1 1 0 0 1 1-1h3.5a1 1 0 0 1 1 1c0 1.25.2 2.45.57 3.57a1 1 0 0 1-.25 1.11l-2.2 2.2z"></path></svg>
{personalInfo.phone}
</div>
)}
{personalInfo.email && (
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M20 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V6a2 2 0 0 0-2-2zm0 4l-8 5-8-5V6l8 5 8-5v2z"></path></svg>
{personalInfo.email}
</div>
)}
{personalInfo.linkedin && (
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M19 0h-14c-2.761 0-5 2.239-5 5v14c0 2.761 2.239 5 5 5h14c2.762 0 5-2.239 5-5v-14c0-2.761-2.238-5-5-5zm-11 19h-3v-11h3v11zm-1.5-12.268c-.966 0-1.75-.79-1.75-1.764s.784-1.764 1.75-1.764 1.75.79 1.75 1.764-.783 1.764-1.75 1.764zm13.5 12.268h-3v-5.604c0-3.368-4-3.113-4 0v5.604h-3v-11h3v1.765c1.396-2.586 7-2.777 7 2.476v6.759z"></path></svg>
{personalInfo.linkedin}
</div>
)}
{personalInfo.portfolio && (
<div style={{ display: 'flex', alignItems: 'center', gap: '6px' }}>
<svg width="12" height="12" viewBox="0 0 24 24" fill="currentColor"><path d="M12 0c-6.626 0-12 5.373-12 12 0 5.302 3.438 9.8 8.207 11.387.599.111.793-.261.793-.577v-2.234c-3.338.726-4.033-1.416-4.033-1.416-.546-1.387-1.333-1.756-1.333-1.756-1.089-.745.083-.729.083-.729 1.205.084 1.839 1.237 1.839 1.237 1.07 1.834 2.807 1.304 3.492.997.107-.775.418-1.305.762-1.604-2.665-.305-5.467-1.334-5.467-5.931 0-1.311.469-2.381 1.236-3.221-.124-.303-.535-1.524.117-3.176 0 0 1.008-.322 3.301 1.23.957-.266 1.983-.399 3.003-.404 1.02.005 2.047.138 3.006.404 2.291-1.552 3.297-1.23 3.297-1.23.653 1.653.242 2.874.118 3.176.77.84 1.235 1.911 1.235 3.221 0 4.609-2.807 5.624-5.479 5.921.43.372.823 1.102.823 2.222v3.293c0 .319.192.694.801.576 4.765-1.589 8.199-6.086 8.199-11.386 0-6.627-5.373-12-12-12z"></path></svg>
{personalInfo.portfolio}
</div>
)}
</div>
</div>
{/* Summary */}
{summary && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Summary
</h2>
<p style={{ fontSize: '13px', margin: 0, textAlign: 'justify' }}>
{summary}
</p>
</div>
)}
{/* Education */}
{education.some(edu => edu.institution || edu.degree) && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Education
</h2>
{education.map(edu => (
(edu.institution || edu.degree) ? (
<div key={edu.id} style={{ marginBottom: '8px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px' }}>
<span style={{ fontWeight: 'bold' }}>{edu.institution}</span>
<span>{edu.location || ''}</span>
</div>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px', fontStyle: 'italic' }}>
<span>{edu.degree}</span>
<span>{edu.startDate} {edu.startDate && edu.endDate && '–'} {edu.endDate}</span>
</div>
</div>
) : null
))}
</div>
)}
{/* Technical Skills */}
{skills && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Technical Skills
</h2>
<p style={{ fontSize: '13px', margin: 0 }}>
{skills}
</p>
</div>
)}
{/* Professional Experience */}
{experience.some(exp => exp.company || exp.role) && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Experience
</h2>
{experience.map(exp => (
(exp.company || exp.role) ? (
<div key={exp.id} style={{ marginBottom: '12px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px' }}>
<span style={{ fontWeight: 'bold' }}>{exp.role}</span>
<span style={{ fontWeight: 'bold' }}>{exp.startDate} {exp.startDate && exp.endDate && '–'} {exp.endDate}</span>
</div>
<div style={{ fontSize: '13px', fontStyle: 'italic', marginBottom: '4px' }}>
{exp.company}
</div>
{exp.description && (
<div style={{ fontSize: '13px' }}>
{exp.description.split('\n').map((line, i) => {
const trimmedLine = line.trim();
if (!trimmedLine) return null;
return (
<div style={{ display: 'flex', marginBottom: '2px', paddingLeft: '16px' }} key={i}>
<span style={{ marginRight: '8px' }}>–</span>
<span>{trimmedLine.replace(/^[β€’-]\s*/, '')}</span>
</div>
);
})}
</div>
)}
</div>
) : null
))}
</div>
)}
{/* Projects */}
{projects.some(proj => proj.name || proj.description) && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Projects
</h2>
{projects.map(proj => (
(proj.name || proj.description) ? (
<div key={proj.id} style={{ marginBottom: '10px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between', fontSize: '13px', marginBottom: '4px' }}>
<span><strong style={{ fontWeight: 'bold' }}>{proj.name}</strong>{proj.link ? ` | ${proj.link}` : ''}</span>
</div>
{proj.description && (
<div style={{ fontSize: '13px' }}>
{proj.description.split('\n').map((line, i) => {
const trimmedLine = line.trim();
if (!trimmedLine) return null;
return (
<div style={{ display: 'flex', marginBottom: '2px', paddingLeft: '16px' }} key={i}>
<span style={{ marginRight: '8px' }}>–</span>
<span>{trimmedLine.replace(/^[β€’-]\s*/, '')}</span>
</div>
);
})}
</div>
)}
</div>
) : null
))}
</div>
)}
{/* Certifications */}
{certifications.some(cert => cert.name || cert.issuer) && (
<div style={{ marginBottom: '16px' }}>
<h2 style={resumeSectionHeaderStyle}>
Certifications
</h2>
{certifications.map(cert => (
(cert.name || cert.issuer) ? (
<div key={cert.id} style={{ marginBottom: '4px', fontSize: '13px' }}>
<div style={{ display: 'flex', justifyContent: 'space-between' }}>
<span>
{cert.issuer && <strong style={{ fontWeight: 'bold' }}>{cert.issuer}</strong>}
{cert.issuer && cert.name && ' β€” '}
{cert.name && <span>{cert.name}</span>}
</span>
<span>{cert.date}</span>
</div>
</div>
) : null
))}
</div>
)}
</div>
</div>
</div>
);
}