diff --git a/cv.json b/cv.json new file mode 100644 index 0000000..3145a07 --- /dev/null +++ b/cv.json @@ -0,0 +1,112 @@ +{ + "name": "Ivan Kirilov Dimitrov", + "description": "My professional resume", + "title": "Software Developer", + "email": "ivan@idimitrov.dev", + "github": "ivandimitrov8080", + "website": "idimitrov.dev", + "upwork": "idimitrov", + "experience": [ + { + "company": "idimitrov.dev", + "position": "Software Developer / Owner", + "location": "Worldwide", + "from": "2023", + "to": "9999", + "description": "This is my software consulting and development business. It offers web development services to businesses around the world. Please head over to my resume website or my Upwork profile to learn more.", + "links": [ + { + "text": "Upwork", + "href": "https://www.upwork.com/freelancers/idimitrov" + }, + { + "text": "Resume", + "href": "https://www.idimitrov.dev/cases" + } + ], + "feedback": "100% Job Success" + }, + { + "company": "Stepsy", + "position": "Freelance Full Stack Software Developer", + "location": "Estonia", + "from": "29 Jul 2023", + "to": "5 Nov 2023", + "description": "As a software developer working with stepsy.co, I was responsible for implementing their brand new wiki web app stepsy.wiki. Working on this greenfield project allowed me to make fundamental technical decisions that had a positive impact on further development.", + "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." + }, + { + "company": "RA Creative", + "position": "Full Stack Software Developer", + "location": "Nottingham, UK", + "from": "Dec 2020", + "to": "20 Jan 2023", + "description": "As a software developer at RA Creative, I was responsible for delivering software solutions to an eCommerce business operating in 2 continents - Europe and North America. Watches of Switzerland Group is an international retailer of world leading luxury watch and jewellery brands. It has a market cap of £1.5B.", + "links": [ + { + "text": "RA Creative", + "href": "https://racreative.co.uk/" + }, + { + "text": "Parcel Lab case study", + "href": "https://www.idimitrov.dev/c/cases/parcellab.md" + }, + { + "text": "Wells Fargo case study", + "href": "https://www.idimitrov.dev/c/cases/wellsfargo.md" + } + ] + }, + { + "company": "Central Net", + "position": "Full Stack + Mobile Software Developer", + "location": "Blagoevgrad, Bulgaria", + "from": "May 2016", + "to": "May 2020", + "description": "Developed a full-stack web + android app helping students book exams, browse resources, see events, news and more." + } + ], + "education": [ + { + "institution": "SWU 'Neofit Rilski'", + "location": "Blagoevgrad, Bulgaria", + "degree": "Bachelor's", + "field": "Electronics", + "from": "Sep 2016", + "to": "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." + } + ], + "certificates": [ + { + "name": "Oracle Certified Professional, Java SE 8 Programmer", + "issuer": "Oracle", + "description": "An Oracle Certified Professional, Java SE 8 Programmer has validated their Java development skills by answering challenging, real-world, scenario-based questions that measure problem solving skills using Java code.", + "date": "19 May 2020", + "links": [ + { + "text": "Credly", + "href": "https://www.credly.com/badges/281fbd5f-ca29-4235-b023-a9b93af2f6c5/public_url" + } + ] + }, + { + "name": "Oracle Certified Associate, Java SE 8 Programmer", + "issuer": "Oracle", + "description": "An Oracle Certified Associate, Java SE 8 Programmer has demonstrated knowledge of object-oriented concepts, the Java programming language and general knowledge of Java platforms and technologies.", + "date": "17 Mar 2020", + "links": [ + { + "text": "Credly", + "href": "https://www.credly.com/badges/910f311b-0f7f-4911-b945-5ded663408ec/public_url" + } + ] + } + ] +} diff --git a/cv.tsx b/cv.tsx index 4cb4f2e..1c2fb61 100644 --- a/cv.tsx +++ b/cv.tsx @@ -1,387 +1,158 @@ -import { - Page, - Text, - View, - Document, - Link, - Svg, - Path, -} from "@react-pdf/renderer"; +import { Page, Text, View, Document } from "@react-pdf/renderer"; import ReactPDF from "@react-pdf/renderer"; import fs from "fs"; -import { createTw } from "react-pdf-tailwind"; -const tw = createTw({}); +import { svg, tw } from "./theme/lib"; +import SvgLink from "./theme/link"; +import Experience, { Exp } from "./theme/experience"; +import Education, { Edu } from "./theme/education"; +import Certificate, { Cert } from "./theme/certificate"; -type A = { - text: string; - href: string; - icon?: string[]; -}; - -type Experience = { - company: string; - position: string; - location: string; - from: Date; - to: Date; - description: string; - links?: A[]; - feedback?: string; -}; - -type Education = { - institution: string; - location: string; - degree: string; - field: string; - from: Date; - to: Date; - summary: string; - links?: A[]; -}; - -type Certificate = { - name: string; - issuer: string; - description: string; - date: Date; - links?: A[]; -}; - -const createSvg = (paths: string[]) => ( - - {paths.map((p) => ( - - ))} - -); - -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", - ]), - 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 = ({ - company, - position, - location, - from, - to, - description, - links, - feedback, -}: Experience) => ( - - - - {position} - at - - {company}, {location} - - from - {from.toDateString()} - to - {to > new Date() ? "present" : to.toDateString()} - - {links && ( - - {links.map((l) => ( - - {link(l)} - - ))} - - )} - - {description} - - {feedback && ( - - let - clientFeedback - = - "{feedback}" - ; - - )} - - -); - -const education = ({ - institution, - location, - degree, - field, - from, - to, - summary, - links, -}: Education) => ( - - - - - Studied {degree} of {field} - - at - - {institution}, {location} - - from - {from.toDateString()} - to - {to.toDateString()} - - {links && ( - - {links.map((l) => ( - - {link(l)} - - ))} - - )} - - {summary} - - - -); - -const certificate = ({ - name, - issuer, - description, - date, - links, -}: Certificate) => ( - - - - {name} - from - {issuer} - on - {date.toDateString()} - - {links && ( - - {links.map((l) => ( - - {link(l)} - - ))} - - )} - - {description} - - - -); - -const link = ({ text, href, icon }: A) => ( - - {icon || svg.link} - {text} - -); -const Links = () => ( - - {link({ - text: "GitHub", - href: "https://github.com/ivandimitrov8080", - 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, - })} - -); - -const Intro = () => ( - - Ivan K. Dimitrov - Software Developer - - -); - -const pageStyles = tw( - "w-full h-full text-slate-50 bg-gray-900 flex flex-col p-12 text-base" -); const divider = ( ); -const CV = () => ( +type CV = { + name: string; + description: string; + title: string; + email: string; + github?: string; + website?: string; + upwork?: string; + phone?: string; + experience?: Exp[]; + education?: Edu[]; + certificates?: Cert[]; +}; + +let data: CV = {} as CV; + +const Cv = () => ( - - - {divider} - Experience - - {experience({ - company: "idimitrov.dev", - position: "Software Developer / Owner", - location: "Worldwide", - from: new Date("2023"), - to: new Date("9999"), - description: - "This is my software consulting and development business. It offers web development services to businesses around the world. Please head over to my resume website or my Upwork profile to learn more.", - links: [ - { - text: "Upwork", - href: "https://www.upwork.com/freelancers/idimitrov", - }, - { - text: "Resume", - href: "https://www.idimitrov.dev/cases", - }, - ], - feedback: "100% Job Success" - })} - {experience({ - company: "Stepsy", - position: "Freelance Full Stack Software Developer", - location: "Estonia", - from: new Date("29 Jul 2023"), - to: new Date("5 Nov 2023"), - description: - "As a software developer working with stepsy.co, I was responsible for implementing their brand new wiki web app stepsy.wiki. Working on this greenfield project allowed me to make fundamental technical decisions that had a positive impact on further development.", - 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.", - })} - {experience({ - company: "RA Creative", - position: "Full Stack Software Developer", - location: "Nottingham, UK", - from: new Date("Dec 2020"), - to: new Date("20 Jan 2023"), - description: - "As a software developer at RA Creative, I was responsible for delivering software solutions to an eCommerce business operating in 2 continents - Europe and North America. Watches of Switzerland Group is an international retailer of world leading luxury watch and jewellery brands. It has a market cap of £1.5B.", - links: [ - { text: "RA Creative", href: "https://racreative.co.uk/" }, - { - text: "Parcel Lab case study", - href: "https://www.idimitrov.dev/c/cases/parcellab.md", - }, - { - text: "Wells Fargo case study", - href: "https://www.idimitrov.dev/c/cases/wellsfargo.md", - }, - ], - })} + + + {data.name} + {data.title} + + {data.github && ( + + )} + {data.upwork && ( + + )} + + {data.phone && ( + + )} + {data.website && ( + + )} + - - - {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.", - })} + {divider} + Experience + {data.experience?.map((e, i) => ( + + ))} {divider} Education - {education({ - institution: "SWU '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.", - })} + {data.education?.map((e, i) => ( + + ))} {divider} Certificates - {certificate({ - name: "Oracle Certified Professional, Java SE 8 Programmer", - issuer: "Oracle", - description: - "An Oracle Certified Professional, Java SE 8 Programmer has validated their Java development skills by answering challenging, real-world, scenario-based questions that measure problem solving skills using Java code.", - date: new Date("19 May 2020"), - links: [ - { - text: "Credly", - href: "https://www.credly.com/badges/281fbd5f-ca29-4235-b023-a9b93af2f6c5/public_url", - }, - ], - })} - {certificate({ - name: "Oracle Certified Associate, Java SE 8 Programmer", - issuer: "Oracle", - description: - "An Oracle Certified Associate, Java SE 8 Programmer has demonstrated knowledge of object-oriented concepts, the Java programming language and general knowledge of Java platforms and technologies.", - date: new Date("17 Mar 2020"), - links: [ - { - text: "Credly", - href: "https://www.credly.com/badges/910f311b-0f7f-4911-b945-5ded663408ec/public_url", - }, - ], - })} + {data.certificates?.map((c, i) => ( + + ))} ); +const parseData = () => { + const d = fs.readFileSync("./cv.json", { encoding: "utf8" }); + const json: CV = JSON.parse(d); + json.experience = json.experience?.map((e) => ({ + ...e, + from: new Date(e.from), + to: new Date(e.to), + })); + json.education = json.education?.map((e) => ({ + ...e, + from: new Date(e.from), + to: new Date(e.to), + })); + json.certificates = json.certificates?.map((c) => ({ + ...c, + date: new Date(c.date), + })); + data = json; +}; + const outDir = process.env.out || "./"; const pname = process.env.pname || "cv"; @@ -389,4 +160,6 @@ if (!fs.existsSync(outDir)) { fs.mkdirSync(outDir, { recursive: true }); } -ReactPDF.render(, `${outDir}/${pname}.pdf`); +parseData(); + +ReactPDF.render(, `${outDir}/${pname}.pdf`); diff --git a/flake.nix b/flake.nix index dcf726f..da54a33 100644 --- a/flake.nix +++ b/flake.nix @@ -19,7 +19,7 @@ system = "x86_64-linux"; pkgs = nixpkgs.legacyPackages.${system}; pname = "cv"; - version = "0.1.0"; + version = "0.1.1"; src = ./.; nvim = ide.nvim.${system}.standalone { plugins = { @@ -37,21 +37,21 @@ nativeBuildInputs = with pkgs; [ bun ]; - shellHook = '' - echo "$$" > ./pid - monitor() { - while true; do - inotifywait -e modify ${pname}.tsx > /dev/null 2>&1 - make - pkill -HUP mupdf - done - } - monitor > /dev/null 2>&1 & - ''; in { - devShells.default = pkgs.mkShell { - inherit pname buildInputs nativeBuildInputs shellHook; + devShells.${system}.default = pkgs.mkShell { + inherit pname buildInputs nativeBuildInputs; + shellHook = '' + echo "$$" > ./pid + monitor() { + while true; do + inotifywait -e modify ${pname}.tsx > /dev/null 2>&1 + make + pkill -HUP mupdf + done + } + monitor > /dev/null 2>&1 & + ''; }; packages.${system}.default = pkgs.buildNpmPackage rec { inherit nativeBuildInputs pname version src; diff --git a/package.json b/package.json index 883c4c3..b6f90cb 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "cv", - "version": "0.1.0", + "version": "0.1.1", "module": "cv.tsx", "type": "module", "devDependencies": { diff --git a/theme/certificate.tsx b/theme/certificate.tsx new file mode 100644 index 0000000..cdab528 --- /dev/null +++ b/theme/certificate.tsx @@ -0,0 +1,51 @@ +import { Text, View } from "@react-pdf/renderer"; +import { tw } from "./lib"; +import SvgLink from "./link"; + +export type Cert = { + name: string; + issuer: string; + description: string; + date: Date; + links?: A[]; +}; + +export default function Certificate({ + name, + issuer, + description, + date, + links, +}: Cert) { + return ( + + + + {name} + from + {issuer} + on + {date.toDateString()} + + {links && ( + + {links.map((l) => ( + + + + ))} + + )} + + {description} + + + + ); +} diff --git a/theme/education.tsx b/theme/education.tsx new file mode 100644 index 0000000..54aafde --- /dev/null +++ b/theme/education.tsx @@ -0,0 +1,63 @@ +import { Text, View } from "@react-pdf/renderer"; +import { tw } from "./lib"; +import SvgLink from "./link"; + +export type Edu = { + institution: string; + location: string; + degree: string; + field: string; + from: Date; + to: Date; + summary: string; + links?: A[]; +}; + +export default function Education({ + institution, + location, + degree, + field, + from, + to, + summary, + links, +}: Edu) { + return ( + + + + + Studied {degree} of {field} + + at + + {institution}, {location} + + from + {from.toDateString()} + to + {to.toDateString()} + + {links && ( + + {links.map((l) => ( + + + + ))} + + )} + + {summary} + + + + ); +} diff --git a/theme/experience.tsx b/theme/experience.tsx new file mode 100644 index 0000000..ff49d4a --- /dev/null +++ b/theme/experience.tsx @@ -0,0 +1,72 @@ +import { Text, View } from "@react-pdf/renderer"; +import { tw } from "./lib"; +import SvgLink from "./link"; + +export type Exp = { + company: string; + position: string; + location: string; + from: Date; + to: Date; + description: string; + links?: A[]; + feedback?: string; +}; + +export default function Experience({ + company, + position, + location, + from, + to, + description, + links, + feedback, +}: Exp) { + return ( + + + + {position} + at + + {company}, {location} + + from + {from.toDateString()} + to + + {to > new Date() ? "present" : to.toDateString()} + + + {links && ( + + {links.map((l) => ( + + + + ))} + + )} + + {description} + + {feedback && ( + + let + clientFeedback + = + "{feedback}" + ; + + )} + + + ); +} diff --git a/theme/lib.tsx b/theme/lib.tsx new file mode 100644 index 0000000..95fb22f --- /dev/null +++ b/theme/lib.tsx @@ -0,0 +1,30 @@ +import { Path, Svg } from "@react-pdf/renderer"; +import { createTw } from "react-pdf-tailwind"; +export const tw = createTw({}); + +const createSvg = (paths: string[]) => ( + + {paths.map((p) => ( + + ))} + +); + +export 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", + ]), + 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", + ]), + phone: createSvg([ + "M21.384,17.752a2.108,2.108,0,0,1-.522,3.359,7.543,7.543,0,0,1-5.476.642C10.5,20.523,3.477,13.5,2.247,8.614a7.543,7.543,0,0,1,.642-5.476,2.108,2.108,0,0,1,3.359-.522L8.333,4.7a2.094,2.094,0,0,1,.445,2.328A3.877,3.877,0,0,1,8,8.2c-2.384,2.384,5.417,10.185,7.8,7.8a3.877,3.877,0,0,1,1.173-.781,2.092,2.092,0,0,1,2.328.445Z", + ]), + 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", + ]), +}; diff --git a/theme/link.tsx b/theme/link.tsx new file mode 100644 index 0000000..d8fd857 --- /dev/null +++ b/theme/link.tsx @@ -0,0 +1,14 @@ +import { Text, Link } from "@react-pdf/renderer"; +import { svg, tw } from "./lib"; + +export default function SvgLink({ text, href, icon }: A) { + return ( + + {icon || svg.link} + {text} + + ); +} diff --git a/theme/types.d.ts b/theme/types.d.ts new file mode 100644 index 0000000..8113fad --- /dev/null +++ b/theme/types.d.ts @@ -0,0 +1,5 @@ +type A = { + text: string; + href: string; + icon?: string[]; +};