Files
sitemente/app/mission-control/resume/page.tsx
T
horus 4d7073cab2 fix(resume): update preview to match Gemini's exact design
- Smaller font sizes (9-11px)
- Experience with border-left line and dots
- Education formatting with specific sizes
- Skills/Languages as bullet lists
- Main-header and section-header styling
2026-03-24 00:24:03 +01:00

595 lines
22 KiB
TypeScript
Raw Blame History

This file contains invisible Unicode characters
This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"use client";
import { useState, useEffect } from "react";
import BackToMC from "@/components/mission-control/BackToMC";
interface Experience {
id: string;
title: string;
company: string;
period: string;
description: string;
}
interface Education {
id: string;
degree: string;
school: string;
year: string;
}
interface Skill {
id: string;
name: string;
}
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..."
});
// 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."
}
]);
// Education
const [education, setEducation] = useState<Education[]>([
{
id: "1",
degree: "Degree Name",
school: "University Name",
year: "2016 - 2020"
}
]);
// Skills
const [skills, setSkills] = useState<Skill[]>([
{ id: "1", name: "Skill 1" },
{ id: "2", name: "Skill 2" },
{ id: "3", name: "Skill 3" }
]);
const [languages, setLanguages] = useState([
{ id: "1", name: "Language 1", level: "Fluent" },
{ id: "2", name: "Language 2", level: "Native" }
]);
// Versions
const [versions, setVersions] = useState<string[]>(["Default"]);
const [currentVersion, setCurrentVersion] = useState("Default");
// CRUD operations
const addExperience = () => {
setExperiences([...experiences, {
id: Date.now().toString(),
title: "New Position",
company: "Company",
period: "2023 - Present",
description: "Description..."
}]);
};
const removeExperience = (id: string) => {
setExperiences(experiences.filter(e => e.id !== id));
};
const updateExperience = (id: string, field: keyof Experience, value: string) => {
setExperiences(experiences.map(e => e.id === id ? { ...e, [field]: value } : e));
};
const addEducation = () => {
setEducation([...education, {
id: Date.now().toString(),
degree: "New Degree",
school: "School Name",
year: "2020 - 2024"
}]);
};
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 saveVersion = () => {
const name = prompt("Enter version name:", `Resume - ${currentVersion}`);
if (name && !versions.includes(name)) {
setVersions([...versions, name]);
setCurrentVersion(name);
alert(`Version "${name}" saved!`);
}
};
const loadVersion = (version: string) => {
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();
};
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">📄 Resume Builder</h1>
<div className="flex gap-2">
<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>
</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/Download
</button>
</div>
</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={personalInfo.name}
onChange={(e) => setPersonalInfo({...personalInfo, 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">Professional Title</label>
<input
type="text"
value={personalInfo.title}
onChange={(e) => setPersonalInfo({...personalInfo, title: 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={personalInfo.email}
onChange={(e) => setPersonalInfo({...personalInfo, 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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Website</label>
<input
type="text"
value={personalInfo.website}
onChange={(e) => setPersonalInfo({...personalInfo, website: 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>
<textarea
value={personalInfo.summary}
onChange={(e) => setPersonalInfo({...personalInfo, summary: e.target.value})}
rows={3}
className="w-full bg-slate-700 border border-slate-600 rounded-lg px-3 py-2 text-white"
/>
</div>
</div>
{/* Experience */}
<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 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">
{experiences.map((exp) => (
<div key={exp.id} 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">Job Title</label>
<input
type="text"
value={exp.title}
onChange={(e) => updateExperience(exp.id, "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(exp.id, "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 className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Period</label>
<input
type="text"
value={exp.period}
onChange={(e) => updateExperience(exp.id, "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 className="col-span-2">
<label className="block text-sm text-slate-400 mb-1">Description</label>
<textarea
value={exp.description}
onChange={(e) => updateExperience(exp.id, "description", e.target.value)}
rows={3}
className="w-full bg-slate-600 border border-slate-500 rounded-lg px-3 py-2 text-white text-sm"
/>
</div>
</div>
<button
onClick={() => removeExperience(exp.id)}
className="mt-2 text-red-400 text-sm hover:text-red-300"
>
Remove
</button>
</div>
))}
</div>
</div>
{/* Education */}
<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 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">
{education.map((edu) => (
<div key={edu.id} 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">Degree</label>
<input
type="text"
value={edu.degree}
onChange={(e) => updateEducation(edu.id, "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"
/>
</div>
<div>
<label className="block text-sm text-slate-400 mb-1">Year</label>
<input
type="text"
value={edu.year}
onChange={(e) => updateEducation(edu.id, "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>
<button
onClick={() => removeEducation(edu.id)}
className="mt-2 text-red-400 text-sm hover:text-red-300"
>
Remove
</button>
</div>
))}
</div>
</div>
{/* Skills */}
<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 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">
{skills.map((skill) => (
<div key={skill.id} className="flex items-center gap-2 bg-slate-700 px-3 py-1 rounded-lg">
<input
type="text"
value={skill.name}
onChange={(e) => updateSkill(skill.id, e.target.value)}
className="bg-transparent border-none text-white text-sm w-24"
/>
<button
onClick={() => removeSkill(skill.id)}
className="text-slate-400 hover:text-red-400 text-lg"
>
×
</button>
</div>
))}
</div>
</div>
{/* 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 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>
))}
</div>
</div>
</div>
) : (
/* Resume Preview - Gemini Design */
<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;
}
.resume-sidebar {
width: 280px;
background-color: #f3f3f3;
padding: 2.5rem 1.5rem;
}
.resume-main {
flex: 1;
padding: 3.5rem 3rem;
background: white;
}
.resume-photo {
width: 176px;
height: 176px;
border-radius: 9999px;
border: 10px solid #e2e2e2;
background: #d1d1d1;
margin: 0 auto 2rem;
}
.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: 16px;
font-weight: 800;
letter-spacing: 0.15em;
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="resume-sidebar">
<div className="resume-photo" />
{/* 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>
</div>
{/* Skills Section */}
<div className="mb-8">
<h3 className="section-header">Skills</h3>
<div className="text-[11px] text-gray-700 space-y-1 font-medium">
{skills.map((skill) => (
<p key={skill.id}> {skill.name}</p>
))}
</div>
</div>
{/* Languages Section */}
<div>
<h3 className="section-header">Language</h3>
<div className="text-[11px] text-gray-700 space-y-1 font-medium">
{languages.map((lang) => (
<p key={lang.id}> {lang.name}</p>
))}
</div>
</div>
</div>
{/* Main Content */}
<div className="resume-main">
<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>
{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>
</div>
<p className="text-[14px] font-bold text-blue-600 mb-3">{exp.company}</p>
<div className="text-[11px] text-gray-600 space-y-1.5 leading-snug whitespace-pre-line">
{exp.description}
</div>
</div>
))}
</div>
{/* Education */}
<div>
<h3 className="section-header">Education</h3>
{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>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
);
}