From 59a3056c0b8343fc0d7ce42192bc6e623a922bb6 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Sat, 9 Mar 2024 22:45:57 +0100 Subject: [PATCH 1/6] fixed cache not revalidating at all --- package.json | 12 +++++----- src/utils/landing.ts | 56 +++++++++++++++++++++++--------------------- yarn.lock | 18 +++++++------- 3 files changed, 44 insertions(+), 42 deletions(-) diff --git a/package.json b/package.json index 8446bd8..587b9e0 100644 --- a/package.json +++ b/package.json @@ -19,6 +19,7 @@ "@nextui-org/react": "^2.2.10", "@prisma/client": "4.10.1", "@tanstack/react-query": "^4.24.9", + "autoprefixer": "^10.4.18", "axios": "^1.1.2", "bcrypt": "^5.1.0", "framer-motion": "^11.0.8", @@ -26,22 +27,21 @@ "iron-session": "^6.3.1", "next": "^14.1.1", "next-themes": "^0.2.1", + "postcss": "^8.4.35", "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "tailwindcss": "^3.4.1", "timeago.js": "^4.0.2", - "zod": "^3.20.6", - "autoprefixer": "^10.4.18", - "postcss": "^8.4.35", - "tailwindcss": "^3.4.1" + "zod": "^3.20.6" }, "devDependencies": { "@trivago/prettier-plugin-sort-imports": "^4.0.0", "@types/bcrypt": "^5.0.0", "@types/fs-extra": "^11.0.4", "@types/node": "^15.12.1", - "@types/react": "^17.0.9", - "@types/react-dom": "^17.0.6", + "@types/react": "^18.2.64", + "@types/react-dom": "^18.2.21", "eslint": "^7.28.0", "eslint-config-next": "13.1.6", "prettier": "^2.3.1", diff --git a/src/utils/landing.ts b/src/utils/landing.ts index 174a2e9..45478b7 100644 --- a/src/utils/landing.ts +++ b/src/utils/landing.ts @@ -3,44 +3,46 @@ import path from "path"; import { avatarFileFormat } from "./constants"; +import { cache } from "react"; + import Landing, { LandingModel } from "@models/landing"; import exists from "@utils/fileExists"; -export const readLandingJson = async ( - dataDirLocation: string -): Promise => { - const landingJsonLocation = path.join(dataDirLocation, "landing.json"); +export const readLandingJson = cache( + async (dataDirLocation: string): Promise => { + const landingJsonLocation = path.join(dataDirLocation, "landing.json"); - const fileExists = await exists(landingJsonLocation); + const fileExists = await exists(landingJsonLocation); - if (!fileExists) { - throw new Error( - `Could not find landing json file at: ${landingJsonLocation}` - ); - } + if (!fileExists) { + throw new Error( + `Could not find landing json file at: ${landingJsonLocation}` + ); + } - const rawJson: unknown = await readJson(landingJsonLocation); + const rawJson: unknown = await readJson(landingJsonLocation); - const landingResult = LandingModel.safeParse(rawJson); + const landingResult = LandingModel.safeParse(rawJson); - if (!landingResult.success) - throw new Error(`Failed to parse landing json: ${landingResult.error}`); + if (!landingResult.success) + throw new Error(`Failed to parse landing json: ${landingResult.error}`); - return landingResult.data; -}; + return landingResult.data; + } +); -export const readAvatarFile = async ( - dataDirLocation: string -): Promise => { - const avatarFileLocation = path.join( - dataDirLocation, - `avatar.${avatarFileFormat}` - ); +export const readAvatarFile = cache( + async (dataDirLocation: string): Promise => { + const avatarFileLocation = path.join( + dataDirLocation, + `avatar.${avatarFileFormat}` + ); - const imageData = await readFile(avatarFileLocation); + const imageData = await readFile(avatarFileLocation); - const base64Image = Buffer.from(imageData).toString("base64"); + const base64Image = Buffer.from(imageData).toString("base64"); - return `data:image/${avatarFileFormat};base64,${base64Image}`; -}; + return `data:image/${avatarFileFormat};base64,${base64Image}`; + } +); diff --git a/yarn.lock b/yarn.lock index af94aae..9b97a51 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2279,17 +2279,17 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/react-dom@^17.0.6": - version "17.0.25" - resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-17.0.25.tgz#e0e5b3571e1069625b3a3da2b279379aa33a0cb5" - integrity sha512-urx7A7UxkZQmThYA4So0NelOVjx3V4rNFVJwp0WZlbIK5eM4rNJDiN3R/E9ix0MBh6kAEojk/9YL+Te6D9zHNA== +"@types/react-dom@^18.2.21": + version "18.2.21" + resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.2.21.tgz#b8c81715cebdebb2994378616a8d54ace54f043a" + integrity sha512-gnvBA/21SA4xxqNXEwNiVcP0xSGHh/gi1VhWv9Bl46a0ItbTT5nFY+G9VSQpaG/8N/qdJpJ+vftQ4zflTtnjLw== dependencies: - "@types/react" "^17" + "@types/react" "*" -"@types/react@^17", "@types/react@^17.0.9": - version "17.0.75" - resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.75.tgz#cffbc76840a12fcadaf5a3cf14878bb06efcf73d" - integrity sha512-MSA+NzEzXnQKrqpO63CYqNstFjsESgvJAdAyyJ1n6ZQq/GLgf6nOfIKwk+Twuz0L1N6xPe+qz5xRCJrbhMaLsw== +"@types/react@*", "@types/react@^18.2.64": + version "18.2.64" + resolved "https://registry.yarnpkg.com/@types/react/-/react-18.2.64.tgz#3700fbb6b2fa60a6868ec1323ae4cbd446a2197d" + integrity sha512-MlmPvHgjj2p3vZaxbQgFUQFvD8QiZwACfGqEdDSWou5yISWxDQ4/74nCAwsUiX7UFLKZz3BbVSPj+YxeoGGCfg== dependencies: "@types/prop-types" "*" "@types/scheduler" "*" From 5493f0c140900999957eef1013ca592650e39ab2 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Sun, 10 Mar 2024 01:35:10 +0100 Subject: [PATCH 2/6] added basic cv page --- data/cv.json | 56 +++++++++++++++ package.json | 2 + public/cv.jpg | Bin 0 -> 31781 bytes src/app/(landing)/Header.tsx | 34 ++++----- src/app/cv/Cv.tsx | 136 +++++++++++++++++++++++++++++++++++ src/app/cv/page.tsx | 12 ++++ src/models/cv.ts | 46 ++++++++++++ src/utils/cv.ts | 16 +++++ src/utils/json.ts | 23 ++++++ src/utils/landing.ts | 20 +----- yarn.lock | 10 +++ 11 files changed, 318 insertions(+), 37 deletions(-) create mode 100644 data/cv.json create mode 100644 public/cv.jpg create mode 100644 src/app/cv/Cv.tsx create mode 100644 src/app/cv/page.tsx create mode 100644 src/models/cv.ts create mode 100644 src/utils/cv.ts create mode 100644 src/utils/json.ts diff --git a/data/cv.json b/data/cv.json new file mode 100644 index 0000000..7fbefdb --- /dev/null +++ b/data/cv.json @@ -0,0 +1,56 @@ +{ + "fullName": "Guus van Meerveld", + "role": "Computer programmer", + "description": "As a computer programmer working with precision and being methodical are of incredible importance, as a simple minor oversight can change people's lives. This is exactly the way I prefer to work. Methodically approaching a problem, taking it apart step by step and finding an optimal, yet creative solution.", + "contact": { + "website": "https://guusvanmeerveld.dev", + "email": "contact@guusvanmeerveld.dev", + "linkedIn": "https://linkedin.com/in/guus-van-meerveld-038357210", + "git": "https://github.com/guusvanmeerveld" + }, + "skills": [ + { + "name": "DevOps", + "year": "1/1/2022", + "value": 0.8 + }, + { + "name": "Linux", + "year": "1/1/2020", + "value": 0.9 + }, + { + "name": "Web development", + "year": "1/1/2017", + "value": 0.7 + } + ], + "programmingLanguages": [ + { + "name": "Rust", + "year": "1/1/2022", + "value": 0.8 + }, + { "name": "Typescript & Javascript", "value": 0.9, "year": "1/1/2017" }, + { "name": "Java", "value": 0.6, "year": "1/8/2022" }, + { "name": "Python", "value": 0.6, "year": "1/2/2023" }, + { "name": "Scala", "value": 0.5, "year": "1/8/2023" } + ], + "education": [ + { + "title": "Bachelor Artificial Intelligence", + "timeFrame": "2022 - Present", + "institution": "Radboud University", + "location": "Nijmegen, Netherlands", + "skills": ["Mathematics", "Neuroscience", "Computer science"] + }, + { + "title": "VWO (NT&G)", + "timeFrame": "2016 - 2022", + "institution": "RSG Pantarijn MHV Wageningen", + "location": "Wageningen, Netherlands", + "skills": ["Biology", "Physics", "Mathematics"] + } + ], + "experience": [] +} diff --git a/package.json b/package.json index 587b9e0..983d39e 100644 --- a/package.json +++ b/package.json @@ -24,6 +24,7 @@ "bcrypt": "^5.1.0", "framer-motion": "^11.0.8", "fs-extra": "^11.2.0", + "humanize-duration": "^3.31.0", "iron-session": "^6.3.1", "next": "^14.1.1", "next-themes": "^0.2.1", @@ -39,6 +40,7 @@ "@trivago/prettier-plugin-sort-imports": "^4.0.0", "@types/bcrypt": "^5.0.0", "@types/fs-extra": "^11.0.4", + "@types/humanize-duration": "^3.27.4", "@types/node": "^15.12.1", "@types/react": "^18.2.64", "@types/react-dom": "^18.2.21", diff --git a/public/cv.jpg b/public/cv.jpg new file mode 100644 index 0000000000000000000000000000000000000000..89ae87061dc05afb3cce84469a6c1ef3516cab2d GIT binary patch literal 31781 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 = ({ {socials.map((social) => ( - - - + + + + + ))} diff --git a/src/app/cv/Cv.tsx b/src/app/cv/Cv.tsx new file mode 100644 index 0000000..2161248 --- /dev/null +++ b/src/app/cv/Cv.tsx @@ -0,0 +1,136 @@ +"use client"; + +import { Card, CardBody } from "@nextui-org/card"; +import { Divider } from "@nextui-org/divider"; +import { Image } from "@nextui-org/image"; +import { Listbox, ListboxItem } from "@nextui-org/listbox"; +import { Progress } from "@nextui-org/progress"; +import { Spacer } from "@nextui-org/spacer"; +import { Component } from "@typings/component"; +import humanizeDuration from "humanize-duration"; +import NextImage from "next/image"; + +import CvProps, { + Education as EducationProps, + Skill as SkillProps +} from "@models/cv"; + +const Skill: Component<{ skill: SkillProps }> = ({ skill }) => { + const duration = new Date().getTime() - skill.year.getTime(); + + const durationInYears = humanizeDuration(duration, { + units: ["y"], + round: true + }); + + return ( +
+ +
+ ); +}; + +const Education: Component<{ education: EducationProps }> = ({ education }) => { + return ( + + +
+
+

{education.title}

+

+ {education.timeFrame} +

+
+
+

{education.institution}

+

+ {education.location} +

+
+
+ + + + + {education.skills.map((skill) => ( + {skill} + ))} + +
+
+ ); +}; + +export const Cv: Component<{ data: CvProps }> = ({ data }) => { + return ( +
+
+ {`Professional + + + +
+
+

{data.fullName}

+ +

{data.role}

+
+
+
+ + + +

Professional profile

+

{data.description}

+ + + +
+
+

Skills

+ + {data.skills.map((skill) => ( + + ))} + + + +

Programming Languages

+ + {data.programmingLanguages.map((skill) => ( + + ))} + +
+ +
+

Education

+ + {data.education.map((education) => { + return ( + <> + + + + ); + })} +
+
+
+ ); +}; diff --git a/src/app/cv/page.tsx b/src/app/cv/page.tsx new file mode 100644 index 0000000..ebaaed5 --- /dev/null +++ b/src/app/cv/page.tsx @@ -0,0 +1,12 @@ +import { Cv } from "./Cv"; + +import { dataDirLocation } from "@utils/constants"; +import { readCvJson } from "@utils/cv"; + +export default async function Page() { + const cv = await readCvJson(dataDirLocation); + + return ; +} + +export const revalidate = 3600; diff --git a/src/models/cv.ts b/src/models/cv.ts new file mode 100644 index 0000000..a7d05a3 --- /dev/null +++ b/src/models/cv.ts @@ -0,0 +1,46 @@ +import z from "zod"; + +const SkillModel = z.object({ + name: z.string(), + year: z.coerce.date(), + value: z.number().min(0).max(1) +}); + +export type Skill = z.infer; + +const EducationModel = z.object({ + title: z.string(), + timeFrame: z.string(), + institution: z.string(), + location: z.string(), + skills: z.string().array() +}); + +export type Education = z.infer; + +const ExperienceModel = z.object({ + title: z.string(), + timeFrame: z.string(), + role: z.string(), + description: z.string() +}); + +export const CvPropsModel = z.object({ + fullName: z.string(), + role: z.string(), + description: z.string(), + contact: z.object({ + website: z.string(), + email: z.string().email(), + linkedIn: z.string().url(), + git: z.string().url() + }), + skills: SkillModel.array(), + programmingLanguages: SkillModel.array(), + education: EducationModel.array(), + experience: ExperienceModel.array() +}); + +export type CvProps = z.infer; + +export default CvProps; diff --git a/src/utils/cv.ts b/src/utils/cv.ts new file mode 100644 index 0000000..266bc5a --- /dev/null +++ b/src/utils/cv.ts @@ -0,0 +1,16 @@ +import { readJson } from "fs-extra"; +import path from "path"; + +import { readAndParseJsonFile } from "./json"; + +import { cache } from "react"; + +import CvProps, { CvPropsModel } from "@models/cv"; + +export const readCvJson = cache( + async (dataDirLocation: string): Promise => { + const cvJsonLocation = path.join(dataDirLocation, "cv.json"); + + return await readAndParseJsonFile(cvJsonLocation, CvPropsModel); + } +); diff --git a/src/utils/json.ts b/src/utils/json.ts new file mode 100644 index 0000000..85e52d1 --- /dev/null +++ b/src/utils/json.ts @@ -0,0 +1,23 @@ +import { readJson } from "fs-extra"; +import z from "zod"; + +import exists from "@utils/fileExists"; + +export const readAndParseJsonFile = async ( + location: string, + model: z.ZodType +): Promise => { + const fileExists = await exists(location); + + if (!fileExists) { + throw new Error(`Could not find json file at: ${location}`); + } + + const rawJson: unknown = await readJson(location); + + const result = model.safeParse(rawJson); + + if (!result.success) throw new Error(`Failed to parse json: ${result.error}`); + + return result.data; +}; diff --git a/src/utils/landing.ts b/src/utils/landing.ts index 45478b7..debd293 100644 --- a/src/utils/landing.ts +++ b/src/utils/landing.ts @@ -2,33 +2,17 @@ import { readFile, readJson } from "fs-extra"; import path from "path"; import { avatarFileFormat } from "./constants"; +import { readAndParseJsonFile } from "./json"; import { cache } from "react"; import Landing, { LandingModel } from "@models/landing"; -import exists from "@utils/fileExists"; - export const readLandingJson = cache( async (dataDirLocation: string): Promise => { const landingJsonLocation = path.join(dataDirLocation, "landing.json"); - const fileExists = await exists(landingJsonLocation); - - if (!fileExists) { - throw new Error( - `Could not find landing json file at: ${landingJsonLocation}` - ); - } - - const rawJson: unknown = await readJson(landingJsonLocation); - - const landingResult = LandingModel.safeParse(rawJson); - - if (!landingResult.success) - throw new Error(`Failed to parse landing json: ${landingResult.error}`); - - return landingResult.data; + return await readAndParseJsonFile(landingJsonLocation, LandingModel); } ); diff --git a/yarn.lock b/yarn.lock index 9b97a51..750bce3 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2199,6 +2199,11 @@ resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== +"@types/humanize-duration@^3.27.4": + version "3.27.4" + resolved "https://registry.yarnpkg.com/@types/humanize-duration/-/humanize-duration-3.27.4.tgz#51d6d278213374735440bc3749de920935e9127e" + integrity sha512-yaf7kan2Sq0goxpbcwTQ+8E9RP6HutFBPv74T/IA/ojcHKhuKVlk2YFYyHhWZeLvZPzzLE3aatuQB4h0iqyyUA== + "@types/json5@^0.0.29": version "0.0.29" resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" @@ -3732,6 +3737,11 @@ https-proxy-agent@^5.0.0: agent-base "6" debug "4" +humanize-duration@^3.31.0: + version "3.31.0" + resolved "https://registry.yarnpkg.com/humanize-duration/-/humanize-duration-3.31.0.tgz#a0384d22555024cd17e6e9f8561540d37756bf4c" + integrity sha512-fRrehgBG26NNZysRlTq1S+HPtDpp3u+Jzdc/d5A4cEzOD86YLAkDaJyJg8krSdCi7CJ+s7ht3fwRj8Dl+Btd0w== + ieee754@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/ieee754/-/ieee754-1.2.1.tgz#8eb7a10a63fff25d15a57b001586d177d1b0d352" From 35f1c82168291944170368fe295fadd5eb9a6290 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Sun, 10 Mar 2024 01:36:46 +0100 Subject: [PATCH 3/6] fix: missing key on list in cv --- src/app/cv/Cv.tsx | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/app/cv/Cv.tsx b/src/app/cv/Cv.tsx index 2161248..4e03ff7 100644 --- a/src/app/cv/Cv.tsx +++ b/src/app/cv/Cv.tsx @@ -10,6 +10,8 @@ import { Component } from "@typings/component"; import humanizeDuration from "humanize-duration"; import NextImage from "next/image"; +import { Fragment } from "react"; + import CvProps, { Education as EducationProps, Skill as SkillProps @@ -112,6 +114,7 @@ export const Cv: Component<{ data: CvProps }> = ({ data }) => { {data.programmingLanguages.map((skill) => ( ))} + @@ -120,13 +123,10 @@ export const Cv: Component<{ data: CvProps }> = ({ data }) => { {data.education.map((education) => { return ( - <> + - - + + ); })} From e3c73fc3fc4506e28bcfdde98a2f10986d5d95d4 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Sun, 10 Mar 2024 23:06:53 +0100 Subject: [PATCH 4/6] added sharp package --- package.json | 1 + yarn.lock | 158 ++++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 158 insertions(+), 1 deletion(-) diff --git a/package.json b/package.json index 983d39e..7ccff9d 100644 --- a/package.json +++ b/package.json @@ -32,6 +32,7 @@ "react": "^18.2.0", "react-dom": "^18.2.0", "react-icons": "^5.0.1", + "sharp": "^0.33.2", "tailwindcss": "^3.4.1", "timeago.js": "^4.0.2", "zod": "^3.20.6" diff --git a/yarn.lock b/yarn.lock index 750bce3..dca3e9b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -153,6 +153,13 @@ "@babel/helper-validator-identifier" "^7.22.20" to-fast-properties "^2.0.0" +"@emnapi/runtime@^0.45.0": + version "0.45.0" + resolved "https://registry.yarnpkg.com/@emnapi/runtime/-/runtime-0.45.0.tgz#e754de04c683263f34fd0c7f32adfe718bbe4ddd" + integrity sha512-Txumi3td7J4A/xTTwlssKieHKTGl3j4A1tglBx72auZ49YK7ePY6XZricgIg9mnZT4xPfA+UPCUdnhRuEFDL+w== + dependencies: + tslib "^2.4.0" + "@emotion/is-prop-valid@^0.8.2": version "0.8.8" resolved "https://registry.yarnpkg.com/@emotion/is-prop-valid/-/is-prop-valid-0.8.8.tgz#db28b1c4368a259b60a97311d6a952d4fd01ac1a" @@ -233,6 +240,119 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@img/sharp-darwin-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.2.tgz#0a52a82c2169112794dac2c71bfba9e90f7c5bd1" + integrity sha512-itHBs1rPmsmGF9p4qRe++CzCgd+kFYktnsoR1sbIAfsRMrJZau0Tt1AH9KVnufc2/tU02Gf6Ibujx+15qRE03w== + optionalDependencies: + "@img/sharp-libvips-darwin-arm64" "1.0.1" + +"@img/sharp-darwin-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.2.tgz#982e26bb9d38a81f75915c4032539aed621d1c21" + integrity sha512-/rK/69Rrp9x5kaWBjVN07KixZanRr+W1OiyKdXcbjQD6KbW+obaTeBBtLUAtbBsnlTTmWthw99xqoOS7SsySDg== + optionalDependencies: + "@img/sharp-libvips-darwin-x64" "1.0.1" + +"@img/sharp-libvips-darwin-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.1.tgz#81e83ffc2c497b3100e2f253766490f8fad479cd" + integrity sha512-kQyrSNd6lmBV7O0BUiyu/OEw9yeNGFbQhbxswS1i6rMDwBBSX+e+rPzu3S+MwAiGU3HdLze3PanQ4Xkfemgzcw== + +"@img/sharp-libvips-darwin-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.1.tgz#fc1fcd9d78a178819eefe2c1a1662067a83ab1d6" + integrity sha512-eVU/JYLPVjhhrd8Tk6gosl5pVlvsqiFlt50wotCvdkFGf+mDNBJxMh+bvav+Wt3EBnNZWq8Sp2I7XfSjm8siog== + +"@img/sharp-libvips-linux-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.1.tgz#26eb8c556a9b0db95f343fc444abc3effb67ebcf" + integrity sha512-bnGG+MJjdX70mAQcSLxgeJco11G+MxTz+ebxlz8Y3dxyeb3Nkl7LgLI0mXupoO+u1wRNx/iRj5yHtzA4sde1yA== + +"@img/sharp-libvips-linux-arm@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.1.tgz#2a377b959ff7dd6528deee486c25461296a4fa8b" + integrity sha512-FtdMvR4R99FTsD53IA3LxYGghQ82t3yt0ZQ93WMZ2xV3dqrb0E8zq4VHaTOuLEAuA83oDawHV3fd+BsAPadHIQ== + +"@img/sharp-libvips-linux-s390x@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.1.tgz#af28ac9ba929204467ecdf843330d791e9421e10" + integrity sha512-3+rzfAR1YpMOeA2zZNp+aYEzGNWK4zF3+sdMxuCS3ey9HhDbJ66w6hDSHDMoap32DueFwhhs3vwooAB2MaK4XQ== + +"@img/sharp-libvips-linux-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.1.tgz#4273d182aa51912e655e1214ea47983d7c1f7f8d" + integrity sha512-3NR1mxFsaSgMMzz1bAnnKbSAI+lHXVTqAHgc1bgzjHuXjo4hlscpUxc0vFSAPKI3yuzdzcZOkq7nDPrP2F8Jgw== + +"@img/sharp-libvips-linuxmusl-arm64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.1.tgz#d150c92151cea2e8d120ad168b9c358d09c77ce8" + integrity sha512-5aBRcjHDG/T6jwC3Edl3lP8nl9U2Yo8+oTl5drd1dh9Z1EBfzUKAJFUDTDisDjUwc7N4AjnPGfCA3jl3hY8uDg== + +"@img/sharp-libvips-linuxmusl-x64@1.0.1": + version "1.0.1" + resolved "https://registry.yarnpkg.com/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.1.tgz#e297c1a4252c670d93b0f9e51fca40a7a5b6acfd" + integrity sha512-dcT7inI9DBFK6ovfeWRe3hG30h51cBAP5JXlZfx6pzc/Mnf9HFCQDLtYf4MCBjxaaTfjCCjkBxcy3XzOAo5txw== + +"@img/sharp-linux-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.2.tgz#af3409f801a9bee1d11d0c7e971dcd6180f80022" + integrity sha512-pz0NNo882vVfqJ0yNInuG9YH71smP4gRSdeL09ukC2YLE6ZyZePAlWKEHgAzJGTiOh8Qkaov6mMIMlEhmLdKew== + optionalDependencies: + "@img/sharp-libvips-linux-arm64" "1.0.1" + +"@img/sharp-linux-arm@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.2.tgz#181f7466e6ac074042a38bfb679eb82505e17083" + integrity sha512-Fndk/4Zq3vAc4G/qyfXASbS3HBZbKrlnKZLEJzPLrXoJuipFNNwTes71+Ki1hwYW5lch26niRYoZFAtZVf3EGA== + optionalDependencies: + "@img/sharp-libvips-linux-arm" "1.0.1" + +"@img/sharp-linux-s390x@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.2.tgz#9c171f49211f96fba84410b3e237b301286fa00f" + integrity sha512-MBoInDXDppMfhSzbMmOQtGfloVAflS2rP1qPcUIiITMi36Mm5YR7r0ASND99razjQUpHTzjrU1flO76hKvP5RA== + optionalDependencies: + "@img/sharp-libvips-linux-s390x" "1.0.1" + +"@img/sharp-linux-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.2.tgz#b956dfc092adc58c2bf0fae2077e6f01a8b2d5d7" + integrity sha512-xUT82H5IbXewKkeF5aiooajoO1tQV4PnKfS/OZtb5DDdxS/FCI/uXTVZ35GQ97RZXsycojz/AJ0asoz6p2/H/A== + optionalDependencies: + "@img/sharp-libvips-linux-x64" "1.0.1" + +"@img/sharp-linuxmusl-arm64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.2.tgz#10e0ec5a79d1234c6a71df44c9f3b0bef0bc0f15" + integrity sha512-F+0z8JCu/UnMzg8IYW1TMeiViIWBVg7IWP6nE0p5S5EPQxlLd76c8jYemG21X99UzFwgkRo5yz2DS+zbrnxZeA== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" + +"@img/sharp-linuxmusl-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.2.tgz#29e0030c24aa27c38201b1fc84e3d172899fcbe0" + integrity sha512-+ZLE3SQmSL+Fn1gmSaM8uFusW5Y3J9VOf+wMGNnTtJUMUxFhv+P4UPaYEYT8tqnyYVaOVGgMN/zsOxn9pSsO2A== + optionalDependencies: + "@img/sharp-libvips-linuxmusl-x64" "1.0.1" + +"@img/sharp-wasm32@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-wasm32/-/sharp-wasm32-0.33.2.tgz#38d7c740a22de83a60ad1e6bcfce17462b0d4230" + integrity sha512-fLbTaESVKuQcpm8ffgBD7jLb/CQLcATju/jxtTXR1XCLwbOQt+OL5zPHSDMmp2JZIeq82e18yE0Vv7zh6+6BfQ== + dependencies: + "@emnapi/runtime" "^0.45.0" + +"@img/sharp-win32-ia32@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.2.tgz#09456314e223f68e5417c283b45c399635c16202" + integrity sha512-okBpql96hIGuZ4lN3+nsAjGeggxKm7hIRu9zyec0lnfB8E7Z6p95BuRZzDDXZOl2e8UmR4RhYt631i7mfmKU8g== + +"@img/sharp-win32-x64@0.33.2": + version "0.33.2" + resolved "https://registry.yarnpkg.com/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.2.tgz#148e96dfd6e68747da41a311b9ee4559bb1b1471" + integrity sha512-E4magOks77DK47FwHUIGH0RYWSgRBfGdK56kIHSVeB9uIS4pPFr4N2kIVsXdQQo4LzOsENKV5KAhRlRL7eMAdg== + "@internationalized/date@^3.5.2": version "3.5.2" resolved "https://registry.yarnpkg.com/@internationalized/date/-/date-3.5.2.tgz#d760ace32bb47e869b8c607a4a786c8b208aacc2" @@ -2964,7 +3084,7 @@ dequal@^2.0.3: resolved "https://registry.yarnpkg.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== -detect-libc@^2.0.0: +detect-libc@^2.0.0, detect-libc@^2.0.2: version "2.0.2" resolved "https://registry.yarnpkg.com/detect-libc/-/detect-libc-2.0.2.tgz#8ccf2ba9315350e1241b88d0ac3b0e1fbd99605d" integrity sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw== @@ -4900,6 +5020,13 @@ semver@^7.2.1, semver@^7.3.5, semver@^7.3.7: dependencies: lru-cache "^6.0.0" +semver@^7.5.4: + version "7.6.0" + resolved "https://registry.yarnpkg.com/semver/-/semver-7.6.0.tgz#1a46a4db4bffcccd97b743b5005c8325f23d4e2d" + integrity sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg== + dependencies: + lru-cache "^6.0.0" + set-blocking@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7" @@ -4925,6 +5052,35 @@ set-function-name@^2.0.0, set-function-name@^2.0.1: functions-have-names "^1.2.3" has-property-descriptors "^1.0.0" +sharp@^0.33.2: + version "0.33.2" + resolved "https://registry.yarnpkg.com/sharp/-/sharp-0.33.2.tgz#fcd52f2c70effa8a02160b1bfd989a3de55f2dfb" + integrity sha512-WlYOPyyPDiiM07j/UO+E720ju6gtNtHjEGg5vovUk1Lgxyjm2LFO+37Nt/UI3MMh2l6hxTWQWi7qk3cXJTutcQ== + dependencies: + color "^4.2.3" + detect-libc "^2.0.2" + semver "^7.5.4" + optionalDependencies: + "@img/sharp-darwin-arm64" "0.33.2" + "@img/sharp-darwin-x64" "0.33.2" + "@img/sharp-libvips-darwin-arm64" "1.0.1" + "@img/sharp-libvips-darwin-x64" "1.0.1" + "@img/sharp-libvips-linux-arm" "1.0.1" + "@img/sharp-libvips-linux-arm64" "1.0.1" + "@img/sharp-libvips-linux-s390x" "1.0.1" + "@img/sharp-libvips-linux-x64" "1.0.1" + "@img/sharp-libvips-linuxmusl-arm64" "1.0.1" + "@img/sharp-libvips-linuxmusl-x64" "1.0.1" + "@img/sharp-linux-arm" "0.33.2" + "@img/sharp-linux-arm64" "0.33.2" + "@img/sharp-linux-s390x" "0.33.2" + "@img/sharp-linux-x64" "0.33.2" + "@img/sharp-linuxmusl-arm64" "0.33.2" + "@img/sharp-linuxmusl-x64" "0.33.2" + "@img/sharp-wasm32" "0.33.2" + "@img/sharp-win32-ia32" "0.33.2" + "@img/sharp-win32-x64" "0.33.2" + shebang-command@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/shebang-command/-/shebang-command-2.0.0.tgz#ccd0af4f8835fbdc265b82461aaf0c36663f34ea" From 47af546637f0423a629df41baa0b02ff29686299 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Sun, 10 Mar 2024 23:30:32 +0100 Subject: [PATCH 5/6] improved mobile view of cv + added experience section --- data/cv.json | 11 +++++-- src/app/cv/Cv.tsx | 79 +++++++++++++++++++++++++++++++++++++---------- src/models/cv.ts | 2 ++ src/utils/cv.ts | 1 - 4 files changed, 73 insertions(+), 20 deletions(-) diff --git a/data/cv.json b/data/cv.json index 7fbefdb..f9291ec 100644 --- a/data/cv.json +++ b/data/cv.json @@ -45,12 +45,19 @@ "skills": ["Mathematics", "Neuroscience", "Computer science"] }, { - "title": "VWO (NT&G)", + "title": "Highschool VWO (NT&G)", "timeFrame": "2016 - 2022", "institution": "RSG Pantarijn MHV Wageningen", "location": "Wageningen, Netherlands", "skills": ["Biology", "Physics", "Mathematics"] } ], - "experience": [] + "experience": [ + { + "title": "Albert Heijn", + "timeFrame": "2020 - 2023", + "role": "Store employee", + "description": "" + } + ] } diff --git a/src/app/cv/Cv.tsx b/src/app/cv/Cv.tsx index 4e03ff7..b2b4cc1 100644 --- a/src/app/cv/Cv.tsx +++ b/src/app/cv/Cv.tsx @@ -14,6 +14,7 @@ import { Fragment } from "react"; import CvProps, { Education as EducationProps, + Experience as ExperienceProps, Skill as SkillProps } from "@models/cv"; @@ -69,22 +70,49 @@ const Education: Component<{ education: EducationProps }> = ({ education }) => { ); }; +const Experience: Component<{ experience: ExperienceProps }> = ({ + experience +}) => { + return ( + + +
+
+

+ {experience.role} at{" "} + {experience.title} +

+

+ {experience.timeFrame} +

+
+
+ + +
+
+ ); +}; + export const Cv: Component<{ data: CvProps }> = ({ data }) => { return ( -
-
- {`Professional +
+
+
+ {`Professional +
-
-
+
+

{data.fullName}

{data.role}

@@ -103,22 +131,39 @@ export const Cv: Component<{ data: CvProps }> = ({ data }) => {

Skills

- {data.skills.map((skill) => ( - - ))} + {data.skills + .sort((a, b) => b.value - a.value) + .map((skill) => ( + + ))}

Programming Languages

- {data.programmingLanguages.map((skill) => ( - - ))} + {data.programmingLanguages + .sort((a, b) => b.value - a.value) + .map((skill) => ( + + ))}
+

Experience

+ + {data.experience.map((experience) => { + return ( + + + + + ); + })} + + +

Education

{data.education.map((education) => { diff --git a/src/models/cv.ts b/src/models/cv.ts index a7d05a3..db86b0e 100644 --- a/src/models/cv.ts +++ b/src/models/cv.ts @@ -25,6 +25,8 @@ const ExperienceModel = z.object({ description: z.string() }); +export type Experience = z.infer; + export const CvPropsModel = z.object({ fullName: z.string(), role: z.string(), diff --git a/src/utils/cv.ts b/src/utils/cv.ts index 266bc5a..58de519 100644 --- a/src/utils/cv.ts +++ b/src/utils/cv.ts @@ -1,4 +1,3 @@ -import { readJson } from "fs-extra"; import path from "path"; import { readAndParseJsonFile } from "./json"; From 832beb8905cb083ad5c3aa14cb99fd5baa5cff36 Mon Sep 17 00:00:00 2001 From: Guus van Meerveld Date: Tue, 12 Mar 2024 15:57:56 +0100 Subject: [PATCH 6/6] move favicon to app dir --- {public => src/app}/favicon.ico | Bin 1 file changed, 0 insertions(+), 0 deletions(-) rename {public => src/app}/favicon.ico (100%) diff --git a/public/favicon.ico b/src/app/favicon.ico similarity index 100% rename from public/favicon.ico rename to src/app/favicon.ico