Files
sitemente/app/mission-control/resume/page.tsx
T
horus 8cd30cf5dc 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
2026-03-24 00:29:35 +01:00

717 lines
29 KiB
TypeScript

"use client";
import { useState, useEffect } from "react";
import BackToMC from "@/components/mission-control/BackToMC";
interface ResumeData {
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;
company: string;
period: string;
location: string;
description: string[];
}[];
photoUrl: string;
}
const initialResume: ResumeData = {
name: "HAITHAM KHALIFA",
phone: "+34 614 821 331",
email: "Haitham@Khalifa.se",
linkedin: "linkedin.com/in/haithamekhalifa/",
location: "Málaga, Spain",
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" },
{ degree: "Bachelor of Computer Science", school: "Modern Academy Maadi", year: "Egypt, Cairo - 2001 - 2006" }
],
skills: ["E-commerce Strategy", "Digital Marketing", "SEO/SEM", "Product Ownership", "Team Leadership"],
languages: ["Arabic", "English", "Swedish"],
experience: [
{
title: "Product Owner & IT Consultant",
company: "Protein.com",
period: "2020-2025",
location: "Sweden, Malmö",
description: [
"Managed product backlog to drive e-commerce sales.",
"Developed/executed SEO strategies (+20% organic traffic).",
"Implemented/managed MarTech for e-commerce performance.",
"Drove Agile practices.",
"Utilized data analytics to optimize e-commerce growth.",
"Managed product data feeds."
]
},
{
title: "Founder & CEO",
company: "HostPioneers.com",
period: "2012-2021",
location: "Denmark, Copenhagen",
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: ""
};
export default function ResumeBuilderPage() {
const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
const [versions, setVersions] = useState<string[]>(["Main Resume"]);
const [currentVersion, setCurrentVersion] = useState("Main Resume");
const [resume, setResume] = useState<ResumeData>(initialResume);
const [photoPreview, setPhotoPreview] = useState<string>("");
// Load from localStorage on mount
useEffect(() => {
const saved = localStorage.getItem('haitham_resumes_v2');
if (saved) {
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);
}
}
}, []);
const saveToDisk = () => {
const saved = localStorage.getItem('haitham_resumes_v2');
let allResumes: Record<string, ResumeData> = {};
if (saved) {
try {
allResumes = JSON.parse(saved);
} catch (e) {}
}
allResumes[currentVersion] = { ...resume, photoUrl: photoPreview };
localStorage.setItem('haitham_resumes_v2', JSON.stringify(allResumes));
localStorage.setItem('haitham_current_profile_v2', currentVersion);
};
const loadProfile = (name: string) => {
const saved = localStorage.getItem('haitham_resumes_v2');
if (saved) {
try {
const data = JSON.parse(saved);
if (data[name]) {
setResume(data[name]);
setPhotoPreview(data[name].photoUrl || "");
setCurrentVersion(name);
}
} catch (e) {}
}
};
const handlePhotoUpload = (e: React.ChangeEvent<HTMLInputElement>) => {
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 createNewProfile = () => {
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 (
<div className="min-h-screen bg-slate-950 text-white">
<BackToMC />
{/* 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="flex items-center gap-4">
<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">
<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
onClick={() => setActiveTab("edit")}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === "edit" ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300"
}`}
>
Edit
</button>
<button
onClick={() => setActiveTab("preview")}
className={`px-4 py-2 rounded-lg text-sm font-medium transition-colors ${
activeTab === "preview" ? "bg-blue-600 text-white" : "bg-slate-700 text-slate-300"
}`}
>
Preview
</button>
</div>
{activeTab === "edit" ? (
<div className="p-6 max-w-4xl mx-auto space-y-6">
{/* Personal Info */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">👤 Personal Information</h2>
<div className="grid grid-cols-2 gap-4">
<div>
<label className="block text-sm text-slate-400 mb-1">Full Name</label>
<input
type="text"
value={resume.name}
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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Phone</label>
<input
type="text"
value={resume.phone}
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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Email</label>
<input
type="email"
value={resume.email}
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">LinkedIn</label>
<input
type="text"
value={resume.linkedin}
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"
/>
</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 className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">About Me</label>
<textarea
value={resume.about}
onChange={(e) => setResume({...resume, about: e.target.value})}
rows={4}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/>
</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 */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">💼 Experience</h2>
<div className="space-y-4">
{resume.experience.map((exp, index) => (
<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>
<label className="block text-sm text-slate-400 mb-1">Title</label>
<input
type="text"
value={exp.title}
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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Company</label>
<input
type="text"
value={exp.company}
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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Period</label>
<input
type="text"
value={exp.period}
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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Location</label>
<input
type="text"
value={exp.location}
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"
/>
</div>
<div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Description (one bullet per line)</label>
<textarea
value={exp.description.join('\n')}
onChange={(e) => updateExperience(index, "description", e.target.value.split('\n'))}
rows={5}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/>
</div>
</div>
</div>
))}
</div>
</div>
{/* Education */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">🎓 Education</h2>
<div className="space-y-4">
{resume.education.map((edu, index) => (
<div key={index} className="bg-slate-700/50 rounded-lg p-4 border border-slate-600">
<div className="grid grid-cols-3 gap-4">
<div className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Degree</label>
<input
type="text"
value={edu.degree}
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">Year</label>
<input
type="text"
value={edu.year}
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"
/>
</div>
<div className="col-span-3">
<label className="block text-sm text-slate-400 mb-1">School</label>
<input
type="text"
value={edu.school}
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"
/>
</div>
</div>
</div>
))}
</div>
</div>
{/* Skills */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">🛠️ Skills</h2>
<div className="flex flex-wrap gap-2">
{resume.skills.map((skill, index) => (
<div key={index} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
<input
type="text"
value={skill}
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"
/>
<button
onClick={() => setResume({...resume, skills: resume.skills.filter((_, i) => i !== index)})}
className="text-slate-400 hover:text-red-400 text-lg"
>
×
</button>
</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>
{/* Languages */}
<div className="bg-slate-800 rounded-xl p-6 border border-slate-700">
<h2 className="text-lg font-bold mb-4 text-blue-400">🌍 Languages</h2>
<div className="flex flex-wrap gap-2">
{resume.languages.map((lang, index) => (
<div key={index} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
<input
type="text"
value={lang}
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"
/>
<button
onClick={() => setResume({...resume, languages: resume.languages.filter((_, i) => i !== index)})}
className="text-slate-400 hover:text-red-400 text-lg"
>
×
</button>
</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>
) : (
/* Resume Preview */
<div className="p-8">
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
.resume-preview {
width: 850px;
min-height: 1100px;
background: white;
box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
margin: 0 auto;
display: flex;
font-family: 'Inter', sans-serif;
}
.sidebar {
width: 280px;
background-color: #f3f3f3;
padding: 2.5rem 1.5rem;
}
.main-content {
flex: 1;
padding: 3.5rem 3rem;
background: white;
}
.circle-frame {
width: 176px;
height: 176px;
border-radius: 9999px;
border: 10px solid #e2e2e2;
overflow: hidden;
margin: 0 auto 2rem;
background: #d1d1d1;
}
.circle-frame img {
width: 100%;
height: 100%;
object-fit: cover;
}
.section-header {
font-size: 11px;
font-weight: 700;
color: #1a1a1a;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 12px;
text-transform: capitalize;
}
.main-header {
font-size: 24px;
font-weight: 800;
letter-spacing: 0.05em;
color: #1a1a1a;
border-bottom: 2px solid #1a1a1a;
padding-bottom: 8px;
margin-bottom: 24px;
text-transform: uppercase;
}
.experience-item {
position: relative;
padding-left: 32px;
border-left: 2px solid #e5e5e5;
padding-bottom: 24px;
}
.experience-dot {
position: absolute;
left: -9px;
top: 4px;
width: 16px;
height: 16px;
background-color: #333;
border: 3px solid white;
border-radius: 9999px;
z-index: 10;
}
@media print {
body { background: white; }
.resume-preview { box-shadow: none; margin: 0; }
}
`}</style>
<div className="resume-preview">
{/* Sidebar */}
<div className="sidebar">
<div className="circle-frame">
{photoPreview ? (
<img src={photoPreview} alt={resume.name} />
) : (
<div className="w-full h-full flex items-center justify-center text-gray-400">📷</div>
)}
</div>
<h1 className="text-2xl font-black text-center text-gray-900 mb-8 tracking-tighter uppercase leading-none">
{resume.name}
</h1>
{/* Contact Section */}
<div className="space-y-3 text-[11px] text-gray-700 mb-10">
<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">{resume.phone}</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">{resume.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">{resume.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">{resume.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">
{resume.about}
</div>
</div>
{/* Skills Section */}
<div className="mb-10">
<h2 className="section-header">Skills</h2>
<div className="text-[11px] text-gray-700 space-y-1 font-medium">
{resume.skills.map((skill, i) => (
<p key={i}>• {skill}</p>
))}
</div>
</div>
{/* Languages Section */}
<div>
<h2 className="section-header">Language</h2>
<div className="text-[11px] text-gray-700 space-y-1 font-medium">
{resume.languages.map((lang, i) => (
<p key={i}>• {lang}</p>
))}
</div>
</div>
</div>
{/* Main Content */}
<div className="main-content">
<h1 className="main-header">{resume.name}</h1>
{/* Experience */}
<div className="mb-10">
<h2 className="section-header">Experience</h2>
{resume.experience.map((exp, i) => (
<div key={i} className="experience-item">
<div className="experience-dot" />
<div className="flex justify-between items-baseline mb-1">
<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>
</div>
<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">
{exp.description.map((desc, j) => (
<p key={j}>• {desc}</p>
))}
</div>
</div>
))}
</div>
{/* Education */}
<div>
<h2 className="section-header">Education</h2>
{resume.education.map((edu, i) => (
<div key={i} className="mb-4">
<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-[9px] text-gray-400 font-bold">{edu.year}</p>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
);
}