Compare commits

...

10 Commits

Author SHA1 Message Date
a9223eea7e
updates 2024-07-24 18:47:45 +02:00
3f2e7cd352 Squashed commit of the following:
commit ef058ba8e3498eb0b0606d8cf55c41a8718b7820
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Sat Dec 23 19:20:50 2023 +0200

    set version

commit 3b6cf8c60b0803b8316cdd5b0884671b980b3600
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Sat Dec 23 19:17:45 2023 +0200

    formatting

commit 218cfd137f7c1b71399036f6101b4614aaec1406
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Sat Dec 23 19:15:11 2023 +0200

    add more fields to data json

commit bdb87f634b6e1da9dba7c9fd5637731bb39df4f2
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Sat Dec 23 18:51:30 2023 +0200

    parsing json file

commit 7d83d7d85ad61bf181a30bf4a1855928f222d768
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Sat Dec 23 18:23:10 2023 +0200

    move data to json file

commit c6fcac47cbd1da4003d9c562a59f9d2075c6b789
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Fri Dec 22 20:08:29 2023 +0200

    separate files

commit 2079aa20aa78ae8c8bd4d0be2b87fa637cf2f8bb
Author: Ivan Dimitrov <ivan@idimitrov.dev>
Date:   Fri Dec 22 19:37:47 2023 +0200

    make it wrap automatically
2023-12-23 19:21:12 +02:00
54bd70ddc5 version 0.1.0 2023-12-19 16:33:47 +02:00
59e5d22a5a new shit 2023-11-29 13:06:05 +02:00
ba524cf739 arch 2023-11-21 12:41:31 +02:00
23100f656c cleanup 2023-11-17 10:39:33 +02:00
da5bd4fc83 ra case study links 2023-11-16 07:40:00 +02:00
efdc17719c certificates 2023-11-15 18:06:03 +02:00
8c7abe5818 formatting 2023-11-13 20:45:59 +02:00
8b7770a7aa refactor 2023-11-13 20:45:09 +02:00
16 changed files with 2412 additions and 474 deletions

1
.gitignore vendored
View File

@ -1,3 +1,4 @@
node_modules
outputs
pid
result

BIN
bun.lockb

Binary file not shown.

112
cv.json Normal file
View File

@ -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"
}
]
}
]
}

562
cv.tsx
View File

