Files
atridotdad/src/pages/resume.astro
Atridad Lahiji 9577cd8bc6
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m22s
Icon handling was gross before. Fixing
2025-06-12 16:24:55 -06:00

355 lines
15 KiB
Plaintext

---
import { Icon } from "astro-icon/components";
import Layout from "../layouts/Layout.astro";
import ResumeSkills from "../components/ResumeSkills";
import { siteConfig } from "../config/data";
import "../styles/global.css";
interface ResumeData {
basics: {
name: string;
email: string;
url?: { href: string };
};
sections: {
summary: { name: string; content: string };
profiles: {
name: string;
items: {
network: string;
username: string;
url: { href: string };
}[];
};
skills: {
name: string;
items: { id: string; name: string; level: number }[];
};
experience: {
name: string;
items: {
id: string;
company: string;
position: string;
date: string;
location: string;
summary: string;
url?: { href: string };
}[];
};
education: {
name: string;
items: {
id: string;
institution: string;
studyType: string;
area: string;
date: string;
summary: string;
}[];
};
volunteer: {
name: string;
items: {
id: string;
organization: string;
position: string;
date: string;
}[];
};
};
}
let resumeData: ResumeData | undefined = undefined;
let fetchError: string | null = null;
// Check if resume JSON file is configured before attempting to fetch
if (!siteConfig.resume.jsonFile || !siteConfig.resume.jsonFile.trim()) {
return Astro.redirect('/');
}
try {
// Get the base URL
const baseUrl = Astro.url.origin;
// Fetch the JSON file from the public directory
const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`);
if (!response.ok) {
throw new Error(
`Failed to fetch resume: ${response.status} ${response.statusText}`,
);
}
resumeData = await response.json();
if (resumeData && resumeData.sections && resumeData.sections.skills) {
const resumeSkills = resumeData.sections.skills;
if (resumeSkills.items) {
const tsSkill = resumeSkills.items.find(
(s) => s.name === "Typescrpt",
);
if (tsSkill) {
tsSkill.name = "Typescript";
}
}
}
} catch (error) {
console.error("Error loading resume data:", error);
// Return to home page when resume data cannot be loaded
return Astro.redirect('/');
}
const data = resumeData;
const resumeConfig = siteConfig.resume;
// At this point, data is guaranteed to exist since we redirect on error
if (!data) {
return Astro.redirect('/');
}
---
<Layout title="Resume">
<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 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.sections.profiles.items.find(
(p) => p.network === "GitHub",
) && (
<a
href={
data.sections.profiles.items.find(
(p) => p.network === "GitHub",
)!.url.href
}
target="_blank"
rel="noopener noreferrer"
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
>
<Icon name="simple-icons:github" /> GitHub
</a>
)}
{data.sections.profiles.items.find(
(p) => p.network === "linkedin",
) && (
<a
href={
data.sections.profiles.items.find(
(p) => p.network === "linkedin",
)!.url.href
}
target="_blank"
rel="noopener noreferrer"
class="link link-hover inline-flex items-center gap-1 text-sm sm:text-base"
>
<Icon name="simple-icons:linkedin" /> LinkedIn
</a>
)}
</div>
{resumeConfig.pdfFile?.path && (
<div class="text-center mb-6 sm:mb-8">
<a
href={resumeConfig.pdfFile.path}
download={resumeConfig.pdfFile.filename}
class="btn btn-primary inline-flex items-center gap-2 text-sm sm:text-base"
>
<Icon name="mdi:download" /> {resumeConfig.pdfFile.displayText}
</a>
</div>
)}
{data.sections.summary && resumeConfig.sections.summary?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.summary.title || data.sections.summary.name || "Summary"}
</h2>
<div set:html={data.sections.summary.content} />
</div>
</div>
)}
{data.sections.profiles &&
data.sections.profiles.items &&
data.sections.profiles.items.length > 0 &&
resumeConfig.sections.profiles?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.profiles.title || data.sections.profiles.name || "Profiles"}
</h2>
<div class="flex flex-wrap gap-3 sm:gap-4">
{data.sections.profiles.items.map(
(profile) => {
// Use Simple Icons directly based on network name
// Convert network name to lowercase and use simple-icons format
const iconName = `simple-icons:${profile.network.toLowerCase()}`;
return (
<a
href={profile.url.href}
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>
</div>
</div>
)}
{data.sections.skills &&
data.sections.skills.items &&
data.sections.skills.items.length > 0 &&
resumeConfig.sections.skills?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.skills.title || data.sections.skills.name || "Skills"}
</h2>
<ResumeSkills
skills={data.sections.skills.items}
client:load
/>
</div>
</div>
)}
{data.sections.experience &&
data.sections.experience.items &&
data.sections.experience.items.length > 0 &&
resumeConfig.sections.experience?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.experience.title || data.sections.experience.name || "Experience"}
</h2>
<div class="space-y-4 sm:space-y-6">
{data.sections.experience.items.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>
<div
class="prose prose-sm sm:prose-base max-w-none"
set:html={experience.summary}
/>
{experience.url && experience.url.href && (
<a
href={experience.url.href}
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" />
Company Website
</a>
)}
</div>
),
)}
</div>
</div>
</div>
)}
{data.sections.education &&
data.sections.education.items &&
data.sections.education.items.length > 0 &&
resumeConfig.sections.education?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.education.title || data.sections.education.name || "Education"}
</h2>
<div class="space-y-4">
{data.sections.education.items.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.studyType} in{" "}
{education.area}
</span>
<span class="block sm:inline sm:ml-4">
{education.date}
</span>
</div>
{education.summary && (
<div
class="prose prose-sm sm:prose-base max-w-none"
set:html={education.summary}
/>
)}
</div>
),
)}
</div>
</div>
</div>
)}
{data.sections.volunteer &&
data.sections.volunteer.items &&
data.sections.volunteer.items.length > 0 &&
resumeConfig.sections.volunteer?.enabled && (
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
<div class="card-body p-4 sm:p-6 break-words">
<h2 class="card-title text-xl sm:text-2xl">
{resumeConfig.sections.volunteer.title || data.sections.volunteer.name || "Volunteer Work"}
</h2>
<div class="space-y-4">
{data.sections.volunteer.items.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>
)}
</div>
</Layout>