ResumeViewer.tsxā¢13.7 kB
"use client";
import { useState, useEffect } from "react";
interface ResumeData {
personalInfo: {
name: string;
email: string;
phone?: string;
location?: string;
linkedin?: string;
github?: string;
};
summary: string;
experience: Array<{
company: string;
position: string;
startDate: string;
endDate?: string;
current: boolean;
description: string;
achievements: string[];
technologies: string[];
}>;
education: Array<{
institution: string;
degree: string;
field: string;
startDate: string;
endDate?: string;
gpa?: string;
achievements?: string[];
}>;
skills: string[];
projects?: Array<{
name: string;
description: string;
technologies: string[];
url?: string;
github?: string;
startDate: string;
endDate?: string;
}>;
certifications?: Array<{
name: string;
issuer: string;
date: string;
credentialId?: string;
url?: string;
}>;
}
export default function ResumeViewer() {
const [resumeData, setResumeData] = useState<ResumeData | null>(null);
const [loading, setLoading] = useState(true);
const [error, setError] = useState<string | null>(null);
const [activeSection, setActiveSection] = useState<string>("personal");
useEffect(() => {
loadResumeData();
}, []);
const loadResumeData = async () => {
try {
const response = await fetch("/api/resume");
if (!response.ok) {
throw new Error("Failed to load resume data");
}
const data = await response.json();
setResumeData(data);
} catch (err) {
setError(
"Failed to load resume data. Please check if the data file exists."
);
} finally {
setLoading(false);
}
};
const sections = [
{ id: "personal", label: "Personal Info", icon: "š¤" },
{ id: "summary", label: "Summary", icon: "š" },
{ id: "experience", label: "Experience", icon: "š¼" },
{ id: "education", label: "Education", icon: "š" },
{ id: "skills", label: "Skills", icon: "š ļø" },
{ id: "projects", label: "Projects", icon: "š" },
{ id: "certifications", label: "Certifications", icon: "š" },
];
if (loading) {
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<div className="flex items-center justify-center">
<div className="w-8 h-8 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mr-3"></div>
<span className="text-gray-600 dark:text-gray-400">
Loading resume data...
</span>
</div>
</div>
);
}
if (error || !resumeData) {
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg p-8">
<div className="text-center">
<div className="text-red-500 text-4xl mb-4">ā</div>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white mb-2">
Error Loading Resume
</h3>
<p className="text-gray-600 dark:text-gray-400">
{error || "Resume data not found"}
</p>
</div>
</div>
);
}
const renderPersonalInfo = () => (
<div className="space-y-4">
<div className="text-center">
<h2 className="text-3xl font-bold text-gray-900 dark:text-white">
{resumeData.personalInfo.name}
</h2>
<p className="text-lg text-gray-600 dark:text-gray-400 mt-2">
{resumeData.personalInfo.email}
</p>
{resumeData.personalInfo.phone && (
<p className="text-gray-600 dark:text-gray-400">
{resumeData.personalInfo.phone}
</p>
)}
{resumeData.personalInfo.location && (
<p className="text-gray-600 dark:text-gray-400">
{resumeData.personalInfo.location}
</p>
)}
</div>
<div className="flex justify-center space-x-4 mt-6">
{resumeData.personalInfo.linkedin && (
<a
href={resumeData.personalInfo.linkedin}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
LinkedIn
</a>
)}
{resumeData.personalInfo.github && (
<a
href={resumeData.personalInfo.github}
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-300"
>
GitHub
</a>
)}
</div>
</div>
);
const renderSummary = () => (
<div className="prose dark:prose-invert max-w-none">
<p className="text-gray-700 dark:text-gray-300 leading-relaxed">
{resumeData.summary}
</p>
</div>
);
const renderExperience = () => (
<div className="space-y-6">
{resumeData.experience.map((exp, index) => (
<div key={index} className="border-l-4 border-blue-500 pl-4">
<div className="flex justify-between items-start mb-2">
<div>
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
{exp.position}
</h3>
<p className="text-lg text-blue-600 dark:text-blue-400 font-medium">
{exp.company}
</p>
</div>
<div className="text-right text-sm text-gray-600 dark:text-gray-400">
<p>
{exp.startDate} - {exp.endDate || "Present"}
</p>
{exp.current && (
<span className="inline-block bg-green-100 dark:bg-green-900 text-green-800 dark:text-green-200 px-2 py-1 rounded-full text-xs mt-1">
Current
</span>
)}
</div>
</div>
<p className="text-gray-700 dark:text-gray-300 mb-3">
{exp.description}
</p>
<div className="mb-3">
<h4 className="font-medium text-gray-900 dark:text-white mb-2">
Key Achievements:
</h4>
<ul className="list-disc list-inside space-y-1 text-gray-700 dark:text-gray-300">
{exp.achievements.map((achievement, idx) => (
<li key={idx}>{achievement}</li>
))}
</ul>
</div>
<div>
<h4 className="font-medium text-gray-900 dark:text-white mb-2">
Technologies:
</h4>
<div className="flex flex-wrap gap-2">
{exp.technologies.map((tech, idx) => (
<span
key={idx}
className="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"
>
{tech}
</span>
))}
</div>
</div>
</div>
))}
</div>
);
const renderEducation = () => (
<div className="space-y-4">
{resumeData.education.map((edu, index) => (
<div key={index} className="border-l-4 border-green-500 pl-4">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
{edu.degree} in {edu.field}
</h3>
<p className="text-lg text-green-600 dark:text-green-400 font-medium">
{edu.institution}
</p>
<p className="text-gray-600 dark:text-gray-400">
{edu.startDate} - {edu.endDate || "Present"}
{edu.gpa && ` ⢠GPA: ${edu.gpa}`}
</p>
{edu.achievements && edu.achievements.length > 0 && (
<div className="mt-2">
<h4 className="font-medium text-gray-900 dark:text-white mb-1">
Achievements:
</h4>
<ul className="list-disc list-inside text-gray-700 dark:text-gray-300">
{edu.achievements.map((achievement, idx) => (
<li key={idx}>{achievement}</li>
))}
</ul>
</div>
)}
</div>
))}
</div>
);
const renderSkills = () => (
<div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-3">
{resumeData.skills.map((skill, index) => (
<div
key={index}
className="bg-blue-50 dark:bg-blue-900/20 text-blue-800 dark:text-blue-200 px-3 py-2 rounded-lg text-center font-medium"
>
{skill}
</div>
))}
</div>
);
const renderProjects = () => (
<div className="space-y-6">
{resumeData.projects?.map((project, index) => (
<div
key={index}
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4"
>
<div className="flex justify-between items-start mb-2">
<h3 className="text-xl font-semibold text-gray-900 dark:text-white">
{project.name}
</h3>
<div className="text-sm text-gray-600 dark:text-gray-400">
{project.startDate} - {project.endDate || "Present"}
</div>
</div>
<p className="text-gray-700 dark:text-gray-300 mb-3">
{project.description}
</p>
<div className="mb-3">
<h4 className="font-medium text-gray-900 dark:text-white mb-2">
Technologies:
</h4>
<div className="flex flex-wrap gap-2">
{project.technologies.map((tech, idx) => (
<span
key={idx}
className="bg-gray-100 dark:bg-gray-700 text-gray-800 dark:text-gray-200 px-2 py-1 rounded text-sm"
>
{tech}
</span>
))}
</div>
</div>
<div className="flex space-x-4">
{project.url && (
<a
href={project.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300"
>
View Project
</a>
)}
{project.github && (
<a
href={project.github}
target="_blank"
rel="noopener noreferrer"
className="text-gray-600 hover:text-gray-800 dark:text-gray-400 dark:hover:text-gray-300"
>
GitHub
</a>
)}
</div>
</div>
))}
</div>
);
const renderCertifications = () => (
<div className="space-y-4">
{resumeData.certifications?.map((cert, index) => (
<div
key={index}
className="border border-gray-200 dark:border-gray-700 rounded-lg p-4"
>
<h3 className="text-lg font-semibold text-gray-900 dark:text-white">
{cert.name}
</h3>
<p className="text-gray-600 dark:text-gray-400">
{cert.issuer} ⢠{cert.date}
</p>
{cert.credentialId && (
<p className="text-sm text-gray-500 dark:text-gray-500">
Credential ID: {cert.credentialId}
</p>
)}
{cert.url && (
<a
href={cert.url}
target="_blank"
rel="noopener noreferrer"
className="text-blue-600 hover:text-blue-800 dark:text-blue-400 dark:hover:text-blue-300 text-sm"
>
View Certificate
</a>
)}
</div>
))}
</div>
);
const renderContent = () => {
switch (activeSection) {
case "personal":
return renderPersonalInfo();
case "summary":
return renderSummary();
case "experience":
return renderExperience();
case "education":
return renderEducation();
case "skills":
return renderSkills();
case "projects":
return renderProjects();
case "certifications":
return renderCertifications();
default:
return renderPersonalInfo();
}
};
return (
<div className="bg-white dark:bg-gray-800 rounded-lg shadow-lg">
{/* Header */}
<div className="p-6 border-b border-gray-200 dark:border-gray-700">
<h2 className="text-2xl font-semibold text-gray-900 dark:text-white mb-2">
š Resume Viewer
</h2>
<p className="text-gray-600 dark:text-gray-400">
View and explore your resume data
</p>
</div>
<div className="flex">
{/* Sidebar Navigation */}
<div className="w-64 border-r border-gray-200 dark:border-gray-700 bg-gray-50 dark:bg-gray-900">
<nav className="p-4">
<ul className="space-y-2">
{sections.map((section) => (
<li key={section.id}>
<button
onClick={() => setActiveSection(section.id)}
className={`w-full text-left px-3 py-2 rounded-lg transition-colors ${
activeSection === section.id
? "bg-blue-600 text-white"
: "text-gray-700 dark:text-gray-300 hover:bg-gray-200 dark:hover:bg-gray-700"
}`}
>
<span className="mr-2">{section.icon}</span>
{section.label}
</button>
</li>
))}
</ul>
</nav>
</div>
{/* Content */}
<div className="flex-1 p-6">{renderContent()}</div>
</div>
</div>
);
}