@ -1,453 +1,165 @@
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";
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
}
type Experience = {
company: string
position: string
location: string
from: Date
to: Date
description: string
technologies: string[]
links?: A[]
feedback?: string
}
type Education = {
institution: string
location: string
degree: string
field: string
from: Date
to: Date
summary: string
links?: A[]
}
const tw = createTw({
});
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"
const sectionStyles = "w-full flex flex-col"
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"
const divider =
const divider = (
<View style={tw("w-full mt-4")}>
<View style={tw("w-full border-slate-50 border-b-[.2px]")}></View>
</View>
);
type CV = {
name: string;
description: string;
title: string;
email: string;
github?: string;
website?: string;
upwork?: string;
phone?: string;
experience?: Exp[];
education?: Edu[];
certificates?: Cert[];
};
const github =
<Link src="https://github.com/ivandimitrov8080" style={tw(linkStyles)}>{svg([githubPath])}<Text>github/ivandimitrov8080</Text></Link>
const upwork =
<Link src="https://www.upwork.com/freelancers/idimitrov" style={tw(linkStyles)}>{svg([linkPath])}<Text>upwork/freelancers/idimitrov</Text></Link>
const resume =
<Link src="https://www.idimitrov.dev" style={tw(linkStyles)}>{svg([globePath])}<Text>idimitrov.dev</Text></Link>
const email =
<Link src="mailto:ivan@idimitrov.dev" style={tw(linkStyles)}>{svg([emailPath1, emailPath2])}<Text>ivan@idimitrov.dev</Text></Link>
let data: CV = {} as CV;
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",
"Maven",
"Gradle",
"Ant",
],
web: [
"JavaScript",
"HTML",
"CSS"
],
api: [
"REST",
"SOAP",
],
db: [
"MySQL",
"PostgreSQL"
],
linux: [
"Linux",
"Bash",
"coreutils",
"Ubuntu",
"CentOS",
"RHEL",
"SSH",
"iptables",
"systemd",
"vim",
"Monit",
"CLI",
"pandoc",
"LUKS",
"hexdump",
"dd",
],
git: [
"git",
"GitHub",
"GitLab",
"BitBucket",
],
hybris: [
"SAP hybris",
"ZK Framework",
],
payment: [
"PayPal",
"Adyen",
"V12",
"Wells Fargo Open Banking APIs",
],
dataIntegration: [
"Spring Batch",
"Data Pipeline",
"Scriptella",
"Easy Batch",
"GETL",
"Apache Camel",
"Apache Samza",
"Apache Flink",
"Apache Storm",
"Apache Spark",
"Apache NiFi",
],
python: [
"python",
"BeautifulSoup4",
"requests",
"pypandoc",
"markdownify",
"html2text",
"Poetry",
],
javascript: [
"TypeScript",
"React",
],
nextjs: [
"NextJS 12",
"NextJS 13",
"NextJS 14",
"NextAuth",
"Prisma",
"Vercel",
"Vercel Postgres",
"Formik",
"Framer Motion",
],
styles: [
"CSS",
"SASS",
"TailwindCSS",
"DaisyUI",
"tailwind-scrollbar",
"FontAwesome",
"nProgress",
],
general: [
"Markdown",
"Google",
"DuckDuckGo",
"PDF",
"Email",
],
}
const techKeys = [...Object.keys(tech)] as const
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"]
)
// @ts-ignore
.map(e => tech[e]).flat()
const raTech: string[] = filterKeysExcl(techKeys,
["dataIntegration", "python", "nextjs", "styles"]
)
// @ts-ignore
.map(e => tech[e]).flat()
const ncTech: string[] = filterKeysIncl(techKeys,
["java", "dataIntegration", "git"]
)
// @ts-ignore
.map(e => tech[e]).flat()
const hoi2Tech: string[] = filterKeysIncl(techKeys,
["python", "nextjs", "styles", "linux", "git", "general"]
)
// @ts-ignore
.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"])
const experience = ({
company,
position,
location,
from,
to,
description,
technologies,
links,
feedback
}: 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>
{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")}>
{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>
</View>
))}
</View>
</View>
</View>
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>
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 = () => (
const Cv = () => (
<Document
title="CV"
author="Ivan Kirilov Dimitrov"
author={data.name}
subject="My professional resume"
creator="Ivan Dimitrov with react-pdf"
producer="Ivan Dimitrov with react-pdf"
keywords="Ivan Dimitrov Software Developer"
creator={`${data.name} with react-pdf`}
producer={`${data.name} with react-pdf`}
keywords={`${data.name} ${data.title}`}
>
<Page
size="A4"
style={tw(pageStyles)}>
<Intro />
{divider}
<Text style={tw("text-2xl mt-2")}>Experience</Text>
<View style={tw("my-auto")}>
{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."
})}
{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"),
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"
})}
style={tw(
"w-full h-full text-slate-50 bg-gray-900 flex flex-col p-12 text-base"
)}
>
<View style={tw("text-center border-2 border-slate-50 rounded-full")}>
<Text style={tw("text-5xl")}>{data.name}</Text>
<Text>{data.title}</Text>
<View
style={tw("flex flex-row gap-4 w-full text-sm justify-center p-4")}
>
{data.github && (
<SvgLink
text="GitHub"
href={`https://github.com/${data.github}`}
icon={svg.github as any}
/>
)}
{data.upwork && (
<SvgLink
text="Upwork"
href={`https://www.upwork.com/freelancers/${data.upwork}`}
icon={svg.link as any}
/>
)}
<SvgLink
text={data.email}
href={`mailto:${data.email}`}
icon={svg.email as any}
/>
{data.phone && (
<SvgLink
text={data.phone}
href={`tel:${data.phone}`}
icon={svg.phone as any}
/>
)}
{data.website && (
<SvgLink
text={data.website}
href={`https://${data.website}`}
icon={svg.globe as any}
/>
)}
</View>
</Page>
<Page
size="A4"
style={tw(pageStyles)}
>
<View style={tw("my-auto")}>
{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.",
technologies: ncTech,
feedback: "Ivan is a talented developer and was able to understand and write about our developer framework without difficulty."
})}
{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>
</Page>
<Page
size="A4"
style={tw(pageStyles)}
>
<View style={tw("my-2")}>
<View style={tw("my-auto")}>
{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."
})}
<Text style={tw("text-2xl mt-2 text-violet-500")}>Experience</Text>
{data.experience?.map((e, i) => (
<Experience
key={i}
to={e.to}
from={e.from}
links={e.links}
company={e.company}
location={e.location}
position={e.position}
feedback={e.feedback}
description={e.description}
/>
))}
{divider}
<Text style={tw("text-2xl mt-2 text-violet-500")}>Education</Text>
{data.education?.map((e, i) => (
<Education
key={i}
to={e.to}
from={e.from}
links={e.links}
institution={e.institution}
location={e.location}
field={e.field}
degree={e.degree}
summary={e.summary}
/>
))}
{divider}
<Text style={tw("text-2xl mt-2 text-violet-500")}>Certificates</Text>
{data.certificates?.map((c, i) => (
<Certificate
key={i}
date={c.date}
links={c.links}
name={c.name}
description={c.description}
issuer={c.issuer}
/>
))}
</View>
</Page>
</Document>
);
const outDir = process.env.out || "./"
const pname = process.env.pname || "cv"
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";
if (!fs.existsSync(outDir)) {
fs.mkdirSync(outDir, { recursive: true })
fs.mkdirSync(outDir, { recursive: true });
}
ReactPDF.render(<CV />, `${outDir}/${pname}.pdf`);
parseData();
ReactPDF.render(<Cv />, `${outDir}/${pname}.pdf`);

