formatting

This commit is contained in:
Ivan Dimitrov 2023-11-13 20:45:59 +02:00
parent 8b7770a7aa
commit 8c7abe5818

331
cv.tsx
View File

@ -1,44 +1,48 @@
import { Page, Text, View, Document, Link, Svg, Path } from "@react-pdf/renderer"; import {
Page,
Text,
View,
Document,
Link,
Svg,
Path,
} from "@react-pdf/renderer";
import ReactPDF from "@react-pdf/renderer"; import ReactPDF from "@react-pdf/renderer";
import fs from "fs"; import fs from "fs";
import { createTw } from "react-pdf-tailwind"; import { createTw } from "react-pdf-tailwind";
const tw = createTw({ const tw = createTw({});
});
type A = { type A = {
text: string text: string;
href: string href: string;
icon?: string[] icon?: string[];
} };
type Experience = { type Experience = {
company: string company: string;
position: string position: string;
location: string location: string;
from: Date from: Date;
to: Date to: Date;
description: string description: string;
technologies: string[] technologies: string[];
links?: A[] links?: A[];
feedback?: string feedback?: string;
} };
type Education = { type Education = {
institution: string institution: string;
location: string location: string;
degree: string degree: string;
field: string field: string;
from: Date from: Date;
to: Date to: Date;
summary: string summary: string;
links?: A[] links?: A[];
} };
const tech = { const tech = {
android: [ android: ["Android", "Android Studio"],
"Android",
"Android Studio",
],
java: [ java: [
"Java", "Java",
"JPA", "JPA",
@ -59,19 +63,9 @@ const tech = {
"Gradle", "Gradle",
"Ant", "Ant",
], ],
web: [ web: ["JavaScript", "HTML", "CSS"],
"JavaScript", api: ["REST", "SOAP"],
"HTML", db: ["MySQL", "PostgreSQL"],
"CSS"
],
api: [
"REST",
"SOAP",
],
db: [
"MySQL",
"PostgreSQL"
],
linux: [ linux: [
"Linux", "Linux",
"Bash", "Bash",
@ -90,22 +84,9 @@ const tech = {
"hexdump", "hexdump",
"dd", "dd",
], ],
git: [ git: ["git", "GitHub", "GitLab", "BitBucket"],
"git", hybris: ["SAP hybris", "ZK Framework"],
"GitHub", payment: ["PayPal", "Adyen", "V12", "Wells Fargo Open Banking APIs"],
"GitLab",
"BitBucket",
],
hybris: [
"SAP hybris",
"ZK Framework",
],
payment: [
"PayPal",
"Adyen",
"V12",
"Wells Fargo Open Banking APIs",
],
dataIntegration: [ dataIntegration: [
"Spring Batch", "Spring Batch",
"Data Pipeline", "Data Pipeline",
@ -128,10 +109,7 @@ const tech = {
"html2text", "html2text",
"Poetry", "Poetry",
], ],
javascript: [ javascript: ["TypeScript", "React"],
"TypeScript",
"React",
],
nextjs: [ nextjs: [
"NextJS 12", "NextJS 12",
"NextJS 13", "NextJS 13",
@ -152,40 +130,47 @@ const tech = {
"FontAwesome", "FontAwesome",
"nProgress", "nProgress",
], ],
general: [ general: ["Markdown", "Google", "DuckDuckGo", "PDF", "Email"],
"Markdown", } as const;
"Google",
"DuckDuckGo",
"PDF",
"Email",
],
} as const
const techKeys = Object.keys(tech) as Array<keyof typeof tech> const techKeys = Object.keys(tech) as Array<keyof typeof tech>;
type TechKeys = keyof typeof tech type TechKeys = keyof typeof tech;
const skills = (skills: TechKeys[]): string[] => { const skills = (skills: TechKeys[]): string[] => {
return skills.map(s => tech[s]).flat() return skills.map((s) => tech[s]).flat();
} };
const skillsInverted = (skills: TechKeys[]): string[] => { const skillsInverted = (skills: TechKeys[]): string[] => {
return techKeys.filter(k => !skills.includes(k)).map(s => tech[s]).flat() return techKeys
} .filter((k) => !skills.includes(k))
.map((s) => tech[s])
.flat();
};
const createSvg = (paths: string[]) => const createSvg = (paths: string[]) => (
<Svg style={tw("w-4 h-4")} viewBox="0 0 19 19"> <Svg style={tw("w-4 h-4")} viewBox="0 0 19 19">
{paths.map(p => ( {paths.map((p) => (
<Path key={p} fill="#99f6e4" d={p} /> <Path key={p} fill="#99f6e4" d={p} />
))} ))}
</Svg> </Svg>
);
const svg = { const svg = {
github: createSvg(["M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z"]), github: createSvg([
link: createSvg(["M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7"]), "M10 .333A9.911 9.911 0 0 0 6.866 19.65c.5.092.678-.215.678-.477 0-.237-.01-1.017-.014-1.845-2.757.6-3.338-1.169-3.338-1.169a2.627 2.627 0 0 0-1.1-1.451c-.9-.615.07-.6.07-.6a2.084 2.084 0 0 1 1.518 1.021 2.11 2.11 0 0 0 2.884.823c.044-.503.268-.973.63-1.325-2.2-.25-4.516-1.1-4.516-4.9A3.832 3.832 0 0 1 4.7 7.068a3.56 3.56 0 0 1 .095-2.623s.832-.266 2.726 1.016a9.409 9.409 0 0 1 4.962 0c1.89-1.282 2.717-1.016 2.717-1.016.366.83.402 1.768.1 2.623a3.827 3.827 0 0 1 1.02 2.659c0 3.807-2.319 4.644-4.525 4.889a2.366 2.366 0 0 1 .673 1.834c0 1.326-.012 2.394-.012 2.72 0 .263.18.572.681.475A9.911 9.911 0 0 0 10 .333Z",
email: createSvg(["m10.036 8.278 9.258-7.79A1.979 1.979 0 0 0 18 0H2A1.987 1.987 0 0 0 .641.541l9.395 7.737Z", "M11.241 9.817c-.36.275-.801.425-1.255.427-.428 0-.845-.138-1.187-.395L0 2.6V14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2.5l-8.759 7.317Z"]), ]),
globe: createSvg(["M6.487 1.746c0 4.192 3.592 1.66 4.592 5.754 0 .828 1 1.5 2 1.5s2-.672 2-1.5a1.5 1.5 0 0 1 1.5-1.5h1.5m-16.02.471c4.02 2.248 1.776 4.216 4.878 5.645C10.18 13.61 9 19 9 19m9.366-6h-2.287a3 3 0 0 0-3 3v2m6-8a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z"]) link: createSvg([
} "M11.013 7.962a3.519 3.519 0 0 0-4.975 0l-3.554 3.554a3.518 3.518 0 0 0 4.975 4.975l.461-.46m-.461-4.515a3.518 3.518 0 0 0 4.975 0l3.553-3.554a3.518 3.518 0 0 0-4.974-4.975L10.3 3.7",
]),
email: createSvg([
"m10.036 8.278 9.258-7.79A1.979 1.979 0 0 0 18 0H2A1.987 1.987 0 0 0 .641.541l9.395 7.737Z",
"M11.241 9.817c-.36.275-.801.425-1.255.427-.428 0-.845-.138-1.187-.395L0 2.6V14a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V2.5l-8.759 7.317Z",
]),
globe: createSvg([
"M6.487 1.746c0 4.192 3.592 1.66 4.592 5.754 0 .828 1 1.5 2 1.5s2-.672 2-1.5a1.5 1.5 0 0 1 1.5-1.5h1.5m-16.02.471c4.02 2.248 1.776 4.216 4.878 5.645C10.18 13.61 9 19 9 19m9.366-6h-2.287a3 3 0 0 0-3 3v2m6-8a9 9 0 1 1-18 0 9 9 0 0 1 18 0Z",
]),
};
const experience = ({ const experience = ({
company, company,
@ -196,14 +181,20 @@ const experience = ({
description, description,
technologies, technologies,
links, links,
feedback feedback,
}: Experience) => }: Experience) => (
<View style={tw("w-full flex flex-col")}> <View style={tw("w-full flex flex-col")}>
<View style={tw("w-full flex flex-col mt-2 border-2 border-slate-50 p-4 rounded-2xl")}> <View
style={tw(
"w-full flex flex-col mt-2 border-2 border-slate-50 p-4 rounded-2xl"
)}
>
<View style={tw("flex flex-row flex-wrap gap-1")}> <View style={tw("flex flex-row flex-wrap gap-1")}>
<Text>{position}</Text> <Text>{position}</Text>
<Text>at</Text> <Text>at</Text>
<Text>{company}, {location}</Text> <Text>
{company}, {location}
</Text>
<Text>from</Text> <Text>from</Text>
<Text>{from.toDateString()}</Text> <Text>{from.toDateString()}</Text>
<Text>to</Text> <Text>to</Text>
@ -211,7 +202,7 @@ const experience = ({
</View> </View>
{links && ( {links && (
<View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}> <View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}>
{links.map(l => ( {links.map((l) => (
<View key={l.href} style={tw("flex flex-row gap-2 text-sm")}> <View key={l.href} style={tw("flex flex-row gap-2 text-sm")}>
{link(l)} {link(l)}
</View> </View>
@ -227,15 +218,18 @@ const experience = ({
</View> </View>
)} )}
<View style={tw("flex flex-row flex-wrap m-2")}> <View style={tw("flex flex-row flex-wrap m-2")}>
{technologies.map(t => ( {technologies.map((t) => (
<View key={t} style={tw("flex flex-row")}> <View key={t} style={tw("flex flex-row")}>
<View style={tw("border-2 border-green-300 h-1 mx-1 mt-1 rounded-full")}></View> <View
style={tw("border-2 border-green-300 h-1 mx-1 mt-1 rounded-full")}
></View>
<Text style={tw("text-xs")}>{t}</Text> <Text style={tw("text-xs")}>{t}</Text>
</View> </View>
))} ))}
</View> </View>
</View> </View>
</View> </View>
);
const education = ({ const education = ({
institution, institution,
@ -245,14 +239,22 @@ const education = ({
from, from,
to, to,
summary, summary,
links links,
}: Education) => }: Education) => (
<View style={tw("w-full flex flex-col")}> <View style={tw("w-full flex flex-col")}>
<View style={tw("w-full flex flex-col mt-2 border-2 border-slate-50 p-4 rounded-2xl")}> <View
style={tw(
"w-full flex flex-col mt-2 border-2 border-slate-50 p-4 rounded-2xl"
)}
>
<View style={tw("flex flex-row flex-wrap gap-1")}> <View style={tw("flex flex-row flex-wrap gap-1")}>
<Text>Studied {degree} of {field}</Text> <Text>
Studied {degree} of {field}
</Text>
<Text>at</Text> <Text>at</Text>
<Text>{institution}, {location}</Text> <Text>
{institution}, {location}
</Text>
<Text>from</Text> <Text>from</Text>
<Text>{from.toDateString()}</Text> <Text>{from.toDateString()}</Text>
<Text>to</Text> <Text>to</Text>
@ -260,7 +262,7 @@ const education = ({
</View> </View>
{links && ( {links && (
<View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}> <View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}>
{links.map(l => ( {links.map((l) => (
<View key={l.href} style={tw("flex flex-row gap-2")}> <View key={l.href} style={tw("flex flex-row gap-2")}>
{link(l)} {link(l)}
</View> </View>
@ -272,16 +274,38 @@ const education = ({
</View> </View>
</View> </View>
</View> </View>
);
const link = ({ text, href, icon }: A) => <Link src={href} style={tw("no-underline text-slate-50 flex flex-row gap-1")}>{icon || svg.link}<Text>{text}</Text></Link> const link = ({ text, href, icon }: A) => (
<Link src={href} style={tw("no-underline text-slate-50 flex flex-row gap-1")}>
{icon || svg.link}
<Text>{text}</Text>
</Link>
);
const Links = () => ( const Links = () => (
<View style={tw("flex flex-row gap-4 w-full text-sm justify-center p-4")}> <View style={tw("flex flex-row gap-4 w-full text-sm justify-center p-4")}>
{link({ text: "GitHub", href: "https://github.com/ivandimitrov8080", icon: svg.github as any })} {link({
{link({ text: "Upwork", href: "https://www.upwork.com/freelancers/idimitrov", icon: svg.link as any })} text: "GitHub",
{link({ text: "ivan@idimitrov.dev", href: "mailto:ivan@idimitrov.dev", icon: svg.email as any })} href: "https://github.com/ivandimitrov8080",
{link({ text: "idimitrov.dev", href: "https://www.idimitrov.dev", icon: svg.globe as any })} icon: svg.github as any,
})}
{link({
text: "Upwork",
href: "https://www.upwork.com/freelancers/idimitrov",
icon: svg.link as any,
})}
{link({
text: "ivan@idimitrov.dev",
href: "mailto:ivan@idimitrov.dev",
icon: svg.email as any,
})}
{link({
text: "idimitrov.dev",
href: "https://www.idimitrov.dev",
icon: svg.globe as any,
})}
</View> </View>
) );
const Intro = () => ( const Intro = () => (
<View style={tw("text-center border-2 border-slate-50 rounded-full")}> <View style={tw("text-center border-2 border-slate-50 rounded-full")}>
@ -289,13 +313,16 @@ const Intro = () => (
<Text style={tw("text-sm")}>Software Developer</Text> <Text style={tw("text-sm")}>Software Developer</Text>
<Links /> <Links />
</View> </View>
) );
const pageStyles = tw("w-full h-full text-slate-50 bg-slate-950 flex flex-col p-12 text-base") const pageStyles = tw(
const divider = "w-full h-full text-slate-50 bg-slate-950 flex flex-col p-12 text-base"
);
const divider = (
<View style={tw("w-full mt-4")}> <View style={tw("w-full mt-4")}>
<View style={tw("w-full border-slate-50 border-b-[.2px]")}></View> <View style={tw("w-full border-slate-50 border-b-[.2px]")}></View>
</View> </View>
);
const CV = () => ( const CV = () => (
<Document <Document
@ -306,9 +333,7 @@ const CV = () => (
producer="Ivan Dimitrov with react-pdf" producer="Ivan Dimitrov with react-pdf"
keywords="Ivan Dimitrov Software Developer" keywords="Ivan Dimitrov Software Developer"
> >
<Page <Page size="A4" style={pageStyles}>
size="A4"
style={(pageStyles)}>
<Intro /> <Intro />
{divider} {divider}
<Text style={tw("text-2xl mt-2")}>Experience</Text> <Text style={tw("text-2xl mt-2")}>Experience</Text>
@ -319,9 +344,11 @@ const CV = () => (
location: "UK", location: "UK",
from: new Date("21 Sep 2023"), from: new Date("21 Sep 2023"),
to: new Date("5 Nov 2023"), to: new Date("5 Nov 2023"),
description: "Diagnosed and analyzed a faulty LUKS encrypted drive on a remote server.", description:
"Diagnosed and analyzed a faulty LUKS encrypted drive on a remote server.",
technologies: [...tech.linux], technologies: [...tech.linux],
feedback: "Ivan was great to work with. Bought his own ideas and expertise, and workshopped a solution with me. Has a wealth of knowledge and I'd very happily work with him again." feedback:
"Ivan was great to work with. Bought his own ideas and expertise, and workshopped a solution with me. Has a wealth of knowledge and I'd very happily work with him again.",
})} })}
{experience({ {experience({
company: "Stepsy", company: "Stepsy",
@ -329,13 +356,23 @@ const CV = () => (
location: "Estonia", location: "Estonia",
from: new Date("29 Jul 2023"), from: new Date("29 Jul 2023"),
to: new Date("5 Nov 2023"), to: new Date("5 Nov 2023"),
description: "Created a multi-tenant knowledge base website based on Google APIs", description:
technologies: skills(["nextjs", "styles", "linux", "git", "general"]) "Created a multi-tenant knowledge base website based on Google APIs",
.concat(["googleapis", "Fuse.js", "Interact.js"]), technologies: skills([
"nextjs",
"styles",
"linux",
"git",
"general",
]).concat(["googleapis", "Fuse.js", "Interact.js"]),
links: [ links: [
{ text: "Case Study", href: "https://www.idimitrov.dev/c/cases/stepsy.wiki.md" } {
text: "Case Study",
href: "https://www.idimitrov.dev/c/cases/stepsy.wiki.md",
},
], ],
feedback: "Great experience working with Ivan! Ready to implement your vision, also advises on how it should be done." feedback:
"Great experience working with Ivan! Ready to implement your vision, also advises on how it should be done.",
})} })}
{experience({ {experience({
company: "HOI 2 Bunker", company: "HOI 2 Bunker",
@ -343,16 +380,22 @@ const CV = () => (
location: "UK", location: "UK",
from: new Date("22 Jun 2023"), from: new Date("22 Jun 2023"),
to: new Date("27 Jun 2023"), to: new Date("27 Jun 2023"),
description: "Scraped an old wiki website with over 500 pages and created a new static site generated using Markdown and NextJS.", description:
technologies: skills(["python", "nextjs", "styles", "linux", "git", "general"]), "Scraped an old wiki website with over 500 pages and created a new static site generated using Markdown and NextJS.",
feedback: "Ivan went above and beyond to make sure I was happy with the final result" technologies: skills([
"python",
"nextjs",
"styles",
"linux",
"git",
"general",
]),
feedback:
"Ivan went above and beyond to make sure I was happy with the final result",
})} })}
</View> </View>
</Page> </Page>
<Page <Page size="A4" style={pageStyles}>
size="A4"
style={(pageStyles)}
>
<View style={tw("my-auto")}> <View style={tw("my-auto")}>
{experience({ {experience({
company: "North Concepts", company: "North Concepts",
@ -360,9 +403,13 @@ const CV = () => (
location: "Canada", location: "Canada",
from: new Date("25 May 2023"), from: new Date("25 May 2023"),
to: new Date("20 Jun 2023"), to: new Date("20 Jun 2023"),
description: "Wrote technical documentation and content for the DataPipeline library by NorthConcepts.", description:
technologies: skills(["java", "dataIntegration", "git"]).concat("WordPress"), "Wrote technical documentation and content for the DataPipeline library by NorthConcepts.",
feedback: "Ivan is a talented developer and was able to understand and write about our developer framework without difficulty." technologies: skills(["java", "dataIntegration", "git"]).concat(
"WordPress"
),
feedback:
"Ivan is a talented developer and was able to understand and write about our developer framework without difficulty.",
})} })}
{experience({ {experience({
company: "RA Creative", company: "RA Creative",
@ -370,13 +417,14 @@ const CV = () => (
location: "Nottingham, UK", location: "Nottingham, UK",
from: new Date("Dec 2020"), from: new Date("Dec 2020"),
to: new Date("20 Jan 2023"), to: new Date("20 Jan 2023"),
description: "Worked on seven international eCommerce web apps serving customers in the US and Europe.", description:
"Worked on seven international eCommerce web apps serving customers in the US and Europe.",
technologies: skillsInverted(["dataIntegration", "python", "nextjs"]), technologies: skillsInverted(["dataIntegration", "python", "nextjs"]),
links: [ links: [
{ text: "RA Creative", href: "https://racreative.co.uk/" }, { text: "RA Creative", href: "https://racreative.co.uk/" },
{ text: "Parcel Lab", href: "https://parcellab.com/" }, { text: "Parcel Lab", href: "https://parcellab.com/" },
{ text: "Wells Fargo", href: "https://www.wellsfargo.com/" }, { text: "Wells Fargo", href: "https://www.wellsfargo.com/" },
] ],
})} })}
{experience({ {experience({
company: "Central Net", company: "Central Net",
@ -384,15 +432,20 @@ const CV = () => (
location: "Blagoevgrad, Bulgaria", location: "Blagoevgrad, Bulgaria",
from: new Date("May 2016"), from: new Date("May 2016"),
to: new Date("May 2020"), to: new Date("May 2020"),
description: "Developed a full-stack web + android app helping students book exams, browse resources, see events, news and more.", description:
technologies: skillsInverted(["hybris", "payment", "dataIntegration", "python", "nextjs", "styles"]) "Developed a full-stack web + android app helping students book exams, browse resources, see events, news and more.",
technologies: skillsInverted([
"hybris",
"payment",
"dataIntegration",
"python",
"nextjs",
"styles",
]),
})} })}
</View> </View>
</Page> </Page>
<Page <Page size="A4" style={pageStyles}>
size="A4"
style={(pageStyles)}
>
<View style={tw("my-2")}> <View style={tw("my-2")}>
{divider} {divider}
<Text style={tw("text-2xl mt-2")}>Education</Text> <Text style={tw("text-2xl mt-2")}>Education</Text>
@ -403,19 +456,19 @@ const CV = () => (
field: "Electronics", field: "Electronics",
from: new Date("Sep 2016"), from: new Date("Sep 2016"),
to: new Date("Jun 2018"), to: new Date("Jun 2018"),
summary: "This is an engineering degree focused on the science of electronics and electrical engineering. It studies the physical properties of individual electrons and the forces that take place when current is flowing through a circuit. Those same forces make computers possible through carefully engineered logic gates built with transistors." summary:
"This is an engineering degree focused on the science of electronics and electrical engineering. It studies the physical properties of individual electrons and the forces that take place when current is flowing through a circuit. Those same forces make computers possible through carefully engineered logic gates built with transistors.",
})} })}
</View> </View>
</Page> </Page>
</Document> </Document>
); );
const outDir = process.env.out || "./" const outDir = process.env.out || "./";
const pname = process.env.pname || "cv" const pname = process.env.pname || "cv";
if (!fs.existsSync(outDir)) { if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true }) fs.mkdirSync(outDir, { recursive: true });
} }
ReactPDF.render(<CV />, `${outDir}/${pname}.pdf`); ReactPDF.render(<CV />, `${outDir}/${pname}.pdf`);