Files
sitemente/app/mission-control/resume/page.tsx
T
horus 29312444d0 feat(resume): replace with Gemini's resume builder
- Two-column resume layout (sidebar + main)
- Edit mode with forms for all sections
- Preview mode with live resume preview
- Version saving system
- Print/Download functionality
- Personal info, Experience, Education, Skills, Languages
2026-03-24 00:22:40 +01:00

581 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 */
<div className="p-8">
<style>{`
@import url('https://fonts.googleapis.com/css2?family=Roboto:wght@300;400;500;700&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: 'Roboto', 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;
}
.resume-section-header {
font-size: 1.1rem;
font-weight: 700;
color: #1a1a1a;
border-bottom: 1px solid #ccc;
padding-bottom: 4px;
margin-bottom: 12px;
text-transform: capitalize;
}
.resume-main-header {
font-size: 1.6rem;
font-weight: 800;
letter-spacing: 0.15em;
color: #1a1a1a;
border-bottom: 2px solid #1a1a1a;
padding-bottom: 8px;
margin-bottom: 24px;
text-transform: uppercase;
}
@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-6">
<h3 className="resume-section-header">Contact</h3>
<div className="space-y-2 text-sm 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-6">
<h3 className="resume-section-header">Skills</h3>
<div className="space-y-1">
{skills.map((skill) => (
<div key={skill.id} className="text-sm text-gray-700">
{skill.name}
</div>
))}
</div>
</div>
{/* Languages Section */}
<div className="mb-6">
<h3 className="resume-section-header">Languages</h3>
<div className="space-y-1">
{languages.map((lang) => (
<div key={lang.id} className="text-sm text-gray-700">
{lang.name} ({lang.level})
</div>
))}
</div>
</div>
</div>
{/* Main Content */}
<div className="resume-main">
<h1 className="resume-main-header">{personalInfo.name}</h1>
<p className="text-lg text-gray-600 mb-6 uppercase tracking-wide">{personalInfo.title}</p>
{/* Summary */}
<div className="mb-6">
<h3 className="resume-section-header">Profile</h3>
<p className="text-sm text-gray-700 leading-relaxed">{personalInfo.summary}</p>
</div>
{/* Experience */}
<div className="mb-6">
<h3 className="resume-section-header">Experience</h3>
{experiences.map((exp) => (
<div key={exp.id} className="mb-4">
<div className="flex justify-between items-start">
<div>
<h4 className="font-bold text-gray-800">{exp.title}</h4>
<p className="text-gray-600">{exp.company}</p>
</div>
<span className="text-sm text-gray-500">{exp.period}</span>
</div>
<p className="text-sm text-gray-700 mt-2 leading-relaxed">{exp.description}</p>
</div>
))}
</div>
{/* Education */}
<div>
<h3 className="resume-section-header">Education</h3>
{education.map((edu) => (
<div key={edu.id} className="mb-3">
<h4 className="font-bold text-gray-800">{edu.degree}</h4>
<p className="text-gray-600">{edu.school} {edu.year}</p>
</div>
))}
</div>
</div>
</div>
</div>
)}
</div>
);
}