import React, { useState, useRef } from 'react'; import ReactDOM from 'react-dom/client'; import { GoogleGenAI, Type } from "@google/genai"; import { Mail, Phone, MapPin, Link as LinkIcon, Globe, Plus, Trash2, Wand2, Upload, User, X, Sparkles, FileText, FileDown, Palette, ChevronDown } from 'lucide-react'; // --- TYPES --- export interface Experience { id: string; role: string; company: string; date: string; description: string; } export interface Education { id: string; degree: string; school: string; date: string; } export interface CVData { personalInfo: { fullName: string; email: string; phone: string; location: string; website: string; summary: string; photo?: string; }; experience: Experience[]; education: Education[]; skills: string[]; } export type Theme = 'modern' | 'classic' | 'creative' | 'minimal' | 'professional'; export const INITIAL_CV_DATA: CVData = { personalInfo: { fullName: "Alex Doe", email: "alex.doe@example.com", phone: "(555) 123-4567", location: "New York, NY", website: "linkedin.com/in/alexdoe", summary: "Senior Software Engineer with 8+ years of experience in building scalable web applications. Proven track record of leading teams and delivering high-impact projects on time.", }, experience: [ { id: "1", role: "Senior Frontend Engineer", company: "Tech Solutions Inc.", date: "2020 - Present", description: "• Led the migration of legacy codebase to React 18, improving performance by 40%.\n• Mentored a team of 5 junior developers, conducting code reviews and technical workshops.\n• Implemented a new design system using Tailwind CSS.", }, { id: "2", role: "Software Developer", company: "Creative Web Agency", date: "2017 - 2020", description: "• Developed responsive websites for over 20 clients using HTML, CSS, and JavaScript.\n• Collaborated with designers to ensure pixel-perfect implementation of UI mockups.\n• Optimized database queries to reduce API response times.", }, ], education: [ { id: "1", degree: "B.S. Computer Science", school: "University of Technology", date: "2013 - 2017", }, ], skills: ["React", "TypeScript", "Node.js", "Tailwind CSS", "AWS", "GraphQL", "Agile Leadership"], }; // --- GEMINI SERVICE --- // Initialize with API Key safely const apiKey = process.env.API_KEY || ""; const ai = new GoogleGenAI({ apiKey }); export const parseResumeText = async (text: string): Promise => { const model = "gemini-2.5-flash"; const response = await ai.models.generateContent({ model, contents: `Parse the following raw text into a structured JSON resume. If specific fields are missing, make a best guess or leave them empty string. Ensure the output strictly follows the schema. Raw Text: ${text}`, config: { responseMimeType: "application/json", responseSchema: { type: Type.OBJECT, properties: { personalInfo: { type: Type.OBJECT, properties: { fullName: { type: Type.STRING }, email: { type: Type.STRING }, phone: { type: Type.STRING }, location: { type: Type.STRING }, website: { type: Type.STRING }, summary: { type: Type.STRING }, photo: { type: Type.STRING, description: "Leave empty unless a photo URL is explicitly provided" }, }, required: ["fullName", "email"], }, experience: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { role: { type: Type.STRING }, company: { type: Type.STRING }, date: { type: Type.STRING }, description: { type: Type.STRING }, }, required: ["role", "company"], }, }, education: { type: Type.ARRAY, items: { type: Type.OBJECT, properties: { degree: { type: Type.STRING }, school: { type: Type.STRING }, date: { type: Type.STRING }, }, required: ["degree", "school"], }, }, skills: { type: Type.ARRAY, items: { type: Type.STRING }, }, }, required: ["personalInfo", "experience", "education", "skills"], }, }, }); if (!response.text) { throw new Error("No response from AI"); } const data = JSON.parse(response.text); return { ...data, experience: data.experience.map((e: any) => ({ ...e, id: Math.random().toString(36).substr(2, 9) })), education: data.education.map((e: any) => ({ ...e, id: Math.random().toString(36).substr(2, 9) })), }; }; export const enhanceDescription = async (text: string): Promise => { const model = "gemini-2.5-flash"; const response = await ai.models.generateContent({ model, contents: `Rewrite the following resume bullet points to be more professional, action-oriented, and impactful. Keep it concise. Text: "${text}"`, }); return response.text || text; }; export const generateSummary = async (data: Pick): Promise => { const model = "gemini-2.5-flash"; const experienceText = data.experience.map(e => `${e.role} at ${e.company} (${e.date}): ${e.description}`).join('\n'); const educationText = data.education.map(e => `${e.degree} at ${e.school}`).join('\n'); const skillsText = data.skills.join(', '); const response = await ai.models.generateContent({ model, contents: `Write a professional resume summary (max 3-4 sentences) based on the following details. Highlight years of experience, key roles, and major skills. Do not include placeholders. Experience: ${experienceText} Education: ${educationText} Skills: ${skillsText}`, }); return response.text || ""; }; // --- COMPONENTS --- // 1. CV PREVIEW interface CVPreviewProps { data: CVData; theme: Theme; } const CVPreview: React.FC = ({ data, theme }) => { const { personalInfo, experience, education, skills } = data; const ContactItem = ({ icon: Icon, text }: { icon: any, text: string }) => (
{text}
); // Creative Theme if (theme === 'creative') { return (
{personalInfo.photo && (
Profile
)}