View File

@ -1,17 +1,169 @@
{
"nodes": {
"devshell": {
"inputs": {
"flake-utils": "flake-utils_2",
"nixpkgs": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717408969,
"narHash": "sha256-Q0OEFqe35fZbbRPPRdrjTUUChKVhhWXz3T9ZSKmaoVY=",
"owner": "numtide",
"repo": "devshell",
"rev": "1ebbe68d57457c8cae98145410b164b5477761f4",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "devshell",
"type": "github"
}
},
"flake-compat": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_2": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-compat_3": {
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"revCount": 57,
"type": "tarball",
"url": "https://api.flakehub.com/f/pinned/edolstra/flake-compat/1.0.1/018afb31-abd1-7bff-a5e4-cff7e18efb7a/source.tar.gz"
},
"original": {
"type": "tarball",
"url": "https://flakehub.com/f/edolstra/flake-compat/1.tar.gz"
}
},
"flake-compat_4": {
"flake": false,
"locked": {
"lastModified": 1696426674,
"narHash": "sha256-kvjfFW7WAETZlt09AgDn1MrtKzP7t90Vf7vypd3OL1U=",
"owner": "edolstra",
"repo": "flake-compat",
"rev": "0f9255e01c2351cc7d116c072cb317785dd33b33",
"type": "github"
},
"original": {
"owner": "edolstra",
"repo": "flake-compat",
"type": "github"
}
},
"flake-parts": {
"inputs": {
"nixpkgs-lib": [
"ide",
"neovim-nightly-overlay",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717285511,
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-parts_2": {
"inputs": {
"nixpkgs-lib": [
"ide",
"neovim-nightly-overlay",
"hercules-ci-effects",
"nixpkgs"
]
},
"locked": {
"lastModified": 1712014858,
"narHash": "sha256-sB4SWl2lX95bExY2gMFG5HIzvva5AVMJd4Igm+GpZNw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "9126214d0a59633752a136528f5f3b9aa8565b7d",
"type": "github"
},
"original": {
"id": "flake-parts",
"type": "indirect"
}
},
"flake-parts_3": {
"inputs": {
"nixpkgs-lib": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1717285511,
"narHash": "sha256-iKzJcpdXih14qYVcZ9QC9XuZYnPc6T8YImb6dX166kw=",
"owner": "hercules-ci",
"repo": "flake-parts",
"rev": "2a55567fcf15b1b1c7ed712a2c6fadaec7412ea8",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "flake-parts",
"type": "github"
}
},
"flake-utils": {
"inputs": {
"systems": [
"ide",
"systems"
]
},
"locked": {
"lastModified": 1694529238,
"narHash": "sha256-zsNZZGTGnMOf9YpHKJqMSsa0dXbfmxeoJ7xHlrt+xmY=",
"lastModified": 1710146030,
"narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "ff7b65b44d01cf9ba6a71320833626af21126384",
"rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
"type": "github"
},
"original": {
@ -20,29 +172,327 @@
"type": "github"
}
},
"flake-utils_2": {
"inputs": {
"systems": "systems"
},
"locked": {
"lastModified": 1701680307,
"narHash": "sha256-kAuep2h5ajznlPMD9rnQyffWG8EM/C73lejGofXvdM8=",
"owner": "numtide",
"repo": "flake-utils",
"rev": "4022d587cbbfd70fe950c1e2083a02621806a725",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "flake-utils",
"type": "github"
}
},
"git-hooks": {
"inputs": {
"flake-compat": "flake-compat_2",
"gitignore": "gitignore",
"nixpkgs": [
"ide",
"neovim-nightly-overlay",
"nixpkgs"
],
"nixpkgs-stable": [
"ide",
"neovim-nightly-overlay",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718879355,
"narHash": "sha256-RTyqP4fBX2MdhNuMP+fnR3lIwbdtXhyj7w7fwtvgspc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "8cd35b9496d21a6c55164d8547d9d5280162b07a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"git-hooks_2": {
"inputs": {
"flake-compat": "flake-compat_4",
"gitignore": "gitignore_2",
"nixpkgs": [
"ide",
"nixvim",
"nixpkgs"
],
"nixpkgs-stable": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718879355,
"narHash": "sha256-RTyqP4fBX2MdhNuMP+fnR3lIwbdtXhyj7w7fwtvgspc=",
"owner": "cachix",
"repo": "git-hooks.nix",
"rev": "8cd35b9496d21a6c55164d8547d9d5280162b07a",
"type": "github"
},
"original": {
"owner": "cachix",
"repo": "git-hooks.nix",
"type": "github"
}
},
"gitignore": {
"inputs": {
"nixpkgs": [
"ide",
"neovim-nightly-overlay",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"gitignore_2": {
"inputs": {
"nixpkgs": [
"ide",
"nixvim",
"git-hooks",
"nixpkgs"
]
},
"locked": {
"lastModified": 1709087332,
"narHash": "sha256-HG2cCnktfHsKV0s4XW83gU3F57gaTljL9KNSuG6bnQs=",
"owner": "hercules-ci",
"repo": "gitignore.nix",
"rev": "637db329424fd7e46cf4185293b9cc8c88c95394",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "gitignore.nix",
"type": "github"
}
},
"hercules-ci-effects": {
"inputs": {
"flake-parts": "flake-parts_2",
"nixpkgs": [
"ide",
"neovim-nightly-overlay",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718018037,
"narHash": "sha256-03rLBd/lKecgaKz0j5ESUf9lDn5R0SJatZTKLL5unWE=",
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"rev": "0ab08b23ce3c3f75fe9a5598756b6fb8bcf0b414",
"type": "github"
},
"original": {
"owner": "hercules-ci",
"repo": "hercules-ci-effects",
"type": "github"
}
},
"home-manager": {
"inputs": {
"nixpkgs": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719037157,
"narHash": "sha256-aOKd8+mhBsLQChCu1mn/W5ww79ta5cXVE59aJFrifM8=",
"owner": "nix-community",
"repo": "home-manager",
"rev": "cd886711998fe5d9ff7979fdd4b4cbd17b1f1511",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "home-manager",
"type": "github"
}
},
"ide": {
"inputs": {
"flake-utils": "flake-utils",
"neovim-nightly-overlay": "neovim-nightly-overlay",
"nixpkgs": [
"nixpkgs"
],
"nixvim": "nixvim",
"systems": "systems_2"
},
"locked": {
"lastModified": 1720387774,
"narHash": "sha256-vbdLOPW2s5HZ/aRJl2GtcL1d4racetoPRn6W7dGVl+E=",
"owner": "ivandimitrov8080",
"repo": "flake-ide",
"rev": "7194c89a92430d755aabd11d2eae25d13b6e8f00",
"type": "github"
},
"original": {
"owner": "ivandimitrov8080",
"repo": "flake-ide",
"type": "github"
}
},
"neovim-nightly-overlay": {
"inputs": {
"flake-compat": "flake-compat",
"flake-parts": "flake-parts",
"git-hooks": "git-hooks",
"hercules-ci-effects": "hercules-ci-effects",
"neovim-src": "neovim-src",
"nixpkgs": [
"ide",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719211247,
"narHash": "sha256-GaEckCf2RaHzoEDj/j07BIV6eyDOT5wCFVSdbbkZ87U=",
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"rev": "820da0e6b6127df9ad05ef3af40d767577e21ba1",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "neovim-nightly-overlay",
"type": "github"
}
},
"neovim-src": {
"flake": false,
"locked": {
"lastModified": 1719138008,
"narHash": "sha256-+rM0RjvuW6/vzxdJxEU6KvQEF159NXrgB+irtS044Cc=",
"owner": "neovim",
"repo": "neovim",
"rev": "be999e6a0e5b251b2b37500d06636d4167334c6e",
"type": "github"
},
"original": {
"owner": "neovim",
"repo": "neovim",
"type": "github"
}
},
"nix-darwin": {
"inputs": {
"nixpkgs": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1719128254,
"narHash": "sha256-I7jMpq0CAOZA/i70+HDQO/ulLttyQu/K70cSESiMX7A=",
"owner": "lnl7",
"repo": "nix-darwin",
"rev": "50581970f37f06a4719001735828519925ef8310",
"type": "github"
},
"original": {
"owner": "lnl7",
"repo": "nix-darwin",
"type": "github"
}
},
"nixpkgs": {
"locked": {
"lastModified": 1699806023,
"narHash": "sha256-RqoU2yh61nzsfbtgjZwIrCMPwyxkUR7Eze+4hjEM6TY=",
"lastModified": 1721562059,
"narHash": "sha256-Tybxt65eyOARf285hMHIJ2uul8SULjFZbT9ZaEeUnP8=",
"owner": "NixOS",
"repo": "nixpkgs",
"rev": "54da2986be07ad2ffbade0b3cefaf7f0c2553c88",
"rev": "68c9ed8bbed9dfce253cc91560bf9043297ef2fe",
"type": "github"
},
"original": {
"owner": "NixOS",
"ref": "nixos-unstable",
"repo": "nixpkgs",
"type": "github"
}
},
"nixvim": {
"inputs": {
"devshell": "devshell",
"flake-compat": "flake-compat_3",
"flake-parts": "flake-parts_3",
"git-hooks": "git-hooks_2",
"home-manager": "home-manager",
"nix-darwin": "nix-darwin",
"nixpkgs": [
"ide",
"nixpkgs"
],
"treefmt-nix": "treefmt-nix"
},
"locked": {
"lastModified": 1719228487,
"narHash": "sha256-eJUcZAjOcGAoh97ZRsy+ls8IkHPMpDuh0IpRKSmoWs4=",
"owner": "nix-community",
"repo": "nixvim",
"rev": "66c8592b31845cb0a1335ecc31ea40e89bed1a38",
"type": "github"
},
"original": {
"owner": "nix-community",
"repo": "nixvim",
"type": "github"
}
},
"root": {
"inputs": {
"flake-utils": "flake-utils",
"nixpkgs": "nixpkgs",
"systems": "systems"
"ide": "ide",
"nixpkgs": "nixpkgs"
}
},
"systems": {
"locked": {
"lastModified": 1681028828,
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
"owner": "nix-systems",
"repo": "default",
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
"type": "github"
},
"original": {
"owner": "nix-systems",
"repo": "default",
"type": "github"
}
},
"systems_2": {
"locked": {
"lastModified": 1680978846,
"narHash": "sha256-Gtqg8b/v49BFDpDetjclCYXm8mAnTrUzR0JnE2nv5aw=",
@ -56,6 +506,28 @@
"repo": "x86_64-linux",
"type": "github"
}
},
"treefmt-nix": {
"inputs": {
"nixpkgs": [
"ide",
"nixvim",
"nixpkgs"
]
},
"locked": {
"lastModified": 1718522839,
"narHash": "sha256-ULzoKzEaBOiLRtjeY3YoGFJMwWSKRYOic6VNw2UyTls=",
"owner": "numtide",
"repo": "treefmt-nix",
"rev": "68eb1dc333ce82d0ab0c0357363ea17c31ea1f81",
"type": "github"
},
"original": {
"owner": "numtide",
"repo": "treefmt-nix",
"type": "github"
}
}
},
"root": "root",

View File

@ -1,28 +1,40 @@
{
description = "CV template";
inputs = {
nixpkgs.url = "github:NixOS/nixpkgs";
systems.url = "github:nix-systems/x86_64-linux";
flake-utils = {
url = "github:numtide/flake-utils";
inputs.systems.follows = "systems";
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
ide = {
url = "github:ivandimitrov8080/flake-ide";
inputs.nixpkgs.follows = "nixpkgs";
};
};
outputs = { self, nixpkgs, flake-utils, systems }:
flake-utils.lib.eachDefaultSystem (system:
outputs = { nixpkgs, ide, ... }:
let
pkgs = nixpkgs.legacyPackages.${system};
pname = "cv";
version = "0.0.1";
src = ./.;
buildInputs = with pkgs; [
system = "x86_64-linux";
pkgs = import nixpkgs {
inherit system; overlays = [
(final: prev: {
nvim = ide.nvim.${system}.standalone.default {
plugins = {
lsp.servers = {
tsserver.enable = true;
jsonls.enable = true;
tailwindcss.enable = true;
};
};
};
})
];
nativeBuildInputs = with pkgs; [
nodejs_20
};
pname = "cv";
version = "0.1.1";
src = ./.;
in
{
devShells.${system}.default = pkgs.mkShell {
inherit pname;
buildInputs = with pkgs; [
nvim
bun
inotify-tools
];
shellHook = ''
echo "$$" > ./pid
@ -35,16 +47,24 @@
}
monitor > /dev/null 2>&1 &
'';
in
{
devShells.default = pkgs.mkShell {
inherit pname buildInputs nativeBuildInputs shellHook;
};
packages.default = pkgs.stdenv.mkDerivation {
inherit buildInputs nativeBuildInputs pname version src;
packages.${system}.default = pkgs.mkYarnPackage {
inherit pname version src;
nativeBuildInputs = [ pkgs.bun ];
offlineCache = pkgs.fetchYarnDeps {
yarnLock = "${src}/yarn.lock";
hash = "sha256-HKoIv3YMKy5uZ/wT2grg+Z6k4CMykY9dj/okfA2uias=";
};
buildPhase = ''
yarn --offline build
'';
doDist = false;
postInstall = ''
ls -alh
rm -rf $out/lib
rm -rf $out/libexec
rm -rf $out/bin
'';
};
};
});
}

View File

@ -2,12 +2,12 @@ main = $(pname).tsx
default: all
all:
-bun $(main)
bun $(main)
clean:
rm -f $(pname)
install: $(pname)
mkdir -p $(out)/bin
install $(pname).pdf $(out)/bin/$(pname)
mkdir -p $(out)
install $(pname).pdf $(out)/$(pname)

View File

@ -1,18 +1,26 @@
{
"name": "cv",
"version": "0.1.1",
"module": "cv.tsx",
"type": "module",
"devDependencies": {
"bun-types": "latest"
"@react-pdf/renderer": "latest",
"@types/node": "latest",
"@types/react": "latest",
"bun-types": "latest",
"react": "latest",
"react-pdf-tailwind": "latest"
},
"scripts": {
"build": "bun cv.tsx"
},
"peerDependencies": {
"typescript": "^5.0.0"
"typescript": "latest"
},
"dependencies": {
"@react-pdf/renderer": "^3.1.14",
"@types/node": "^20.9.0",
"@types/react": "^18.2.37",
"react": "^18.2.0",
"react-pdf-tailwind": "^2.1.0"
"prettier": {
"trailingComma": "es5",
"tabWidth": 2,
"semi": true,
"singleQuote": false
}
}

117
tech.ts Normal file
View File

@ -0,0 +1,117 @@
const tech = {
android: ["Android", "Android Studio"],
architectures: [
"Microservices",
"MVC",
"Layered architecture",
"DDD",
"EDA",
"Publish-subscribe",
"Client-server",
"REST",
"Pipes and filters",
],
java: [
"Java",
"JPA",
"Hibernate",
"Spring Framework",
"Spring Boot",
"Lombok",
"Spring MVC",
"Thymeleaf",
"JSP",
"JSTL",
"XML",
"Spring Security",
"OAuth2",
"H2",
"Spring Boot Actuator",
"Maven",
"Gradle",
"Ant",
],
web: ["JavaScript", "HTML", "CSS"],
api: ["REST", "SOAP"],
db: ["MySQL", "PostgreSQL"],
linux: [
"Linux",
"Bash",
"coreutils",
"Ubuntu",
"CentOS",
"RHEL",
"SSH",
"iptables",
"systemd",
"vim",
"Monit",
"CLI",
"pandoc",
"LUKS",
"hexdump",
"dd",
],
git: ["git", "GitHub", "GitLab", "BitBucket"],
hybris: ["SAP hybris", "ZK Framework"],
payment: ["PayPal", "Adyen", "V12", "Wells Fargo Open Banking APIs"],
dataIntegration: [
"Spring Batch",
"Data Pipeline",
"Scriptella",
"Easy Batch",
"GETL",
"Apache Camel",
"Apache Samza",
"Apache Flink",
"Apache Storm",
"Apache Spark",
"Apache NiFi",
],
python: [
"python",
"BeautifulSoup4",
"requests",
"pypandoc",
"markdownify",
"html2text",
"Poetry",
],
javascript: ["TypeScript", "React"],
nextjs: [
"NextJS 12",
"NextJS 13",
"NextJS 14",
"NextAuth",
"Prisma",
"Vercel",
"Vercel Postgres",
"Formik",
"Framer Motion",
],
styles: [
"CSS",
"SASS",
"TailwindCSS",
"DaisyUI",
"tailwind-scrollbar",
"FontAwesome",
"nProgress",
],
general: ["Markdown", "Google", "DuckDuckGo", "PDF", "Email"],
} as const;
const techKeys = Object.keys(tech) as Array<keyof typeof tech>;
type TechKeys = keyof typeof tech;
export const skills = (skills: TechKeys[]): string[] => {
return skills.map((s) => tech[s]).flat();
};
export const skillsInverted = (skills: TechKeys[]): string[] => {
return techKeys
.filter((k) => !skills.includes(k))
.map((s) => tech[s])
.flat();
};

51
theme/certificate.tsx Normal file
View File

@ -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 (
<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("flex flex-row flex-wrap gap-1")}>
<Text style={tw("text-amber-500")}>{name}</Text>
<Text>from</Text>
<Text style={tw("text-blue-500")}>{issuer}</Text>
<Text>on</Text>
<Text style={tw("text-lime-500")}>{date.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")}>
<SvgLink text={l.text} href={l.href} icon={l.icon} />
</View>
))}
</View>
)}
<View style={tw("m-4")}>
<Text style={tw("text-neutral-400")}>{description}</Text>
</View>
</View>
</View>
);
}

63
theme/education.tsx Normal file
View File

@ -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 (
<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("flex flex-row flex-wrap gap-1")}>
<Text style={tw("text-amber-500")}>
Studied {degree} of {field}
</Text>
<Text>at</Text>
<Text style={tw("text-blue-500")}>
{institution}, {location}
</Text>
<Text>from</Text>
<Text style={tw("text-lime-500")}>{from.toDateString()}</Text>
<Text>to</Text>
<Text style={tw("text-lime-500")}>{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")}>
<SvgLink text={l.text} href={l.href} icon={l.icon} />
</View>
))}
</View>
)}
<View style={tw("m-4")}>
<Text style={tw("text-neutral-400")}>{summary}</Text>
</View>
</View>
</View>
);
}

72
theme/experience.tsx Normal file
View File

@ -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 (
<View wrap={false} 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("flex flex-row flex-wrap gap-1")}>
<Text style={tw("text-amber-500")}>{position}</Text>
<Text>at</Text>
<Text style={tw("text-blue-500")}>
{company}, {location}
</Text>
<Text>from</Text>
<Text style={tw("text-lime-500")}>{from.toDateString()}</Text>
<Text>to</Text>
<Text style={tw("text-lime-500")}>
{to > new Date() ? "present" : 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 text-sm")}>
<SvgLink text={l.text} href={l.href} icon={l.icon} />
</View>
))}
</View>
)}
<View style={tw("m-4")}>
<Text style={tw("text-neutral-400")}>{description}</Text>
</View>
{feedback && (
<Text style={tw("w-full m-auto mb-1")}>
<Text style={tw("text-amber-500")}>let </Text>
<Text>clientFeedback</Text>
<Text style={tw("text-amber-500")}> = </Text>
<Text style={tw("text-green-500")}>"{feedback}"</Text>
<Text>;</Text>
</Text>
)}
</View>
</View>
);
}

30
theme/lib.tsx Normal file
View File

@ -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[]) => (
<Svg style={tw("w-4 h-4")} viewBox="0 0 19 19">
{paths.map((p) => (
<Path key={p} fill="#99f6e4" d={p} />
))}
</Svg>
);
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",
]),
};

14
theme/link.tsx Normal file
View File

@ -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 (
<Link
src={href}
style={tw("no-underline text-slate-50 flex flex-row gap-1")}
>
{icon || svg.link}
<Text>{text}</Text>
</Link>
);
}

5
theme/types.d.ts vendored Normal file
View File

@ -0,0 +1,5 @@
type A = {
text: string;
href: string;
icon?: string[];
};

1261
yarn.lock Normal file

File diff suppressed because it is too large Load Diff