This commit is contained in:
@ -12,6 +12,7 @@ import { personalInfo, homepageSections } from "../config/data";
|
||||
alt={personalInfo.profileImage.alt}
|
||||
height={personalInfo.profileImage.height}
|
||||
width={personalInfo.profileImage.width}
|
||||
loading="eager"
|
||||
/>
|
||||
|
||||
<h1
|
||||
|
@ -63,11 +63,16 @@ interface ResumeData {
|
||||
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 for the current request
|
||||
// Get the base URL
|
||||
const baseUrl = Astro.url.origin;
|
||||
|
||||
// Fetch the JSON file from the public directory using config
|
||||
// Fetch the JSON file from the public directory
|
||||
const response = await fetch(`${baseUrl}${siteConfig.resume.jsonFile}`);
|
||||
|
||||
if (!response.ok) {
|
||||
@ -91,287 +96,276 @@ try {
|
||||
}
|
||||
} 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;
|
||||
// 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('/');
|
||||
}
|
||||
---
|
||||
|
||||
{
|
||||
(!data || fetchError) && (
|
||||
<Layout title="Resume">
|
||||
<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>
|
||||
<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>
|
||||
</Layout>
|
||||
)
|
||||
}
|
||||
)}
|
||||
|
||||
{
|
||||
data && !fetchError && (
|
||||
<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>
|
||||
)}
|
||||
{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>
|
||||
)}
|
||||
|
||||
<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.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) => {
|
||||
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 === "twitter"
|
||||
) {
|
||||
iconName = "simple-icons:x";
|
||||
} else if (
|
||||
networkLower === "youtube"
|
||||
) {
|
||||
iconName =
|
||||
"simple-icons:youtube";
|
||||
}
|
||||
|
||||
{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">
|
||||
<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} />
|
||||
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.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">
|
||||
<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) => {
|
||||
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 === "twitter"
|
||||
) {
|
||||
iconName = "simple-icons:x";
|
||||
} else if (
|
||||
networkLower === "youtube"
|
||||
) {
|
||||
iconName =
|
||||
"simple-icons:youtube";
|
||||
}
|
||||
{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>
|
||||
)}
|
||||
|
||||
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>
|
||||
{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.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">
|
||||
<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>
|
||||
{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.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">
|
||||
<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>
|
||||
{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>
|
||||
)}
|
||||
|
||||
{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">
|
||||
<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">
|
||||
<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>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</Layout>
|
||||
|
Reference in New Issue
Block a user