Contact

{personalInfo.email &&
{personalInfo.email}
} {personalInfo.phone &&
{personalInfo.phone}
} {personalInfo.location &&
{personalInfo.location}
} {personalInfo.website &&
{personalInfo.website}
}
{skills.length > 0 && (

Skills

{skills.map((skill, index) => ( {skill} ))}
)} {education.length > 0 && (

Education

{education.map((edu) => (
{edu.school}
{edu.degree}
{edu.date}
))}
)}

{personalInfo.fullName}

{personalInfo.summary}

{experience.length > 0 && (

Experience

{experience.map((exp) => (

{exp.role}

{exp.date}
{exp.company}

{exp.description}

))}
)}
); } // Classic Theme if (theme === 'classic') { return (

{personalInfo.fullName}

{personalInfo.email && {personalInfo.email}} {personalInfo.phone && • {personalInfo.phone}} {personalInfo.location && • {personalInfo.location}} {personalInfo.website && • {personalInfo.website}}
{personalInfo.summary && (

Professional Profile

{personalInfo.summary}

)} {experience.length > 0 && (

Professional Experience

{experience.map(exp => (
{exp.company}
{exp.date}
{exp.role}

{exp.description}

))}
)} {education.length > 0 && (

Education

{education.map(edu => (
{edu.school}
{edu.degree}
{edu.date}
))}
)} {skills.length > 0 && (

Core Competencies

{skills.join(" • ")}
)}
); } // Minimal Theme if (theme === 'minimal') { return (

{personalInfo.fullName}

{personalInfo.email && {personalInfo.email}} {personalInfo.phone && | {personalInfo.phone}} {personalInfo.location && | {personalInfo.location}} {personalInfo.website && | {personalInfo.website}}
{personalInfo.summary && (

Summary

{personalInfo.summary}

)} {experience.length > 0 && (

Experience

{experience.map(exp => (
{exp.role} {exp.date}
{exp.company}

{exp.description}

))}
)} {education.length > 0 && (

Education

{education.map(edu => (
{edu.school} {edu.degree}
{edu.date}
))}
)} {skills.length > 0 && (

Skills

{skills.join(", ")}
)}
); } // Professional Theme if (theme === 'professional') { return (

{personalInfo.fullName}

{personalInfo.email &&
{personalInfo.email}
} {personalInfo.phone &&
{personalInfo.phone}
} {personalInfo.location &&
{personalInfo.location}
} {personalInfo.website &&
{personalInfo.website}
}
{personalInfo.summary && (

Professional Summary

{personalInfo.summary}

)} {experience.length > 0 && (

Work History

{experience.map(exp => (

{exp.role}

{exp.date}
{exp.company}

{exp.description}

))}
)}
{education.length > 0 && (

Education

{education.map(edu => (
{edu.school}
{edu.degree}
{edu.date}
))}
)} {skills.length > 0 && (

Skills

    {skills.map((skill, i) => (
  • {skill}
  • ))}
)}
); } // Modern Theme (Default) return (
{personalInfo.photo && (
Profile
)}

{personalInfo.fullName}

