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:
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import { useState, useEffect } from "react";
|
||||
import { useState } from "react";
|
||||
import BackToMC from "@/components/mission-control/BackToMC";
|
||||
|
||||
interface Experience {
|
||||
@@ -8,6 +8,7 @@ interface Experience {
|
||||
title: string;
|
||||
company: string;
|
||||
period: string;
|
||||
location: string;
|
||||
description: string;
|
||||
}
|
||||
|
||||
@@ -16,6 +17,7 @@ interface Education {
|
||||
degree: string;
|
||||
school: string;
|
||||
year: string;
|
||||
location: string;
|
||||
}
|
||||
|
||||
interface Skill {
|
||||
@@ -25,28 +27,28 @@ interface Skill {
|
||||
|
||||
export default function ResumeBuilderPage() {
|
||||
const [activeTab, setActiveTab] = useState<"edit" | "preview">("edit");
|
||||
const [resumeName, setResumeName] = useState("My Resume");
|
||||
|
||||
// Personal Info
|
||||
const [personalInfo, setPersonalInfo] = useState({
|
||||
name: "Your Name",
|
||||
title: "Professional Title",
|
||||
email: "email@example.com",
|
||||
phone: "+34 XXX XXX XXX",
|
||||
location: "City, Country",
|
||||
website: "www.yoursite.com",
|
||||
linkedin: "linkedin.com/in/yourprofile",
|
||||
summary: "A brief professional summary..."
|
||||
name: "HAITHAM KHALIFA",
|
||||
title: "Senior Full-Stack Developer & Entrepreneur",
|
||||
email: "Haitham@Khalifa.se",
|
||||
phone: "+34 614 821 331",
|
||||
location: "Málaga, Spain",
|
||||
linkedin: "linkedin.com/in/haithamekhalifa/",
|
||||
website: "haithamkhalifa.com",
|
||||
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
|
||||
const [experiences, setExperiences] = useState<Experience[]>([
|
||||
{
|
||||
id: "1",
|
||||
title: "Job Title",
|
||||
company: "Company Name",
|
||||
period: "2020 - Present",
|
||||
description: "Key responsibilities and achievements in this role."
|
||||
title: "Product Owner & IT Consultant",
|
||||
company: "Protein.com",
|
||||
period: "2020-2025",
|
||||
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[]>([
|
||||
{
|
||||
id: "1",
|
||||
degree: "Degree Name",
|
||||
school: "University Name",
|
||||
year: "2016 - 2020"
|
||||
degree: "Intensive Software Development Academy",
|
||||
school: "Lund University",
|
||||
year: "2019",
|
||||
location: "Sweden, Lund"
|
||||
},
|
||||
{
|
||||
id: "2",
|
||||
degree: "Bachelor of Computer Science",
|
||||
school: "Modern Academy Maadi",
|
||||
year: "2001 - 2006",
|
||||
location: "Egypt, Cairo"
|
||||
}
|
||||
]);
|
||||
|
||||
// Skills
|
||||
const [skills, setSkills] = useState<Skill[]>([
|
||||
{ id: "1", name: "Skill 1" },
|
||||
{ id: "2", name: "Skill 2" },
|
||||
{ id: "3", name: "Skill 3" }
|
||||
{ id: "1", name: "E-commerce Strategy" },
|
||||
{ id: "2", name: "Digital Marketing" },
|
||||
{ id: "3", name: "SEO/SEM" },
|
||||
{ id: "4", name: "Product Ownership" },
|
||||
{ id: "5", name: "Team Leadership" }
|
||||
]);
|
||||
|
||||
// Languages
|
||||
const [languages, setLanguages] = useState([
|
||||
{ id: "1", name: "Language 1", level: "Fluent" },
|
||||
{ id: "2", name: "Language 2", level: "Native" }
|
||||
{ id: "1", name: "Arabic" },
|
||||
{ id: "2", name: "English" },
|
||||
{ id: "3", name: "Swedish" }
|
||||
]);
|
||||
|
||||
// Versions
|
||||
@@ -82,8 +96,9 @@ export default function ResumeBuilderPage() {
|
||||
id: Date.now().toString(),
|
||||
title: "New Position",
|
||||
company: "Company",
|
||||
period: "2023 - Present",
|
||||
description: "Description..."
|
||||
period: "2023-Present",
|
||||
location: "City, Country",
|
||||
description: "• Key responsibility.\n• Achievement."
|
||||
}]);
|
||||
};
|
||||
|
||||
@@ -99,8 +114,9 @@ export default function ResumeBuilderPage() {
|
||||
setEducation([...education, {
|
||||
id: Date.now().toString(),
|
||||
degree: "New Degree",
|
||||
school: "School Name",
|
||||
year: "2020 - 2024"
|
||||
school: "University",
|
||||
year: "2020 - 2024",
|
||||
location: "City, Country"
|
||||
}]);
|
||||
};
|
||||
|
||||
@@ -124,12 +140,23 @@ export default function ResumeBuilderPage() {
|
||||
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);
|
||||
alert(`Version "${name}" saved!`);
|
||||
}
|
||||
};
|
||||
|
||||
@@ -137,15 +164,6 @@ export default function ResumeBuilderPage() {
|
||||
setCurrentVersion(version);
|
||||
};
|
||||
|
||||
const deleteVersion = (version: string) => {
|
||||
if (versions.length > 1) {
|
||||
setVersions(versions.filter(v => v !== version));
|
||||
if (currentVersion === version) {
|
||||
setCurrentVersion(versions[0]);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handlePrint = () => {
|
||||
window.print();
|
||||
};
|
||||
@@ -157,7 +175,7 @@ export default function ResumeBuilderPage() {
|
||||
{/* 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">📄 Resume Builder</h1>
|
||||
<h1 className="text-xl font-bold text-blue-400">Haitham's Resume Manager</h1>
|
||||
<div className="flex gap-2">
|
||||
<button
|
||||
onClick={() => setActiveTab("edit")}
|
||||
@@ -197,7 +215,7 @@ export default function ResumeBuilderPage() {
|
||||
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/Download
|
||||
🖨️ Print to PDF
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -254,21 +272,21 @@ export default function ResumeBuilderPage() {
|
||||
/>
|
||||
</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
|
||||
type="text"
|
||||
value={personalInfo.website}
|
||||
onChange={(e) => setPersonalInfo({...personalInfo, website: e.target.value})}
|
||||
value={personalInfo.linkedin}
|
||||
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"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<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
|
||||
value={personalInfo.summary}
|
||||
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"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
</div>
|
||||
<div className="col-span-2">
|
||||
<div>
|
||||
<label className="block text-sm text-slate-400 mb-1">Period</label>
|
||||
<input
|
||||
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"
|
||||
/>
|
||||
</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">
|
||||
<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
|
||||
value={exp.description}
|
||||
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"
|
||||
/>
|
||||
</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"
|
||||
/>
|
||||
</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>
|
||||
<button
|
||||
onClick={() => removeEducation(edu.id)}
|
||||
@@ -409,7 +445,7 @@ export default function ResumeBuilderPage() {
|
||||
type="text"
|
||||
value={skill.name}
|
||||
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
|
||||
onClick={() => removeSkill(skill.id)}
|
||||
@@ -424,18 +460,37 @@ export default function ResumeBuilderPage() {
|
||||
|
||||
{/* Languages */}
|
||||
<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">
|
||||
{languages.map((lang) => (
|
||||
<div key={lang.id} className="bg-slate-700 px-3 py-2 rounded-lg text-sm">
|
||||
{lang.name} - {lang.level}
|
||||
<div key={lang.id} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
|
||||
<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>
|
||||
) : (
|
||||
/* Resume Preview - Gemini Design */
|
||||
/* Resume Preview - Haitham's Design */
|
||||
<div className="p-8">
|
||||
<style>{`
|
||||
@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;
|
||||
font-family: 'Inter', sans-serif;
|
||||
}
|
||||
.resume-sidebar {
|
||||
.sidebar {
|
||||
width: 280px;
|
||||
background-color: #f3f3f3;
|
||||
padding: 2.5rem 1.5rem;
|
||||
}
|
||||
.resume-main {
|
||||
.main-content {
|
||||
flex: 1;
|
||||
padding: 3.5rem 3rem;
|
||||
background: white;
|
||||
}
|
||||
.resume-photo {
|
||||
.circle-frame {
|
||||
width: 176px;
|
||||
height: 176px;
|
||||
border-radius: 9999px;
|
||||
border: 10px solid #e2e2e2;
|
||||
background: #d1d1d1;
|
||||
overflow: hidden;
|
||||
margin: 0 auto 2rem;
|
||||
background: #d1d1d1;
|
||||
}
|
||||
.circle-frame img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
.section-header {
|
||||
font-size: 11px;
|
||||
@@ -476,9 +537,9 @@ export default function ResumeBuilderPage() {
|
||||
text-transform: capitalize;
|
||||
}
|
||||
.main-header {
|
||||
font-size: 16px;
|
||||
font-size: 24px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.15em;
|
||||
letter-spacing: 0.05em;
|
||||
color: #1a1a1a;
|
||||
border-bottom: 2px solid #1a1a1a;
|
||||
padding-bottom: 8px;
|
||||
@@ -510,24 +571,46 @@ export default function ResumeBuilderPage() {
|
||||
|
||||
<div className="resume-preview">
|
||||
{/* Sidebar */}
|
||||
<div className="resume-sidebar">
|
||||
<div className="resume-photo" />
|
||||
|
||||
<div className="sidebar">
|
||||
<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 */}
|
||||
<div className="mb-8">
|
||||
<h3 className="section-header">Contact</h3>
|
||||
<div className="space-y-2 text-[11px] text-gray-700">
|
||||
<p>{personalInfo.email}</p>
|
||||
<p>{personalInfo.phone}</p>
|
||||
<p>{personalInfo.location}</p>
|
||||
<p>{personalInfo.website}</p>
|
||||
<p>{personalInfo.linkedin}</p>
|
||||
<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">{personalInfo.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">{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>
|
||||
|
||||
{/* Skills Section */}
|
||||
<div className="mb-8">
|
||||
<h3 className="section-header">Skills</h3>
|
||||
<div className="mb-10">
|
||||
<h2 className="section-header">Skills</h2>
|
||||
<div className="text-[11px] text-gray-700 space-y-1 font-medium">
|
||||
{skills.map((skill) => (
|
||||
<p key={skill.id}>• {skill.name}</p>
|
||||
@@ -537,7 +620,7 @@ export default function ResumeBuilderPage() {
|
||||
|
||||
{/* Languages Section */}
|
||||
<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">
|
||||
{languages.map((lang) => (
|
||||
<p key={lang.id}>• {lang.name}</p>
|
||||
@@ -547,26 +630,20 @@ export default function ResumeBuilderPage() {
|
||||
</div>
|
||||
|
||||
{/* Main Content */}
|
||||
<div className="resume-main">
|
||||
<div className="main-content">
|
||||
<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 */}
|
||||
<div className="mb-8">
|
||||
<h3 className="section-header">Experience</h3>
|
||||
<div className="mb-10">
|
||||
<h2 className="section-header">Experience</h2>
|
||||
{experiences.map((exp) => (
|
||||
<div key={exp.id} className="experience-item">
|
||||
<div className="experience-dot" />
|
||||
<div className="flex justify-between items-baseline mb-1">
|
||||
<h4 className="text-[18px] font-bold text-gray-800 leading-none">{exp.title}</h4>
|
||||
<span className="text-[12px] font-bold text-gray-400">{exp.period}</span>
|
||||
<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-[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">
|
||||
{exp.description}
|
||||
</div>
|
||||
@@ -576,12 +653,12 @@ export default function ResumeBuilderPage() {
|
||||
|
||||
{/* Education */}
|
||||
<div>
|
||||
<h3 className="section-header">Education</h3>
|
||||
<h2 className="section-header">Education</h2>
|
||||
{education.map((edu) => (
|
||||
<div key={edu.id} 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>
|
||||
<p className="text-[9px] text-gray-400 font-bold">{edu.location} - {edu.year}</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user