feat(resume): full redesign matching Haitham's design

- Circle photo frame with placeholder
- Name in sidebar and main header
- Contact with icons in gray circles
- About Me section instead of Profile
- Experience with border-left dots
- Location field in experience
- Education with location
- Skills and Languages as bullet lists
- Version manager with save/load
- Print to PDF button
This commit is contained in:
2026-03-24 00:27:13 +01:00
parent 4d7073cab2
commit abbd6d2940
+160 -83
View File
@@ -1,6 +1,6 @@
"use client"; "use client";
import { useState, useEffect } from "react"; import { useState } from "react";
import BackToMC from "@/components/mission-control/BackToMC"; import BackToMC from "@/components/mission-control/BackToMC";
interface Experience { interface Experience {
@@ -8,6 +8,7 @@ interface Experience {
title: string; title: string;
company: string; company: string;
period: string; period: string;
location: string;
description: string; description: string;
} }
@@ -16,6 +17,7 @@ interface Education {
degree: string; degree: string;
school: string; school: string;
year: string; year: string;
location: string;
} }
interface Skill { interface Skill {
@@ -25,28 +27,28 @@ interface Skill {
export default function ResumeBuilderPage() { export default function ResumeBuilderPage() {
const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit"); const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
const [resumeName, setResumeName] = useState("My Resume");
// Personal Info // Personal Info
const [personalInfo, setPersonalInfo] = useState({ const [personalInfo, setPersonalInfo] = useState({
name: "Your Name", name: "HAITHAM KHALIFA",
title: "Professional Title", title: "Senior Full-Stack Developer & Entrepreneur",
email: "email@example.com", email: "Haitham@Khalifa.se",
phone: "+34 XXX XXX XXX", phone: "+34 614 821 331",
location: "City, Country", location: "Málaga, Spain",
website: "www.yoursite.com", linkedin: "linkedin.com/in/haithamekhalifa/",
linkedin: "linkedin.com/in/yourprofile", website: "haithamkhalifa.com",
summary: "A brief professional summary..." 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."
}); });
// Experience // Experience
const [experiences, setExperiences] = useState<Experience[]>([ const [experiences, setExperiences] = useState<Experience[]>([
{ {
id: "1", id: "1",
title: "Job Title", title: "Product Owner & IT Consultant",
company: "Company Name", company: "Protein.com",
period: "2020 - Present", period: "2020-2025",
description: "Key responsibilities and achievements in this role." 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."
} }
]); ]);
@@ -54,22 +56,34 @@ export default function ResumeBuilderPage() {
const [education, setEducation] = useState<Education[]>([ const [education, setEducation] = useState<Education[]>([
{ {
id: "1", id: "1",
degree: "Degree Name", degree: "Intensive Software Development Academy",
school: "University Name", school: "Lund University",
year: "2016 - 2020" year: "2019",
location: "Sweden, Lund"
},
{
id: "2",
degree: "Bachelor of Computer Science",
school: "Modern Academy Maadi",
year: "2001 - 2006",
location: "Egypt, Cairo"
} }
]); ]);
// Skills // Skills
const [skills, setSkills] = useState<Skill[]>([ const [skills, setSkills] = useState<Skill[]>([
{ id: "1", name: "Skill 1" }, { id: "1", name: "E-commerce Strategy" },
{ id: "2", name: "Skill 2" }, { id: "2", name: "Digital Marketing" },
{ id: "3", name: "Skill 3" } { id: "3", name: "SEO/SEM" },
{ id: "4", name: "Product Ownership" },
{ id: "5", name: "Team Leadership" }
]); ]);
// Languages
const [languages, setLanguages] = useState([ const [languages, setLanguages] = useState([
{ id: "1", name: "Language 1", level: "Fluent" }, { id: "1", name: "Arabic" },
{ id: "2", name: "Language 2", level: "Native" } { id: "2", name: "English" },
{ id: "3", name: "Swedish" }
]); ]);
// Versions // Versions
@@ -83,7 +97,8 @@ export default function ResumeBuilderPage() {
title: "New Position", title: "New Position",
company: "Company", company: "Company",
period: "2023-Present", period: "2023-Present",
description: "Description..." location: "City, Country",
description: "• Key responsibility.\n• Achievement."
}]); }]);
}; };
@@ -99,8 +114,9 @@ export default function ResumeBuilderPage() {
setEducation([...education, { setEducation([...education, {
id: Date.now().toString(), id: Date.now().toString(),
degree: "New Degree", degree: "New Degree",
school: "School Name", school: "University",
year: "2020 - 2024" year: "2020 - 2024",
location: "City, Country"
}]); }]);
}; };
@@ -124,12 +140,23 @@ export default function ResumeBuilderPage() {
setSkills(skills.map(s => s.id === id ? { ...s, name } : s)); 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 saveVersion = () => {
const name = prompt("Enter version name:", `Resume - ${currentVersion}`); const name = prompt("Enter version name:", `Resume - ${currentVersion}`);
if (name && !versions.includes(name)) { if (name && !versions.includes(name)) {
setVersions([...versions, name]); setVersions([...versions, name]);
setCurrentVersion(name); setCurrentVersion(name);
alert(`Version "${name}" saved!`);
} }
}; };
@@ -137,15 +164,6 @@ export default function ResumeBuilderPage() {
setCurrentVersion(version); setCurrentVersion(version);
}; };
const deleteVersion = (version: string) => {
if (versions.length > 1) {
setVersions(versions.filter(v => v !== version));
if (currentVersion === version) {
setCurrentVersion(versions[0]);
}
}
};
const handlePrint = () => { const handlePrint = () => {
window.print(); window.print();
}; };
@@ -157,7 +175,7 @@ export default function ResumeBuilderPage() {
{/* Toolbar */} {/* Toolbar */}
<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">📄 Resume Builder</h1> <h1 className="text-xl font-bold text-blue-400">Haitham's Resume Manager</h1>
<div className="flex gap-2"> <div className="flex gap-2">
<button <button
onClick={() => setActiveTab("edit")} onClick={() => setActiveTab("edit")}
@@ -197,7 +215,7 @@ export default function ResumeBuilderPage() {
onClick={handlePrint} onClick={handlePrint}
className="bg-slate-600 hover:bg-slate-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors" className="bg-slate-600 hover:bg-slate-500 text-white px-4 py-2 rounded-lg text-sm font-medium transition-colors"
> >
🖨 Print/Download 🖨️ Print to PDF
</button> </button>
</div> </div>
</div> </div>
@@ -254,21 +272,21 @@ export default function ResumeBuilderPage() {
/> />
</div> </div>
<div> <div>
<label className="block text-sm text-slate-400 mb-1">Website</label> <label className="block text-sm text-slate-400 mb-1">LinkedIn</label>
<input <input
type="text" type="text"
value={personalInfo.website} value={personalInfo.linkedin}
onChange={(e) => setPersonalInfo({...personalInfo, website: e.target.value})} onChange={(e) => setPersonalInfo({...personalInfo, 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> </div>
<div className="mt-4"> <div className="mt-4">
<label className="block text-sm text-slate-400 mb-1">Professional Summary</label> <label className="block text-sm text-slate-400 mb-1">About Me</label>
<textarea <textarea
value={personalInfo.summary} value={personalInfo.summary}
onChange={(e) => setPersonalInfo({...personalInfo, summary: e.target.value})} onChange={(e) => setPersonalInfo({...personalInfo, summary: e.target.value})}
rows={3} 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>
@@ -307,7 +325,7 @@ export default function ResumeBuilderPage() {
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>
<label className="block text-sm text-slate-400 mb-1">Period</label> <label className="block text-sm text-slate-400 mb-1">Period</label>
<input <input
type="text" type="text"
@@ -316,12 +334,21 @@ export default function ResumeBuilderPage() {
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>
<label className="block text-sm text-slate-400 mb-1">Location</label>
<input
type="text"
value={exp.location}
onChange={(e) => updateExperience(exp.id, "location", 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 className="col-span-2"> <div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Description</label> <label className="block text-sm text-slate-400 mb-1">Description (one bullet per line, start with •)</label>
<textarea <textarea
value={exp.description} value={exp.description}
onChange={(e) => updateExperience(exp.id, "description", e.target.value)} onChange={(e) => updateExperience(exp.id, "description", e.target.value)}
rows={3} 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>
@@ -379,6 +406,15 @@ export default function ResumeBuilderPage() {
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>
<label className="block text-sm text-slate-400 mb-1">Location</label>
<input
type="text"
value={edu.location}
onChange={(e) => updateEducation(edu.id, "location", 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> </div>
<button <button
onClick={() => removeEducation(edu.id)} onClick={() => removeEducation(edu.id)}
@@ -409,7 +445,7 @@ export default function ResumeBuilderPage() {
type="text" type="text"
value={skill.name} value={skill.name}
onChange={(e) => updateSkill(skill.id, e.target.value)} onChange={(e) => updateSkill(skill.id, e.target.value)}
className="bg-transparent border-none text-white text-sm w-24" className="bg-transparent border-none text-white text-sm w-32"
/> />
<button <button
onClick={() => removeSkill(skill.id)} onClick={() => removeSkill(skill.id)}
@@ -424,18 +460,37 @@ export default function ResumeBuilderPage() {
{/* 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">
<h2 className="text-lg font-bold text-blue-400 mb-4">🌍 Languages</h2> <div className="flex items-center justify-between mb-4">
<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) => ( {languages.map((lang) => (
<div key={lang.id} className="bg-slate-700 px-3 py-2 rounded-lg text-sm"> <div key={lang.id} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
{lang.name} - {lang.level} <input
type="text"
value={lang.name}
onChange={(e) => updateLanguage(lang.id, e.target.value)}
className="bg-transparent border-none text-white text-sm w-24"
/>
<button
onClick={() => removeLanguage(lang.id)}
className="text-slate-400 hover:text-red-400 text-lg"
>
×
</button>
</div> </div>
))} ))}
</div> </div>
</div> </div>
</div> </div>
) : ( ) : (
/* Resume Preview - Gemini Design */ /* Resume Preview - Haitham's Design */
<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');
@@ -448,23 +503,29 @@ export default function ResumeBuilderPage() {
display: flex; display: flex;
font-family: 'Inter', sans-serif; font-family: 'Inter', sans-serif;
} }
.resume-sidebar { .sidebar {
width: 280px; width: 280px;
background-color: #f3f3f3; background-color: #f3f3f3;
padding: 2.5rem 1.5rem; padding: 2.5rem 1.5rem;
} }
.resume-main { .main-content {
flex: 1; flex: 1;
padding: 3.5rem 3rem; padding: 3.5rem 3rem;
background: white; background: white;
} }
.resume-photo { .circle-frame {
width: 176px; width: 176px;
height: 176px; height: 176px;
border-radius: 9999px; border-radius: 9999px;
border: 10px solid #e2e2e2; border: 10px solid #e2e2e2;
background: #d1d1d1; overflow: hidden;
margin: 0 auto 2rem; margin: 0 auto 2rem;
background: #d1d1d1;
}
.circle-frame img {
width: 100%;
height: 100%;
object-fit: cover;
} }
.section-header { .section-header {
font-size: 11px; font-size: 11px;
@@ -476,9 +537,9 @@ export default function ResumeBuilderPage() {
text-transform: capitalize; text-transform: capitalize;
} }
.main-header { .main-header {
font-size: 16px; font-size: 24px;
font-weight: 800; font-weight: 800;
letter-spacing: 0.15em; letter-spacing: 0.05em;
color: #1a1a1a; color: #1a1a1a;
border-bottom: 2px solid #1a1a1a; border-bottom: 2px solid #1a1a1a;
padding-bottom: 8px; padding-bottom: 8px;
@@ -510,24 +571,46 @@ export default function ResumeBuilderPage() {
<div className="resume-preview"> <div className="resume-preview">
{/* Sidebar */} {/* Sidebar */}
<div className="resume-sidebar"> <div className="sidebar">
<div className="resume-photo" /> <div className="circle-frame">
<img src="Resume.jpg" alt={personalInfo.name} onError="this.src='https://via.placeholder.com/176?text=Photo'" />
</div>
<h1 className="text-2xl font-black text-center text-gray-900 mb-8 tracking-tighter uppercase leading-none">
{personalInfo.name}
</h1>
{/* Contact Section */} {/* Contact Section */}
<div className="mb-8"> <div className="space-y-3 text-[11px] text-gray-700 mb-10">
<h3 className="section-header">Contact</h3> <div className="flex items-center gap-3">
<div className="space-y-2 text-[11px] text-gray-700"> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]">📞</span>
<p>{personalInfo.email}</p> <span className="font-medium">{personalInfo.phone}</span>
<p>{personalInfo.phone}</p> </div>
<p>{personalInfo.location}</p> <div className="flex items-center gap-3">
<p>{personalInfo.website}</p> <span className="w-5 h-5 flex items-center justify-center bg-gray-800 text-white rounded-full text-[10px]"></span>
<p>{personalInfo.linkedin}</p> <span className="font-medium">{personalInfo.email}</span>
</div>
<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="font-medium">{personalInfo.linkedin}</span>
</div>
<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="font-medium">{personalInfo.location}</span>
</div>
</div>
{/* About Me */}
<div className="mb-10">
<h2 className="section-header">About Me</h2>
<div className="text-[10px] leading-relaxed text-gray-600 font-medium text-justify">
{personalInfo.summary}
</div> </div>
</div> </div>
{/* Skills Section */} {/* Skills Section */}
<div className="mb-8"> <div className="mb-10">
<h3 className="section-header">Skills</h3> <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) => ( {skills.map((skill) => (
<p key={skill.id}> {skill.name}</p> <p key={skill.id}> {skill.name}</p>
@@ -537,7 +620,7 @@ export default function ResumeBuilderPage() {
{/* Languages Section */} {/* Languages Section */}
<div> <div>
<h3 className="section-header">Language</h3> <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) => ( {languages.map((lang) => (
<p key={lang.id}> {lang.name}</p> <p key={lang.id}> {lang.name}</p>
@@ -547,26 +630,20 @@ export default function ResumeBuilderPage() {
</div> </div>
{/* Main Content */} {/* Main Content */}
<div className="resume-main"> <div className="main-content">
<h1 className="main-header">{personalInfo.name}</h1> <h1 className="main-header">{personalInfo.name}</h1>
{/* Summary */}
<div className="mb-8">
<h3 className="section-header">Profile</h3>
<p className="text-[11px] text-gray-700 leading-relaxed">{personalInfo.summary}</p>
</div>
{/* Experience */} {/* Experience */}
<div className="mb-8"> <div className="mb-10">
<h3 className="section-header">Experience</h3> <h2 className="section-header">Experience</h2>
{experiences.map((exp) => ( {experiences.map((exp) => (
<div key={exp.id} className="experience-item"> <div key={exp.id} 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">
<h4 className="text-[18px] font-bold text-gray-800 leading-none">{exp.title}</h4> <h3 className="text-lg font-bold text-gray-800 leading-none">{exp.title}</h3>
<span className="text-[12px] 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-[14px] font-bold text-blue-600 mb-3">{exp.company}</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 whitespace-pre-line">
{exp.description} {exp.description}
</div> </div>
@@ -576,12 +653,12 @@ export default function ResumeBuilderPage() {
{/* Education */} {/* Education */}
<div> <div>
<h3 className="section-header">Education</h3> <h2 className="section-header">Education</h2>
{education.map((edu) => ( {education.map((edu) => (
<div key={edu.id} className="mb-4"> <div key={edu.id} 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.year}</p> <p className="text-[9px] text-gray-400 font-bold">{edu.location} - {edu.year}</p>
</div> </div>
))} ))}
</div> </div>