Update resume and make pdf template easier to modify
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m46s
All checks were successful
Docker Deploy / build-and-push (push) Successful in 3m46s
This commit is contained in:
Binary file not shown.
@ -167,3 +167,14 @@ level = 5
|
|||||||
organization = "Big Brother Big Sisters"
|
organization = "Big Brother Big Sisters"
|
||||||
position = "Mentor"
|
position = "Mentor"
|
||||||
date = "2021 – 2022"
|
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"
|
||||||
|
@ -67,8 +67,8 @@ export const resumeConfig: ResumeConfig = {
|
|||||||
displayText: "Download Resume (PDF)",
|
displayText: "Download Resume (PDF)",
|
||||||
},
|
},
|
||||||
layout: {
|
layout: {
|
||||||
leftColumn: ["experience", "awards"],
|
leftColumn: ["experience", "volunteer"],
|
||||||
rightColumn: ["skills", "education", "volunteer"],
|
rightColumn: ["skills", "education", "awards"],
|
||||||
},
|
},
|
||||||
sections: {
|
sections: {
|
||||||
enabled: [
|
enabled: [
|
||||||
|
@ -14,7 +14,6 @@ async function getSimpleIcon(iconName: string): Promise<string> {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
const svgContent = await response.text();
|
const svgContent = await response.text();
|
||||||
// Add inline styles for proper sizing and color
|
|
||||||
return svgContent.replace(
|
return svgContent.replace(
|
||||||
"<svg",
|
"<svg",
|
||||||
'<svg style="width: 12px; height: 12px; display: inline-block; vertical-align: middle; fill: currentColor;"',
|
'<svg style="width: 12px; height: 12px; display: inline-block; vertical-align: middle; fill: currentColor;"',
|
||||||
@ -84,61 +83,35 @@ interface ResumeData {
|
|||||||
}[];
|
}[];
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper function to generate sections for a column
|
// Template helper functions
|
||||||
function generateColumnSections(
|
const createSection = (title: string, content: string, spacing = "space-y-3") => `
|
||||||
sectionNames: string[] = [],
|
|
||||||
sectionData: { [key: string]: any },
|
|
||||||
): string {
|
|
||||||
return sectionNames
|
|
||||||
.map((sectionName) => {
|
|
||||||
const section = sectionData[sectionName];
|
|
||||||
if (
|
|
||||||
!section ||
|
|
||||||
!section.data ||
|
|
||||||
!section.data.length ||
|
|
||||||
!section.enabled
|
|
||||||
) {
|
|
||||||
return "";
|
|
||||||
}
|
|
||||||
|
|
||||||
return `
|
|
||||||
<section>
|
<section>
|
||||||
<h2 class="text-sm font-semibold text-gray-900 mb-2 pb-1 border-b border-gray-300">
|
<h2 class="text-sm font-semibold text-gray-900 mb-2 pb-1 border-b border-gray-300">
|
||||||
${section.title}
|
${title}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="${section.spacing}">
|
<div class="${spacing}">
|
||||||
${section.html}
|
${content}
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
`;
|
`;
|
||||||
})
|
|
||||||
.join("");
|
|
||||||
}
|
|
||||||
|
|
||||||
const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
const createExperienceItem = (exp: any) => `
|
||||||
const resumeConfig = siteConfig.resume;
|
<div class="mb-3 pl-2 border-l-2 border-blue-600">
|
||||||
|
<h3 class="text-xs font-semibold text-gray-900 mb-1">${exp.position}</h3>
|
||||||
|
<div class="text-xs text-gray-600 mb-1">
|
||||||
|
<span class="font-medium">${exp.company}</span>
|
||||||
|
<span class="mx-1">•</span>
|
||||||
|
<span>${exp.date}</span>
|
||||||
|
<span class="mx-1">•</span>
|
||||||
|
<span>${exp.location}</span>
|
||||||
|
</div>
|
||||||
|
<ul class="text-xs text-gray-700 leading-tight ml-3 list-disc">
|
||||||
|
${exp.description.map((item: string) => `<li class="mb-1">${item}</li>`).join("")}
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
`;
|
||||||
|
|
||||||
// Get layout configuration with defaults
|
const createSkillItem = (skill: any) => {
|
||||||
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
|
|
||||||
const emailIcon = getMdiIcon("mdi:email");
|
|
||||||
|
|
||||||
const skillsHTML =
|
|
||||||
data.skills
|
|
||||||
?.map((skill) => {
|
|
||||||
const progressValue = skill.level * 20;
|
const progressValue = skill.level * 20;
|
||||||
return `
|
return `
|
||||||
<div class="mb-1">
|
<div class="mb-1">
|
||||||
@ -151,41 +124,11 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
};
|
||||||
.join("") || "";
|
|
||||||
|
|
||||||
const experienceHTML =
|
const createEducationItem = (edu: any) => {
|
||||||
data.experience
|
|
||||||
?.map((exp) => {
|
|
||||||
const descriptionList = exp.description
|
|
||||||
.map((item) => `<li class="mb-1">${item}</li>`)
|
|
||||||
.join("");
|
|
||||||
|
|
||||||
return `
|
|
||||||
<div class="mb-3 pl-2 border-l-2 border-blue-600">
|
|
||||||
<h3 class="text-xs font-semibold text-gray-900 mb-1">${exp.position}</h3>
|
|
||||||
<div class="text-xs text-gray-600 mb-1">
|
|
||||||
<span class="font-medium">${exp.company}</span>
|
|
||||||
<span class="mx-1">•</span>
|
|
||||||
<span>${exp.date}</span>
|
|
||||||
<span class="mx-1">•</span>
|
|
||||||
<span>${exp.location}</span>
|
|
||||||
</div>
|
|
||||||
<ul class="text-xs text-gray-700 leading-tight ml-3 list-disc">
|
|
||||||
${descriptionList}
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
`;
|
|
||||||
})
|
|
||||||
.join("") || "";
|
|
||||||
|
|
||||||
const educationHTML =
|
|
||||||
data.education
|
|
||||||
?.map((edu) => {
|
|
||||||
const detailsList = edu.details
|
const detailsList = edu.details
|
||||||
? edu.details
|
? edu.details.map((detail: string) => `<li class="mb-1">${detail}</li>`).join("")
|
||||||
.map((detail) => `<li class="mb-1">${detail}</li>`)
|
|
||||||
.join("")
|
|
||||||
: "";
|
: "";
|
||||||
|
|
||||||
return `
|
return `
|
||||||
@ -199,13 +142,9 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
${detailsList ? `<ul class="text-xs text-gray-700 leading-tight ml-3 list-disc">${detailsList}</ul>` : ""}
|
${detailsList ? `<ul class="text-xs text-gray-700 leading-tight ml-3 list-disc">${detailsList}</ul>` : ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
};
|
||||||
.join("") || "";
|
|
||||||
|
|
||||||
const volunteerHTML =
|
const createVolunteerItem = (vol: any) => `
|
||||||
data.volunteer
|
|
||||||
?.map((vol) => {
|
|
||||||
return `
|
|
||||||
<div class="mb-2 pl-2 border-l-2 border-purple-600">
|
<div class="mb-2 pl-2 border-l-2 border-purple-600">
|
||||||
<h3 class="text-xs font-semibold text-gray-900 mb-1">${vol.organization}</h3>
|
<h3 class="text-xs font-semibold text-gray-900 mb-1">${vol.organization}</h3>
|
||||||
<div class="text-xs text-gray-600">
|
<div class="text-xs text-gray-600">
|
||||||
@ -215,13 +154,8 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
|
||||||
.join("") || "";
|
|
||||||
|
|
||||||
const awardsHTML =
|
const createAwardItem = (award: any) => `
|
||||||
data.awards
|
|
||||||
?.map((award) => {
|
|
||||||
return `
|
|
||||||
<div class="mb-2 pl-2 border-l-2 border-yellow-600">
|
<div class="mb-2 pl-2 border-l-2 border-yellow-600">
|
||||||
<h3 class="text-xs font-semibold text-gray-900 mb-1">${award.title}</h3>
|
<h3 class="text-xs font-semibold text-gray-900 mb-1">${award.title}</h3>
|
||||||
<div class="text-xs text-gray-600 mb-1">
|
<div class="text-xs text-gray-600 mb-1">
|
||||||
@ -232,16 +166,12 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
${award.description ? `<div class="text-xs text-gray-700 leading-tight">${award.description}</div>` : ""}
|
${award.description ? `<div class="text-xs text-gray-700 leading-tight">${award.description}</div>` : ""}
|
||||||
</div>
|
</div>
|
||||||
`;
|
`;
|
||||||
})
|
|
||||||
.join("") || "";
|
|
||||||
|
|
||||||
return `
|
const createHead = (name: string) => `
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en">
|
|
||||||
<head>
|
<head>
|
||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>${data.basics.name} - Resume</title>
|
<title>${name} - Resume</title>
|
||||||
<script src="https://cdn.tailwindcss.com"></script>
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
<style>
|
<style>
|
||||||
@media print {
|
@media print {
|
||||||
@ -250,21 +180,21 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
-webkit-print-color-adjust: exact;
|
-webkit-print-color-adjust: exact;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.resume-container {
|
.resume-container {
|
||||||
max-width: 8.5in;
|
max-width: 8.5in;
|
||||||
min-height: 11in;
|
min-height: 11in;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body class="bg-white text-gray-900 text-xs leading-tight p-3">
|
`;
|
||||||
<div class="resume-container mx-auto">
|
|
||||||
|
const createHeader = (basics: any, emailIcon: string, profileIcons: { [key: string]: string }) => `
|
||||||
<header class="text-center mb-3 pb-2 border-b-2 border-gray-300">
|
<header class="text-center mb-3 pb-2 border-b-2 border-gray-300">
|
||||||
<h1 class="text-3xl font-bold text-gray-900 mb-1">${data.basics.name}</h1>
|
<h1 class="text-3xl font-bold text-gray-900 mb-1">${basics.name}</h1>
|
||||||
<div class="flex justify-center items-center flex-wrap gap-4 text-xs text-gray-600">
|
<div class="flex justify-center items-center flex-wrap gap-4 text-xs text-gray-600">
|
||||||
${data.basics.email ? `<div class="flex items-center gap-1">${emailIcon} ${data.basics.email}</div>` : ""}
|
${basics.email ? `<div class="flex items-center gap-1">${emailIcon} ${basics.email}</div>` : ""}
|
||||||
${data.basics.profiles
|
${basics.profiles
|
||||||
?.map((profile) => {
|
?.map((profile: any) => {
|
||||||
const icon = profileIcons[profile.network] || "";
|
const icon = profileIcons[profile.network] || "";
|
||||||
const displayUrl = profile.url
|
const displayUrl = profile.url
|
||||||
.replace(/^https?:\/\//, "")
|
.replace(/^https?:\/\//, "")
|
||||||
@ -275,110 +205,111 @@ const generateResumeHTML = async (data: ResumeData): Promise<string> => {
|
|||||||
}
|
}
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
`;
|
||||||
|
|
||||||
${data.summary && resumeConfig.sections.summary?.enabled
|
const createSummarySection = (summary: any, resumeConfig: any) => {
|
||||||
? `
|
if (!summary || !resumeConfig.sections.summary?.enabled) return "";
|
||||||
|
|
||||||
|
return `
|
||||||
<section class="mb-3">
|
<section class="mb-3">
|
||||||
<h2 class="text-sm font-semibold text-gray-900 mb-2 pb-1 border-b border-gray-300">
|
<h2 class="text-sm font-semibold text-gray-900 mb-2 pb-1 border-b border-gray-300">
|
||||||
${resumeConfig.sections.summary.title || "Summary"}
|
${resumeConfig.sections.summary.title || "Summary"}
|
||||||
</h2>
|
</h2>
|
||||||
<div class="text-xs text-gray-700 leading-tight">${data.summary.content}</div>
|
<div class="text-xs text-gray-700 leading-tight">${summary.content}</div>
|
||||||
</section>
|
</section>
|
||||||
`
|
`;
|
||||||
: ""
|
};
|
||||||
}
|
|
||||||
|
|
||||||
|
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 config = sectionConfig[sectionName as keyof typeof sectionConfig];
|
||||||
|
const content = sections[sectionName];
|
||||||
|
|
||||||
|
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<string> => {
|
||||||
|
const resumeConfig = siteConfig.resume;
|
||||||
|
const layout = resumeConfig.layout || {
|
||||||
|
leftColumn: ["experience", "volunteer", "awards"],
|
||||||
|
rightColumn: ["skills", "education"],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Pre-fetch icons
|
||||||
|
const profileIcons = await fetchProfileIcons(data.basics.profiles);
|
||||||
|
const emailIcon = getMdiIcon("mdi:email");
|
||||||
|
|
||||||
|
// 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 `
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
${createHead(data.basics.name)}
|
||||||
|
<body class="bg-white text-gray-900 text-xs leading-tight p-3">
|
||||||
|
<div class="resume-container mx-auto">
|
||||||
|
${createHeader(data.basics, emailIcon, profileIcons)}
|
||||||
|
${createSummarySection(data.summary, resumeConfig)}
|
||||||
<div class="grid grid-cols-2 gap-4">
|
<div class="grid grid-cols-2 gap-4">
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
${generateColumnSections(layout.leftColumn, {
|
${createColumnSections(layout.leftColumn ?? [], sections, resumeConfig)}
|
||||||
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",
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="space-y-4">
|
<div class="space-y-4">
|
||||||
${generateColumnSections(layout.rightColumn, {
|
${createColumnSections(layout.rightColumn ?? [], sections, resumeConfig)}
|
||||||
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",
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -12,16 +12,16 @@ interface ResumeData {
|
|||||||
name: string;
|
name: string;
|
||||||
email: string;
|
email: string;
|
||||||
website?: string;
|
website?: string;
|
||||||
profiles: {
|
profiles?: {
|
||||||
network: string;
|
network: string;
|
||||||
username: string;
|
username: string;
|
||||||
url: string;
|
url: string;
|
||||||
}[];
|
}[];
|
||||||
};
|
};
|
||||||
summary: {
|
summary?: {
|
||||||
content: string;
|
content: string;
|
||||||
};
|
};
|
||||||
experience: {
|
experience?: {
|
||||||
company: string;
|
company: string;
|
||||||
position: string;
|
position: string;
|
||||||
location: string;
|
location: string;
|
||||||
@ -29,23 +29,23 @@ interface ResumeData {
|
|||||||
description: string[];
|
description: string[];
|
||||||
url?: string;
|
url?: string;
|
||||||
}[];
|
}[];
|
||||||
education: {
|
education?: {
|
||||||
institution: string;
|
institution: string;
|
||||||
degree: string;
|
degree: string;
|
||||||
field: string;
|
field: string;
|
||||||
date: string;
|
date: string;
|
||||||
details?: string[];
|
details?: string[];
|
||||||
}[];
|
}[];
|
||||||
skills: {
|
skills?: {
|
||||||
name: string;
|
name: string;
|
||||||
level: number;
|
level: number;
|
||||||
}[];
|
}[];
|
||||||
volunteer: {
|
volunteer?: {
|
||||||
organization: string;
|
organization: string;
|
||||||
position: string;
|
position: string;
|
||||||
date: string;
|
date: string;
|
||||||
}[];
|
}[];
|
||||||
awards: {
|
awards?: {
|
||||||
title: string;
|
title: string;
|
||||||
organization: string;
|
organization: string;
|
||||||
date: 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()}`;
|
const iconName = `simple-icons:${profile.network.toLowerCase()}`;
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
|
Reference in New Issue
Block a user