Files
atridotdad/src/pages/resume.astro
Atridad Lahiji a26c990a21
Some checks failed
Docker Deploy / build-and-push (push) Has been cancelled
4.0.0
2026-01-24 17:24:00 -07:00

339 lines
14 KiB
Plaintext

---
import { Icon } from "astro-icon/components";
import Layout from "../layouts/Layout.astro";
import ResumeSkills from "../components/ResumeSkills.vue";
import ResumeDownloadButton from "../components/ResumeDownloadButton.vue";
import ResumeSettingsModal from "../components/ResumeSettingsModal.vue";
import { config } from "../config";
import "../styles/global.css";
import * as TOML from "@iarna/toml";
interface ResumeData {
basics: {
name: string;
email: string;
website?: string;
profiles?: {
network: string;
username: string;
url: string;
}[];
};
summary?: {
content: string;
};
experience?: {
company: string;
position: string;
location: string;
date: string;
description: string[];
url?: string;
}[];
education?: {
institution: string;
degree: string;
field: string;
date: string;
details?: string[];
}[];
skills?: {
name: string;
level: number;
}[];
volunteer?: {
organization: string;
position: string;
date: string;
}[];
awards?: {
title: string;
organization: string;
date: string;
description?: string;
}[];
}
let resumeData: ResumeData | undefined = undefined;
let fetchError: string | null = null;
if (!config.resumeConfig.tomlFile || !config.resumeConfig.tomlFile.trim()) {
return Astro.redirect("/");
}
try {
let tomlContent: string;
if (config.resumeConfig.tomlFile.startsWith("/")) {
const baseUrl = Astro.url.origin;
const response = await fetch(
`${baseUrl}${config.resumeConfig.tomlFile}`,
);
if (!response.ok) {
throw new Error(
`Failed to fetch resume: ${response.status} ${response.statusText}`,
);
}
tomlContent = await response.text();
} else {
tomlContent = config.resumeConfig.tomlFile;
}
resumeData = TOML.parse(tomlContent) as unknown as ResumeData;
} catch (error) {
console.error("Error loading resume data:", error);
return Astro.redirect("/");
}
const data = resumeData;
const resumeConfig = config.resumeConfig;
if (!data) {
return Astro.redirect("/");
}
---
<Layout title="Resume">
<ResumeSettingsModal client:load />
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl w-full">
<h1
class="text-3xl sm:text-4xl font-bold text-primary mb-4 sm:mb-6 text-center"
>
{data.basics.name}
</h1>
<div
class="flex justify-center items-center flex-wrap gap-x-3 sm:gap-x-4 gap-y-2 mb-4 sm:mb-6"
>
{
data.basics.email && (
<a
href={`mailto:${data.basics.email}`}
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
>
<Icon name="mdi:email" /> {data.basics.email}
</a>
)
}
{
data.basics.profiles?.map((profile) => {
const iconName = `simple-icons:${profile.network.toLowerCase()}`;
return (
<a
href={profile.url}
target="_blank"
rel="noopener noreferrer"
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
>
<Icon name={iconName} />
{profile.network}
</a>
);
})
}
</div>
<ResumeDownloadButton client:load />
{
data.summary &&
resumeConfig.sections.enabled.includes("summary") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.summary?.title ||
"Summary"}
</h2>
<div>{data.summary.content}</div>
</div>
</div>
)
}
{
data.skills &&
data.skills.length > 0 &&
resumeConfig.sections.enabled.includes("skills") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.skills?.title ||
"Skills"}
</h2>
<ResumeSkills
skills={data.skills.map((skill, index) => ({
id: `skill-${index}`,
name: skill.name,
level: skill.level,
}))}
client:load
/>
</div>
</div>
)
}
{
data.experience &&
data.experience.length > 0 &&
resumeConfig.sections.enabled.includes("experience") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.experience?.title ||
"Experience"}
</h2>
<div class="space-y-4 sm:space-y-6">
{data.experience.map((experience) => (
<div class="border-l-2 border-primary pl-4 sm:pl-6">
<h3 class="text-lg sm:text-xl font-semibold">
{experience.position}
</h3>
<div class="flex flex-col sm:flex-row sm:items-center sm:gap-4 text-sm sm:text-base text-base-content/70 mb-2">
<span class="font-medium">
{experience.company}
</span>
<span>{experience.date}</span>
<span>{experience.location}</span>
</div>
<ul class="list-disc list-inside space-y-1 text-sm sm:text-base">
{experience.description.map(
(item) => (
<li>{item}</li>
),
)}
</ul>
{experience.url && (
<a
href={experience.url}
target="_blank"
rel="noopener noreferrer"
class="inline-flex items-center gap-1 text-primary hover:text-primary-focus text-sm mt-2"
>
<Icon name="mdi:link" />
Website
</a>
)}
</div>
))}
</div>
</div>
</div>
)
}
{
data.education &&
data.education.length > 0 &&
resumeConfig.sections.enabled.includes("education") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.education?.title ||
"Education"}
</h2>
<div class="space-y-4">
{data.education.map((education) => (
<div class="border-l-2 border-secondary pl-4 sm:pl-6">
<h3 class="text-lg sm:text-xl font-semibold">
{education.institution}
</h3>
<div class="text-sm sm:text-base text-base-content/70 mb-2">
<span class="font-medium">
{education.degree} in{" "}
{education.field}
</span>
<span class="block sm:inline sm:ml-4">
{education.date}
</span>
</div>
{education.details && (
<ul class="list-disc list-inside space-y-1 text-sm sm:text-base">
{education.details.map(
(detail) => (
<li>{detail}</li>
),
)}
</ul>
)}
</div>
))}
</div>
</div>
</div>
)
}
{
data.volunteer &&
data.volunteer.length > 0 &&
resumeConfig.sections.enabled.includes("volunteer") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.volunteer?.title ||
"Volunteer Work"}
</h2>
<div class="space-y-4">
{data.volunteer.map((volunteer) => (
<div class="border-l-2 border-accent pl-4 sm:pl-6">
<h3 class="text-lg sm:text-xl font-semibold">
{volunteer.organization}
</h3>
<div class="text-sm sm:text-base text-base-content/70 mb-2">
<span class="font-medium">
{volunteer.position}
</span>
<span class="block sm:inline sm:ml-4">
{volunteer.date}
</span>
</div>
</div>
))}
</div>
</div>
</div>
)
}
{
data.awards &&
data.awards.length > 0 &&
resumeConfig.sections.enabled.includes("awards") && (
<div class="card bg-base-300 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 wrap-break-word">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.awards?.title ||
"Awards & Recognition"}
</h2>
<div class="space-y-4">
{data.awards.map((award) => (
<div class="border-l-2 border-warning pl-4 sm:pl-6">
<h3 class="text-lg sm:text-xl font-semibold">
{award.title}
</h3>
<div class="text-sm sm:text-base text-base-content/70 mb-2">
<span class="font-medium">
{award.organization}
</span>
<span class="block sm:inline sm:ml-4">
{award.date}
</span>
</div>
{award.description && (
<div class="text-sm sm:text-base text-base-content/80 mt-2">
{award.description}
</div>
)}
</div>
))}
</div>
</div>
</div>
)
}
</div>
</Layout>