cv/cv.tsx

454 lines
13 KiB
TypeScript
Raw Normal View History

2023-11-13 14:10:39 +01:00
import { Page, Text, View, Document, Link, Svg, Path } from "@react-pdf/renderer";
2023-11-13 12:33:50 +01:00
import ReactPDF from "@react-pdf/renderer";
import fs from "fs";
import { createTw } from "react-pdf-tailwind";
2023-11-13 14:10:39 +01:00
type A = {
text: string
href: string
}
2023-11-13 12:33:50 +01:00
type Experience = {
company: string
position: string
location: string
from: Date
to: Date
description: string
technologies: string[]
2023-11-13 14:10:39 +01:00
links?: A[]
2023-11-13 16:11:07 +01:00
feedback?: string
2023-11-13 12:33:50 +01:00
}
2023-11-13 16:58:00 +01:00
type Education = {
institution: string
location: string
degree: string
field: string
from: Date
to: Date
summary: string
links?: A[]
}
2023-11-13 12:33:50 +01:00
const tw = createTw({
2023-11-12 18:07:31 +01:00
});
2023-11-13 14:10:39 +01:00
const linkStyles = "no-underline text-slate-50 flex flex-row gap-1"
const pageStyles = "w-full h-full text-slate-50 bg-slate-950 flex flex-col p-12 text-base"
2023-11-13 15:36:17 +01:00
const sectionStyles = "w-full flex flex-col"
2023-11-13 14:10:39 +01:00
const svg = (paths: string[]) =>
<Svg style={tw("w-4 h-4")} viewBox="0 0 19 19">
{paths.map(p => (
<Path key={p} fill="#99f6e4" d={p} />
))}
</Svg>
const linkPath = "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"
const githubPath = "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"
const emailPath1 = "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"
const emailPath2 = "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"
const globePath = "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"
2023-11-13 12:33:50 +01:00
const divider =
<View style={tw("w-full mt-4")}>
2023-11-13 16:58:00 +01:00
<View style={tw("w-full border-slate-50 border-b-[.2px]")}></View>
2023-11-13 12:33:50 +01:00
</View>
const github =
2023-11-13 14:10:39 +01:00
<Link src="https://github.com/ivandimitrov8080" style={tw(linkStyles)}>{svg([githubPath])}<Text>github/ivandimitrov8080</Text></Link>
2023-11-13 12:33:50 +01:00
const upwork =
2023-11-13 14:10:39 +01:00
<Link src="https://www.upwork.com/freelancers/idimitrov" style={tw(linkStyles)}>{svg([linkPath])}<Text>upwork/freelancers/idimitrov</Text></Link>
2023-11-13 12:33:50 +01:00
const resume =
2023-11-13 14:10:39 +01:00
<Link src="https://www.idimitrov.dev" style={tw(linkStyles)}>{svg([globePath])}<Text>idimitrov.dev</Text></Link>
2023-11-13 12:33:50 +01:00
const email =
2023-11-13 14:10:39 +01:00
<Link src="mailto:ivan@idimitrov.dev" style={tw(linkStyles)}>{svg([emailPath1, emailPath2])}<Text>ivan@idimitrov.dev</Text></Link>
2023-11-13 12:33:50 +01:00
const tech = {
android: [
"Android",
"Android Studio",
],
java: [
"Java",
"JPA",
"Hibernate",
"Spring Framework",
"Spring Boot",
"Lombok",
"Spring MVC",
"Thymeleaf",
"JSP",
"JSTL",
"XML",
"Spring Security",
"OAuth2",
"H2",
"Spring Boot Actuator",
2023-11-13 15:36:17 +01:00
"Maven",
"Gradle",
"Ant",
2023-11-13 12:33:50 +01:00
],
web: [
"JavaScript",
"HTML",
"CSS"
],
api: [
"REST",
"SOAP",
],
db: [
"MySQL",
"PostgreSQL"
],
linux: [
"Linux",
"Bash",
"coreutils",
"Ubuntu",
"CentOS",
"RHEL",
"SSH",
"iptables",
"systemd",
"vim",
"Monit",
"CLI",
2023-11-13 15:36:17 +01:00
"pandoc",
2023-11-13 16:36:25 +01:00
"LUKS",
"hexdump",
"dd",
2023-11-13 12:33:50 +01:00
],
2023-11-13 14:10:39 +01:00
git: [
"git",
"GitHub",
"GitLab",
"BitBucket",
],
2023-11-13 12:33:50 +01:00
hybris: [
"SAP hybris",
"ZK Framework",
],
payment: [
"PayPal",
"Adyen",
"V12",
"Wells Fargo Open Banking APIs",
2023-11-13 14:10:39 +01:00
],
dataIntegration: [
"Spring Batch",
"Data Pipeline",
"Scriptella",
"Easy Batch",
"GETL",
"Apache Camel",
"Apache Samza",
"Apache Flink",
"Apache Storm",
"Apache Spark",
"Apache NiFi",
],
2023-11-13 15:36:17 +01:00
python: [
"python",
"BeautifulSoup4",
"requests",
"pypandoc",
"markdownify",
"html2text",
"Poetry",
],
2023-11-13 16:11:07 +01:00
javascript: [
"TypeScript",
"React",
],
2023-11-13 15:36:17 +01:00
nextjs: [
"NextJS 12",
"NextJS 13",
"NextJS 14",
2023-11-13 16:11:07 +01:00
"NextAuth",
"Prisma",
"Vercel",
"Vercel Postgres",
"Formik",
"Framer Motion",
2023-11-13 15:36:17 +01:00
],
styles: [
"CSS",
"SASS",
"TailwindCSS",
"DaisyUI",
"tailwind-scrollbar",
2023-11-13 16:11:07 +01:00
"FontAwesome",
"nProgress",
2023-11-13 15:36:17 +01:00
],
general: [
"Markdown",
"Google",
"DuckDuckGo",
"PDF",
"Email",
],
2023-11-13 12:33:50 +01:00
}
const techKeys = [...Object.keys(tech)] as const
2023-11-13 15:36:17 +01:00
const filterKeysIncl = (obj: readonly string[], keys: string[]) => {
return obj.filter(e => keys.includes(e))
}
const filterKeysExcl = (obj: readonly string[], keys: string[]) => {
return obj.filter(e => !keys.includes(e))
}
const cnetTech: string[] = filterKeysExcl(techKeys,
["hybris", "payment", "dataIntegration", "python", "nextjs", "styles"]
)
2023-11-13 12:33:50 +01:00
// @ts-ignore
.map(e => tech[e]).flat()
2023-11-13 15:36:17 +01:00
const raTech: string[] = filterKeysExcl(techKeys,
["dataIntegration", "python", "nextjs", "styles"]
)
// @ts-ignore
.map(e => tech[e]).flat()
const ncTech: string[] = filterKeysIncl(techKeys,
["java", "dataIntegration", "git"]
)
2023-11-13 12:33:50 +01:00
// @ts-ignore
.map(e => tech[e]).flat()
2023-11-13 15:36:17 +01:00
const hoi2Tech: string[] = filterKeysIncl(techKeys,
["python", "nextjs", "styles", "linux", "git", "general"]
)
// @ts-ignore
2023-11-13 16:11:07 +01:00
.map(e => tech[e]).flat().concat(["Wix", "Fuse.js"])
const stepsyTech: string[] = filterKeysIncl(techKeys,
["nextjs", "styles", "linux", "git", "general"]
)
// @ts-ignore
.map(e => tech[e]).flat().concat(["googleapis", "Fuse.js", "Interact.js"])
2023-11-13 12:33:50 +01:00
const experience = ({
company,
position,
location,
from,
to,
description,
2023-11-13 14:10:39 +01:00
technologies,
2023-11-13 16:11:07 +01:00
links,
feedback
2023-11-13 15:36:17 +01:00
}: Experience) =>
<View style={tw(sectionStyles)}>
<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")}>
<Text>{position}</Text>
<Text>at</Text>
<Text>{company}, {location}</Text>
<Text>from</Text>
<Text>{from.toDateString()}</Text>
<Text>to</Text>
<Text>{to.toDateString()}</Text>
</View>
{links && (
<View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}>
{links.map(l => (
<View key={l.href} style={tw("flex flex-row gap-2")}>
{svg([linkPath])}
<Text style={tw("text-sm")}>{l.text}</Text>
</View>
))}
</View>
)}
<View style={tw("m-4")}>
<Text>{description}</Text>
</View>
2023-11-13 16:11:07 +01:00
{feedback && (
<View style={tw("w-full m-auto mb-1")}>
<Text>Result: "{feedback}"</Text>
</View>
)}
<View style={tw("flex flex-row flex-wrap m-2")}>
2023-11-13 15:36:17 +01:00
{technologies.map(t => (
<View key={t} style={tw("flex flex-row")}>
<View style={tw("border-2 border-teal-200 h-1 mx-1 mt-1 rounded-full")}></View>
<Text style={tw("text-xs")}>{t}</Text>
2023-11-13 14:10:39 +01:00
</View>
))}
</View>
2023-11-13 12:33:50 +01:00
</View>
</View>
2023-11-13 16:58:00 +01:00
const education = ({
institution,
location,
degree,
field,
from,
to,
summary,
links
}: Education) =>
<View style={tw(sectionStyles)}>
<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")}>
<Text>Studied {degree} of {field}</Text>
<Text>at</Text>
<Text>{institution}, {location}</Text>
<Text>from</Text>
<Text>{from.toDateString()}</Text>
<Text>to</Text>
<Text>{to.toDateString()}</Text>
</View>
{links && (
<View style={tw("flex flex-row flex-wrap w-full gap-2 justify-center")}>
{links.map(l => (
<View key={l.href} style={tw("flex flex-row gap-2")}>
{svg([linkPath])}
<Text style={tw("text-sm")}>{l.text}</Text>
</View>
))}
</View>
)}
<View style={tw("m-4")}>
<Text>{summary}</Text>
</View>
</View>
</View>
2023-11-13 12:33:50 +01:00
const Links = () => (
<View style={tw("flex flex-row gap-4 w-full text-sm justify-center p-4")}>
{github}
{upwork}
{email}
{resume}
</View>
)
const Intro = () => (
<View style={tw("text-center border-2 border-slate-50 rounded-full")}>
<Text style={tw("text-5xl")}>Ivan K. Dimitrov</Text>
<Text style={tw("text-sm")}>Software Developer</Text>
<Links />
</View>
)
const CV = () => (
2023-11-13 14:10:39 +01:00
<Document
title="CV"
author="Ivan Kirilov Dimitrov"
subject="My professional resume"
creator="Ivan Dimitrov with react-pdf"
producer="Ivan Dimitrov with react-pdf"
keywords="Ivan Dimitrov Software Developer"
>
2023-11-13 12:33:50 +01:00
<Page
size="A4"
2023-11-13 14:10:39 +01:00
style={tw(pageStyles)}>
2023-11-13 12:33:50 +01:00
<Intro />
{divider}
2023-11-13 16:11:07 +01:00
<Text style={tw("text-2xl mt-2")}>Experience</Text>
2023-11-13 15:36:17 +01:00
<View style={tw("my-auto")}>
2023-11-13 16:11:07 +01:00
{experience({
company: "N/A",
position: "Freelance Systems Administrator",
location: "UK",
from: new Date("21 Sep 2023"),
to: new Date("5 Nov 2023"),
description: "Diagnosed and analyzed a faulty LUKS encrypted drive on a remote server.",
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."
})}
{experience({
company: "Stepsy",
position: "Freelance Full Stack Software Developer",
location: "Estonia",
from: new Date("29 Jul 2023"),
to: new Date("5 Nov 2023"),
description: "Created a multi-tenant knowledge base website based on Google APIs",
technologies: stepsyTech,
links: [
{ 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."
})}
2023-11-13 15:36:17 +01:00
{experience({
company: "HOI 2 Bunker",
position: "Freelance Full Stack Software Developer",
location: "UK",
from: new Date("22 Jun 2023"),
to: new Date("27 Jun 2023"),
2023-11-13 16:11:07 +01:00
description: "Scraped an old wiki website with over 500 pages and created a new static site generated using Markdown and NextJS.",
technologies: hoi2Tech,
feedback: "Ivan went above and beyond to make sure I was happy with the final result"
2023-11-13 15:36:17 +01:00
})}
2023-11-13 16:11:07 +01:00
</View>
</Page>
<Page
size="A4"
style={tw(pageStyles)}
>
<View style={tw("my-auto")}>
2023-11-13 15:36:17 +01:00
{experience({
company: "North Concepts",
position: "Technical Writer for Java Data Pipeline Library",
location: "Canada",
from: new Date("25 May 2023"),
to: new Date("20 Jun 2023"),
description: "Wrote technical documentation and content for the DataPipeline library by NorthConcepts.",
2023-11-13 16:11:07 +01:00
technologies: ncTech,
feedback: "Ivan is a talented developer and was able to understand and write about our developer framework without difficulty."
2023-11-13 15:36:17 +01:00
})}
{experience({
company: "RA Creative",
position: "Full Stack Software Developer",
location: "Nottingham, UK",
from: new Date("Dec 2020"),
to: new Date("20 Jan 2023"),
description: "Worked on seven international eCommerce web apps serving customers in the US and Europe.",
technologies: raTech,
links: [
{ text: "RA Creative", href: "https://racreative.co.uk/" },
{ text: "Parcel Lab", href: "https://parcellab.com/" },
{ text: "Wells Fargo", href: "https://www.wellsfargo.com/" },
]
})}
{experience({
company: "Central Net",
position: "Full Stack + Mobile Software Developer",
location: "Blagoevgrad, Bulgaria",
from: new Date("May 2016"),
to: new Date("May 2020"),
description: "Developed a full-stack web + android app helping students book exams, browse resources, see events, news and more.",
technologies: cnetTech
})}
</View>
2023-11-12 18:07:31 +01:00
</Page>
2023-11-13 16:58:00 +01:00
<Page
size="A4"
style={tw(pageStyles)}
>
<View style={tw("my-2")}>
{divider}
<Text style={tw("text-2xl mt-2")}>Education</Text>
{education({
institution: "South-West University 'Neofit Rilski'",
location: "Blagoevgrad, Bulgaria",
degree: "Bachelor's",
field: "Electronics",
from: new Date("Sep 2016"),
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."
})}
</View>
</Page>
2023-11-12 18:07:31 +01:00
</Document>
);
const outDir = process.env.out || "./"
const pname = process.env.pname || "cv"
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true })
}
2023-11-13 12:33:50 +01:00
ReactPDF.render(<CV />, `${outDir}/${pname}.pdf`);
2023-11-12 18:07:31 +01:00