diff --git a/public/files/Atridad_Lahiji_Resume.pdf b/public/files/Atridad_Lahiji_Resume.pdf deleted file mode 100644 index 5e5191c..0000000 Binary files a/public/files/Atridad_Lahiji_Resume.pdf and /dev/null differ diff --git a/public/files/resume.toml b/public/files/resume.toml index 0262337..ea7a575 100644 --- a/public/files/resume.toml +++ b/public/files/resume.toml @@ -167,3 +167,14 @@ level = 5 organization = "Big Brother Big Sisters" position = "Mentor" date = "2021 – 2022" + +[[awards]] +title = "IT Innovation Award - Team" +organization = "University of Alberta IST" +date = "2020" +description = "The IT Innovation Award recognizes one team for their innovative use of hardware and/or software technology to successfully deploy a major IT project with significant impact to research, teaching, administration and/or the University experience." + +[[awards]] +title = "IT Client Service Award - Team" +organization = "University of Alberta IST" +date = "2021" diff --git a/src/config/data.ts b/src/config/data.ts index a6e630b..8565e7e 100644 --- a/src/config/data.ts +++ b/src/config/data.ts @@ -67,8 +67,8 @@ export const resumeConfig: ResumeConfig = { displayText: "Download Resume (PDF)", }, layout: { - leftColumn: ["experience", "awards"], - rightColumn: ["skills", "education", "volunteer"], + leftColumn: ["experience", "volunteer"], + rightColumn: ["skills", "education", "awards"], }, sections: { enabled: [ diff --git a/src/pages/api/resume/pdf.ts b/src/pages/api/resume/pdf.ts index f0991d8..56b7b56 100644 --- a/src/pages/api/resume/pdf.ts +++ b/src/pages/api/resume/pdf.ts @@ -14,7 +14,6 @@ async function getSimpleIcon(iconName: string): Promise { return ""; } const svgContent = await response.text(); - // Add inline styles for proper sizing and color return svgContent.replace( " ` +
+

+ ${title} +

+
+ ${content} +
+
+`; + +const createExperienceItem = (exp: any) => ` +
+

${exp.position}

+
+ ${exp.company} + + ${exp.date} + + ${exp.location} +
+
    + ${exp.description.map((item: string) => `
  • ${item}
  • `).join("")} +
+
+`; + +const createSkillItem = (skill: any) => { + const progressValue = skill.level * 20; + return ` +
+
+ ${skill.name} + ${skill.level}/5 +
+
+
+
+
+ `; +}; + +const createEducationItem = (edu: any) => { + const detailsList = edu.details + ? edu.details.map((detail: string) => `
  • ${detail}
  • `).join("") + : ""; + + return ` +
    +

    ${edu.institution}

    +
    + ${edu.degree} in ${edu.field} + + ${edu.date} +
    + ${detailsList ? `
      ${detailsList}
    ` : ""} +
    + `; +}; + +const createVolunteerItem = (vol: any) => ` +
    +

    ${vol.organization}

    +
    + ${vol.position} + + ${vol.date} +
    +
    +`; + +const createAwardItem = (award: any) => ` +
    +

    ${award.title}

    +
    + ${award.organization} + + ${award.date} +
    + ${award.description ? `
    ${award.description}
    ` : ""} +
    +`; + +const createHead = (name: string) => ` + + + + ${name} - Resume + + + +`; + +const createHeader = (basics: any, emailIcon: string, profileIcons: { [key: string]: string }) => ` +
    +

    ${basics.name}

    +
    + ${basics.email ? `
    ${emailIcon} ${basics.email}
    ` : ""} + ${basics.profiles + ?.map((profile: any) => { + const icon = profileIcons[profile.network] || ""; + const displayUrl = profile.url + .replace(/^https?:\/\//, "") + .replace(/\/$/, ""); + return `
    ${icon} ${displayUrl}
    `; + }) + .join("") || "" + } +
    +
    +`; + +const createSummarySection = (summary: any, resumeConfig: any) => { + if (!summary || !resumeConfig.sections.summary?.enabled) return ""; + + return ` +
    +

    + ${resumeConfig.sections.summary.title || "Summary"} +

    +
    ${summary.content}
    +
    + `; +}; + +const createColumnSections = ( + sectionNames: string[], + sections: { [key: string]: string }, + resumeConfig: any +) => { + const sectionConfig = { + experience: { + title: resumeConfig.sections.experience?.title || "Experience", + enabled: resumeConfig.sections.experience?.enabled, + spacing: "space-y-3", + }, + skills: { + title: resumeConfig.sections.skills?.title || "Skills", + enabled: resumeConfig.sections.skills?.enabled, + spacing: "space-y-1", + }, + education: { + title: resumeConfig.sections.education?.title || "Education", + enabled: resumeConfig.sections.education?.enabled, + spacing: "space-y-3", + }, + volunteer: { + title: resumeConfig.sections.volunteer?.title || "Volunteer Work", + enabled: resumeConfig.sections.volunteer?.enabled, + spacing: "space-y-2", + }, + awards: { + title: resumeConfig.sections.awards?.title || "Awards & Recognition", + enabled: resumeConfig.sections.awards?.enabled, + spacing: "space-y-2", + }, + }; + return sectionNames .map((sectionName) => { - const section = sectionData[sectionName]; - if ( - !section || - !section.data || - !section.data.length || - !section.enabled - ) { - return ""; - } + const config = sectionConfig[sectionName as keyof typeof sectionConfig]; + const content = sections[sectionName]; - return ` -
    -

    - ${section.title} -

    -
    - ${section.html} -
    -
    - `; + if (!config || !content || !config.enabled) return ""; + + return createSection(config.title, content, config.spacing); }) .join(""); -} +}; + +const fetchProfileIcons = async (profiles: any[]) => { + const profileIcons: { [key: string]: string } = {}; + if (profiles) { + for (const profile of profiles) { + const iconName = profile.network.toLowerCase(); + profileIcons[profile.network] = await getSimpleIcon(iconName); + } + } + return profileIcons; +}; const generateResumeHTML = async (data: ResumeData): Promise => { const resumeConfig = siteConfig.resume; - - // Get layout configuration with defaults const layout = resumeConfig.layout || { leftColumn: ["experience", "volunteer", "awards"], rightColumn: ["skills", "education"], }; - // Pre-fetch icons for profiles - const profileIcons: { [key: string]: string } = {}; - if (data.basics.profiles) { - for (const profile of data.basics.profiles) { - const iconName = profile.network.toLowerCase(); - profileIcons[profile.network] = await getSimpleIcon(iconName); - } - } - - // Get email icon + // Pre-fetch icons + const profileIcons = await fetchProfileIcons(data.basics.profiles); const emailIcon = getMdiIcon("mdi:email"); - const skillsHTML = - data.skills - ?.map((skill) => { - const progressValue = skill.level * 20; - return ` -
    -
    - ${skill.name} - ${skill.level}/5 -
    -
    -
    -
    -
    - `; - }) - .join("") || ""; - - const experienceHTML = - data.experience - ?.map((exp) => { - const descriptionList = exp.description - .map((item) => `
  • ${item}
  • `) - .join(""); - - return ` -
    -

    ${exp.position}

    -
    - ${exp.company} - - ${exp.date} - - ${exp.location} -
    -
      - ${descriptionList} -
    -
    - `; - }) - .join("") || ""; - - const educationHTML = - data.education - ?.map((edu) => { - const detailsList = edu.details - ? edu.details - .map((detail) => `
  • ${detail}
  • `) - .join("") - : ""; - - return ` -
    -

    ${edu.institution}

    -
    - ${edu.degree} in ${edu.field} - - ${edu.date} -
    - ${detailsList ? `
      ${detailsList}
    ` : ""} -
    - `; - }) - .join("") || ""; - - const volunteerHTML = - data.volunteer - ?.map((vol) => { - return ` -
    -

    ${vol.organization}

    -
    - ${vol.position} - - ${vol.date} -
    -
    - `; - }) - .join("") || ""; - - const awardsHTML = - data.awards - ?.map((award) => { - return ` -
    -

    ${award.title}

    -
    - ${award.organization} - - ${award.date} -
    - ${award.description ? `
    ${award.description}
    ` : ""} -
    - `; - }) - .join("") || ""; + // Generate section content + const sections = { + experience: Array.isArray(data.experience) ? data.experience.map(createExperienceItem).join("") : "", + skills: Array.isArray(data.skills) ? data.skills.map(createSkillItem).join("") : "", + education: Array.isArray(data.education) ? data.education.map(createEducationItem).join("") : "", + volunteer: Array.isArray(data.volunteer) ? data.volunteer.map(createVolunteerItem).join("") : "", + awards: Array.isArray(data.awards) ? data.awards.map(createAwardItem).join("") : "", + }; return ` - - - - - - ${data.basics.name} - Resume - - - - -
    -
    -

    ${data.basics.name}

    -
    - ${data.basics.email ? `
    ${emailIcon} ${data.basics.email}
    ` : ""} - ${data.basics.profiles - ?.map((profile) => { - const icon = profileIcons[profile.network] || ""; - const displayUrl = profile.url - .replace(/^https?:\/\//, "") - .replace(/\/$/, ""); - return `
    ${icon} ${displayUrl}
    `; - }) - .join("") || "" - } -
    -
    - - ${data.summary && resumeConfig.sections.summary?.enabled - ? ` -
    -

    - ${resumeConfig.sections.summary.title || "Summary"} -

    -
    ${data.summary.content}
    -
    - ` - : "" - } - + + + ${createHead(data.basics.name)} + +
    + ${createHeader(data.basics, emailIcon, profileIcons)} + ${createSummarySection(data.summary, resumeConfig)}
    -
    - ${generateColumnSections(layout.leftColumn, { - experience: { - data: data.experience, - html: experienceHTML, - title: - resumeConfig.sections.experience?.title || "Experience", - enabled: resumeConfig.sections.experience?.enabled, - spacing: "space-y-3", - }, - volunteer: { - data: data.volunteer, - html: volunteerHTML, - title: - resumeConfig.sections.volunteer?.title || - "Volunteer Work", - enabled: resumeConfig.sections.volunteer?.enabled, - spacing: "space-y-2", - }, - awards: { - data: data.awards, - html: awardsHTML, - title: - resumeConfig.sections.awards?.title || - "Awards & Recognition", - enabled: resumeConfig.sections.awards?.enabled, - spacing: "space-y-2", - }, - skills: { - data: data.skills, - html: skillsHTML, - title: resumeConfig.sections.skills?.title || "Skills", - enabled: resumeConfig.sections.skills?.enabled, - spacing: "space-y-1", - }, - education: { - data: data.education, - html: educationHTML, - title: - resumeConfig.sections.education?.title || "Education", - enabled: resumeConfig.sections.education?.enabled, - spacing: "space-y-3", - }, - })} -
    - -
    - ${generateColumnSections(layout.rightColumn, { - experience: { - data: data.experience, - html: experienceHTML, - title: - resumeConfig.sections.experience?.title || "Experience", - enabled: resumeConfig.sections.experience?.enabled, - spacing: "space-y-3", - }, - volunteer: { - data: data.volunteer, - html: volunteerHTML, - title: - resumeConfig.sections.volunteer?.title || - "Volunteer Work", - enabled: resumeConfig.sections.volunteer?.enabled, - spacing: "space-y-2", - }, - awards: { - data: data.awards, - html: awardsHTML, - title: - resumeConfig.sections.awards?.title || - "Awards & Recognition", - enabled: resumeConfig.sections.awards?.enabled, - spacing: "space-y-2", - }, - skills: { - data: data.skills, - html: skillsHTML, - title: resumeConfig.sections.skills?.title || "Skills", - enabled: resumeConfig.sections.skills?.enabled, - spacing: "space-y-1", - }, - education: { - data: data.education, - html: educationHTML, - title: - resumeConfig.sections.education?.title || "Education", - enabled: resumeConfig.sections.education?.enabled, - spacing: "space-y-3", - }, - })} -
    +
    + ${createColumnSections(layout.leftColumn ?? [], sections, resumeConfig)} +
    +
    + ${createColumnSections(layout.rightColumn ?? [], sections, resumeConfig)} +
    -
    - - - `; +
    + + + `; }; export const GET: APIRoute = async ({ request }) => { diff --git a/src/pages/resume.astro b/src/pages/resume.astro index ea66d6c..87704ae 100644 --- a/src/pages/resume.astro +++ b/src/pages/resume.astro @@ -12,16 +12,16 @@ interface ResumeData { name: string; email: string; website?: string; - profiles: { + profiles?: { network: string; username: string; url: string; }[]; }; - summary: { + summary?: { content: string; }; - experience: { + experience?: { company: string; position: string; location: string; @@ -29,23 +29,23 @@ interface ResumeData { description: string[]; url?: string; }[]; - education: { + education?: { institution: string; degree: string; field: string; date: string; details?: string[]; }[]; - skills: { + skills?: { name: string; level: number; }[]; - volunteer: { + volunteer?: { organization: string; position: string; date: string; }[]; - awards: { + awards?: { title: string; organization: string; date: string; @@ -111,7 +111,7 @@ if (!data) { ) } { - data.basics.profiles.map((profile) => { + data.basics.profiles?.map((profile) => { const iconName = `simple-icons:${profile.network.toLowerCase()}`; return (