feat(resume): use Haitham's actual data and design

- All 4 experience entries populated
- Both education entries populated
- Skills and languages populated
- Photo upload with edit icon
- LocalStorage version system
- Print to PDF button
- Edit/Preview tabs
This commit is contained in:
2026-03-24 00:29:35 +01:00
parent abbd6d2940
commit 8cd30cf5dc
+343 -298
View File
@@ -1,171 +1,221 @@
"use client"; "use client";
import { useState } from "react"; import { useState, useEffect } from "react";
import BackToMC from "@/components/mission-control/BackToMC"; import BackToMC from "@/components/mission-control/BackToMC";
interface Experience { interface ResumeData {
id: string; name: string;
phone: string;
email: string;
linkedin: string;
location: string;
about: string;
education: { degree: string; school: string; year: string }[];
skills: string[];
languages: string[];
experience: {
title: string; title: string;
company: string; company: string;
period: string; period: string;
location: string; location: string;
description: string; description: string[];
}[];
photoUrl: string;
} }
interface Education { const initialResume: ResumeData = {
id: string;
degree: string;
school: string;
year: string;
location: string;
}
interface Skill {
id: string;
name: string;
}
export default function ResumeBuilderPage() {
const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
// Personal Info
const [personalInfo, setPersonalInfo] = useState({
name: "HAITHAM KHALIFA", name: "HAITHAM KHALIFA",
title: "Senior Full-Stack Developer & Entrepreneur",
email: "Haitham@Khalifa.se",
phone: "+34 614 821 331", phone: "+34 614 821 331",
location: "Málaga, Spain", email: "Haitham@Khalifa.se",
linkedin: "linkedin.com/in/haithamekhalifa/", linkedin: "linkedin.com/in/haithamekhalifa/",
website: "haithamkhalifa.com", location: "Málaga, Spain",
summary: "A results-oriented leader with 15+ years of experience in driving e-commerce growth, optimizing online platforms, and implementing successful digital marketing strategies. Proven ability to increase online sales, improve customer engagement, and manage cross-functional teams to achieve business objectives." about: "A results-oriented leader with 15+ years of experience in driving e-commerce growth, optimizing online platforms, and implementing successful digital marketing strategies. Proven ability to increase online sales, improve customer engagement, and manage cross-functional teams to achieve business objectives.",
}); education: [
{ degree: "Intensive Software Development Academy", school: "Lund University", year: "Sweden, Lund - 2019" },
// Experience { degree: "Bachelor of Computer Science", school: "Modern Academy Maadi", year: "Egypt, Cairo - 2001 - 2006" }
const [experiences, setExperiences] = useState<Experience[]>([ ],
skills: ["E-commerce Strategy", "Digital Marketing", "SEO/SEM", "Product Ownership", "Team Leadership"],
languages: ["Arabic", "English", "Swedish"],
experience: [
{ {
id: "1",
title: "Product Owner & IT Consultant", title: "Product Owner & IT Consultant",
company: "Protein.com", company: "Protein.com",
period: "2020-2025", period: "2020-2025",
location: "Sweden, Malmö", location: "Sweden, Malmö",
description: "• Managed product backlog to drive e-commerce sales.\n• Developed/executed SEO strategies (+20% organic traffic).\n• Implemented/managed MarTech for e-commerce performance.\n• Drove Agile practices.\n• Utilized data analytics to optimize e-commerce growth.\n• Managed product data feeds." description: [
} "Managed product backlog to drive e-commerce sales.",
]); "Developed/executed SEO strategies (+20% organic traffic).",
"Implemented/managed MarTech for e-commerce performance.",
// Education "Drove Agile practices.",
const [education, setEducation] = useState<Education[]>([ "Utilized data analytics to optimize e-commerce growth.",
{ "Managed product data feeds."
id: "1", ]
degree: "Intensive Software Development Academy",
school: "Lund University",
year: "2019",
location: "Sweden, Lund"
}, },
{ {
id: "2", title: "Founder & CEO",
degree: "Bachelor of Computer Science", company: "HostPioneers.com",
school: "Modern Academy Maadi", period: "2012-2021",
year: "2001 - 2006", location: "Denmark, Copenhagen",
location: "Egypt, Cairo" description: [
"Led e-commerce platform development/optimization.",
"Implemented MarTech to drive engagement/revenue.",
"Developed/executed e-commerce marketing strategies.",
"Managed all aspects of the online business.",
"Executed e-commerce marketing (SEO, email, social).",
"Oversaw customer journey."
]
},
{
title: "Social Media Manager (Volunteer)",
company: "Danish Red Cross - Newtimes.dk",
period: "2013-2016",
location: "Denmark",
description: [
"EU-funded project to raise awareness of asylum seekers and policymakers.",
"Developed and managed the website, enhancing SEO and user engagement.",
"Wrote articles and managed social media to increase brand visibility."
]
},
{
title: "C.I.O Deputy",
company: "Cayenne Technologies",
period: "2007-2012",
location: "Egypt, Cairo",
description: [
"Web Business Manager & Deputy Chief Information Officer.",
"Managed projects in the web technology department.",
"Developed and presented web projects to clients and stakeholders.",
"Installed and supported web servers and applications.",
"Improved website traffic and SEO scores for clients."
]
} }
]); ],
photoUrl: ""
};
// Skills export default function ResumeBuilderPage() {
const [skills, setSkills] = useState<Skill[]>([ const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
{ id: "1", name: "E-commerce Strategy" }, const [versions, setVersions] = useState<string[]>(["Main Resume"]);
{ id: "2", name: "Digital Marketing" }, const [currentVersion, setCurrentVersion] = useState("Main Resume");
{ id: "3", name: "SEO/SEM" }, const [resume, setResume] = useState<ResumeData>(initialResume);
{ id: "4", name: "Product Ownership" }, const [photoPreview, setPhotoPreview] = useState<string>("");
{ id: "5", name: "Team Leadership" }
]);
// Languages // Load from localStorage on mount
const [languages, setLanguages] = useState([ useEffect(() => {
{ id: "1", name: "Arabic" }, const saved = localStorage.getItem('haitham_resumes_v2');
{ id: "2", name: "English" }, if (saved) {
{ id: "3", name: "Swedish" } try {
]); const data = JSON.parse(saved);
const keys = Object.keys(data);
if (keys.length > 0) {
setVersions(keys);
setCurrentVersion(keys[0]);
setResume(data[keys[0]]);
if (data[keys[0]].photoUrl) {
setPhotoPreview(data[keys[0]].photoUrl);
}
}
} catch (e) {
console.error("Failed to load saved resumes", e);
}
}
}, []);
// Versions const saveToDisk = () => {
const [versions, setVersions] = useState<string[]>(["Default"]); const saved = localStorage.getItem('haitham_resumes_v2');
const [currentVersion, setCurrentVersion] = useState("Default"); let allResumes: Record<string, ResumeData> = {};
if (saved) {
// CRUD operations try {
const addExperience = () => { allResumes = JSON.parse(saved);
setExperiences([...experiences, { } catch (e) {}
id: Date.now().toString(), }
title: "New Position", allResumes[currentVersion] = { ...resume, photoUrl: photoPreview };
company: "Company", localStorage.setItem('haitham_resumes_v2', JSON.stringify(allResumes));
period: "2023-Present", localStorage.setItem('haitham_current_profile_v2', currentVersion);
location: "City, Country",
description: "• Key responsibility.\n• Achievement."
}]);
}; };
const removeExperience = (id: string) => { const loadProfile = (name: string) => {
setExperiences(experiences.filter(e => e.id !== id)); const saved = localStorage.getItem('haitham_resumes_v2');
}; if (saved) {
try {
const updateExperience = (id: string, field: keyof Experience, value: string) => { const data = JSON.parse(saved);
setExperiences(experiences.map(e => e.id === id ? { ...e, [field]: value } : e)); if (data[name]) {
}; setResume(data[name]);
setPhotoPreview(data[name].photoUrl || "");
const addEducation = () => {
setEducation([...education, {
id: Date.now().toString(),
degree: "New Degree",
school: "University",
year: "2020 - 2024",
location: "City, Country"
}]);
};
const removeEducation = (id: string) => {
setEducation(education.filter(e => e.id !== id));
};
const updateEducation = (id: string, field: keyof Education, value: string) => {
setEducation(education.map(e => e.id === id ? { ...e, [field]: value } : e));
};
const addSkill = () => {
setSkills([...skills, { id: Date.now().toString(), name: "New Skill" }]);
};
const removeSkill = (id: string) => {
setSkills(skills.filter(s => s.id !== id));
};
const updateSkill = (id: string, name: string) => {
setSkills(skills.map(s => s.id === id ? { ...s, name } : s));
};
const addLanguage = () => {
setLanguages([...languages, { id: Date.now().toString(), name: "New Language" }]);
};
const removeLanguage = (id: string) => {
setLanguages(languages.filter(l => l.id !== id));
};
const updateLanguage = (id: string, name: string) => {
setLanguages(languages.map(l => l.id === id ? { ...l, name } : l));
};
const saveVersion = () => {
const name = prompt("Enter version name:", `Resume - ${currentVersion}`);
if (name && !versions.includes(name)) {
setVersions([...versions, name]);
setCurrentVersion(name); setCurrentVersion(name);
} }
} catch (e) {}
}
}; };
const loadVersion = (version: string) => { const handlePhotoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
setCurrentVersion(version); const file = e.target.files?.[0];
if (file) {
const reader = new FileReader();
reader.onload = (event) => {
const url = event.target?.result as string;
setPhotoPreview(url);
setResume({ ...resume, photoUrl: url });
};
reader.readAsDataURL(file);
}
}; };
const handlePrint = () => { const createNewProfile = () => {
window.print(); const name = prompt("Name this version (e.g., Geely IT Manager):");
if (name && !versions.includes(name)) {
const newResume = { ...resume, photoUrl: photoPreview };
const saved = localStorage.getItem('haitham_resumes_v2');
let allResumes: Record<string, ResumeData> = {};
if (saved) {
try {
allResumes = JSON.parse(saved);
} catch (e) {}
}
allResumes[name] = newResume;
localStorage.setItem('haitham_resumes_v2', JSON.stringify(allResumes));
setVersions([...versions, name]);
setCurrentVersion(name);
setResume(newResume);
} else if (name && versions.includes(name)) {
alert("This name already exists.");
}
};
const deleteProfile = () => {
if (versions.length <= 1) return alert("You need at least one profile.");
if (confirm(`Delete "${currentVersion}"?`)) {
const saved = localStorage.getItem('haitham_resumes_v2');
let allResumes: Record<string, ResumeData> = {};
if (saved) {
try {
allResumes = JSON.parse(saved);
} catch (e) {}
}
delete allResumes[currentVersion];
localStorage.setItem('haitham_resumes_v2', JSON.stringify(allResumes));
const remaining = Object.keys(allResumes);
if (remaining.length > 0) {
setVersions(remaining);
loadProfile(remaining[0]);
}
}
};
const showStatus = (msg: string) => {
alert(msg);
};
const updateExperience = (index: number, field: keyof typeof resume.experience[0], value: string) => {
const newExp = [...resume.experience];
newExp[index] = { ...newExp[index], [field]: value };
setResume({ ...resume, experience: newExp });
};
const updateEducation = (index: number, field: string, value: string) => {
const newEdu = [...resume.education];
newEdu[index] = { ...newEdu[index], [field]: value };
setResume({ ...resume, education: newEdu });
}; };
return ( return (
@@ -176,7 +226,34 @@ export default function ResumeBuilderPage() {
<div className="sticky top-0 z-50 bg-slate-800 border-b border-slate-700 px-6 py-3 flex items-center justify-between"> <div className="sticky top-0 z-50 bg-slate-800 border-b border-slate-700 px-6 py-3 flex items-center justify-between">
<div className="flex items-center gap-4"> <div className="flex items-center gap-4">
<h1 className="text-xl font-bold text-blue-400">Haitham's Resume Manager</h1> <h1 className="text-xl font-bold text-blue-400">Haitham's Resume Manager</h1>
<select
value={currentVersion}
onChange={(e) => loadProfile(e.target.value)}
className="bg-gray-700 text-white px-3 py-1 rounded border border-gray-600 outline-none"
>
{versions.map(v => (
<option key={v} value={v}>{v}</option>
))}
</select>
</div>
<div className="flex gap-2"> <div className="flex gap-2">
<button onClick={createNewProfile} className="bg-blue-600 hover:bg-blue-700 px-4 py-1 rounded text-sm font-medium transition">
New Version
</button>
<button onClick={() => { saveToDisk(); showStatus('Changes Saved'); }} className="bg-green-600 hover:bg-green-700 px-4 py-1 rounded text-sm font-medium transition">
Save Changes
</button>
<button onClick={() => window.print()} className="bg-gray-600 hover:bg-gray-700 px-4 py-1 rounded text-sm font-medium transition">
Print to PDF
</button>
<button onClick={deleteProfile} className="bg-red-600 hover:bg-red-700 px-4 py-1 rounded text-sm font-medium transition">
Delete
</button>
</div>
</div>
{/* Tabs */}
<div className="bg-slate-900/50 border-b border-slate-700 px-6 py-2 flex gap-4">
<button <button
onClick={() => setActiveTab("edit")} onClick={() => setActiveTab("edit")}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${ className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
@@ -194,31 +271,6 @@ export default function ResumeBuilderPage() {
Preview Preview
</button> </button>
</div> </div>
</div>
<div className="flex items-center gap-3">
<select
value={currentVersion}
onChange={(e) => loadVersion(e.target.value)}
className="bg-slate-700 text-white px-3 py-2 rounded-lg text-sm"
>
{versions.map(v => (
<option key={v} value={v}>{v}</option>
))}
</select>
<button
onClick={saveVersion}
className="bg-green-600 hover:bg-green-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
>
💾 Save Version
</button>
<button
onClick={handlePrint}
className="bg-slate-600 hover:bg-slate-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
>
🖨️ Print to PDF
</button>
</div>
</div>
{activeTab === "edit" ? ( {activeTab === "edit" ? (
<div className="p-6 max-w-4xl mx-auto space-y-6"> <div className="p-6 max-w-4xl mx-auto space-y-6">
@@ -230,17 +282,17 @@ export default function ResumeBuilderPage() {
<label className="block text-sm text-slate-400 mb-1">Full Name</label> <label className="block text-sm text-slate-400 mb-1">Full Name</label>
<input <input
type="text" type="text"
value={personalInfo.name} value={resume.name}
onChange={(e) => setPersonalInfo({...personalInfo, name: e.target.value})} onChange={(e) => setResume({...resume, name: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white" className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-slate-400 mb-1">Professional Title</label> <label className="block text-sm text-slate-400 mb-1">Phone</label>
<input <input
type="text" type="text"
value={personalInfo.title} value={resume.phone}
onChange={(e) => setPersonalInfo({...personalInfo, title: e.target.value})} onChange={(e) => setResume({...resume, phone: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white" className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/> />
</div> </div>
@@ -248,26 +300,8 @@ export default function ResumeBuilderPage() {
<label className="block text-sm text-slate-400 mb-1">Email</label> <label className="block text-sm text-slate-400 mb-1">Email</label>
<input <input
type="email" type="email"
value={personalInfo.email} value={resume.email}
onChange={(e) => setPersonalInfo({...personalInfo, email: e.target.value})} onChange={(e) => setResume({...resume, email: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Phone</label>
<input
type="tel"
value={personalInfo.phone}
onChange={(e) => setPersonalInfo({...personalInfo, phone: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Location</label>
<input
type="text"
value={personalInfo.location}
onChange={(e) => setPersonalInfo({...personalInfo, location: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white" className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/> />
</div> </div>
@@ -275,44 +309,74 @@ export default function ResumeBuilderPage() {
<label className="block text-sm text-slate-400 mb-1">LinkedIn</label> <label className="block text-sm text-slate-400 mb-1">LinkedIn</label>
<input <input
type="text" type="text"
value={personalInfo.linkedin} value={resume.linkedin}
onChange={(e) => setPersonalInfo({...personalInfo, linkedin: e.target.value})} onChange={(e) => setResume({...resume, linkedin: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white" className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/> />
</div> </div>
<div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Location</label>
<input
type="text"
value={resume.location}
onChange={(e) => setResume({...resume, location: e.target.value})}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/>
</div> </div>
<div className="mt-4"> <div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">About Me</label> <label className="block text-sm text-slate-400 mb-1">About Me</label>
<textarea <textarea
value={personalInfo.summary} value={resume.about}
onChange={(e) => setPersonalInfo({...personalInfo, summary: e.target.value})} onChange={(e) => setResume({...resume, about: e.target.value})}
rows={4} rows={4}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white" className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/> />
</div> </div>
</div> </div>
</div>
{/* Photo Upload */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">📷 Photo</h2>
<div className="flex items-center gap-4">
<div className="relative">
<div className="w-44 h-44 rounded-full border-8 border-gray-300 overflow-hidden bg-gray-200">
{photoPreview ? (
<img src={photoPreview} alt="Profile" className="w-full h-full object-cover" />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400 text-4xl">📷</div>
)}
</div>
<label className="absolute bottom-0 right-0 w-10 h-10 bg-blue-600 hover:bg-blue-500 rounded-full flex items-center justify-center cursor-pointer shadow-lg">
<span className="text-xl">✏️</span>
<input
type="file"
accept="image/*"
onChange={handlePhotoUpload}
className="hidden"
/>
</label>
</div>
<div className="text-sm text-slate-400">
<p>Click the pencil icon to upload your photo</p>
<p>Recommended: Square image, at least 300x300px</p>
</div>
</div>
</div>
{/* Experience */} {/* Experience */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700"> <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<div className="flex items-center justify-between mb-4"> <h2 className="text-lg font-bold mb-4 text-blue-400">💼 Experience</h2>
<h2 className="text-lg font-bold text-blue-400">💼 Experience</h2>
<button
onClick={addExperience}
className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div>
<div className="space-y-4"> <div className="space-y-4">
{experiences.map((exp) => ( {resume.experience.map((exp, index) => (
<div key={exp.id} className="bg-slate-700/50 rounded-lg p-4 border border-slate-600"> <div key={index} className="bg-slate-700/50 rounded-lg p-4 border border-slate-600">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-2 gap-4">
<div> <div>
<label className="block text-sm text-slate-400 mb-1">Job Title</label> <label className="block text-sm text-slate-400 mb-1">Title</label>
<input <input
type="text" type="text"
value={exp.title} value={exp.title}
onChange={(e) => updateExperience(exp.id, "title", e.target.value)} onChange={(e) => updateExperience(index, "title", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
@@ -321,7 +385,7 @@ export default function ResumeBuilderPage() {
<input <input
type="text" type="text"
value={exp.company} value={exp.company}
onChange={(e) => updateExperience(exp.id, "company", e.target.value)} onChange={(e) => updateExperience(index, "company", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
@@ -330,7 +394,7 @@ export default function ResumeBuilderPage() {
<input <input
type="text" type="text"
value={exp.period} value={exp.period}
onChange={(e) => updateExperience(exp.id, "period", e.target.value)} onChange={(e) => updateExperience(index, "period", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
@@ -339,26 +403,20 @@ export default function ResumeBuilderPage() {
<input <input
type="text" type="text"
value={exp.location} value={exp.location}
onChange={(e) => updateExperience(exp.id, "location", e.target.value)} onChange={(e) => updateExperience(index, "location", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
<div className="col-span-2"> <div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Description (one bullet per line, start with •)</label> <label className="block text-sm text-slate-400 mb-1">Description (one bullet per line)</label>
<textarea <textarea
value={exp.description} value={exp.description.join('\n')}
onChange={(e) => updateExperience(exp.id, "description", e.target.value)} onChange={(e) => updateExperience(index, "description", e.target.value.split('\n'))}
rows={5} rows={5}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
</div> </div>
<button
onClick={() => removeExperience(exp.id)}
className="mt-2 text-red-400 text-sm hover:text-red-300"
>
Remove
</button>
</div> </div>
))} ))}
</div> </div>
@@ -366,34 +424,17 @@ export default function ResumeBuilderPage() {
{/* Education */} {/* Education */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700"> <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<div className="flex items-center justify-between mb-4"> <h2 className="text-lg font-bold mb-4 text-blue-400">🎓 Education</h2>
<h2 className="text-lg font-bold text-blue-400">🎓 Education</h2>
<button
onClick={addEducation}
className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div>
<div className="space-y-4"> <div className="space-y-4">
{education.map((edu) => ( {resume.education.map((edu, index) => (
<div key={edu.id} className="bg-slate-700/50 rounded-lg p-4 border border-slate-600"> <div key={index} className="bg-slate-700/50 rounded-lg p-4 border border-slate-600">
<div className="grid grid-cols-2 gap-4"> <div className="grid grid-cols-3 gap-4">
<div> <div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Degree</label> <label className="block text-sm text-slate-400 mb-1">Degree</label>
<input <input
type="text" type="text"
value={edu.degree} value={edu.degree}
onChange={(e) => updateEducation(edu.id, "degree", e.target.value)} onChange={(e) => updateEducation(index, "degree", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">School</label>
<input
type="text"
value={edu.school}
onChange={(e) => updateEducation(edu.id, "school", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
@@ -402,26 +443,20 @@ export default function ResumeBuilderPage() {
<input <input
type="text" type="text"
value={edu.year} value={edu.year}
onChange={(e) => updateEducation(edu.id, "year", e.target.value)} onChange={(e) => updateEducation(index, "year", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
<div> <div className="col-span-3">
<label className="block text-sm text-slate-400 mb-1">Location</label> <label className="block text-sm text-slate-400 mb-1">School</label>
<input <input
type="text" type="text"
value={edu.location} value={edu.school}
onChange={(e) => updateEducation(edu.id, "location", e.target.value)} onChange={(e) => updateEducation(index, "school", e.target.value)}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm" className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/> />
</div> </div>
</div> </div>
<button
onClick={() => removeEducation(edu.id)}
className="mt-2 text-red-400 text-sm hover:text-red-300"
>
Remove
</button>
</div> </div>
))} ))}
</div> </div>
@@ -429,68 +464,72 @@ export default function ResumeBuilderPage() {
{/* Skills */} {/* Skills */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700"> <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<div className="flex items-center justify-between mb-4"> <h2 className="text-lg font-bold mb-4 text-blue-400">🛠️ Skills</h2>
<h2 className="text-lg font-bold text-blue-400">🛠️ Skills</h2>
<button
onClick={addSkill}
className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{skills.map((skill) => ( {resume.skills.map((skill, index) => (
<div key={skill.id} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg"> <div key={index} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
<input <input
type="text" type="text"
value={skill.name} value={skill}
onChange={(e) => updateSkill(skill.id, e.target.value)} onChange={(e) => {
const newSkills = [...resume.skills];
newSkills[index] = e.target.value;
setResume({...resume, skills: newSkills});
}}
className="bg-transparent border-none text-white text-sm w-32" className="bg-transparent border-none text-white text-sm w-32"
/> />
<button <button
onClick={() => removeSkill(skill.id)} onClick={() => setResume({...resume, skills: resume.skills.filter((_, i) => i !== index)})}
className="text-slate-400 hover:text-red-400 text-lg" className="text-slate-400 hover:text-red-400 text-lg"
> >
× ×
</button> </button>
</div> </div>
))} ))}
<button
onClick={() => setResume({...resume, skills: [...resume.skills, "New Skill"]})}
className="bg-blue-600 hover:bg-blue-500 px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div> </div>
</div> </div>
{/* Languages */} {/* Languages */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700"> <div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<div className="flex items-center justify-between mb-4"> <h2 className="text-lg font-bold mb-4 text-blue-400">🌍 Languages</h2>
<h2 className="text-lg font-bold text-blue-400">🌍 Languages</h2>
<button
onClick={addLanguage}
className="bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div>
<div className="flex flex-wrap gap-2"> <div className="flex flex-wrap gap-2">
{languages.map((lang) => ( {resume.languages.map((lang, index) => (
<div key={lang.id} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg"> <div key={index} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
<input <input
type="text" type="text"
value={lang.name} value={lang}
onChange={(e) => updateLanguage(lang.id, e.target.value)} onChange={(e) => {
const newLangs = [...resume.languages];
newLangs[index] = e.target.value;
setResume({...resume, languages: newLangs});
}}
className="bg-transparent border-none text-white text-sm w-24" className="bg-transparent border-none text-white text-sm w-24"
/> />
<button <button
onClick={() => removeLanguage(lang.id)} onClick={() => setResume({...resume, languages: resume.languages.filter((_, i) => i !== index)})}
className="text-slate-400 hover:text-red-400 text-lg" className="text-slate-400 hover:text-red-400 text-lg"
> >
× ×
</button> </button>
</div> </div>
))} ))}
<button
onClick={() => setResume({...resume, languages: [...resume.languages, "New Language"]})}
className="bg-blue-600 hover:bg-blue-500 px-3 py-1 rounded-lg text-sm"
>
+ Add
</button>
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
/* Resume Preview - Haitham's Design */ /* Resume Preview */
<div className="p-8"> <div className="p-8">
<style>{` <style>{`
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap'); @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
@@ -573,30 +612,34 @@ export default function ResumeBuilderPage() {
{/* Sidebar */} {/* Sidebar */}
<div className="sidebar"> <div className="sidebar">
<div className="circle-frame"> <div className="circle-frame">
<img src="Resume.jpg" alt={personalInfo.name} onError="this.src='https://via.placeholder.com/176?text=Photo'" /> {photoPreview ? (
<img src={photoPreview} alt={resume.name} />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400">📷</div>
)}
</div> </div>
<h1 className="text-2xl font-black text-center text-gray-900 mb-8 tracking-tighter uppercase leading-none"> <h1 className="text-2xl font-black text-center text-gray-900 mb-8 tracking-tighter uppercase leading-none">
{personalInfo.name} {resume.name}
</h1> </h1>
{/* Contact Section */} {/* Contact Section */}
<div className="space-y-3 text-[11px] text-gray-700 mb-10"> <div className="space-y-3 text-[11px] text-gray-700 mb-10">
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">📞</span> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">📞</span>
<span className="font-medium">{personalInfo.phone}</span> <span className="font-medium">{resume.phone}</span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">✉️</span> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">✉️</span>
<span className="font-medium">{personalInfo.email}</span> <span className="font-medium">{resume.email}</span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px] font-bold">in</span> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px] font-bold">in</span>
<span className="font-medium">{personalInfo.linkedin}</span> <span className="font-medium">{resume.linkedin}</span>
</div> </div>
<div className="flex items-center gap-3"> <div className="flex items-center gap-3">
<span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">📍</span> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">📍</span>
<span className="font-medium">{personalInfo.location}</span> <span className="font-medium">{resume.location}</span>
</div> </div>
</div> </div>
@@ -604,7 +647,7 @@ export default function ResumeBuilderPage() {
<div className="mb-10"> <div className="mb-10">
<h2 className="section-header">About Me</h2> <h2 className="section-header">About Me</h2>
<div className="text-[10px] leading-relaxed text-gray-600 font-medium text-justify"> <div className="text-[10px] leading-relaxed text-gray-600 font-medium text-justify">
{personalInfo.summary} {resume.about}
</div> </div>
</div> </div>
@@ -612,8 +655,8 @@ export default function ResumeBuilderPage() {
<div className="mb-10"> <div className="mb-10">
<h2 className="section-header">Skills</h2> <h2 className="section-header">Skills</h2>
<div className="text-[11px] text-gray-700 space-y-1 font-medium"> <div className="text-[11px] text-gray-700 space-y-1 font-medium">
{skills.map((skill) => ( {resume.skills.map((skill, i) => (
<p key={skill.id}> {skill.name}</p> <p key={i}>• {skill}</p>
))} ))}
</div> </div>
</div> </div>
@@ -622,8 +665,8 @@ export default function ResumeBuilderPage() {
<div> <div>
<h2 className="section-header">Language</h2> <h2 className="section-header">Language</h2>
<div className="text-[11px] text-gray-700 space-y-1 font-medium"> <div className="text-[11px] text-gray-700 space-y-1 font-medium">
{languages.map((lang) => ( {resume.languages.map((lang, i) => (
<p key={lang.id}> {lang.name}</p> <p key={i}>• {lang}</p>
))} ))}
</div> </div>
</div> </div>
@@ -631,21 +674,23 @@ export default function ResumeBuilderPage() {
{/* Main Content */} {/* Main Content */}
<div className="main-content"> <div className="main-content">
<h1 className="main-header">{personalInfo.name}</h1> <h1 className="main-header">{resume.name}</h1>
{/* Experience */} {/* Experience */}
<div className="mb-10"> <div className="mb-10">
<h2 className="section-header">Experience</h2> <h2 className="section-header">Experience</h2>
{experiences.map((exp) => ( {resume.experience.map((exp, i) => (
<div key={exp.id} className="experience-item"> <div key={i} className="experience-item">
<div className="experience-dot" /> <div className="experience-dot" />
<div className="flex justify-between items-baseline mb-1"> <div className="flex justify-between items-baseline mb-1">
<h3 className="text-lg font-bold text-gray-800 leading-none">{exp.title}</h3> <h3 className="text-lg font-bold text-gray-800 leading-none">{exp.title}</h3>
<span className="text-xs font-bold text-gray-400">{exp.period}</span> <span className="text-xs font-bold text-gray-400">{exp.period}</span>
</div> </div>
<p className="text-sm font-bold text-blue-600 mb-3">{exp.company} | {exp.location}</p> <p className="text-sm font-bold text-blue-600 mb-3">{exp.company} | {exp.location}</p>
<div className="text-[11px] text-gray-600 space-y-1.5 leading-snug whitespace-pre-line"> <div className="text-[11px] text-gray-600 space-y-1.5 leading-snug">
{exp.description} {exp.description.map((desc, j) => (
<p key={j}>• {desc}</p>
))}
</div> </div>
</div> </div>
))} ))}
@@ -654,11 +699,11 @@ export default function ResumeBuilderPage() {
{/* Education */} {/* Education */}
<div> <div>
<h2 className="section-header">Education</h2> <h2 className="section-header">Education</h2>
{education.map((edu) => ( {resume.education.map((edu, i) => (
<div key={edu.id} className="mb-4"> <div key={i} className="mb-4">
<p className="text-[11px] font-bold text-gray-800 leading-tight">{edu.degree}</p> <p className="text-[11px] font-bold text-gray-800 leading-tight">{edu.degree}</p>
<p className="text-[10px] text-gray-600">{edu.school}</p> <p className="text-[10px] text-gray-600">{edu.school}</p>
<p className="text-[9px] text-gray-400 font-bold">{edu.location} - {edu.year}</p> <p className="text-[9px] text-gray-400 font-bold">{edu.year}</p>
</div> </div>
))} ))}
</div> </div>