{personalInfo.email && } {personalInfo.phone && } {personalInfo.location && } {personalInfo.website && }
{personalInfo.summary && (

Professional Summary

{personalInfo.summary}

)} {experience.length > 0 && (

Experience

{experience.map((exp) => (

{exp.role}

{exp.date}
{exp.company}

{exp.description}

))}
)} {education.length > 0 && (

Education

{education.map((edu) => (

{edu.school}

{edu.date}
{edu.degree}
))}
)} {skills.length > 0 && (

Skills

{skills.map((skill, index) => ( {skill} ))}
)}
); }; // 2. EDITOR interface EditorProps { data: CVData; onChange: (data: CVData) => void; } const Editor: React.FC = ({ data, onChange }) => { const [enhancingId, setEnhancingId] = useState(null); const [isGeneratingSummary, setIsGeneratingSummary] = useState(false); const photoInputRef = useRef(null); const updatePersonalInfo = (field: keyof CVData['personalInfo'], value: string) => { onChange({ ...data, personalInfo: { ...data.personalInfo, [field]: value }, }); }; const handlePhotoUpload = (e: React.ChangeEvent) => { const file = e.target.files?.[0]; if (file) { const reader = new FileReader(); reader.onloadend = () => { updatePersonalInfo('photo', reader.result as string); }; reader.readAsDataURL(file); } }; const removePhoto = () => { updatePersonalInfo('photo', ''); if (photoInputRef.current) { photoInputRef.current.value = ''; } }; const updateExperience = (id: string, field: keyof Experience, value: string) => { onChange({ ...data, experience: data.experience.map((exp) => (exp.id === id ? { ...exp, [field]: value } : exp)), }); }; const addExperience = () => { const newExp: Experience = { id: Date.now().toString(), role: 'Role Title', company: 'Company Name', date: 'Date Range', description: '', }; onChange({ ...data, experience: [...data.experience, newExp] }); }; const removeExperience = (id: string) => { onChange({ ...data, experience: data.experience.filter((exp) => exp.id !== id) }); }; const updateEducation = (id: string, field: keyof Education, value: string) => { onChange({ ...data, education: data.education.map((edu) => (edu.id === id ? { ...edu, [field]: value } : edu)), }); }; const addEducation = () => { const newEdu: Education = { id: Date.now().toString(), school: 'School Name', degree: 'Degree', date: 'Date Range', }; onChange({ ...data, education: [...data.education, newEdu] }); }; const removeEducation = (id: string) => { onChange({ ...data, education: data.education.filter((edu) => edu.id !== id) }); }; const updateSkills = (value: string) => { const skillsArray = value.split(',').map(s => s.trim()).filter(Boolean); onChange({ ...data, skills: skillsArray }); }; const handleEnhance = async (id: string, text: string, type: 'experience' | 'summary') => { if (!text) return; setEnhancingId(id); try { const enhancedText = await enhanceDescription(text); if (type === 'experience') { updateExperience(id, 'description', enhancedText); } else { updatePersonalInfo('summary', enhancedText); } } catch (error) { console.error("Failed to enhance", error); alert("Failed to enhance text. Please check your API key and try again."); } finally { setEnhancingId(null); } }; const handleGenerateSummary = async () => { if (data.experience.length === 0 && data.education.length === 0) { alert("Please add experience or education details first to generate a summary."); return; } setIsGeneratingSummary(true); try { const summary = await generateSummary({ experience: data.experience, education: data.education, skills: data.skills }); updatePersonalInfo('summary', summary); } catch (error) { console.error("Failed to generate summary", error); alert("Failed to generate summary. Please check your API key."); } finally { setIsGeneratingSummary(false); } }; return (
{/* Personal Info */}

Personal Details

{data.personalInfo.photo ? ( Profile ) : (
)} {data.personalInfo.photo ? ( ) : ( )}
updatePersonalInfo('fullName', e.target.value)} className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" /> updatePersonalInfo('email', e.target.value)} className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" /> updatePersonalInfo('phone', e.target.value)} className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" /> updatePersonalInfo('location', e.target.value)} className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none" /> updatePersonalInfo('website', e.target.value)} className="p-3 border rounded-lg focus:ring-2 focus:ring-blue-500 outline-none col-span-1 md:col-span-2" />