From 73fa5e3e51c2697c08967ea5a54bc209a54b49fb Mon Sep 17 00:00:00 2001 From: Gautier Darchen Date: Fri, 28 Jun 2024 12:22:52 +0200 Subject: [PATCH] feat(watch): add a new page to list watch resources fetched from a Notion database (#9) --- .github/pull-request-template.md | 1 - .github/workflows/ci.yml | 4 +- .gitignore | 1 + .husky/pre-commit | 2 +- .prettierrc | 2 + README.md | 5 +- commitlint.config.mjs | 28 +- package.json | 9 +- pnpm-lock.yaml | 277 ++++++++++++++++-- renovate.json | 10 +- src/app/home.tsx | 10 +- src/app/layout.tsx | 12 +- src/app/watch/dto/watchResource.dto.ts | 16 + src/app/watch/page.tsx | 51 ++++ src/app/watch/types/index.ts | 12 + src/app/watch/utils/index.ts | 51 ++++ src/app/watch/watchResource.tsx | 68 +++++ src/app/watch/watchResources.tsx | 95 ++++++ src/components/header/Header.tsx | 2 + .../header/components/DesktopMenu.tsx | 67 ++--- src/components/header/components/IconMenu.tsx | 2 +- .../header/components/MobileMenu.tsx | 57 +--- src/components/home/2-about-me/AboutMe.tsx | 6 +- .../home/3-work-experience/WorkExperience.tsx | 5 +- .../components/WorkExperienceTabs.tsx | 5 +- src/components/home/4-projects/Projects.tsx | 2 +- .../home/4-projects/ProjectsList.tsx | 4 +- .../home/4-projects/components/Project.tsx | 28 +- src/components/home/5-skills/Skills.tsx | 48 ++- src/components/home/5-skills/SkillsList.tsx | 30 +- src/components/spinner/Spinner.tsx | 31 ++ src/constants/globals.ts | 2 + src/lib/notion.ts | 30 ++ tailwind.config.ts | 9 +- tsconfig.json | 8 +- 35 files changed, 795 insertions(+), 195 deletions(-) create mode 100644 src/app/watch/dto/watchResource.dto.ts create mode 100644 src/app/watch/page.tsx create mode 100644 src/app/watch/types/index.ts create mode 100644 src/app/watch/utils/index.ts create mode 100644 src/app/watch/watchResource.tsx create mode 100644 src/app/watch/watchResources.tsx create mode 100644 src/components/spinner/Spinner.tsx create mode 100644 src/lib/notion.ts diff --git a/.github/pull-request-template.md b/.github/pull-request-template.md index a438d1b..169b061 100644 --- a/.github/pull-request-template.md +++ b/.github/pull-request-template.md @@ -5,4 +5,3 @@ ## 🧠 Approach - diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4151d24..f077b11 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,10 +25,10 @@ jobs: - name: Setup pnpm uses: pnpm/action-setup@v3 with: - version: 9 + version: 9 - name: Install dependencies run: pnpm install - name: Lint source code - run: pnpm lint \ No newline at end of file + run: pnpm lint diff --git a/.gitignore b/.gitignore index 93c9ea4..a1d4a6c 100755 --- a/.gitignore +++ b/.gitignore @@ -18,6 +18,7 @@ # misc .DS_Store *.pem +.eslintcache # debug npm-debug.log* diff --git a/.husky/pre-commit b/.husky/pre-commit index 009b3f8..cb2c84d 100644 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1 +1 @@ -pnpm lint +pnpm lint-staged diff --git a/.prettierrc b/.prettierrc index 3bfc625..eae99c1 100644 --- a/.prettierrc +++ b/.prettierrc @@ -2,6 +2,8 @@ "semi": false, "singleQuote": true, "trailingComma": "all", + "bracketSpacing": true, + "bracketSameLine": false, "plugins": ["prettier-plugin-tailwindcss"], "tailwindFunctions": ["clsx", "tv"] } diff --git a/README.md b/README.md index 3d42aea..119b58c 100755 --- a/README.md +++ b/README.md @@ -15,13 +15,13 @@ + [![pnpm version](https://img.shields.io/badge/v9.4.0-F69220.svg?logo=pnpm&logoColor=white&label=pnpm)](https://pnpm.io/) [![node version](https://img.shields.io/badge/%3E=20.0.0-3C873A.svg?logo=node.js&logoColor=white&label=node)](https://nodejs.org/en/) - - Logo +Logo ## πŸ‘‹ About this repo @@ -48,7 +48,6 @@ Here is how to setup the project locally to contribute: --- - ## πŸ“¦ Deploy To deploy a new version, simply open a pull request again the `main` branch. diff --git a/commitlint.config.mjs b/commitlint.config.mjs index 427b951..d719f2a 100644 --- a/commitlint.config.mjs +++ b/commitlint.config.mjs @@ -9,22 +9,22 @@ const commitlint = { 2, 'always', [ - "build", - "chore", - "ci", - "docs", - "feat", - "fix", - "perf", - "refactor", - "revert", - "style", - "test", - "upgrade", - "wip", + 'build', + 'chore', + 'ci', + 'docs', + 'feat', + 'fix', + 'perf', + 'refactor', + 'revert', + 'style', + 'test', + 'upgrade', + 'wip', ], ], }, } -export default commitlint \ No newline at end of file +export default commitlint diff --git a/package.json b/package.json index 6992969..41a966f 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ }, "dependencies": { "@headlessui/react": "^2.0.4", + "@notionhq/client": "^2.2.15", "@vercel/analytics": "^1.3.1", "aos": "^2.3.4", "clsx": "^2.1.1", @@ -22,7 +23,6 @@ "react-icons": "^5.2.1", "react-player": "^2.16.0", "react-responsive": "^10.0.0", - "react-scroll": "^1.8.8", "react-slick": "^0.30.2", "serve": "^14.0.1", "tailwind-scrollbar-hide": "^1.1.7", @@ -44,6 +44,7 @@ "eslint-plugin-tailwindcss": "3.17.4", "eslint-plugin-unused-imports": "^4.0.0", "husky": "^9.0.11", + "lint-staged": "^15.2.7", "postcss": "^8.4.38", "prettier": "3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", @@ -51,5 +52,9 @@ "tailwindcss": "^3.4.4", "typescript": "^5.5.2" }, - "packageManager": "pnpm@9.4.0" + "packageManager": "pnpm@9.4.0", + "lint-staged": { + "*.ts*": "eslint --cache --fix", + "*.{js,ts,tsx,css,md}": "prettier --write" + } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0762d46..89e4354 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -11,6 +11,9 @@ importers: '@headlessui/react': specifier: ^2.0.4 version: 2.1.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + '@notionhq/client': + specifier: ^2.2.15 + version: 2.2.15 '@vercel/analytics': specifier: ^1.3.1 version: 1.3.1(next@14.2.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(react@18.3.1) @@ -41,9 +44,6 @@ importers: react-responsive: specifier: ^10.0.0 version: 10.0.0(react@18.3.1) - react-scroll: - specifier: ^1.8.8 - version: 1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) react-slick: specifier: ^0.30.2 version: 0.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1) @@ -102,6 +102,9 @@ importers: husky: specifier: ^9.0.11 version: 9.0.11 + lint-staged: + specifier: ^15.2.7 + version: 15.2.7 postcss: specifier: ^8.4.38 version: 8.4.38 @@ -371,6 +374,10 @@ packages: resolution: {integrity: sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==} engines: {node: '>= 8'} + '@notionhq/client@2.2.15': + resolution: {integrity: sha512-XhdSY/4B1D34tSco/GION+23GMjaS9S2zszcqYkMHo8RcWInymF6L1x+Gk7EmHdrSxNFva2WM8orhC4BwQCwgw==} + engines: {node: '>=12'} + '@pkgjs/parseargs@0.11.0': resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} @@ -436,6 +443,9 @@ packages: '@types/json5@0.0.29': resolution: {integrity: sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==} + '@types/node-fetch@2.6.11': + resolution: {integrity: sha512-24xFj9R5+rfQJLRyM56qh+wnVSYhyXC2tkoBndtY0U+vubqNsYXGjufB2nn8Q6gt0LrARwL6UBtMCSVCwl4B1g==} + '@types/node@20.14.8': resolution: {integrity: sha512-DO+2/jZinXfROG7j7WKFn/3C6nFwxy2lLpgLjEXJz+0XKphZlTLJ14mo8Vfg8X5BWN6XjyESXq+LcYdT7tR3bA==} @@ -551,6 +561,10 @@ packages: ansi-align@3.0.1: resolution: {integrity: sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w==} + ansi-escapes@6.2.1: + resolution: {integrity: sha512-4nJ3yixlEthEJ9Rk4vPcdBRkZvQZlYyu8j4/Mqz5sgIkddmEnH2Yj2ZrnP9S3tQOvSNRUIgVNF/1yPpRAGNRig==} + engines: {node: '>=14.16'} + ansi-regex@5.0.1: resolution: {integrity: sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==} engines: {node: '>=8'} @@ -638,6 +652,9 @@ packages: ast-types-flow@0.0.8: resolution: {integrity: sha512-OH/2E5Fg20h2aPrbe+QL8JZQFko0YZaF+j4mnQ7BGhfavO7OpSLa8a0y9sBwomHdSbkhTS8TQNayBfnW5DwbvQ==} + asynckit@0.4.0: + resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==} + autoprefixer@10.4.19: resolution: {integrity: sha512-BaENR2+zBZ8xXhM4pUaKUxlVdxZ0EZhjvbopwnXmxRUfqDmwSpC2lAi/QXvx7NRdPCo1WKEcEF6mV64si1z4Ew==} engines: {node: ^10 || ^12 || >=14} @@ -743,6 +760,14 @@ packages: resolution: {integrity: sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g==} engines: {node: '>=10'} + cli-cursor@4.0.0: + resolution: {integrity: sha512-VGtlMu3x/4DOtIUwEkRezxUZ2lBacNJCHash0N0WeZDBS+7Ux1dm3XWAgWYxLJFMMdOeXMHXorshEFhbMSGelg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + + cli-truncate@4.0.0: + resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} + engines: {node: '>=18'} + client-only@0.0.1: resolution: {integrity: sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==} @@ -771,6 +796,17 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + + combined-stream@1.0.8: + resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} + engines: {node: '>= 0.8'} + + commander@12.1.0: + resolution: {integrity: sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==} + engines: {node: '>=18'} + commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} @@ -905,6 +941,10 @@ packages: resolution: {integrity: sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==} engines: {node: '>= 0.4'} + delayed-stream@1.0.0: + resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==} + engines: {node: '>=0.4.0'} + didyoumean@1.2.2: resolution: {integrity: sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==} @@ -933,6 +973,9 @@ packages: electron-to-chromium@1.4.810: resolution: {integrity: sha512-Kaxhu4T7SJGpRQx99tq216gCq2nMxJo+uuT6uzz9l8TVN2stL7M06MIIXAtr9jsrLs2Glflgf2vMQRepxawOdQ==} + emoji-regex@10.3.0: + resolution: {integrity: sha512-QpLs9D9v9kArv4lfDEgg1X/gN5XLnf/A6l9cs8SPZLRZR3ZkY9+kwIQTxm+fsSej5UMYGE8fdoaZVIBlqG0XTw==} + emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -1131,6 +1174,9 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} + eventemitter3@5.0.1: + resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} engines: {node: '>=10'} @@ -1188,6 +1234,10 @@ packages: resolution: {integrity: sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA==} engines: {node: '>=14'} + form-data@4.0.0: + resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} + engines: {node: '>= 6'} + fraction.js@4.3.7: resolution: {integrity: sha512-ZsDfxO51wGAXREY55a7la9LScWpwv9RxIrYABrlvOFBlH/ShPnrtsXeuUIfXKKOVicNxQ+o8JTbJvjS4M89yew==} @@ -1227,6 +1277,10 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} + get-east-asian-width@1.2.0: + resolution: {integrity: sha512-2nk+7SIVb14QrgXFHcm84tD4bKQz0RxPuMT8Ag5KPOq7J5fEmAg0UbXdTOSHqNuHSU28k55qnceesxXRZGzKWA==} + engines: {node: '>=18'} + get-intrinsic@1.2.4: resolution: {integrity: sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==} engines: {node: '>= 0.4'} @@ -1435,6 +1489,14 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} + is-fullwidth-code-point@4.0.0: + resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} + engines: {node: '>=12'} + + is-fullwidth-code-point@5.0.0: + resolution: {integrity: sha512-OVa3u9kkBbw7b8Xw5F9P+D/T9X+Z4+JruYVNapTjPYZYUznQ5YfWeFkOj606XYYW8yugTfC8Pj0hYqvi4ryAhA==} + engines: {node: '>=18'} + is-generator-function@1.0.10: resolution: {integrity: sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==} engines: {node: '>= 0.4'} @@ -1605,6 +1667,15 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} + lint-staged@15.2.7: + resolution: {integrity: sha512-+FdVbbCZ+yoh7E/RosSdqKJyUM2OEjTciH0TFNkawKgvFp1zbGlEC39RADg+xKBG1R4mhoH2j85myBQZ5wR+lw==} + engines: {node: '>=18.12.0'} + hasBin: true + + listr2@8.2.3: + resolution: {integrity: sha512-Lllokma2mtoniUOS94CcOErHWAug5iu7HOmDrvWgpw8jyQH2fomgB+7lZS4HWZxytUuQwkGOwe49FvwVaA85Xw==} + engines: {node: '>=18.0.0'} + load-script@1.0.0: resolution: {integrity: sha512-kPEjMFtZvwL9TaZo0uZ2ml+Ye9HUMmPwbYRJ324qF9tqMejwykJ5ggTyvzmrbBeapCAbk98BSbTeovHEEP1uCA==} @@ -1649,6 +1720,10 @@ packages: lodash.upperfirst@4.3.1: resolution: {integrity: sha512-sReKOYJIJf74dhJONhU4e0/shzi1trVbSWDOhKYE5XV2O+H7Sb2Dihwuc7xWxVl+DgFPyTqIN3zMfT9cq5iWDg==} + log-update@6.0.0: + resolution: {integrity: sha512-niTvB4gqvtof056rRIrTZvjNYE4rCUzO6X/X+kYjd7WFxXeJ0NwEFnRxX6ehkvv3jTwrXnNdtAak5XYZuIyPFw==} + engines: {node: '>=18'} + loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -1762,6 +1837,15 @@ packages: sass: optional: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} @@ -1915,6 +1999,11 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + pidtree@0.6.0: + resolution: {integrity: sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==} + engines: {node: '>=0.10'} + hasBin: true + pify@2.3.0: resolution: {integrity: sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==} engines: {node: '>=0.10.0'} @@ -2081,12 +2170,6 @@ packages: peerDependencies: react: '>=16.8.0' - react-scroll@1.9.0: - resolution: {integrity: sha512-mamNcaX9Ng+JeSbBu97nWwRhYvL2oba+xR2GxvyXsbDeGP+gkYIKZ+aDMMj/n20TbV9SCWm/H7nyuNTSiXA6yA==} - peerDependencies: - react: ^15.5.4 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-dom: ^15.5.4 || ^16.0.0 || ^17.0.0 || ^18.0.0 - react-slick@0.30.2: resolution: {integrity: sha512-XvQJi7mRHuiU3b9irsqS9SGIgftIfdV5/tNcURTb5LdIokRA5kIIx3l4rlq2XYHfxcSntXapoRg/GxaVOM1yfg==} peerDependencies: @@ -2152,10 +2235,17 @@ packages: resolution: {integrity: sha512-U7WjGVG9sH8tvjW5SmGbQuui75FiyjAX72HX15DwBBwF9dNiQZRQAg9nnPhYy+TUnE0+VcrttuvNI8oSxZcocA==} hasBin: true + restore-cursor@4.0.0: + resolution: {integrity: sha512-I9fPXU9geO9bHOt9pHHOhOkYerIMsmVaWB0rA2AI9ERh/+x/i7MV5HKBNrg+ljO5eoPVgCcnFuRjJ9uH6I/3eg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + reusify@1.0.4: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rfdc@1.4.1: + resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2235,6 +2325,14 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + slice-ansi@5.0.0: + resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} + engines: {node: '>=12'} + + slice-ansi@7.1.0: + resolution: {integrity: sha512-bSiSngZ/jWeX93BqeIAbImyTbEihizcwNjFoRUIY/T1wWQsfsm2Vw1agPKylXvQTU7iASGdHhyqRlqQzfz+Htg==} + engines: {node: '>=18'} + source-map-js@1.2.0: resolution: {integrity: sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==} engines: {node: '>=0.10.0'} @@ -2251,6 +2349,10 @@ packages: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} + string-argv@0.3.2: + resolution: {integrity: sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==} + engines: {node: '>=0.6.19'} + string-convert@0.2.1: resolution: {integrity: sha512-u/1tdPl4yQnPBjnVrmdLo9gtuLvELKsAoRapekWggdiQNvvvum+jYF329d84NAa660KQw7pB2n36KrIKVoXa3A==} @@ -2262,6 +2364,10 @@ packages: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + string-width@7.1.0: + resolution: {integrity: sha512-SEIJCWiX7Kg4c129n48aDRwLbFb2LJmXXFrWBG4NGaRtMQ3myKPKbwrD1BKqQn74oCoNMBVrfDEr5M9YxCsrkw==} + engines: {node: '>=18'} + string.prototype.includes@2.0.0: resolution: {integrity: sha512-E34CkBgyeqNDcrbU76cDjL5JLcVrtSdYq0MEh/B10r17pRP4ciHLwTgnuLV8Ay6cgEMLkcBkFCKyFZ43YldYzg==} @@ -2389,6 +2495,9 @@ packages: resolution: {integrity: sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==} engines: {node: '>=8.0'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + ts-api-utils@1.3.0: resolution: {integrity: sha512-UQMIo7pb8WRomKR1/+MFVLTroIvDVtMX3K6OUir8ynLyzB8Jeriont2bTAtmNPa1ekAgN7YPDyf6V+ygrdU+eQ==} engines: {node: '>=16'} @@ -2466,6 +2575,12 @@ packages: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + which-boxed-primitive@1.0.2: resolution: {integrity: sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==} @@ -2502,6 +2617,10 @@ packages: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + wrap-ansi@9.0.0: + resolution: {integrity: sha512-G8ura3S+3Z2G+mkgNRq8dqaFZAuxfsxpBB8OCTGRTCtp+l/v9nbFNmCUP1BZMts3G1142MsZfn6eeUKrr4PD1Q==} + engines: {node: '>=18'} + wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -2811,6 +2930,13 @@ snapshots: '@nodelib/fs.scandir': 2.1.5 fastq: 1.17.1 + '@notionhq/client@2.2.15': + dependencies: + '@types/node-fetch': 2.6.11 + node-fetch: 2.7.0 + transitivePeerDependencies: + - encoding + '@pkgjs/parseargs@0.11.0': optional: true @@ -2883,6 +3009,11 @@ snapshots: '@types/json5@0.0.29': {} + '@types/node-fetch@2.6.11': + dependencies: + '@types/node': 20.14.8 + form-data: 4.0.0 + '@types/node@20.14.8': dependencies: undici-types: 5.26.5 @@ -3019,6 +3150,8 @@ snapshots: dependencies: string-width: 4.2.3 + ansi-escapes@6.2.1: {} + ansi-regex@5.0.1: {} ansi-regex@6.0.1: {} @@ -3134,6 +3267,8 @@ snapshots: ast-types-flow@0.0.8: {} + asynckit@0.4.0: {} + autoprefixer@10.4.19(postcss@8.4.38): dependencies: browserslist: 4.23.1 @@ -3162,7 +3297,7 @@ snapshots: dependencies: ansi-align: 3.0.1 camelcase: 7.0.1 - chalk: 5.0.1 + chalk: 5.3.0 cli-boxes: 3.0.0 string-width: 5.1.2 type-fest: 2.19.0 @@ -3248,6 +3383,15 @@ snapshots: cli-boxes@3.0.0: {} + cli-cursor@4.0.0: + dependencies: + restore-cursor: 4.0.0 + + cli-truncate@4.0.0: + dependencies: + slice-ansi: 5.0.0 + string-width: 7.1.0 + client-only@0.0.1: {} clipboardy@3.0.0: @@ -3276,6 +3420,14 @@ snapshots: color-name@1.1.4: {} + colorette@2.0.20: {} + + combined-stream@1.0.8: + dependencies: + delayed-stream: 1.0.0 + + commander@12.1.0: {} + commander@4.1.1: {} compare-func@2.0.0: @@ -3419,6 +3571,8 @@ snapshots: has-property-descriptors: 1.0.2 object-keys: 1.1.1 + delayed-stream@1.0.0: {} + didyoumean@1.2.2: {} dir-glob@3.0.1: @@ -3443,6 +3597,8 @@ snapshots: electron-to-chromium@1.4.810: {} + emoji-regex@10.3.0: {} + emoji-regex@8.0.0: {} emoji-regex@9.2.2: {} @@ -3823,6 +3979,8 @@ snapshots: esutils@2.0.3: {} + eventemitter3@5.0.1: {} + execa@5.1.1: dependencies: cross-spawn: 7.0.3 @@ -3905,6 +4063,12 @@ snapshots: cross-spawn: 7.0.3 signal-exit: 4.1.0 + form-data@4.0.0: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + fraction.js@4.3.7: {} framer-motion@11.2.11(@emotion/is-prop-valid@1.2.2)(react-dom@18.3.1(react@18.3.1))(react@18.3.1): @@ -3933,6 +4097,8 @@ snapshots: get-caller-file@2.0.5: {} + get-east-asian-width@1.2.0: {} + get-intrinsic@1.2.4: dependencies: es-errors: 1.3.0 @@ -4136,6 +4302,12 @@ snapshots: is-fullwidth-code-point@3.0.0: {} + is-fullwidth-code-point@4.0.0: {} + + is-fullwidth-code-point@5.0.0: + dependencies: + get-east-asian-width: 1.2.0 + is-generator-function@1.0.10: dependencies: has-tostringtag: 1.0.2 @@ -4286,6 +4458,30 @@ snapshots: lines-and-columns@1.2.4: {} + lint-staged@15.2.7: + dependencies: + chalk: 5.3.0 + commander: 12.1.0 + debug: 4.3.5 + execa: 8.0.1 + lilconfig: 3.1.2 + listr2: 8.2.3 + micromatch: 4.0.7 + pidtree: 0.6.0 + string-argv: 0.3.2 + yaml: 2.4.5 + transitivePeerDependencies: + - supports-color + + listr2@8.2.3: + dependencies: + cli-truncate: 4.0.0 + colorette: 2.0.20 + eventemitter3: 5.0.1 + log-update: 6.0.0 + rfdc: 1.4.1 + wrap-ansi: 9.0.0 + load-script@1.0.0: {} locate-path@6.0.0: @@ -4318,6 +4514,14 @@ snapshots: lodash.upperfirst@4.3.1: {} + log-update@6.0.0: + dependencies: + ansi-escapes: 6.2.1 + cli-cursor: 4.0.0 + slice-ansi: 7.1.0 + strip-ansi: 7.1.0 + wrap-ansi: 9.0.0 + loose-envify@1.4.0: dependencies: js-tokens: 4.0.0 @@ -4416,6 +4620,10 @@ snapshots: - '@babel/core' - babel-plugin-macros + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.14: {} normalize-path@3.0.0: {} @@ -4560,6 +4768,8 @@ snapshots: picomatch@2.3.1: {} + pidtree@0.6.0: {} + pify@2.3.0: {} pirates@4.0.6: {} @@ -4669,13 +4879,6 @@ snapshots: react: 18.3.1 shallow-equal: 3.1.0 - react-scroll@1.9.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - lodash.throttle: 4.1.1 - prop-types: 15.8.1 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-slick@0.30.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: classnames: 2.5.1 @@ -4750,8 +4953,15 @@ snapshots: path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 + restore-cursor@4.0.0: + dependencies: + onetime: 5.1.2 + signal-exit: 3.0.7 + reusify@1.0.4: {} + rfdc@1.4.1: {} + rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -4851,6 +5061,16 @@ snapshots: slash@3.0.0: {} + slice-ansi@5.0.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 4.0.0 + + slice-ansi@7.1.0: + dependencies: + ansi-styles: 6.2.1 + is-fullwidth-code-point: 5.0.0 + source-map-js@1.2.0: {} split2@4.2.0: {} @@ -4861,6 +5081,8 @@ snapshots: streamsearch@1.1.0: {} + string-argv@0.3.2: {} + string-convert@0.2.1: {} string-width@4.2.3: @@ -4875,6 +5097,12 @@ snapshots: emoji-regex: 9.2.2 strip-ansi: 7.1.0 + string-width@7.1.0: + dependencies: + emoji-regex: 10.3.0 + get-east-asian-width: 1.2.0 + strip-ansi: 7.1.0 + string.prototype.includes@2.0.0: dependencies: define-properties: 1.2.1 @@ -5021,6 +5249,8 @@ snapshots: dependencies: is-number: 7.0.0 + tr46@0.0.3: {} + ts-api-utils@1.3.0(typescript@5.5.2): dependencies: typescript: 5.5.2 @@ -5108,6 +5338,13 @@ snapshots: vary@1.1.2: {} + webidl-conversions@3.0.1: {} + + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + which-boxed-primitive@1.0.2: dependencies: is-bigint: 1.0.4 @@ -5168,6 +5405,12 @@ snapshots: string-width: 5.1.2 strip-ansi: 7.1.0 + wrap-ansi@9.0.0: + dependencies: + ansi-styles: 6.2.1 + string-width: 7.1.0 + strip-ansi: 7.1.0 + wrappy@1.0.2: {} y18n@5.0.8: {} diff --git a/renovate.json b/renovate.json index 94dd310..2f9b84a 100644 --- a/renovate.json +++ b/renovate.json @@ -15,7 +15,13 @@ "stabilityDays": 3, "matchDatasources": ["npm"], "automerge": true, - "matchUpdateTypes": ["minor", "patch", "pin", "digest", "lockFileMaintenance"] + "matchUpdateTypes": [ + "minor", + "patch", + "pin", + "digest", + "lockFileMaintenance" + ] }, { "matchDatasources": ["orb"], @@ -26,4 +32,4 @@ "schedule": ["before 8am on Monday"], "commitMessagePrefix": "upgrade(deps):", "commitMessageAction": "update" -} \ No newline at end of file +} diff --git a/src/app/home.tsx b/src/app/home.tsx index d053f9a..b5d0336 100644 --- a/src/app/home.tsx +++ b/src/app/home.tsx @@ -4,7 +4,6 @@ import { FC, useEffect } from 'react' import Aos from 'aos' import Footer from '@/components/footer/Footer' -import Header from '@/components/header/Header' import MyName from '@/components/home/1-my-name/MyName' import AboutMe from '@/components/home/2-about-me/AboutMe' import WorkExperience from '@/components/home/3-work-experience/WorkExperience' @@ -12,18 +11,14 @@ import Projects from '@/components/home/4-projects/Projects' import Skills from '@/components/home/5-skills/Skills' import GetInTouch from '@/components/home/6-get-in-touch/GetInTouch' import SocialMediaAround from '@/components/home/social-media-around/SocialMediaAround' -import ScreenSizeDetector from '@/components/screen-size-detector/ScreenSizeDetector' const Home: FC = () => { useEffect(() => { Aos.init({ duration: 2000, once: true }) }, []) - const isProd = process.env.NODE_ENV === 'production' - return ( -
-
+ <> @@ -32,8 +27,7 @@ const Home: FC = () => {
- {!isProd && } -
+ ) } diff --git a/src/app/layout.tsx b/src/app/layout.tsx index 4b5061c..e1f2369 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -1,8 +1,10 @@ import { FC, PropsWithChildren } from 'react' import { Metadata } from 'next' +import Header from '@/components/header/Header' +import ScreenSizeDetector from '@/components/screen-size-detector/ScreenSizeDetector' import { WEBSITE_URL } from '@/constants/globals' -import { Analytics } from "@vercel/analytics/react" +import { Analytics } from '@vercel/analytics/react' import './globals.css' import 'aos/dist/aos.css' @@ -38,6 +40,8 @@ export const metadata: Metadata = { } const RootLayout: FC = ({ children }) => { + const isProd = process.env.NODE_ENV === 'production' + return ( @@ -55,8 +59,12 @@ const RootLayout: FC = ({ children }) => { /> - {children} +
+
+ {children} +
+ {!isProd && } ) diff --git a/src/app/watch/dto/watchResource.dto.ts b/src/app/watch/dto/watchResource.dto.ts new file mode 100644 index 0000000..8fea0b5 --- /dev/null +++ b/src/app/watch/dto/watchResource.dto.ts @@ -0,0 +1,16 @@ +export const transformWatchResourceToDTO = (resource: any): WatchResource => { + const { id, properties } = resource + + return { + id, + title: properties.Name.title?.[0].plain_text, + tldr: properties['tl;dr'].rich_text?.[0]?.plain_text, + done: properties.Done.checkbox, + url: properties.URL.url, + type: properties.Type.select.name, + createdAt: new Date(properties['Created at'].created_time), + date: new Date(properties.Date.date.start), + source: properties.Source.select.name, + subSource: properties['Sub-source'].rich_text?.[0]?.plain_text, + } +} diff --git a/src/app/watch/page.tsx b/src/app/watch/page.tsx new file mode 100644 index 0000000..6ebba40 --- /dev/null +++ b/src/app/watch/page.tsx @@ -0,0 +1,51 @@ +import { RiInformation2Fill } from 'react-icons/ri' + +import ArrowIcon from '@/components/icons/ArrowIcon' +import { fetchWatchPages } from '@/lib/notion' + +import { transformWatchResourceToDTO } from './dto/watchResource.dto' +import WatchResources from './watchResources' + +export default async function Page() { + const firstPage = await fetchWatchPages() + const initialResources = firstPage.results.map(transformWatchResourceToDTO) + + return ( +
+ {/* Title */} +
+
+ +
+ + + Technology watch + +
+
+ + {/* Alert block */} +
+
+ +
+ During my daily technology watch, I maintain a database to persist + the resources I have consulted, with a small{' '} + tl;dr section. +
+ Those resources can be retrieved in this page. +
+
+
+ + {/* Resources */} + +
+ ) +} diff --git a/src/app/watch/types/index.ts b/src/app/watch/types/index.ts new file mode 100644 index 0000000..29524e2 --- /dev/null +++ b/src/app/watch/types/index.ts @@ -0,0 +1,12 @@ +type WatchResource = { + id: string + title: string + tldr?: string + done: boolean + url: string + type: string + createdAt: Date + date: Date + source?: string + subSource?: string +} diff --git a/src/app/watch/utils/index.ts b/src/app/watch/utils/index.ts new file mode 100644 index 0000000..e80f817 --- /dev/null +++ b/src/app/watch/utils/index.ts @@ -0,0 +1,51 @@ +type WatchResourceGroups = { + [key: string]: WatchResource[] +} + +export const groupWatchResourcesByMonth = ( + resources: WatchResource[], +): WatchResourceGroups => { + return resources.reduce( + (groups: WatchResourceGroups, resource: WatchResource) => { + const year = resource.date.getFullYear() + const month = (resource.date.getMonth() + 1).toString().padStart(2, '0') // Ajouter un zΓ©ro pour avoir un format 'MM' + + const key = `${year}/${month}` + + if (!groups[key]) { + groups[key] = [] + } + + groups[key].push(resource) + + return groups + }, + {}, + ) +} + +const formatDate = (date: Date): string => { + const year = date.getFullYear() + const month = (date.getMonth() + 1).toString().padStart(2, '0') + const day = date.getDate().toString().padStart(2, '0') + return `${year}/${month}/${day}` +} + +export const groupWatchResourcesByDate = ( + resources: WatchResource[], +): WatchResourceGroups => { + return resources.reduce( + (groups: WatchResourceGroups, resource: WatchResource) => { + const key = formatDate(resource.date) + + if (!groups[key]) { + groups[key] = [] + } + + groups[key].push(resource) + + return groups + }, + {}, + ) +} diff --git a/src/app/watch/watchResource.tsx b/src/app/watch/watchResource.tsx new file mode 100644 index 0000000..bc13fea --- /dev/null +++ b/src/app/watch/watchResource.tsx @@ -0,0 +1,68 @@ +import { FC } from 'react' + +import Badge from '@/components/badge/Badge' + +type Props = { + resource: WatchResource +} + +const ResourceType: FC<{ type: WatchResource['type'] }> = ({ type }) => { + const variant = + type === 'Article' + ? 'teal' + : type === 'Video' + ? 'red' + : type === 'Podcast' + ? 'yellow' + : 'default' + const emoji = + type === 'Article' + ? 'πŸ“' + : type === 'Video' + ? 'πŸ“Ή' + : type === 'Podcast' + ? '🎧' + : '' + + return ( + + {emoji} + {type} + + ) +} + +const WatchResource: FC = ({ resource }) => { + const { title, tldr, source, subSource, url, type } = resource + const tldrWithDot = tldr?.endsWith('.') ? tldr : `${tldr}.` + + return ( + +
+
+ {title} +
+
+
+ {tldrWithDot} +
+
+
+ +
+ + {source} + {subSource && {subSource}} + + +
+
+ ) +} + +export default WatchResource diff --git a/src/app/watch/watchResources.tsx b/src/app/watch/watchResources.tsx new file mode 100644 index 0000000..100d2e8 --- /dev/null +++ b/src/app/watch/watchResources.tsx @@ -0,0 +1,95 @@ +'use client' + +import { FC, useState } from 'react' + +import Spinner from '@/components/spinner/Spinner' +import { fetchWatchPages } from '@/lib/notion' + +import { transformWatchResourceToDTO } from './dto/watchResource.dto' +import { groupWatchResourcesByDate, groupWatchResourcesByMonth } from './utils' +import WatchResource from './watchResource' + +type Props = { + initialResources: WatchResource[] + initialNextPage?: string | null +} + +const WatchResources: FC = ({ initialResources, initialNextPage }) => { + const [watchResources, setWatchResources] = + useState(initialResources) + const [nextPageCursor, setNextPageCursor] = useState< + string | null | undefined + >(initialNextPage) + const [isLoading, setIsLoading] = useState(false) + const [hasNextPage, setHasNextPage] = useState(initialNextPage !== undefined) + + const resourcesByMonth = groupWatchResourcesByMonth(watchResources) + + const loadMoreResources = async () => { + try { + setIsLoading(true) + const response = await fetchWatchPages(nextPageCursor) + const resourcesOfPage = response.results.map(transformWatchResourceToDTO) + setWatchResources([...watchResources, ...resourcesOfPage]) + setNextPageCursor(response.next_cursor) + setHasNextPage(response.has_more) + } catch (e) { + console.error(e) + throw e + } finally { + setIsLoading(false) + } + } + + return ( +
+ {/* Month */} + {Object.entries(resourcesByMonth).map(([month, resourcesForMonth]) => { + const resourcesByWeek = groupWatchResourcesByDate(resourcesForMonth) + return ( +
+

+ {month} +

+
+ + {/* Week */} + {Object.entries(resourcesByWeek).map(([week, resources]) => { + return ( +
+

+ {week} +

+
+ {resources.map((res) => { + return + })} +
+
+ ) + })} +
+ ) + })} + +
+ {hasNextPage ? ( + + ) : ( +
+ All the content has been loaded! +
+ )} +
+
+ ) +} + +export default WatchResources diff --git a/src/components/header/Header.tsx b/src/components/header/Header.tsx index 19133fe..e4f98ab 100644 --- a/src/components/header/Header.tsx +++ b/src/components/header/Header.tsx @@ -1,3 +1,5 @@ +'use client' + import React, { FC, useEffect, useRef, useState } from 'react' import { motion } from 'framer-motion' import { tv } from 'tailwind-variants' diff --git a/src/components/header/components/DesktopMenu.tsx b/src/components/header/components/DesktopMenu.tsx index f754e08..08691ac 100644 --- a/src/components/header/components/DesktopMenu.tsx +++ b/src/components/header/components/DesktopMenu.tsx @@ -1,6 +1,6 @@ import React, { FC } from 'react' +import Link from 'next/link' import { motion } from 'framer-motion' -import { Link as ReactScrollLink } from 'react-scroll' import { tv } from 'tailwind-variants' const menuItem = tv({ @@ -9,21 +9,15 @@ const menuItem = tv({ const DesktopMenu: FC = () => { return ( -
+
- + > 01. About - + { transition={{ type: 'spring', duration: 1.2, delay: 0.7 }} className="text-primary" > - + > 02. Experience - + { transition={{ type: 'spring', duration: 1.2, delay: 0.8 }} className="text-primary" > - + > 03. Projects - + { transition={{ type: 'spring', duration: 1.2, delay: 0.9 }} className="text-primary" > - + > 04. Skills - + { transition={{ type: 'spring', duration: 1.2, delay: 1.2 }} className="text-primary" > - + > 05. Contact - + + + + + + > Tech. watch + @@ -108,4 +89,4 @@ const DesktopMenu: FC = () => { ) } -export default DesktopMenu \ No newline at end of file +export default DesktopMenu diff --git a/src/components/header/components/IconMenu.tsx b/src/components/header/components/IconMenu.tsx index 6817b47..0b80c47 100644 --- a/src/components/header/components/IconMenu.tsx +++ b/src/components/header/components/IconMenu.tsx @@ -9,7 +9,7 @@ type Props = { const IconMenu: FC = ({ rotate, onClick }: Props) => { return (
diff --git a/src/components/header/components/MobileMenu.tsx b/src/components/header/components/MobileMenu.tsx index 3b244ba..5c98a0f 100644 --- a/src/components/header/components/MobileMenu.tsx +++ b/src/components/header/components/MobileMenu.tsx @@ -1,6 +1,6 @@ import { FC } from 'react' +import Link from 'next/link' import { motion } from 'framer-motion' -import { Link } from 'react-scroll' import { tv } from 'tailwind-variants' type Props = { @@ -26,7 +26,7 @@ const MobileMenu: FC = ({ hidden, onClose }) => { initial={{ x: '100%' }} animate={hidden ? { x: '100%' } : { x: '0' }} transition={{ x: { duration: 0.4 } }} - className="fixed z-20 flex h-dvh w-full duration-300 md:hidden" + className="fixed z-20 flex h-dvh w-full duration-300 lg:hidden" >
= ({ hidden, onClose }) => { />
- + 01. About @@ -60,43 +48,28 @@ const MobileMenu: FC = ({ hidden, onClose }) => { Experience - + 03. Projects - + 04. Skills + + 05. + Contact + + - 05. - Contact + Tech. + Watch resources diff --git a/src/components/home/2-about-me/AboutMe.tsx b/src/components/home/2-about-me/AboutMe.tsx index 3288c7b..444cff7 100644 --- a/src/components/home/2-about-me/AboutMe.tsx +++ b/src/components/home/2-about-me/AboutMe.tsx @@ -37,9 +37,9 @@ const AboutMe: FC = () => {
- As a Senior Software Engineer, I really love new technologies and the - challenges they represent in our current society. I particularly - enjoy web development and DevOps activities. + As a Senior Software Engineer, I really love new technologies + and the challenges they represent in our current society. I + particularly enjoy web development and DevOps activities.
diff --git a/src/components/home/3-work-experience/WorkExperience.tsx b/src/components/home/3-work-experience/WorkExperience.tsx index bb86d48..fdd0216 100644 --- a/src/components/home/3-work-experience/WorkExperience.tsx +++ b/src/components/home/3-work-experience/WorkExperience.tsx @@ -23,6 +23,7 @@ const WorkExperience: FC = () => {
{/* Title */}
@@ -38,7 +39,9 @@ const WorkExperience: FC = () => { Work experience & Education - Experience & Education + + Experience & Education +
diff --git a/src/components/home/3-work-experience/components/WorkExperienceTabs.tsx b/src/components/home/3-work-experience/components/WorkExperienceTabs.tsx index 2d56e86..8e5fb41 100644 --- a/src/components/home/3-work-experience/components/WorkExperienceTabs.tsx +++ b/src/components/home/3-work-experience/components/WorkExperienceTabs.tsx @@ -49,10 +49,7 @@ const WorkExperienceTabs: FC = ({ } return ( -
+
{/* Left bar Holder */}
{ return (
{/* Title */}
diff --git a/src/components/home/4-projects/ProjectsList.tsx b/src/components/home/4-projects/ProjectsList.tsx index cb6cea8..160ae1a 100644 --- a/src/components/home/4-projects/ProjectsList.tsx +++ b/src/components/home/4-projects/ProjectsList.tsx @@ -306,8 +306,8 @@ const projects: Project[] = [ description: (
In language C and using the V-Model, set up a version of the{' '} - Othello{' '} - game with an artificial intelligence (AI) with the Min-Max algorithm. + Othello game with an artificial + intelligence (AI) with the Min-Max algorithm.
), technologies: ['C', 'Min-max', 'AI', 'V-Model'], diff --git a/src/components/home/4-projects/components/Project.tsx b/src/components/home/4-projects/components/Project.tsx index e3b6a8d..fed2e7e 100644 --- a/src/components/home/4-projects/components/Project.tsx +++ b/src/components/home/4-projects/components/Project.tsx @@ -348,20 +348,20 @@ const Project: FC = ({ project, index }) => { {(project.video || (project.images && project.images?.length > 0)) && ( -
- -
- )} +
+ +
+ )}
diff --git a/src/components/home/5-skills/Skills.tsx b/src/components/home/5-skills/Skills.tsx index 1ad8770..49d9f74 100644 --- a/src/components/home/5-skills/Skills.tsx +++ b/src/components/home/5-skills/Skills.tsx @@ -34,28 +34,52 @@ const Skills: FC = () => { {/* Skills */} -
- {skillsByLevel.map(level => { - const transition = { y: -3, scale: 1.2, transition: { duration: 0.1 } } +
+ {skillsByLevel.map((level) => { + const transition = { + y: -3, + scale: 1.2, + transition: { duration: 0.1 }, + } return ( -
-
{level.title}
+
+
+ {level.title} +
- {level.skills.map(skill => ( -
+ {level.skills.map((skill) => ( +
- {skill.icon} + + {skill.icon} + - {skill.text} + + {skill.text} +
))}
-
) +
+ ) })}
-
) +
+ ) } -export default Skills \ No newline at end of file +export default Skills diff --git a/src/components/home/5-skills/SkillsList.tsx b/src/components/home/5-skills/SkillsList.tsx index 2cdf0e0..105f2b5 100644 --- a/src/components/home/5-skills/SkillsList.tsx +++ b/src/components/home/5-skills/SkillsList.tsx @@ -1,5 +1,5 @@ -import { ReactNode } from 'react'; -import { DiScrum } from 'react-icons/di'; +import { ReactNode } from 'react' +import { DiScrum } from 'react-icons/di' import { FaAirbnb, FaAngular, @@ -21,7 +21,7 @@ import { FaReact, FaSass, FaWindows, -} from 'react-icons/fa'; +} from 'react-icons/fa' import { SiApachemaven, SiC, @@ -45,15 +45,15 @@ import { SiVite, SiWebpack, SiYarn, -} from 'react-icons/si'; +} from 'react-icons/si' type Skill = { - text: string; - icon: ReactNode; -}; + text: string + icon: ReactNode +} type SkillsByLevel = Array<{ - title: string; + title: string skills: Skill[] className?: string }> @@ -91,7 +91,7 @@ const advancedSkills: Skill[] = [ { text: 'Scrum', icon: }, { text: 'TDD', icon: }, { text: 'SQL', icon: }, -]; +] const intermediateSkills: Skill[] = [ { text: 'Angular', icon: }, @@ -105,9 +105,13 @@ const intermediateSkills: Skill[] = [ { text: 'Neo4j', icon: }, { text: 'FFmpeg', icon: }, { text: 'Lottie', icon: }, -]; +] export const skillsByLevel: SkillsByLevel = [ - { title: "Advanced", skills: advancedSkills, className: "grow md:[&>div]:grid-cols-10" }, - { title: 'Intermediate', skills: intermediateSkills } -] \ No newline at end of file + { + title: 'Advanced', + skills: advancedSkills, + className: 'grow md:[&>div]:grid-cols-10', + }, + { title: 'Intermediate', skills: intermediateSkills }, +] diff --git a/src/components/spinner/Spinner.tsx b/src/components/spinner/Spinner.tsx new file mode 100644 index 0000000..363e3ae --- /dev/null +++ b/src/components/spinner/Spinner.tsx @@ -0,0 +1,31 @@ +import { FC } from 'react' + +type Props = { + className?: string +} + +const Spinner: FC = ({ className }) => { + return ( +
+ + Loading... +
+ ) +} + +export default Spinner diff --git a/src/constants/globals.ts b/src/constants/globals.ts index 072566b..57122e4 100644 --- a/src/constants/globals.ts +++ b/src/constants/globals.ts @@ -1 +1,3 @@ export const WEBSITE_URL = 'https://gautier-darchen.vercel.app/' + +export const WATCH_RESOURCES_PAGE_SIZE = 100 diff --git a/src/lib/notion.ts b/src/lib/notion.ts new file mode 100644 index 0000000..deda3f3 --- /dev/null +++ b/src/lib/notion.ts @@ -0,0 +1,30 @@ +'use server' + +import { WATCH_RESOURCES_PAGE_SIZE } from '@/constants/globals' +import { Client } from '@notionhq/client' + +const notion = new Client({ + auth: process.env.NOTION_TOKEN, +}) + +export const fetchWatchPages = async (cursor?: string | null) => { + return notion.databases.query({ + database_id: process.env.NOTION_DATABASE_ID!, + start_cursor: cursor ?? undefined, + page_size: WATCH_RESOURCES_PAGE_SIZE, + sorts: [ + { + property: 'Date', + direction: 'descending', + }, + { + property: 'Created at', + direction: 'ascending', + }, + ], + filter: { + property: 'Done', + checkbox: { equals: true }, + }, + }) +} diff --git a/tailwind.config.ts b/tailwind.config.ts index 9084874..0afb969 100644 --- a/tailwind.config.ts +++ b/tailwind.config.ts @@ -1,10 +1,8 @@ import type { Config } from 'tailwindcss' -import colors from "tailwindcss/colors" +import colors from 'tailwindcss/colors' export default { - content: [ - './src/**/*.{js,ts,jsx,tsx,mdx}', - ], + content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'], theme: { rotate: { '-180': '-180deg', @@ -56,5 +54,4 @@ export default { variants: { scrollbar: ['rounded'], }, -} satisfies Config; - +} satisfies Config diff --git a/tsconfig.json b/tsconfig.json index 2fef264..32aeaf1 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,6 +21,12 @@ "~/*": ["./*"] } }, - "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts", "./tailwind.config.ts"], + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + "./tailwind.config.ts" + ], "exclude": ["node_modules"] }