This commit is contained in:
@ -1,223 +1,389 @@
|
||||
---
|
||||
import { Icon } from 'astro-icon/components';
|
||||
import Layout from '../layouts/Layout.astro';
|
||||
import SkillsSection from '../components/SkillsSection.tsx';
|
||||
import '../styles/global.css';
|
||||
import { Icon } from "astro-icon/components";
|
||||
import Layout from "../layouts/Layout.astro";
|
||||
import ResumeSkills from "../components/ResumeSkills";
|
||||
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 }[] };
|
||||
};
|
||||
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;
|
||||
|
||||
try {
|
||||
// Get the base URL for the current request
|
||||
const baseUrl = Astro.url.origin;
|
||||
|
||||
// Fetch the JSON file from the public directory
|
||||
const response = await fetch(`${baseUrl}/files/resume.json`);
|
||||
|
||||
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 skillsSection = resumeData.sections.skills;
|
||||
if (skillsSection.items) {
|
||||
const tsSkill = skillsSection.items.find(s => s.name === "Typescrpt");
|
||||
if (tsSkill) {
|
||||
tsSkill.name = "Typescript";
|
||||
}
|
||||
// Get the base URL for the current request
|
||||
const baseUrl = Astro.url.origin;
|
||||
|
||||
// Fetch the JSON file from the public directory
|
||||
const response = await fetch(`${baseUrl}/files/resume.json`);
|
||||
|
||||
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);
|
||||
fetchError = "Unable to load resume data. Please make sure the resume.json file exists in /public/files/";
|
||||
resumeData = undefined;
|
||||
console.error("Error loading resume data:", error);
|
||||
fetchError =
|
||||
"Unable to load resume data. Please make sure the resume.json file exists in /public/files/";
|
||||
resumeData = undefined;
|
||||
}
|
||||
|
||||
const data = resumeData;
|
||||
---
|
||||
|
||||
{(!data || fetchError) && (
|
||||
<Layout>
|
||||
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl text-center w-full">
|
||||
<h1 class="text-2xl font-bold text-red-600">Error loading resume data.</h1>
|
||||
<p>{fetchError || "Please try refreshing the page."}</p>
|
||||
</div>
|
||||
</Layout>
|
||||
)}
|
||||
|
||||
{data && !fetchError && (
|
||||
<Layout>
|
||||
<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>
|
||||
|
||||
<div class="text-center mb-6 sm:mb-8">
|
||||
<a
|
||||
href="/files/Atridad_Lahiji_Resume.pdf"
|
||||
download="Atridad_Lahiji_Resume.pdf"
|
||||
class="btn btn-primary inline-flex items-center gap-2 text-sm sm:text-base"
|
||||
>
|
||||
<Icon name="mdi:download" /> Download Resume (PDF)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{data.sections.summary && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">{data.sections.summary.name || "Summary"}</h2>
|
||||
<div set:html={data.sections.summary.content}></div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.profiles && data.sections.profiles.items && data.sections.profiles.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">{data.sections.profiles.name || "Profiles"}</h2>
|
||||
<div class="flex flex-wrap gap-3 sm:gap-4">
|
||||
{data.sections.profiles.items.map((profile) => {
|
||||
let iconName = "mdi:web";
|
||||
const networkLower = profile.network.toLowerCase();
|
||||
if (networkLower === "github") iconName = "simple-icons:github";
|
||||
else if (networkLower === "linkedin") iconName = "simple-icons:linkedin";
|
||||
else if (networkLower === "gitea") iconName = "simple-icons:gitea";
|
||||
|
||||
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} ({profile.username})
|
||||
</a>
|
||||
);
|
||||
})}
|
||||
{
|
||||
(!data || fetchError) && (
|
||||
<Layout>
|
||||
<div class="container mx-auto p-4 sm:p-6 lg:p-8 max-w-4xl text-center w-full">
|
||||
<h1 class="text-2xl font-bold text-red-600">
|
||||
Error loading resume data.
|
||||
</h1>
|
||||
<p>{fetchError || "Please try refreshing the page."}</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
||||
{data.sections.skills && data.sections.skills.items && data.sections.skills.items.length > 0 && (
|
||||
<SkillsSection
|
||||
title={data.sections.skills.name}
|
||||
skills={data.sections.skills.items}
|
||||
client:visible
|
||||
/>
|
||||
)}
|
||||
{
|
||||
data && !fetchError && (
|
||||
<Layout>
|
||||
<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>
|
||||
|
||||
{data.sections.experience && data.sections.experience.items && data.sections.experience.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">{data.sections.experience.name || "Experience"}</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.experience.items.map((exp, index) => (
|
||||
<details class="collapse collapse-arrow bg-base-100" open={index === 0 ? true : undefined}>
|
||||
<summary class="collapse-title text-lg sm:text-xl font-medium p-3 sm:p-4">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2">
|
||||
<span class="font-semibold">{exp.position} at {exp.company}</span>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 text-sm sm:text-base font-normal">
|
||||
<span>{exp.date}</span>
|
||||
{exp.location && (
|
||||
<span class="text-base-content/70">{exp.location}</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapse-content p-3 sm:p-4">
|
||||
{exp.url && exp.url.href && (
|
||||
<a href={exp.url.href} target="_blank" rel="noopener noreferrer" class="link link-primary block mb-2 text-sm sm:text-base break-all">{exp.url.href}</a>
|
||||
<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 class="mt-2">
|
||||
<ul class="list space-y-1">
|
||||
{exp.summary.replace(/<\/?ul>|<\/?p>/g, '')
|
||||
.split('<li>')
|
||||
.filter(item => item.trim() !== '')
|
||||
.map(item => (
|
||||
<li class="list-row text-sm sm:text-base">
|
||||
{item.replace('</li>', '')}
|
||||
</li>
|
||||
))
|
||||
}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.education && data.sections.education.items && data.sections.education.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">{data.sections.education.name || "Education"}</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.education.items.map((edu, index) => (
|
||||
<div>
|
||||
<h3 class="text-base sm:text-lg font-semibold">{edu.institution}</h3>
|
||||
<p class="text-sm sm:text-base">{edu.studyType} - {edu.area} ({edu.date})</p>
|
||||
{edu.summary && (
|
||||
<div class="ml-2 sm:ml-4 text-xs sm:text-sm mt-1" set:html={edu.summary}></div>
|
||||
)}
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.volunteer && data.sections.volunteer.items && data.sections.volunteer.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">{data.sections.volunteer.name || "Volunteering"}</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.volunteer.items.map((vol, index) => (
|
||||
<div>
|
||||
<h3 class="text-base sm:text-lg font-semibold">{vol.organization}</h3>
|
||||
<p class="text-sm sm:text-base">{vol.position} ({vol.date})</p>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
<div class="text-center mb-6 sm:mb-8">
|
||||
<a
|
||||
href="/files/Atridad_Lahiji_Resume.pdf"
|
||||
download="Atridad_Lahiji_Resume.pdf"
|
||||
class="btn btn-primary inline-flex items-center gap-2 text-sm sm:text-base"
|
||||
>
|
||||
<Icon name="mdi:download" /> Download Resume (PDF)
|
||||
</a>
|
||||
</div>
|
||||
|
||||
{data.sections.summary && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{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 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{data.sections.profiles.name || "Profiles"}
|
||||
</h2>
|
||||
<div class="flex flex-wrap gap-3 sm:gap-4">
|
||||
{data.sections.profiles.items.map(
|
||||
(profile) => {
|
||||
let iconName = "mdi:web";
|
||||
const networkLower =
|
||||
profile.network.toLowerCase();
|
||||
if (networkLower === "github")
|
||||
iconName =
|
||||
"simple-icons:github";
|
||||
else if (
|
||||
networkLower === "linkedin"
|
||||
)
|
||||
iconName =
|
||||
"simple-icons:linkedin";
|
||||
else if (networkLower === "gitea")
|
||||
iconName = "simple-icons:gitea";
|
||||
|
||||
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} (
|
||||
{profile.username})
|
||||
</a>
|
||||
);
|
||||
},
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.skills &&
|
||||
data.sections.skills.items &&
|
||||
data.sections.skills.items.length > 0 && (
|
||||
<ResumeSkills
|
||||
title={data.sections.skills.name}
|
||||
skills={data.sections.skills.items}
|
||||
client:visible
|
||||
/>
|
||||
)}
|
||||
|
||||
{data.sections.experience &&
|
||||
data.sections.experience.items &&
|
||||
data.sections.experience.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{data.sections.experience.name ||
|
||||
"Experience"}
|
||||
</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.experience.items.map(
|
||||
(exp, index) => (
|
||||
<details
|
||||
class="collapse collapse-arrow bg-base-100"
|
||||
open={
|
||||
index === 0
|
||||
? true
|
||||
: undefined
|
||||
}
|
||||
>
|
||||
<summary class="collapse-title text-lg sm:text-xl font-medium p-3 sm:p-4">
|
||||
<div class="flex flex-col sm:flex-row sm:items-center sm:justify-between gap-1 sm:gap-2">
|
||||
<span class="font-semibold">
|
||||
{exp.position} at{" "}
|
||||
{exp.company}
|
||||
</span>
|
||||
<div class="flex flex-col sm:flex-row sm:items-center gap-1 sm:gap-3 text-sm sm:text-base font-normal">
|
||||
<span>
|
||||
{exp.date}
|
||||
</span>
|
||||
{exp.location && (
|
||||
<span class="text-base-content/70">
|
||||
{
|
||||
exp.location
|
||||
}
|
||||
</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="collapse-content p-3 sm:p-4">
|
||||
{exp.url &&
|
||||
exp.url.href && (
|
||||
<a
|
||||
href={
|
||||
exp.url.href
|
||||
}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
class="link link-primary block mb-2 text-sm sm:text-base break-all"
|
||||
>
|
||||
{exp.url.href}
|
||||
</a>
|
||||
)}
|
||||
<div class="mt-2">
|
||||
<ul class="list space-y-1">
|
||||
{exp.summary
|
||||
.replace(
|
||||
/<\/?ul>|<\/?p>/g,
|
||||
"",
|
||||
)
|
||||
.split("<li>")
|
||||
.filter(
|
||||
(item) =>
|
||||
item.trim() !==
|
||||
"",
|
||||
)
|
||||
.map((item) => (
|
||||
<li class="list-row text-sm sm:text-base">
|
||||
{item.replace(
|
||||
"</li>",
|
||||
"",
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.education &&
|
||||
data.sections.education.items &&
|
||||
data.sections.education.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{data.sections.education.name ||
|
||||
"Education"}
|
||||
</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.education.items.map(
|
||||
(edu, index) => (
|
||||
<div>
|
||||
<h3 class="text-base sm:text-lg font-semibold">
|
||||
{edu.institution}
|
||||
</h3>
|
||||
<p class="text-sm sm:text-base">
|
||||
{edu.studyType} - {edu.area}{" "}
|
||||
({edu.date})
|
||||
</p>
|
||||
{edu.summary && (
|
||||
<div
|
||||
class="ml-2 sm:ml-4 text-xs sm:text-sm mt-1"
|
||||
set:html={edu.summary}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
|
||||
{data.sections.volunteer &&
|
||||
data.sections.volunteer.items &&
|
||||
data.sections.volunteer.items.length > 0 && (
|
||||
<div class="card bg-base-200 shadow-xl mb-4 sm:mb-6">
|
||||
<div class="card-body p-4 sm:p-6">
|
||||
<h2 class="card-title text-xl sm:text-2xl">
|
||||
{data.sections.volunteer.name ||
|
||||
"Volunteering"}
|
||||
</h2>
|
||||
<div class="space-y-3 sm:space-y-4">
|
||||
{data.sections.volunteer.items.map(
|
||||
(vol, index) => (
|
||||
<div>
|
||||
<h3 class="text-base sm:text-lg font-semibold">
|
||||
{vol.organization}
|
||||
</h3>
|
||||
<p class="text-sm sm:text-base">
|
||||
{vol.position} ({vol.date})
|
||||
</p>
|
||||
</div>
|
||||
),
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
)}
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
|
Reference in New Issue
Block a user