1
1
mirror of https://github.com/theoludwig/theoludwig.git synced 2025-05-29 22:37:44 +02:00

Compare commits

...

223 Commits

Author SHA1 Message Date
e32c53caa1 chore(release): 2.5.6 [skip ci] 2023-04-02 21:18:14 +00:00
361ea37deb chore: fix CI issues 2023-04-02 23:16:51 +02:00
d49a8a7470 fix: update dependencies to latest 2023-04-02 22:44:09 +02:00
a4996c8251 chore: remove useless runner-dependencies in Dockerfile 2023-01-11 17:42:29 +01:00
b25451e631 chore(release): 2.5.5 [skip ci] 2023-01-10 23:05:24 +00:00
042a861f58 fix: update dependencies to latest 2023-01-10 23:56:46 +01:00
d76db36dbc chore(release): 2.5.4 [skip ci] 2022-12-08 08:54:03 +00:00
99d9dcf334 fix: improve Resume 2022-12-08 09:52:39 +01:00
ece5ded1b4 chore(release): 2.5.3 [skip ci] 2022-11-29 09:33:10 +00:00
1514600998 fix: improve Resume 2022-11-29 10:29:02 +01:00
5f5b328895 chore(release): 2.5.2 [skip ci] 2022-11-19 19:43:26 +00:00
c88887a322 fix: better resume 2022-11-19 20:24:13 +01:00
014044573a chore(release): 2.5.1 [skip ci] 2022-11-10 11:35:11 +00:00
df009c3f7b fix(posts): update broken link in thream-v1.0.0.md 2022-11-10 12:31:48 +01:00
5c85ca2ef1 chore: fix cypress unit tests 2022-11-08 11:00:31 +01:00
07f7942496 chore(release): 2.5.0 [skip ci] 2022-10-27 17:24:30 +00:00
213a3fa182 build(deps): bump Next.js to v13 2022-10-27 19:13:29 +02:00
28d9211583 fix(posts): update git-ultimate-guide 2022-10-23 20:15:07 +02:00
4d085cb148 fix: update biography description 2022-10-23 18:38:37 +02:00
e6c583f2cd ci: fix timeout 2022-10-20 23:57:53 +02:00
232b54588a feat(skills): add PHP and Laravel 2022-10-20 22:44:40 +02:00
c419fb3bb4 chore: remove usage of styled-jsx 2022-10-20 22:44:32 +02:00
03e7e22d74 chore: reduce docker image size 2022-10-20 22:44:32 +02:00
e85c241ed1 feat(posts): add git-ultimate-guide 2022-10-20 22:43:25 +02:00
c1877297f8 refactor: minor changes 2022-08-27 02:30:55 +02:00
83231197dd chore(release): 2.4.1 [skip ci] 2022-08-23 11:33:38 +00:00
a2fe2205bc fix(resume): wrong base path for assets 2022-08-23 13:31:17 +02:00
e1f3dceb07 chore(release): 2.4.0 [skip ci] 2022-08-23 10:33:09 +00:00
0f89fee52f feat: add giscus comments system for blog posts 2022-08-23 12:23:31 +02:00
2fcc7ac384 chore(release): 2.3.2 [skip ci] 2022-07-28 21:06:12 +00:00
9351edf626 chore: use the right resume.json 2022-07-28 23:01:19 +02:00
1f4aa54211 chore: remove jest -> cypress for unit tests 2022-07-28 22:51:12 +02:00
8bc1471cbb chore: easier development for jsonresume-theme-custom thanks to vite 2022-07-28 21:20:41 +02:00
1ebdab18a5 fix: update about, now second year of university 2022-07-23 23:00:58 +02:00
b9b76e839a build(deps): update latest 2022-07-01 23:12:47 +02:00
bc065a2e19 chore(release): 2.3.1 [skip ci] 2022-05-03 08:12:15 +00:00
5d3a287b27 fix(resume): wrong dates 2022-05-03 10:05:11 +02:00
fb689c9bc1 chore(release): 2.3.0 [skip ci] 2022-04-11 10:35:55 +00:00
2c3a70df2a feat(posts): add thream-v1-0-0 2022-04-11 12:31:19 +02:00
bce254a355 chore(release): 2.2.1 [skip ci] 2022-03-24 18:00:10 +00:00
f67d331416 fix: calculate age client side so it updates "automatically" (not only on rebuild) 2022-03-24 18:57:27 +01:00
6abc881e94 chore(release): 2.2.0 [skip ci] 2022-03-24 10:49:45 +00:00
a67d6665ea feat: display age nearby the birth date 2022-03-24 11:45:19 +01:00
1152039663 chore(release): 2.1.0 [skip ci] 2022-03-14 08:15:56 +00:00
919ebd5f3e feat(posts): add mistakes-as-junior-developer 2022-03-14 09:09:46 +01:00
94212f9b5c chore(release): 2.0.2 [skip ci] 2022-02-23 18:52:16 +00:00
bf9347f685 ci: multiple workflows instead of one 2022-02-23 19:46:44 +01:00
896b6051e8 fix: redirect /curriculum-vitae.html to /curriculum-vitae 2022-02-23 19:31:18 +01:00
b5f3552c07 chore(release): 2.0.1 [skip ci] 2022-02-23 10:55:50 +00:00
5fbae8601f fix(posts): spelling mistakes 2022-02-23 11:51:00 +01:00
48d35776a9 fix(resume): usage of experience website 2022-02-23 11:14:53 +01:00
8b9e58c47c chore(release): 2.0.0 [skip ci] 2022-02-23 08:14:21 +00:00
33078ece66 chore: temporarily support Node.js v14 to deploy on Vercel 2022-02-23 09:06:12 +01:00
a2da9618af test(e2e): header should always be visible (sticky) 2022-02-23 09:03:10 +01:00
a467ea7aff feat: usage of VSCode Dark+ syntax highlighting in posts 2022-02-23 00:38:50 +01:00
0e0036b737 feat: add Curriculum vitae 2022-02-22 21:19:42 +01:00
729e540d04 chore: maintenance 2022-02-20 15:12:10 +01:00
e5f4615f7f fix(posts): grammar and orthograph in clean-code (#321) 2022-02-20 15:12:10 +01:00
0bf89f4df5 feat(posts): add clean-code 2022-02-20 15:12:10 +01:00
bcb184e49c feat: add blog (#320) 2022-02-20 15:12:10 +01:00
1505b81233 build(deps): bump Node.js to 16.0.0 and npm to 8.0.0
BREAKING CHANGE: minimum supported Node.js >= 16.0.0 and npm >= 8.0.0

fixes #74
2022-02-20 15:12:10 +01:00
a30355582e feat(skills): add C/C++ 2022-02-20 15:12:10 +01:00
a4effb52f9 feat(skills): add GNU/Linux 2022-02-20 15:12:10 +01:00
52bba0ef9c build(deps): update latest 2022-02-20 15:08:48 +01:00
8ecfeca50d chore(release): 1.3.6 [skip ci] 2021-09-09 08:15:20 +00:00
fd0740d12a fix: add text that I'm a student at University 2021-09-09 10:08:42 +02:00
bd2dc9c9af build(deps-dev): bump babel-jest from 27.1.0 to 27.1.1 (#212) 2021-09-09 08:46:48 +02:00
a53888ab42 build(deps-dev): bump @types/node from 16.7.13 to 16.9.0 (#213) 2021-09-09 08:46:16 +02:00
624e79eecd build(deps-dev): bump jest from 27.1.0 to 27.1.1 (#214) 2021-09-09 08:46:02 +02:00
049ec367fc build(deps-dev): bump tailwindcss from 2.2.11 to 2.2.14 (#211) 2021-09-08 21:27:01 +02:00
56f22d0c9b build(deps-dev): bump tailwindcss from 2.2.9 to 2.2.11 (#207) 2021-09-08 21:17:25 +02:00
9adb67662e build(deps-dev): bump @types/node from 16.7.10 to 16.7.13 (#208) 2021-09-08 21:17:14 +02:00
010087088f build(deps): bump html-react-parser from 1.2.8 to 1.3.0 (#209) 2021-09-08 21:17:00 +02:00
35d4396e80 build(deps): bump sharp from 0.29.0 to 0.29.1 (#210) 2021-09-08 21:16:48 +02:00
934118737a build(deps-dev): bump @typescript-eslint/eslint-plugin (#204) 2021-09-08 21:16:31 +02:00
b692dac926 build(deps): bump crazy-max/ghaction-import-gpg from 3.2.0 to 4 (#200)
Co-authored-by: Divlo <contact@divlo.fr>
2021-09-06 16:37:10 +02:00
dd582652ab build(deps-dev): bump @types/react from 17.0.19 to 17.0.20 (#201) 2021-09-06 16:28:01 +02:00
337352de0c build(deps-dev): bump @semantic-release/git from 9.0.0 to 9.0.1 (#202) 2021-09-06 16:27:44 +02:00
c513268cbb build(deps-dev): bump autoprefixer from 10.3.3 to 10.3.4 (#199) 2021-09-03 09:54:38 +02:00
4fdcb2b667 build(deps-dev): bump start-server-and-test from 1.13.1 to 1.14.0 (#198) 2021-09-03 09:54:17 +02:00
377b8e91a6 chore(release): 1.3.5 [skip ci] 2021-09-01 22:17:55 +00:00
fce29c9d4a build(deps): update latest 2021-09-02 00:13:20 +02:00
c198f47aa9 build(deps-dev): eslint-config-standard-with-typescript to 21.0.1 (#195) 2021-09-01 16:38:44 +02:00
8e051332cd build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.30.0 (#197) 2021-09-01 16:37:39 +02:00
9f3436e1df build(deps-dev): bump tailwindcss from 2.2.8 to 2.2.9 (#196) 2021-09-01 16:37:17 +02:00
2f2373e62f build(deps-dev): bump cypress from 8.3.0 to 8.3.1 (#187) 2021-09-01 16:36:12 +02:00
c6b455dd10 build(deps-dev): bump eslint-plugin-prettier from 3.4.1 to 4.0.0 (#189) 2021-09-01 16:35:35 +02:00
4e089b41f2 build(deps-dev): bump @types/node from 16.7.2 to 16.7.10 (#193) 2021-09-01 16:35:19 +02:00
6c102b1b35 build(deps-dev): bump eslint-config-next from 11.1.0 to 11.1.2 (#194) 2021-09-01 16:35:03 +02:00
52b10944b7 build(deps): bump next from 11.1.0 to 11.1.2 (#192) 2021-09-01 16:34:50 +02:00
db36eb3e7a build(deps-dev): bump jest from 27.0.6 to 27.1.0 (#185) 2021-08-27 12:45:18 +02:00
c739ad951d build(deps-dev): bump babel-jest from 27.0.6 to 27.1.0 (#184) 2021-08-27 12:45:07 +02:00
2802ff029f build(deps-dev): bump tailwindcss from 2.2.7 to 2.2.8 (#182) 2021-08-27 12:43:05 +02:00
1a7457b44b build(deps-dev): bump @types/node from 16.7.1 to 16.7.2 (#183) 2021-08-27 12:41:56 +02:00
ff210f879d build(deps-dev): bump semantic-release from 17.4.6 to 17.4.7 (#178) 2021-08-27 12:41:43 +02:00
607454b360 build(deps-dev): bump eslint-plugin-import from 2.24.1 to 2.24.2 (#176) 2021-08-27 12:41:29 +02:00
d1522fbf44 build(deps): bump node from 16.7.0 to 16.8.0 (#179) 2021-08-27 12:41:06 +02:00
b82eae7499 build(deps-dev): bump autoprefixer from 10.3.2 to 10.3.3 (#181) 2021-08-27 12:40:52 +02:00
73527ce8fe build(deps-dev): bump husky from 7.0.1 to 7.0.2 (#177) 2021-08-27 12:40:30 +02:00
0cd885ee70 build(deps-dev): bump typescript from 4.3.5 to 4.4.2 (#180) 2021-08-27 12:40:16 +02:00
2cb2df975f build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.29.3 (#175) 2021-08-24 02:30:24 +02:00
37f5843adb build(deps-dev): bump semantic-release from 17.4.5 to 17.4.6 (#174) 2021-08-24 02:30:06 +02:00
d794d38f14 test(e2e): visible instead of exist 2021-08-23 19:48:15 +02:00
fc5ba28b8a perf: remove unnecessary fonts weight 2021-08-23 19:41:39 +02:00
b5945150b8 fix: remove Hyper Terminal from tools used 2021-08-23 19:25:17 +02:00
aa12d626d2 perf: uses-responsive-images 2021-08-23 19:17:30 +02:00
6ac4782b7d build(deps-dev): bump @types/node from 16.6.2 to 16.7.1 (#171) 2021-08-23 12:00:52 +02:00
0aa998d593 build(deps-dev): bump eslint-plugin-prettier from 3.4.0 to 3.4.1 (#172) 2021-08-23 12:00:39 +02:00
56f975e53c build(deps-dev): bump autoprefixer from 10.3.1 to 10.3.2 (#173) 2021-08-23 12:00:26 +02:00
5a16d24ea1 build(deps-dev): bump eslint-plugin-import from 2.24.0 to 2.24.1 (#170) 2021-08-20 10:37:53 +02:00
52267005ec build(deps-dev): bump @types/react from 17.0.18 to 17.0.19 (#169) 2021-08-20 10:37:38 +02:00
99b9b12ac9 build(deps-dev): bump @types/node from 16.6.1 to 16.6.2 (#168) 2021-08-19 10:56:35 +02:00
2cae77481f build(deps): bump node from 16.6.2 to 16.7.0 (#167) 2021-08-19 10:55:46 +02:00
e98b47a459 build(deps): bump sharp from 0.28.3 to 0.29.0 (#166) 2021-08-18 11:26:24 +02:00
4cc87758c1 build(deps): bump next-pwa from 5.2.24 to 5.3.1 (#164) 2021-08-17 20:49:12 +02:00
1bb0f31223 build(deps-dev): bump @typescript-eslint/eslint-plugin (#165) 2021-08-17 20:48:56 +02:00
af2dd0bd60 build(deps-dev): bump cypress from 8.2.0 to 8.3.0 (#163) 2021-08-17 20:48:39 +02:00
63d7485c8d build(deps): bump next-pwa from 5.3.0 to 5.2.24 2021-08-16 15:38:56 +02:00
74fde0ea40 build(deps): update latest version 2021-08-16 15:31:35 +02:00
0d2b318818 build(deps): bump node from 16.6.1 to 16.6.2 (#155) 2021-08-13 15:50:10 +02:00
266b3f8589 test: add cypress e2e (#159) 2021-08-13 15:48:29 +02:00
f7d304ca80 build(deps): bump read-pkg from 5.2.0 to 6.0.0 (#136) 2021-08-12 11:03:37 +02:00
63017953d7 chore(release): 1.3.4 [skip ci] 2021-08-11 23:29:15 +00:00
20600eb976 build(deps): bump crazy-max/ghaction-import-gpg from 3.1.0 to 3.2.0 (#154) 2021-08-12 01:21:54 +02:00
7f920b77aa build(deps): bump actions/setup-node from 2.3.1 to 2.4.0 (#152) 2021-08-12 01:21:40 +02:00
4f5dfc63ea perf: reduce build size + add next-secure-headers 2021-08-12 01:19:11 +02:00
712805df93 build(deps): bump actions/setup-node from 2.3.0 to 2.3.1 (#144) 2021-08-04 10:39:56 +02:00
cd68f597c9 build(deps-dev): bump @typescript-eslint/eslint-plugin (#143) 2021-08-04 10:39:35 +02:00
7ec3fe8ced build(deps-dev): bump eslint from 7.31.0 to 7.32.0 (#141) 2021-08-04 10:39:12 +02:00
90d22b2c7f build(deps-dev): bump @types/node from 16.4.7 to 16.4.10 (#142) 2021-08-04 10:38:47 +02:00
4b06fd0522 build(deps): bump node from 16.5.0 to 16.6.1 (#145) 2021-08-04 10:38:28 +02:00
b4427f36c2 build(deps): bump @fortawesome/react-fontawesome to 0.1.15 (#146) 2021-08-04 10:38:07 +02:00
b758c64e02 build(deps-dev): bump @types/node from 16.4.6 to 16.4.7 (#139) 2021-07-30 07:21:41 +02:00
04469b83ea build(deps-dev): bump @types/node from 16.4.4 to 16.4.6 (#138) 2021-07-29 08:16:05 +02:00
36d54666a0 build(deps-dev): bump @types/node from 16.4.3 to 16.4.4 (#137) 2021-07-28 08:17:45 +02:00
a34cefec6e chore(release): set correctly env [skip ci] 2021-07-27 21:34:08 +02:00
5c343395df chore(release): 1.3.3 [skip ci] 2021-07-27 19:06:15 +00:00
028815a7b6 fix: sign release commit and backmerge to develop 2021-07-27 21:01:33 +02:00
a2ad591d6d chore(release): 1.3.2 [skip ci] 2021-07-27 18:04:31 +00:00
7087911756 ci(release): add GH_TOKEN 2021-07-27 20:01:21 +02:00
35b1c4169f ci(release): persist-credentials: false 2021-07-27 19:52:24 +02:00
4c351b8179 chore: update message of release commit 2021-07-27 19:35:08 +02:00
701dccc018 fix: include version in release 2021-07-27 19:26:08 +02:00
5133765f94 chore: add Vercel CLI 2021-07-27 19:00:21 +02:00
3b208c6614 fix: disable hover:underline on link of Respository (open source) 2021-07-27 18:31:41 +02:00
52870fd6a4 fix: replace facebook/jest to vercel/styled-jsx 2021-07-27 11:44:44 +00:00
3a278fec10 feat: add version number in footer 2021-07-27 11:36:35 +00:00
669f592a9f build(deps-dev): bump @typescript-eslint/eslint-plugin (#135) 2021-07-27 11:02:51 +02:00
9c0a3ea1af build(deps-dev): bump @types/react from 17.0.14 to 17.0.15 (#134) 2021-07-26 06:50:08 +02:00
fa8d70bf82 build(deps-dev): bump @commitlint/cli from 12.1.4 to 13.1.0 (#129) 2021-07-26 06:49:55 +02:00
3293fd488e build(deps-dev): bump lint-staged from 11.1.0 to 11.1.1 (#130) 2021-07-26 06:49:39 +02:00
426bee09da build(deps-dev): bump tailwindcss from 2.2.6 to 2.2.7 (#133) 2021-07-26 06:49:16 +02:00
dbc6c84895 build(deps-dev): bump @commitlint/config-conventional to 13.1.0 (#128) 2021-07-26 06:48:57 +02:00
fab539c9d7 build(deps-dev): bump @types/node from 16.4.1 to 16.4.3 (#132) 2021-07-26 06:48:32 +02:00
176ab64a37 build(deps-dev): bump markdownlint-cli from 0.27.1 to 0.28.1 (#131) 2021-07-26 06:48:10 +02:00
1b56bbc694 build(deps-dev): bump @types/node from 16.4.0 to 16.4.1 (#126) 2021-07-23 08:11:04 +02:00
0f9a968081 build(deps-dev): bump lint-staged from 11.0.1 to 11.1.0 (#127) 2021-07-23 08:10:52 +02:00
6b9ff4100d build(deps-dev): bump tailwindcss from 2.2.4 to 2.2.6 (#124) 2021-07-22 07:57:57 +02:00
870bc3d26b build(deps-dev): bump postcss from 8.3.5 to 8.3.6 (#125) 2021-07-22 07:55:45 +02:00
41e4b93427 build(deps-dev): bump @typescript-eslint/eslint-plugin (#120) 2021-07-21 12:57:31 +02:00
72ae4ef01d build(deps): bump next-pwa from 5.2.23 to 5.2.24 (#121) 2021-07-21 12:57:20 +02:00
748259b57c build(deps): bump actions/setup-node from 2.2.0 to 2.3.0 (#122) 2021-07-21 12:57:09 +02:00
fafd606c18 build(deps-dev): bump @types/node from 16.3.3 to 16.4.0 (#123) 2021-07-21 12:56:56 +02:00
b8c3022532 build(deps-dev): bump @types/node from 16.3.2 to 16.3.3 (#119) 2021-07-19 11:32:26 +02:00
46adaee53f build(deps-dev): bump eslint from 7.30.0 to 7.31.0 (#118) 2021-07-19 11:32:04 +02:00
508114152c build(deps-dev): bump @types/node from 16.3.1 to 16.3.2 (#116) 2021-07-15 17:19:26 +02:00
b2852d172c build(deps-dev): bump lint-staged from 11.0.0 to 11.0.1 (#115) 2021-07-15 17:19:13 +02:00
16e3b1e465 build(deps): bump node from 16.4.2 to 16.5.0 (#117) 2021-07-15 17:19:01 +02:00
ae610ff816 build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.28.3 (#114) 2021-07-13 23:24:17 +02:00
7c001f3c30 build(deps-dev): bump autoprefixer from 10.3.0 to 10.3.1 (#113) 2021-07-13 23:18:52 +02:00
7eada755e1 build(deps-dev): bump autoprefixer from 10.2.6 to 10.3.0 (#112) 2021-07-12 13:28:27 +02:00
6909304f15 build(deps-dev): bump @types/node from 16.0.3 to 16.3.1 (#111) 2021-07-10 19:07:50 +02:00
25b2f05170 build(deps-dev): bump @types/react from 17.0.13 to 17.0.14 (#107) 2021-07-10 19:06:30 +02:00
0cc83a811c build(deps): bump node from 16.4.0 to 16.4.2 (#106) 2021-07-10 19:06:10 +02:00
78b14c2620 build(deps-dev): bump @types/node from 16.0.0 to 16.0.3 (#110) 2021-07-10 19:05:51 +02:00
eebdf0edd2 build(deps): bump @fontsource/montserrat from 4.4.5 to 4.5.0 (#109) 2021-07-10 19:05:40 +02:00
62e8005081 build(deps-dev): bump @types/jest from 26.0.23 to 26.0.24 (#105) 2021-07-07 19:04:04 +02:00
6473e9da7d build(deps-dev): bump husky from 7.0.0 to 7.0.1 (#104) 2021-07-07 19:03:52 +02:00
1805997f59 build(deps): bump node from 16.3.0 to 16.4.0 (#103) 2021-07-07 19:03:39 +02:00
fb25c12883 build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.28.2 (#102) 2021-07-06 09:02:07 +02:00
849b758fab build(deps): bump next-pwa to 5.2.23 (#101) 2021-07-06 09:01:44 +02:00
ccf5d42c19 feat: add Open source section 2021-07-04 19:56:05 +02:00
2d68ce59ca docs(readme): usage of json instead of TypeScript (About) 2021-07-04 15:42:52 +02:00
4e6531e341 build(deps-dev): bump @types/react from 17.0.11 to 17.0.13 (#98) 2021-07-02 10:39:07 +02:00
8f2d0817ce build(deps-dev): bump husky from 6.0.0 to 7.0.0 (#99) 2021-07-02 10:38:56 +02:00
7674401e7c build(deps-dev): bump @types/node from 15.12.5 to 15.14.0 (#100) 2021-07-02 10:37:50 +02:00
61983dfc4a build(deps): bump actions/setup-node from 2.1.5 to 2.2.0 (#96) 2021-07-01 09:14:38 +02:00
ed47407b7d build(deps-dev): bump typescript from 4.3.4 to 4.3.5 (#97) 2021-07-01 09:00:18 +02:00
0a79754978 build(deps-dev): bump babel-jest to 27.0.6 (#94) 2021-06-29 11:40:39 +02:00
725afecbf3 build(deps-dev): bump jest to 27.0.6 (#95) 2021-06-29 11:39:23 +02:00
1bf79e55e1 build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.28.1 (#93) 2021-06-29 11:38:57 +02:00
3a369c49fa build(deps-dev): bump prettier from 2.3.1 to 2.3.2 (#90) 2021-06-28 12:03:12 +02:00
e78ccf3db4 build(deps-dev): bump @types/node from 15.12.4 to 15.12.5 (#91) 2021-06-28 12:02:52 +02:00
acafe71f31 build(deps): bump next-themes from 0.0.14 to 0.0.15 (#92) 2021-06-28 12:02:15 +02:00
3ef876b737 feat: add GitLab and npm social medias like README 2021-06-24 20:10:53 +02:00
b30bbc99e9 feat: add new Leon portfolio 2021-06-24 19:57:06 +02:00
235c072c21 feat: add new skills 2021-06-24 19:46:44 +02:00
f5bdd85b73 fix: set Divlo in Header in yellow 2021-06-24 18:39:08 +02:00
b81ae5a9a6 fix: replace "My section" to "Section", delete "My" 2021-06-24 18:32:13 +02:00
1ea5e3f323 build(deps-dev): bump tailwindcss to 2.2.4 (#89) 2021-06-24 09:26:46 +02:00
f6eaef54b9 build(deps-dev): bump @testing-library/react to 12.0.0 (#88) 2021-06-24 09:26:31 +02:00
5b14361d74 build(deps-dev): bump eslint-config-next to 11.0.1 (#84) 2021-06-23 13:28:18 +02:00
d1f9c0eb2f build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.28.0 (#83) 2021-06-23 13:28:02 +02:00
95b27abec1 build(deps-dev): bump babel-jest to 27.0.5 (#85) 2021-06-23 13:27:45 +02:00
228e987d8b build(deps): bump next to 11.0.1 (#86) 2021-06-23 13:27:29 +02:00
7c44102afd build(deps-dev): bump jest to 27.0.5 (#87) 2021-06-23 13:27:14 +02:00
b8410e5628 build(deps-dev): bump tailwindcss to 2.2.2 (#81) 2021-06-21 07:05:31 +02:00
d6f0b12b17 build(deps-dev): bump @types/node to 15.12.4 (#80) 2021-06-21 07:04:50 +02:00
b02e31c373 build(deps-dev): bump eslint to 7.29.0 (#82) 2021-06-21 07:04:09 +02:00
e012d41929 build(deps): bump html-react-parser to 1.2.7 (#79) 2021-06-21 07:03:36 +02:00
4bd77b45e4 build(deps-dev): bump tailwindcss to 2.2.0 (#77) 2021-06-18 11:19:52 +02:00
e43f572588 build(deps-dev): bump postcss to 8.3.5 (#78) 2021-06-18 11:18:44 +02:00
9aecb3cab9 build(deps-dev): bump typescript to 4.3.4 (#76) 2021-06-18 11:18:21 +02:00
f1256ab23f build(deps-dev): bump typescript to 4.3.3 (#75) 2021-06-17 12:21:16 +02:00
892bf0e87a build(deps): bump next to 11.0.0 2021-06-15 20:35:52 +02:00
61ef6c5525 build(deps-dev): bump postcss from 8.3.3 to 8.3.4 (#73) 2021-06-15 11:54:11 +02:00
38405d658e build(deps-dev): bump @typescript-eslint/eslint-plugin to 4.27.0 (#72) 2021-06-15 11:54:01 +02:00
f3b7c315f0 build(deps-dev): bump postcss from 8.3.2 to 8.3.3 (#71) 2021-06-14 10:20:21 +02:00
6950286eec chore: fix some imperfections 2021-06-13 01:53:13 +02:00
60f966c493 chore: usage of node >= 14.0.0 2021-06-12 22:31:34 +00:00
7af4d3c512 chore: add devcontainer 2021-06-12 22:30:02 +00:00
d9b53480be build(deps-dev): bump postcss from 8.3.1 to 8.3.2 (#70) 2021-06-11 09:02:51 +02:00
a574a8ffd1 build(deps-dev): bump postcss from 8.3.0 to 8.3.1 (#69) 2021-06-10 08:48:34 +02:00
144 changed files with 22049 additions and 32686 deletions

View File

@ -1,3 +0,0 @@
{
"presets": ["next/babel"]
}

1
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1 @@
FROM mcr.microsoft.com/devcontainers/javascript-node:18

View File

@ -0,0 +1,21 @@
{
"name": "Divlo",
"dockerComposeFile": "./docker-compose.yml",
"service": "workspace",
"workspaceFolder": "/workspace",
"settings": {
"remote.autoForwardPorts": false
},
"extensions": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker"
],
"forwardPorts": [3000],
"postAttachCommand": ["npm", "install"],
"remoteUser": "node"
}

View File

@ -0,0 +1,8 @@
services:
workspace:
build:
context: './'
dockerfile: './Dockerfile'
volumes:
- '..:/workspace:cached'
command: 'sleep infinity'

View File

@ -1,11 +1,5 @@
.vscode .*
.git !.npmrc
.next
build build
coverage coverage
dist
node_modules node_modules
out
**/workbox-*.js
**/sw.js
**/__test__/**

View File

@ -1,6 +0,0 @@
.next
.lighthouseci
node_modules
next-env.d.ts
**/workbox-*.js
**/sw.js

View File

@ -1,15 +1,15 @@
{ {
"extends": ["standard-with-typescript", "eslint-config-prettier"], "extends": ["conventions", "next/core-web-vitals", "prettier"],
"plugins": ["eslint-plugin-prettier"], "plugins": ["prettier", "unicorn"],
"parserOptions": { "parserOptions": {
"project": "./tsconfig.json" "project": "./tsconfig.json"
}, },
"env": { "env": {
"node": true, "node": true,
"browser": true, "browser": true
"jest": true
}, },
"rules": { "rules": {
"prettier/prettier": "error" "prettier/prettier": "error",
"@next/next/no-img-element": "off"
} }
} }

1
.gitattributes vendored Normal file
View File

@ -0,0 +1 @@
* text=auto eol=lf

View File

@ -1,11 +1,4 @@
<!-- <!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. -->
Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time.
Before submitting your contribution, please take a moment to review this document:
https://github.com/Divlo/Divlo/blob/master/.github/CONTRIBUTING.md
-->
## What changes this PR introduce? ## What changes this PR introduce?

View File

@ -1,16 +0,0 @@
version: 2
updates:
- package-ecosystem: 'github-actions'
directory: '/'
schedule:
interval: 'daily'
- package-ecosystem: 'docker'
directory: '/'
schedule:
interval: 'daily'
- package-ecosystem: 'npm'
directory: '/'
schedule:
interval: 'daily'

View File

@ -1,126 +0,0 @@
name: 'Divlo'
on:
push:
branches: [master]
pull_request:
branches: [master]
jobs:
analyze:
name: 'Analyze'
runs-on: 'ubuntu-latest'
strategy:
fail-fast: false
matrix:
language: ['javascript']
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v1'
with:
languages: ${{ matrix.language }}
- name: 'Perform CodeQL Analysis'
uses: 'github/codeql-action/analyze@v1'
lint:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.1.5'
with:
node-version: '14.x'
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.6'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: 'Install'
run: 'npm ci --cache .npm --prefer-offline'
- run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- run: 'npm run lint:docker'
- run: 'npm run lint:editorconfig'
- run: 'npm run lint:markdown'
- run: 'npm run lint:typescript'
build:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.1.5'
with:
node-version: '14.x'
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.6'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: 'Install'
run: 'npm ci --cache .npm --prefer-offline'
- name: 'Build'
run: 'npm run build'
- name: 'Lighthouse'
run: 'npm run lighthouse'
test:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.1.5'
with:
node-version: '14.x'
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.6'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: 'Install'
run: 'npm ci --cache .npm --prefer-offline'
- name: 'Test'
run: 'npm run test'
release:
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
needs: [analyze, lint, build, test]
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.1.5'
with:
node-version: '14.x'
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.6'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- name: 'Install'
run: 'npm ci --cache .npm --prefer-offline'
- name: 'Release'
run: 'npm run release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

27
.github/workflows/analyze.yml vendored Normal file
View File

@ -0,0 +1,27 @@
name: 'Analyze'
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
analyze:
runs-on: 'ubuntu-latest'
strategy:
fail-fast: false
matrix:
language: ['javascript']
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v2'
with:
languages: ${{ matrix.language }}
- name: 'Perform CodeQL Analysis'
uses: 'github/codeql-action/analyze@v2'

25
.github/workflows/build.yml vendored Normal file
View File

@ -0,0 +1,25 @@
name: 'Build'
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
build:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'

42
.github/workflows/lint.yml vendored Normal file
View File

@ -0,0 +1,42 @@
name: 'Lint'
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
lint:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'lint:commit'
run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- name: 'lint:editorconfig'
run: 'npm run lint:editorconfig'
- name: 'lint:markdown'
run: 'npm run lint:markdown'
- name: 'lint:eslint'
run: 'npm run lint:eslint'
- name: 'lint:prettier'
run: 'npm run lint:prettier'
- name: 'lint:dotenv'
uses: 'dotenv-linter/action-dotenv-linter@v2'
with:
github_token: ${{ secrets.github_token }}

44
.github/workflows/release.yml vendored Normal file
View File

@ -0,0 +1,44 @@
name: 'Release'
on:
push:
branches: [master]
jobs:
release:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
with:
fetch-depth: 0
persist-credentials: false
- name: 'Import GPG key'
uses: 'crazy-max/ghaction-import-gpg@v4'
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Release'
run: 'npm run release'
env:
GH_TOKEN: ${{ secrets.GH_TOKEN }}
GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }}
GIT_COMMITTER_EMAIL: ${{ secrets.GIT_EMAIL }}
- name: 'Deploy to Vercel'
run: 'npm run deploy -- --token="${VERCEL_TOKEN}" --prod'
env:
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}

70
.github/workflows/test.yml vendored Normal file
View File

@ -0,0 +1,70 @@
name: 'Test'
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
test-unit:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Unit Test'
run: 'npm run test:unit'
test-lighthouse:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'
- name: 'html-w3c-validator'
run: 'npm run test:html-w3c-validator'
- name: 'Lighthouse'
run: 'npm run test:lighthouse'
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
test-e2e:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'
- name: 'End To End (e2e) Test'
run: 'npm run test:e2e'

16
.gitignore vendored
View File

@ -11,13 +11,16 @@ out
# production # production
build build
dist dist
public/curriculum-vitae
# PWA
public/workbox-*.js
public/sw.js
# testing # testing
coverage coverage
cypress/screenshots
# PWA cypress/videos
**/workbox-*.js cypress/downloads
**/sw.js
# envs # envs
.env .env
@ -45,3 +48,8 @@ npm-debug.log*
# misc # misc
.DS_Store .DS_Store
.lighthouseci .lighthouseci
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

@ -1,8 +1,8 @@
image: 'gitpod/workspace-full' image: 'gitpod/workspace-full'
tasks: tasks:
- before: 'cp .env.example .env && npm install --global npm@7' - before: 'cp .env.example .env'
init: 'npm clean-install' init: 'npm install'
command: 'npm run dev' command: 'npm run dev'
ports: ports:

View File

@ -0,0 +1,8 @@
{
"urls": [
"http://127.0.0.1:3000/",
"http://127.0.0.1:3000/blog",
"http://127.0.0.1:3000/blog/hello-world"
],
"files": ["./public/curriculum-vitae/index.html"]
}

1
.husky/.gitignore vendored
View File

@ -1 +0,0 @@
_

View File

@ -4,22 +4,22 @@
"startServerCommand": "npm run start", "startServerCommand": "npm run start",
"startServerReadyPattern": "ready on", "startServerReadyPattern": "ready on",
"startServerReadyTimeout": 20000, "startServerReadyTimeout": 20000,
"url": ["http://localhost:3000/"], "url": [
"numberOfRuns": 3 "http://127.0.0.1:3000/",
"http://127.0.0.1:3000/blog",
"http://127.0.0.1:3000/blog/hello-world"
],
"numberOfRuns": 1
}, },
"assert": { "assert": {
"preset": "lighthouse:recommended", "preset": "lighthouse:recommended",
"assertions": { "assertions": {
"legacy-javascript": "off", "csp-xss": "warning",
"unused-javascript": "off", "non-composited-animations": "warning",
"uses-rel-preload": "off", "unused-javascript": "warning",
"csp-xss": "off", "image-size-responsive": "warning",
"canonical": "off", "unsized-images": "warning",
"unsized-images": "off", "color-contrast": "warning"
"uses-responsive-images": "off",
"bypass": "warning",
"color-contrast": "warning",
"preload-lcp-image": "warning"
} }
}, },
"upload": { "upload": {

View File

@ -1,11 +1,6 @@
{ {
"*": ["editorconfig-checker"], "*": ["editorconfig-checker"],
"*.{js,ts,jsx,tsx}": [ "*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
"prettier --write", "*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"],
"eslint --fix", "*.{md,mdx}": ["prettier --write", "markdownlint-cli2 --fix"]
"jest --findRelatedTests"
],
"*.{css,yml,json}": ["prettier --write"],
"*.{md}": ["prettier --write", "markdownlint --dot --fix"],
"./Dockerfile": ["dockerfilelint"]
} }

5
.markdownlint-cli2.jsonc Normal file
View File

@ -0,0 +1,5 @@
{
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
}

View File

@ -1,6 +1,7 @@
{ {
"default": true, "default": true,
"MD013": false, "relative-links": true,
"extends": "markdownlint/style/prettier",
"MD024": false, "MD024": false,
"MD033": false, "MD033": false,
"MD041": false "MD041": false

View File

@ -1,8 +0,0 @@
.next
.lighthouseci
node_modules
next-env.d.ts
package.json
package-lock.json
**/workbox-*.js
**/sw.js

View File

@ -13,6 +13,26 @@
"preset": "conventionalcommits" "preset": "conventionalcommits"
} }
], ],
"@semantic-release/github" [
"@semantic-release/npm",
{
"npmPublish": false
}
],
[
"@semantic-release/git",
{
"assets": ["package.json", "package-lock.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]"
}
],
"@semantic-release/github",
[
"@saithodev/semantic-release-backmerge",
{
"branches": [{ "from": "master", "to": "develop" }],
"backmergeStrategy": "merge"
}
]
] ]
} }

View File

@ -3,11 +3,9 @@
"editorconfig.editorconfig", "editorconfig.editorconfig",
"esbenp.prettier-vscode", "esbenp.prettier-vscode",
"dbaeumer.vscode-eslint", "dbaeumer.vscode-eslint",
"divlo.vscode-styled-jsx-syntax",
"divlo.vscode-styled-jsx-languageserver",
"bradlc.vscode-tailwindcss", "bradlc.vscode-tailwindcss",
"mikestead.dotenv", "mikestead.dotenv",
"coenraads.bracket-pair-colorizer", "davidanson.vscode-markdownlint",
"davidanson.vscode-markdownlint" "ms-azuretools.vscode-docker"
] ]
} }

View File

@ -1,9 +1,14 @@
{ {
"typescript.tsdk": "node_modules/typescript/lib", "typescript.tsdk": "node_modules/typescript/lib",
"editor.defaultFormatter": "esbenp.prettier-vscode", "editor.bracketPairColorization.enabled": true,
"prettier.configPath": ".prettierrc.json", "prettier.configPath": ".prettierrc.json",
"editor.defaultFormatter": "esbenp.prettier-vscode",
"editor.formatOnSave": true, "editor.formatOnSave": true,
"editor.codeActionsOnSave": { "editor.codeActionsOnSave": {
"source.fixAll": true "source.fixAll": true
} },
"eslint.options": {
"ignorePath": ".gitignore"
},
"prettier.ignorePath": ".gitignore"
} }

View File

@ -13,7 +13,7 @@ Thanks a lot for your interest in contributing to **divlo.fr**! 🎉
- **Please first discuss** the change you wish to make via [issue](https://github.com/Divlo/Divlo/issues) before making a change. It might avoid a waste of your time. - **Please first discuss** the change you wish to make via [issue](https://github.com/Divlo/Divlo/issues) before making a change. It might avoid a waste of your time.
- Ensure your code respect [Typescript Standard Style](https://www.npmjs.com/package/ts-standard). - Ensure your code respect linting.
- Make sure your **code passes the tests**. - Make sure your **code passes the tests**.
@ -49,6 +49,11 @@ Scopes define what part of the code changed.
[![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Divlo/Divlo) [![Open in Gitpod](https://gitpod.io/button/open-in-gitpod.svg)](https://gitpod.io/#https://github.com/Divlo/Divlo)
### Prerequisites
- [Node.js](https://nodejs.org/) >= 16.0.0
- [npm](https://www.npmjs.com/) >= 8.0.0
### Installation ### Installation
```sh ```sh
@ -60,15 +65,25 @@ cd Divlo
# Configure environment variables # Configure environment variables
cp .env.example .env cp .env.example .env
# Install
npm install
``` ```
### Development environment with [Docker](https://www.docker.com/) ### Local Development environment
```sh
# Run website
npm run dev
```
### Production environment with [Docker](https://www.docker.com/)
```sh ```sh
# Setup and run all the services for you # Setup and run all the services for you
docker-compose up --build docker compose up --build
``` ```
### Services started ### Services started
- website : `http://localhost:3000` - website : `http://127.0.0.1:3000`

View File

@ -1,11 +1,21 @@
FROM node:16.3.0 FROM node:18.15.0 AS builder-dependencies
WORKDIR /usr/src/application
WORKDIR /usr/src/app COPY ./package*.json ./
RUN chown --recursive node:node /usr/src/app
COPY --chown=node:node ./package*.json ./
RUN npm install RUN npm install
COPY --chown=node:node ./ ./
USER node FROM node:18.15.0 AS builder
WORKDIR /usr/src/application
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
COPY ./ ./
RUN npm run build RUN npm run build
CMD ["npm", "run", "start", "--", "--port", "${PORT}"]
FROM gcr.io/distroless/nodejs18-debian11:latest AS runner
WORKDIR /usr/src/application
ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1
COPY --from=builder /usr/src/application/.next/standalone ./
COPY --from=builder /usr/src/application/.next/static ./.next/static
COPY --from=builder /usr/src/application/public ./public
COPY --from=builder /usr/src/application/locales ./locales
COPY --from=builder /usr/src/application/next.config.js ./next.config.js
CMD ["./server.js"]

View File

@ -1,11 +1,10 @@
<h1 align="center"><a href="https://divlo.fr/">Divlo</a></h1> <h1 align="center"><a href="https://divlo.fr/">Divlo</a></h1>
<p align="center"> <p align="center">
<strong>Developer Full Stack Junior • Passionate about High-Tech</strong> <strong>Developer Full Stack • Open-Source enthusiast</strong>
</p> </p>
<p align="center"> <p align="center">
<a href="https://github.com/Divlo/Divlo/actions?query=workflow%3A%22Divlo%22"><img src="https://github.com/Divlo/Divlo/actions/workflows/Divlo.yml/badge.svg?branch=master" alt="Divlo's CI" /></a>
<a href="https://github.com/Divlo"><img alt="GitHub" src="https://img.shields.io/badge/-GitHub-5A5A5A?style=flat&labelColor=5A5A5A&logo=github&logoColor=white"/></a> <a href="https://github.com/Divlo"><img alt="GitHub" src="https://img.shields.io/badge/-GitHub-5A5A5A?style=flat&labelColor=5A5A5A&logo=github&logoColor=white"/></a>
<a href="https://gitlab.com/Divlo"><img alt="GitLab" src="https://img.shields.io/badge/-GitLab-303030?style=flat&labelColor=303030&logo=gitlab&logoColor=white"/></a> <a href="https://gitlab.com/Divlo"><img alt="GitLab" src="https://img.shields.io/badge/-GitLab-303030?style=flat&labelColor=303030&logo=gitlab&logoColor=white"/></a>
<a href="https://www.npmjs.com/~divlo"><img alt="npm" src="https://img.shields.io/badge/-npm-c4302b?style=flat&labelColor=c4302b&logo=npm&logoColor=white"/></a> <a href="https://www.npmjs.com/~divlo"><img alt="npm" src="https://img.shields.io/badge/-npm-c4302b?style=flat&labelColor=c4302b&logo=npm&logoColor=white"/></a>
@ -20,30 +19,27 @@
## 📜 About ## 📜 About
```typescript ```json
export interface Divlo { {
pronouns: 'He' | 'Him' "name": "Divlo",
birthDate: '31/03/2003' "pronouns": "He/Him",
nationality: 'Alsace, France' "birthDate": "31/03/2003",
interests: [ "nationality": "Alsace, France",
'Developer Full Stack Junior', "interests": ["Open-Source enthusiast", "Passionate about High-Tech"],
'Passionate about High-Tech', "skills": {
'Open-Source enthusiast' "programmingLanguages": ["JavaScript/TypeScript", "Python", "C/C++", "PHP"],
] "frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js/Next.js"],
skills: { "backEnd": ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
languages: ['JavaScript', 'TypeScript', 'Python', 'Dart'] "tools": ["GNU/Linux", "Ubuntu", "Visual Studio Code", "Git", "Docker"]
frontEnd: ['HTML', 'CSS', 'SASS', 'React.js (+ Next.js)', 'Flutter']
backEnd: ['Node.js', 'Strapi', 'MySQL']
tools: ['Ubuntu', 'Hyper Terminal', 'VSCode', 'Git', 'Docker']
} }
} }
``` ```
<hr /> <hr />
## 📈 Stats ## 📈 Statistics
<p align=center> <p align=center>
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=Divlo&show_icons=true&theme=dark" /> <img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=Divlo&show_icons=true&theme=dark" />
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=Divlo&hide=html,css&langs_count=8&layout=compact&theme=dark" /> <img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=Divlo&hide=html,css,javascript&langs_count=8&layout=compact&theme=dark" />
</p> </p>

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import Error404 from 'pages/404'
describe('GET /404', () => {
it('should render', async () => {
const { getByText } = render(<Error404 />)
expect(getByText('404')).toBeInTheDocument()
})
})

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import Error500 from 'pages/500'
describe('GET /500', () => {
it('should render', async () => {
const { getByText } = render(<Error500 />)
expect(getByText('500')).toBeInTheDocument()
})
})

View File

@ -1,48 +1,45 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import Link from 'next/link' import Link from 'next/link'
export interface ErrorPageProps { import type { FooterProps } from './Footer'
import { Footer } from './Footer'
import { Header } from './Header'
export interface ErrorPageProps extends FooterProps {
statusCode: number statusCode: number
message: string message: string
} }
export const ErrorPage: React.FC<ErrorPageProps> = (props) => { export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
const { message, statusCode } = props const { message, statusCode, version } = props
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<> <>
<h1 className='my-6 font-semibold text-4xl'> <div className='flex h-screen flex-col pt-0'>
{t('errors:error')}{' '} <Header showLanguage />
<span className='text-yellow dark:text-yellow-dark'>{statusCode}</span> <main className='flex min-w-full flex-1 flex-col items-center justify-center'>
</h1> <h1 className='my-6 text-4xl font-semibold'>
<p className='text-center text-lg'> {t('errors:error')}{' '}
{message}{' '} <span
<Link href='/'> className='text-yellow dark:text-yellow-dark'
<a className='text-yellow dark:text-yellow-dark hover:underline'> data-cy='status-code'
{t('errors:returnToHomePage')} >
</a> {statusCode}
</Link> </span>
</p> </h1>
<p className='text-center text-lg'>
<style jsx global> {message}{' '}
{` <Link
main { href='/'
display: flex; className='text-yellow hover:underline dark:text-yellow-dark'
flex-direction: column; >
justify-content: center; {t('errors:return-to-home-page')}
align-items: center; </Link>
min-width: 100vw; </p>
flex: 1; </main>
} <Footer version={version} />
#__next { </div>
display: flex;
flex-direction: column;
padding-top: 0;
height: 100vh;
}
`}
</style>
</> </>
) )
} }

View File

@ -1,13 +1,41 @@
import { useMemo } from 'react'
import Link from 'next/link'
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
export const Footer: React.FC = () => { export interface FooterProps {
version: string
}
export const Footer: React.FC<FooterProps> = (props) => {
const { t } = useTranslation() const { t } = useTranslation()
const { version } = props
const versionLink = useMemo(() => {
return `https://github.com/Divlo/Divlo/releases/tag/v${version}`
}, [version])
return ( return (
<footer className='bg-white flex justify-center py-6 text-lg border-t-2 border-gray-600 dark:border-gray-400 dark:bg-black'> <footer className='flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black'>
<p> <p>
<span className='text-yellow dark:text-yellow-dark'>Divlo</span> |{' '} <Link
{t('common:allRightsReserved')} href='/'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Divlo
</Link>{' '}
| {t('common:all-rights-reserved')}
</p>
<p className='mt-1'>
Version{' '}
<a
data-cy='version-link'
className='text-yellow hover:underline dark:text-yellow-dark'
href={versionLink}
target='_blank'
rel='noopener noreferrer'
>
{version}
</a>
</p> </p>
</footer> </footer>
) )

View File

@ -10,8 +10,8 @@ interface HeadProps {
export const Head: React.FC<HeadProps> = (props) => { export const Head: React.FC<HeadProps> = (props) => {
const { const {
title = 'Divlo', title = 'Divlo',
image = '/images/icons/icon-96x96.png', image = 'https://divlo.fr/images/icons/icon-96x96.png',
description = "I'm Divlo, I'm 18 years old, I'm from France - Developer Full Stack Junior • Passionate about High-Tech", description = 'Divlo - Developer Full Stack • Passionate about High-Tech',
url = 'https://divlo.fr/' url = 'https://divlo.fr/'
} = props } = props
@ -39,7 +39,7 @@ export const Head: React.FC<HeadProps> = (props) => {
<meta name='twitter:card' content='summary' /> <meta name='twitter:card' content='summary' />
<meta name='twitter:description' content={description} /> <meta name='twitter:description' content={description} />
<meta name='twitter:title' content={title} /> <meta name='twitter:title' content={title} />
<meta name='twitter:image:src' content={image} /> <meta name='twitter:image' content={image} />
{/* Google Verification */} {/* Google Verification */}
<meta <meta

View File

@ -10,12 +10,15 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
return ( return (
<> <>
<Image <Image
quality={100}
width={35} width={35}
height={35} height={35}
src={`/images/languages/${language}.svg`} src={`/images/languages/${language}.svg`}
alt={language} alt={language}
/> />
<p className='mx-2 text-base'>{language.toUpperCase()}</p> <p data-cy='language-flag-text' className='mx-2 text-base'>
{language.toUpperCase()}
</p>
</> </>
) )
} }

View File

@ -1,60 +1,81 @@
import { useEffect, useState } from 'react' import { useCallback, useEffect, useState, useRef } from 'react'
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import setLanguage from 'next-translate/setLanguage' import setLanguage from 'next-translate/setLanguage'
import classNames from 'clsx'
import i18n from 'i18n.json'
import { Arrow } from './Arrow' import { Arrow } from './Arrow'
import { LanguageFlag } from './LanguageFlag' import { LanguageFlag } from './LanguageFlag'
import { locales } from 'i18n.json'
export const Language: React.FC = () => { export const Language: React.FC = () => {
const { lang: currentLanguage } = useTranslation() const { lang: currentLanguage } = useTranslation()
const [hiddenMenu, setHiddenMenu] = useState(true) const [hiddenMenu, setHiddenMenu] = useState(true)
const languageClickRef = useRef<HTMLDivElement | null>(null)
const handleHiddenMenu = useCallback(() => {
setHiddenMenu((oldHiddenMenu) => {
return !oldHiddenMenu
})
}, [])
useEffect(() => { useEffect(() => {
if (!hiddenMenu) { const handleClickEvent = (event: MouseEvent): void => {
window.document.addEventListener('click', handleHiddenMenu) if (languageClickRef.current == null || event.target == null) {
} else { return
window.document.removeEventListener('click', handleHiddenMenu) }
if (!languageClickRef.current.contains(event.target as Node)) {
setHiddenMenu(true)
}
} }
window.document.addEventListener('click', handleClickEvent)
return () => { return () => {
window.document.removeEventListener('click', handleHiddenMenu) return window.removeEventListener('click', handleClickEvent)
} }
}, [hiddenMenu]) }, [])
const handleLanguage = async (language: string): Promise<void> => { const handleLanguage = async (language: string): Promise<void> => {
await setLanguage(language) await setLanguage(language)
handleHiddenMenu()
}
const handleHiddenMenu = (): void => {
setHiddenMenu(!hiddenMenu)
} }
return ( return (
<div className='flex flex-col justify-center items-center cursor-pointer'> <div className='flex cursor-pointer flex-col items-center justify-center'>
<div className='flex items-center mr-5' onClick={handleHiddenMenu}> <div
ref={languageClickRef}
data-cy='language-click'
className='mr-5 flex items-center'
onClick={handleHiddenMenu}
>
<LanguageFlag language={currentLanguage} /> <LanguageFlag language={currentLanguage} />
<Arrow /> <Arrow />
</div> </div>
{!hiddenMenu && (
<ul className='flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black'> <ul
{locales.map((language, index) => { data-cy='languages-list'
if (language === currentLanguage) { className={classNames(
return null 'absolute top-14 z-10 mr-4 mt-3 flex w-24 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-lightFlag dark:bg-black dark:shadow-darkFlag',
} { hidden: hiddenMenu }
return ( )}
<li >
key={index} {i18n.locales.map((language, index) => {
className='flex items-center justify-center w-full h-12 hover:bg-[#4f545c] hover:bg-opacity-20 pl-2' if (language === currentLanguage) {
onClick={async () => await handleLanguage(language)} return null
> }
<LanguageFlag language={language} /> return (
</li> <li
) key={index}
})} className='flex h-12 w-full items-center justify-center pl-2 hover:bg-[#4f545c] hover:bg-opacity-20'
</ul> onClick={async () => {
)} return await handleLanguage(language)
}}
>
<LanguageFlag language={language} />
</li>
)
})}
</ul>
</div> </div>
) )
} }

View File

@ -1,4 +1,5 @@
import { useEffect, useState } from 'react' import { useEffect, useState } from 'react'
import classNames from 'clsx'
import { useTheme } from 'next-themes' import { useTheme } from 'next-themes'
export const SwitchTheme: React.FC = () => { export const SwitchTheme: React.FC = () => {
@ -13,115 +14,65 @@ export const SwitchTheme: React.FC = () => {
return null return null
} }
return ( const handleClick = (): void => {
<> setTheme(theme === 'dark' ? 'light' : 'dark')
<div }
className='toggle-button'
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
>
<div className='toggle-theme-button'>
<div className='toggle-track'>
<div className='toggle-track-check'>
<span className='toggle_Dark'>🌜</span>
</div>
<div className='toggle-track-x'>
<span className='toggle_Light'>🌞</span>
</div>
</div>
<div className='toggle-thumb' />
<input
type='checkbox'
aria-label='Dark mode toggle'
className='toggle-screenreader-only'
defaultChecked
/>
</div>
</div>
<style jsx> return (
{` <div
.toggle-button { className='flex items-center'
display: flex; data-cy='switch-theme-click'
align-items: center; onClick={handleClick}
} >
.toggle-theme-button { <div className='relative inline-block cursor-pointer touch-pan-x select-none border-0 bg-transparent p-0'>
touch-action: pan-x; <div className='h-[24px] w-[50px] rounded-[30px] bg-[#4d4d4d] p-0 text-white transition-all duration-200 ease-in-out'>
display: inline-block; <div
position: relative; data-cy='switch-theme-dark'
cursor: pointer; className={classNames(
background-color: transparent; 'absolute bottom-0 left-[8px] top-0 mb-auto mt-auto h-[10px] w-[14px] leading-[0] transition-opacity duration-[250ms] ease-in-out',
border: 0; {
padding: 0; 'opacity-100': theme === 'dark',
user-select: none; 'opacity-0': theme === 'light'
} }
.toggle-track { )}
width: 50px; >
height: 24px; <span className='relative flex h-[10px] w-[10px] items-center justify-center'>
padding: 0; 🌜
border-radius: 30px; </span>
background-color: #4d4d4d; </div>
transition: all 0.2s ease; <div
color: #fff; data-cy='switch-theme-light'
} className={classNames(
.toggle-track-check { 'absolute bottom-0 right-[10px] top-0 mb-auto mt-auto h-[10px] w-[10px] leading-[0]',
position: absolute; {
width: 14px; 'opacity-100': theme === 'light',
height: 10px; 'opacity-0': theme === 'dark'
top: 0; }
bottom: 0; )}
margin-top: auto; >
margin-bottom: auto; <span className='relative flex h-[10px] w-[10px] items-center justify-center'>
line-height: 0; 🌞
left: 8px; </span>
opacity: ${theme === 'dark' ? 1 : 0}; </div>
transition: opacity 0.25s ease; </div>
} <div
.toggle-track-x { className={classNames(
position: absolute; 'absolute top-[1px] box-border h-[22px] w-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out',
width: 10px; {
height: 10px; 'left-[27px]': theme === 'dark',
top: 0; 'left-0': theme === 'light'
bottom: 0; }
margin-top: auto; )}
margin-bottom: auto; style={{ border: '1px solid #4d4d4d' }}
line-height: 0; />
right: 10px; <input
opacity: ${theme === 'dark' ? 0 : 1}; data-cy='switch-theme-input'
} type='checkbox'
.toggle_Dark, aria-label='Dark mode toggle'
.toggle_Light { className='absolute m-[-1px] h-[1px] w-[1px] overflow-hidden border-0 p-0'
align-items: center; defaultChecked
display: flex; />
height: 10px; </div>
justify-content: center; </div>
position: relative;
width: 10px;
}
.toggle-thumb {
position: absolute;
left: ${theme === 'dark' ? '27px' : '0px'};
width: 22px;
height: 22px;
border: 1px solid #4d4d4d;
border-radius: 50%;
background-color: #fafafa;
box-sizing: border-box;
transition: all 0.25s ease;
top: 1px;
color: #fff;
}
.toggle-screenreader-only {
border: 0;
clip: rect(0 0 0 0);
height: 1px;
margin: -1px;
overflow: hidden;
padding: 0;
position: absolute;
width: 1px;
}
`}
</style>
</>
) )
} }

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import { Header } from '..'
describe('<Header />', () => {
it('should render', async () => {
const { getByText } = render(<Header />)
expect(getByText('Divlo')).toBeInTheDocument()
})
})

View File

@ -4,26 +4,40 @@ import Image from 'next/image'
import { Language } from './Language' import { Language } from './Language'
import { SwitchTheme } from './SwitchTheme' import { SwitchTheme } from './SwitchTheme'
export const Header: React.FC = () => { export interface HeaderProps {
showLanguage?: boolean
}
export const Header: React.FC<HeaderProps> = (props) => {
const { showLanguage = false } = props
return ( return (
<header className='bg-white sticky top-0 z-50 flex w-full justify-between px-6 py-2 border-b-2 border-gray-600 dark:border-gray-400 dark:bg-black'> <header className='sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black'>
<Link href='/'> <Link href='/'>
<a> <div className='flex items-center justify-center'>
<div className='flex items-center justify-center'> <Image
<Image quality={100}
width={60} width={60}
height={60} height={60}
src='/images/divlo_icon_small.png' src='/images/divlo_icon_small.png'
alt='Divlo' alt='Divlo'
/> />
<strong className='ml-1 font-headline font-semibold hidden xs:block'> <strong className='ml-1 hidden font-headline font-semibold text-yellow dark:text-yellow-dark xs:block'>
Divlo Divlo
</strong> </strong>
</div> </div>
</a>
</Link> </Link>
<div className='flex justify-between'> <div className='flex justify-between'>
<Language /> <div className='flex flex-col items-center justify-center px-6'>
<Link
href='/blog'
data-cy='header-blog-link'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Blog
</Link>
</div>
{showLanguage && <Language />}
<SwitchTheme /> <SwitchTheme />
</div> </div>
</header> </header>

View File

@ -10,8 +10,8 @@ export const InterestParagraph: React.FC<InterestParagraphProps> = (props) => {
return ( return (
<> <>
<p className='text-center my-6 text-gray dark:text-gray-dark'> <p className='my-6 text-center text-gray dark:text-gray-dark'>
<strong className='text-yellow font-medium text-lg dark:text-yellow-dark'> <strong className='text-lg font-semibold text-yellow dark:text-yellow-dark'>
{title} {title}
</strong> </strong>
<br /> <br />

View File

@ -1,5 +1,5 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome' import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons' import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
interface InterestItemProps { interface InterestItemProps {
title: string title: string
@ -10,9 +10,9 @@ export const InterestItem: React.FC<InterestItemProps> = (props) => {
const { fontAwesomeIcon, title } = props const { fontAwesomeIcon, title } = props
return ( return (
<li className='interest-item my-2 mx-2 w-8 h-8' title={title}> <li className='interest-item mx-2 my-2 h-8 w-8' title={title}>
<FontAwesomeIcon <FontAwesomeIcon
className='text-yellow cursor-pointer h-full w-full block dark:text-yellow-dark' className='block h-full w-full text-yellow dark:text-yellow-dark'
icon={fontAwesomeIcon} icon={fontAwesomeIcon}
/> />
</li> </li>

View File

@ -5,12 +5,9 @@ import { InterestItem } from './InterestItem'
export const InterestsList: React.FC = () => { export const InterestsList: React.FC = () => {
return ( return (
<div className='flex justify-center my-4'> <div className='my-4 flex justify-center'>
<ul className='flex justify-around p-0 m-0 list-none w-96'> <ul className='m-0 flex w-96 list-none justify-around p-0'>
<InterestItem <InterestItem title='Developer Full Stack' fontAwesomeIcon={faCode} />
title='Developer Full Stack Junior'
fontAwesomeIcon={faCode}
/>
<InterestItem <InterestItem
title='Passionate about High-Tech' title='Passionate about High-Tech'
fontAwesomeIcon={faMicrochip} fontAwesomeIcon={faMicrochip}

View File

@ -1,6 +1,7 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import { InterestParagraph, InterestParagraphProps } from './InterestParagraph' import type { InterestParagraphProps } from './InterestParagraph'
import { InterestParagraph } from './InterestParagraph'
import { InterestsList } from './InterestsList' import { InterestsList } from './InterestsList'
export const Interests: React.FC = () => { export const Interests: React.FC = () => {
@ -15,13 +16,11 @@ export const Interests: React.FC = () => {
) )
return ( return (
<> <div className='max-w-full'>
<div className='max-w-full'> {paragraphs.map((paragraph, index) => {
{paragraphs.map((paragraph, index) => { return <InterestParagraph key={index} {...paragraph} />
return <InterestParagraph key={index} {...paragraph} /> })}
})} <InterestsList />
<InterestsList /> </div>
</div>
</>
) )
} }

View File

@ -0,0 +1,24 @@
import { ShadowContainer } from 'components/design/ShadowContainer'
import { GitHubIcon } from 'components/Profile/SocialMediaList/SocialMediaIcons/GitHubIcon'
export interface RepositoryProps {
name: string
description: string
href: string
}
export const Repository: React.FC<RepositoryProps> = (props) => {
const { name, description, href } = props
return (
<ShadowContainer className='relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2'>
<a href={href} target='_blank' rel='noopener noreferrer'>
<div className='flex'>
<GitHubIcon className='mr-2 h-6' />
<span className='text-yellow dark:text-yellow-dark'>{name}</span>
</div>
<p className='my-4'>{description}</p>
</a>
</ShadowContainer>
)
}

View File

@ -0,0 +1,35 @@
import useTranslation from 'next-translate/useTranslation'
import { Repository } from './Repository'
export const OpenSource: React.FC = () => {
const { t } = useTranslation()
return (
<div className='mt-0 flex max-w-full flex-col items-center'>
<p className='text-center'>{t('home:open-source.description')}</p>
<div className='my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2'>
<Repository
name='nodejs/node'
description='Node.js JavaScript runtime 🐢🚀'
href='https://github.com/nodejs/node/commits?author=Divlo'
/>
<Repository
name='standard/standard'
description='🌟 JavaScript Style Guide, with linter & automatic code fixer'
href='https://github.com/standard/standard/commits?author=Divlo'
/>
<Repository
name='nrwl/nx'
description='Smart, Extensible Build Framework'
href='https://github.com/nrwl/nx/commits?author=Divlo'
/>
<Repository
name='vercel/next.js'
description='The React Framework for Production'
href='https://github.com/vercel/next.js/commits?author=Divlo'
/>
</div>
</div>
)
}

View File

@ -1,6 +1,7 @@
import { ShadowContainer } from 'components/design/ShadowContainer'
import Image from 'next/image' import Image from 'next/image'
import { ShadowContainer } from 'components/design/ShadowContainer'
export interface PortfolioItemProps { export interface PortfolioItemProps {
title: string title: string
description: string description: string
@ -12,7 +13,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
const { title, description, link, image } = props const { title, description, link, image } = props
return ( return (
<ShadowContainer className='cursor-pointer relative items-center sm:ml-10'> <ShadowContainer className='relative cursor-pointer items-center sm:ml-10'>
<a <a
className='group inline-flex justify-center' className='group inline-flex justify-center'
target='_blank' target='_blank'
@ -22,15 +23,16 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
> >
<div className='flex justify-center'> <div className='flex justify-center'>
<Image <Image
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5' quality={100}
className='h-auto w-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
width={300} width={300}
height={300} height={300}
src={image} src={image}
alt={title} alt={title}
/> />
</div> </div>
<div className='opacity-0 transition-opacity duration-500 h-auto absolute text-center overflow-hidden bottom-0 group-hover:opacity-100'> <div className='absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100'>
<h3 className='text-yellow text-xl font-semibold my-6 dark:text-yellow-dark'> <h3 className='my-6 text-xl font-semibold text-yellow dark:text-yellow-dark'>
{title} {title}
</h3> </h3>
<p className='my-6'>{description}</p> <p className='my-6'>{description}</p>

View File

@ -1,6 +1,7 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import { PortfolioItem, PortfolioItemProps } from './PortfolioItem' import type { PortfolioItemProps } from './PortfolioItem'
import { PortfolioItem } from './PortfolioItem'
export const Portfolio: React.FC = () => { export const Portfolio: React.FC = () => {
const { t } = useTranslation('home') const { t } = useTranslation('home')
@ -14,7 +15,7 @@ export const Portfolio: React.FC = () => {
) )
return ( return (
<div className='flex flex-wrap justify-center px-3 w-full'> <div className='flex w-full flex-wrap justify-center px-3'>
{items.map((item, index) => { {items.map((item, index) => {
return <PortfolioItem key={index} {...item} /> return <PortfolioItem key={index} {...item} />
})} })}

View File

@ -1,12 +1,23 @@
import Translation from 'next-translate/Trans' import useTranslation from 'next-translate/useTranslation'
export const ProfileDescriptionBottom: React.FC = () => { export const ProfileDescriptionBottom: React.FC = () => {
const { t, lang } = useTranslation()
return ( return (
<p className='mt-8 mb-8 font-normal text-base text-gray dark:text-gray-dark'> <p className='mb-8 mt-8 text-base font-normal text-gray dark:text-gray-dark'>
<Translation {t('home:about.description-bottom')}
i18nKey='home:about.descriptionBottom' {lang === 'fr' && (
components={[<br key='break' />]} <>
/> <br />
<br />
<a
href='/curriculum-vitae'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Curriculum vitæ
</a>
</>
)}
</p> </p>
) )
} }

View File

@ -1,44 +1,17 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
export const ProfileInfo: React.FC = () => { export const ProfileInformation: React.FC = () => {
const { t } = useTranslation() const { t } = useTranslation()
return ( return (
<> <div className='mb-6 border-b-2 border-gray-600 pb-2 font-headline dark:border-gray-400'>
<div className='pb-2 mb-6 border-b-2 font-headline border-gray-600 dark:border-gray-400'> <h1 className='mb-2 text-4xl'>
<h1 className='text-4xl mb-2'> {t('home:about.i-am')}{' '}
{t('home:about.IAm')}{' '} <strong className='font-semibold text-yellow dark:text-yellow-dark'>
<strong className='font-semibold text-yellow dark:text-yellow-dark'> Divlo
Divlo </strong>
</strong> </h1>
</h1> <h2 className='mb-3 text-base'>{t('home:about.description')}</h2>
<h2 className='text-base mb-3'>{t('home:about.description')}</h2> </div>
</div>
<style jsx>
{`
.profile-info {
padding-bottom: 25px;
margin-bottom: 25px;
border-bottom: 1px solid #dedede;
}
.profile-title {
font-size: 36px;
line-height: 1.1;
font-weight: 300;
margin-bottom: 10px;
}
.profile-title > strong {
font-weight: 600;
}
.profile-description {
font-size: 17.4px;
font-weight: 400;
line-height: 1.1;
margin: 0;
}
`}
</style>
</>
) )
} }

View File

@ -8,72 +8,22 @@ export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
const { title, value, link } = props const { title, value, link } = props
return ( return (
<> <li className='mb-3 before:table after:clear-both after:table'>
<li className='profile-list__item'> <strong className='float-left block w-28 text-sm font-bold text-black dark:text-white'>
<strong className='profile-list__item-title text-black dark:text-white'> {title}
{title} </strong>
</strong> <span className='mb-4 ml-0 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
<span className='profile-list__item-info text-gray dark:text-gray-dark'> {link != null ? (
{link != null ? ( <a
<a className='text-gray hover:underline dark:text-gray-dark'
className='text-gray dark:text-gray-dark hover:underline' href={link}
href={link} >
> {value}
{value} </a>
</a> ) : (
) : ( value
value )}
)} </span>
</span> </li>
</li>
<style jsx>
{`
.profile-list__item {
margin-bottom: 13px;
}
.profile-list__item::after,
.profile-list__item::before {
content: ' ';
display: table;
}
.profile-list__item::after {
clear: both;
}
.profile-list__item-title {
display: block;
width: 120px;
float: left;
font-size: 12px;
font-weight: 700;
line-height: 20px;
text-transform: uppercase;
}
.profile-list__item-info {
display: block;
margin-left: 125px;
font-size: 15px;
font-weight: 400;
line-height: 20px;
}
@media (max-width: 576px) {
.profile-list__item-title {
margin-bottom: 3px;
}
.profile-list__item-info {
margin-left: 0;
margin-bottom: 15px;
}
.profile-list__item-info,
.profile-list__item-title {
width: 100%;
float: none;
line-height: 1.2;
}
}
`}
</style>
</>
) )
} }

View File

@ -1,13 +1,24 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import { useMemo } from 'react'
import { DIVLO_BIRTHDAY, DIVLO_BIRTHDAY_DATE, getAge } from 'utils/getAge'
import { ProfileItem } from './ProfileItem' import { ProfileItem } from './ProfileItem'
export const ProfileList: React.FC = () => { export const ProfileList: React.FC = () => {
const { t } = useTranslation('home') const { t } = useTranslation('home')
const age = useMemo(() => {
return getAge(DIVLO_BIRTHDAY)
}, [])
return ( return (
<ul className='m-0 p-0 list-none'> <ul className='m-0 list-none p-0'>
<ProfileItem title={t('home:about.birthDate')} value='31/03/2003' /> <ProfileItem title={t('home:about.full-name')} value='Théo LUDWIG' />
<ProfileItem
title={t('home:about.birth-date')}
value={`${DIVLO_BIRTHDAY_DATE} (${age} ${t('home:about.years-old')})`}
/>
<ProfileItem title={t('home:about.nationality')} value='Alsace, France' /> <ProfileItem title={t('home:about.nationality')} value='Alsace, France' />
<ProfileItem <ProfileItem
title='Email' title='Email'

View File

@ -1,14 +1,11 @@
import Image from 'next/image' import Image from 'next/image'
import DivloLogo from 'public/images/divlo_logo.png'
export const ProfileLogo: React.FC = () => { export const ProfileLogo: React.FC = () => {
return ( return (
<div className='px-2 py-6'> <div className='max-h-[370px] max-w-[370px] px-2 py-6'>
<Image <Image quality={100} src={DivloLogo} alt='Divlo' priority />
width={370}
height={370}
src='/images/divlo_logo.png'
alt='Divlo'
/>
</div> </div>
) )
} }

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const GitLabIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>GitLab</title>
<path d='M4.845.904c-.435 0-.82.28-.955.692C2.639 5.449 1.246 9.728.07 13.335a1.437 1.437 0 00.522 1.607l11.071 8.045c.2.145.472.144.67-.004l11.073-8.04a1.436 1.436 0 00.522-1.61c-1.285-3.942-2.683-8.256-3.817-11.746a1.004 1.004 0 00-.957-.684.987.987 0 00-.949.69l-2.405 7.408H8.203l-2.41-7.408a.987.987 0 00-.942-.69h-.006zm-.006 1.42l2.173 6.678H2.675zm14.326 0l2.168 6.678h-4.341zm-10.593 7.81h6.862c-1.142 3.52-2.288 7.04-3.434 10.559L8.572 10.135zm-5.514.005h4.321l3.086 9.5zm13.567 0h4.325c-2.467 3.17-4.95 6.328-7.411 9.502 1.028-3.167 2.059-6.334 3.086-9.502zM2.1 10.762l6.977 8.947-7.817-5.682a.305.305 0 01-.112-.341zm19.798 0l.952 2.922a.305.305 0 01-.11.341v.002l-7.82 5.68.026-.035z' />
</Icon>
)
}

View File

@ -1,11 +1,16 @@
import classNames from 'clsx'
export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => { export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
const { children, ...rest } = props const { children, className, ...rest } = props
return ( return (
<svg <svg
xmlns='http://www.w3.org/2000/svg' xmlns='http://www.w3.org/2000/svg'
viewBox='0 0 24 24' viewBox='0 0 24 24'
className='dark:text-white text-black w-8 h-8 fill-current' className={classNames(
'h-8 w-8 fill-current text-black dark:text-white',
className
)}
{...rest} {...rest}
> >
{children} {children}

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const NPMIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>npm</title>
<path d='M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z' />
</Icon>
)
}

View File

@ -3,11 +3,13 @@ interface SocialMediaItemProps {
ariaLabel: string ariaLabel: string
} }
export const SocialMediaItem: React.FC<SocialMediaItemProps> = (props) => { export const SocialMediaItem: React.FC<
React.PropsWithChildren<SocialMediaItemProps>
> = (props) => {
const { link, ariaLabel, children } = props const { link, ariaLabel, children } = props
return ( return (
<li className='inline-block mx-4 my-1'> <li className='mx-4 my-1 inline-block'>
<a <a
href={link} href={link}
aria-label={ariaLabel} aria-label={ariaLabel}

View File

@ -1,19 +1,27 @@
import { SocialMediaItem } from './SocialMediaItem' import { SocialMediaItem } from './SocialMediaItem'
import { TwitterIcon } from './SocialMediaIcons/TwitterIcon' import { TwitterIcon } from './SocialMediaIcons/TwitterIcon'
import { GitHubIcon } from './SocialMediaIcons/GitHubIcon' import { GitHubIcon } from './SocialMediaIcons/GitHubIcon'
import { GitLabIcon } from './SocialMediaIcons/GitLabIcon'
import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon' import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon'
import { TwitchIcon } from './SocialMediaIcons/TwitchIcon' import { TwitchIcon } from './SocialMediaIcons/TwitchIcon'
import { EmailIcon } from './SocialMediaIcons/EmailIcon' import { EmailIcon } from './SocialMediaIcons/EmailIcon'
import { NPMIcon } from './SocialMediaIcons/NPMIcon'
export const SocialMediaList: React.FC = () => { export const SocialMediaList: React.FC = () => {
return ( return (
<ul className='social-media-list m-0 mt-2 py-4 list-none text-center'> <ul className='social-media-list m-0 mt-2 list-none py-4 text-center'>
<SocialMediaItem link='https://twitter.com/Divlo_FR' ariaLabel='Twitter'>
<TwitterIcon />
</SocialMediaItem>
<SocialMediaItem link='https://github.com/Divlo' ariaLabel='GitHub'> <SocialMediaItem link='https://github.com/Divlo' ariaLabel='GitHub'>
<GitHubIcon /> <GitHubIcon />
</SocialMediaItem> </SocialMediaItem>
<SocialMediaItem link='https://gitlab.com/Divlo' ariaLabel='GitLab'>
<GitLabIcon />
</SocialMediaItem>
<SocialMediaItem link='https://www.npmjs.com/~divlo' ariaLabel='NPM'>
<NPMIcon />
</SocialMediaItem>
<SocialMediaItem link='https://twitter.com/Divlo_FR' ariaLabel='Twitter'>
<TwitterIcon />
</SocialMediaItem>
<SocialMediaItem <SocialMediaItem
link='https://www.youtube.com/c/Divlo' link='https://www.youtube.com/c/Divlo'
ariaLabel='YouTube' ariaLabel='YouTube'

View File

@ -1,14 +1,14 @@
import { ProfileDescriptionBottom } from './ProfileDescriptionBottom' import { ProfileDescriptionBottom } from './ProfileDescriptionBottom'
import { ProfileInfo } from './ProfileInfo' import { ProfileInformation } from './ProfileInfo'
import { ProfileList } from './ProfileList' import { ProfileList } from './ProfileList'
import { ProfileLogo } from './ProfileLogo' import { ProfileLogo } from './ProfileLogo'
export const Profile: React.FC = () => { export const Profile: React.FC = () => {
return ( return (
<div className='flex flex-col justify-center items-center px-10 pt-2 md:pt-10 xl:pt-0 md:flex-row'> <div className='flex flex-col items-center justify-center px-10 pt-2 md:flex-row md:pt-10'>
<ProfileLogo /> <ProfileLogo />
<div> <div>
<ProfileInfo /> <ProfileInformation />
<ProfileList /> <ProfileList />
<ProfileDescriptionBottom /> <ProfileDescriptionBottom />
</div> </div>

View File

@ -1,14 +1,28 @@
import { useTheme } from 'next-themes'
import Image from 'next/image' import Image from 'next/image'
import { useMemo } from 'react'
import type { SkillName } from './skills'
import { skills } from './skills' import { skills } from './skills'
export interface SkillProps { export interface SkillComponentProps {
skill: keyof typeof skills skill: SkillName
} }
export const Skill: React.FC<SkillProps> = (props) => { export const SkillComponent: React.FC<SkillComponentProps> = (props) => {
const { skill } = props const { skill } = props
const skillProperties = skills[skill] const skillProperties = skills[skill]
const { theme } = useTheme()
const image = useMemo(() => {
if (typeof skillProperties.image === 'string') {
return skillProperties.image
}
if (theme === 'light') {
return skillProperties.image.light
}
return skillProperties.image.dark
}, [skillProperties, theme])
return ( return (
<a <a
@ -18,7 +32,14 @@ export const Skill: React.FC<SkillProps> = (props) => {
rel='noopener noreferrer' rel='noopener noreferrer'
> >
<div className='text-center'> <div className='text-center'>
<Image width={60} height={60} alt={skill} src={skillProperties.image} /> <Image
className='inline h-auto w-auto'
quality={100}
width={60}
height={60}
alt={skill}
src={image}
/>
<p className='mt-1'>{skill}</p> <p className='mt-1'>{skill}</p>
</div> </div>
</a> </a>

View File

@ -10,15 +10,15 @@ export const SkillsSection: React.FC<SkillsSectionProps> = (props) => {
return ( return (
<ShadowContainer> <ShadowContainer>
<div className='w-full px-4 mx-auto'> <div className='mx-auto w-full px-4'>
<div className='flex flex-wrap px-4 py-6'> <div className='flex flex-wrap px-4 py-6'>
<div className='flex-1'> <div className='flex-1'>
<div className='mb-8 border-b border-gray-600 dark:border-opacity-10 dark:border-white'> <div className='mb-8 border-b border-gray-600 dark:border-white dark:border-opacity-10'>
<h3 className='text-yellow font-semibold text-xl my-3 dark:text-yellow-dark'> <h3 className='my-3 text-xl font-semibold text-yellow dark:text-yellow-dark'>
{title} {title}
</h3> </h3>
</div> </div>
<div className='flex justify-around flex-wrap'>{children}</div> <div className='flex flex-wrap justify-around'>{children}</div>
</div> </div>
</div> </div>
</div> </div>

View File

@ -1,6 +1,6 @@
import useTranslation from 'next-translate/useTranslation' import useTranslation from 'next-translate/useTranslation'
import { Skill } from './Skill' import { SkillComponent } from './Skill'
import { SkillsSection } from './SkillsSection' import { SkillsSection } from './SkillsSection'
export const Skills: React.FC = () => { export const Skills: React.FC = () => {
@ -9,32 +9,33 @@ export const Skills: React.FC = () => {
return ( return (
<> <>
<SkillsSection title={t('home:skills.languages')}> <SkillsSection title={t('home:skills.languages')}>
<Skill skill='JavaScript' /> <SkillComponent skill='JavaScript' />
<Skill skill='TypeScript' /> <SkillComponent skill='TypeScript' />
<Skill skill='Python' /> <SkillComponent skill='Python' />
<Skill skill='Dart' /> <SkillComponent skill='C/C++' />
<SkillComponent skill='PHP' />
</SkillsSection> </SkillsSection>
<SkillsSection title='Front-end'> <SkillsSection title='Front-end'>
<Skill skill='HTML' /> <SkillComponent skill='HTML' />
<Skill skill='CSS' /> <SkillComponent skill='CSS' />
<Skill skill='SASS' /> <SkillComponent skill='Tailwind CSS' />
<Skill skill='React.js (+ Next.js)' /> <SkillComponent skill='React.js (+ Next.js)' />
<Skill skill='Flutter' />
</SkillsSection> </SkillsSection>
<SkillsSection title='Back-end'> <SkillsSection title='Back-end'>
<Skill skill='Node.js' /> <SkillComponent skill='Laravel' />
<Skill skill='Strapi' /> <SkillComponent skill='Node.js' />
<Skill skill='MySQL' /> <SkillComponent skill='Fastify' />
<SkillComponent skill='PostgreSQL' />
</SkillsSection> </SkillsSection>
<SkillsSection title={t('home:skills.softwareTools')}> <SkillsSection title={t('home:skills.software-tools')}>
<Skill skill='Ubuntu' /> <SkillComponent skill='GNU/Linux' />
<Skill skill='Hyper' /> <SkillComponent skill='Ubuntu' />
<Skill skill='Visual Studio Code' /> <SkillComponent skill='Visual Studio Code' />
<Skill skill='Git' /> <SkillComponent skill='Git' />
<Skill skill='Docker' /> <SkillComponent skill='Docker' />
</SkillsSection> </SkillsSection>
</> </>
) )

View File

@ -1,3 +1,8 @@
export interface Skill {
link: string
image: string | { [key: string]: string }
}
export const skills = { export const skills = {
JavaScript: { JavaScript: {
link: 'https://developer.mozilla.org/docs/Web/JavaScript', link: 'https://developer.mozilla.org/docs/Web/JavaScript',
@ -11,6 +16,18 @@ export const skills = {
link: 'https://www.python.org/', link: 'https://www.python.org/',
image: '/images/skills/Python.png' image: '/images/skills/Python.png'
}, },
'C/C++': {
link: 'https://isocpp.org/',
image: '/images/skills/C-Cpp.png'
},
PHP: {
link: 'https://www.php.net/',
image: '/images/skills/PHP.png'
},
Laravel: {
link: 'https://laravel.com/',
image: '/images/skills/Laravel.png'
},
Dart: { Dart: {
link: 'https://dart.dev/', link: 'https://dart.dev/',
image: '/images/skills/Dart.png' image: '/images/skills/Dart.png'
@ -27,6 +44,10 @@ export const skills = {
link: 'https://developer.mozilla.org/docs/Web/CSS', link: 'https://developer.mozilla.org/docs/Web/CSS',
image: '/images/skills/CSS.png' image: '/images/skills/CSS.png'
}, },
'Tailwind CSS': {
link: 'https://tailwindcss.com/',
image: '/images/skills/TailwindCSS.png'
},
SASS: { SASS: {
link: 'https://sass-lang.com/', link: 'https://sass-lang.com/',
image: '/images/skills/SASS.svg' image: '/images/skills/SASS.svg'
@ -39,6 +60,24 @@ export const skills = {
link: 'https://nodejs.org/', link: 'https://nodejs.org/',
image: '/images/skills/NodeJS.png' image: '/images/skills/NodeJS.png'
}, },
Fastify: {
link: 'https://www.fastify.io/',
image: {
light: '/images/skills/Fastify-light.png',
dark: '/images/skills/Fastify-dark.png'
}
},
Prisma: {
link: 'https://www.prisma.io/',
image: {
light: '/images/skills/Prisma-light.png',
dark: '/images/skills/Prisma-dark.png'
}
},
PostgreSQL: {
link: 'https://www.postgresql.org/',
image: '/images/skills/PostgreSQL.png'
},
MySQL: { MySQL: {
link: 'https://www.mysql.com/', link: 'https://www.mysql.com/',
image: '/images/skills/MySQL.png' image: '/images/skills/MySQL.png'
@ -63,8 +102,14 @@ export const skills = {
link: 'https://ubuntu.com/', link: 'https://ubuntu.com/',
image: '/images/skills/Ubuntu.png' image: '/images/skills/Ubuntu.png'
}, },
'GNU/Linux': {
link: 'https://www.gnu.org/',
image: '/images/skills/GNU-Linux.png'
},
Docker: { Docker: {
link: 'https://www.docker.com/', link: 'https://www.docker.com/',
image: '/images/skills/Docker.png' image: '/images/skills/Docker.png'
} }
} as const } as const
export type SkillName = keyof typeof skills

View File

@ -1,15 +0,0 @@
import { render } from '@testing-library/react'
import { ErrorPage } from '../ErrorPage'
describe('<ErrorPage />', () => {
it('should render the message and statusCode', async () => {
const messageContent = 'message content'
const statusCode = 404
const { getByText } = render(
<ErrorPage statusCode={statusCode} message={messageContent} />
)
expect(getByText(messageContent)).toBeInTheDocument()
expect(getByText(statusCode)).toBeInTheDocument()
})
})

View File

@ -1,10 +0,0 @@
import { render } from '@testing-library/react'
import { Footer } from '../Footer'
describe('<Footer />', () => {
it('should render', async () => {
const { getByText } = render(<Footer />)
expect(getByText('Divlo')).toBeInTheDocument()
})
})

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react' import { useEffect, useRef } from 'react'
export const RevealFade: React.FC = (props) => { export const RevealFade: React.FC<React.PropsWithChildren> = (props) => {
const { children } = props const { children } = props
const htmlElement = useRef<HTMLDivElement>(null) const htmlElement = useRef<HTMLDivElement>(null)
@ -10,7 +10,8 @@ export const RevealFade: React.FC = (props) => {
(entries, observer) => { (entries, observer) => {
entries.forEach((entry) => { entries.forEach((entry) => {
if (entry.isIntersecting) { if (entry.isIntersecting) {
entry.target.classList.add('reveal-visible') entry.target.className =
'opacity-100 visible translate-y-0 transition-all duration-700 ease-in-out'
observer.unobserve(entry.target) observer.unobserve(entry.target)
} }
}) })
@ -25,26 +26,8 @@ export const RevealFade: React.FC = (props) => {
}, []) }, [])
return ( return (
<> <div ref={htmlElement} className='invisible -translate-y-7 opacity-0'>
<div ref={htmlElement} className='reveal'> {children}
{children} </div>
</div>
<style jsx>
{`
.reveal {
opacity: 0;
visibility: hidden;
transform: translateY(-30px);
}
.reveal-visible {
opacity: 1;
visibility: visible;
transform: translateY(0);
transition: all 500ms ease-out 100ms;
}
`}
</style>
</>
) )
} }

View File

@ -1,20 +1,11 @@
import { forwardRef } from 'react'
type SectionHeadingProps = React.ComponentPropsWithRef<'h2'> type SectionHeadingProps = React.ComponentPropsWithRef<'h2'>
export const SectionHeading = forwardRef< export const SectionHeading: React.FC<SectionHeadingProps> = (props) => {
HTMLHeadingElement,
SectionHeadingProps
>((props, ref) => {
const { children, ...rest } = props const { children, ...rest } = props
return ( return (
<h2 <h2 {...rest} className='mb-3 mt-1 text-center text-4xl font-semibold'>
ref={ref}
{...rest}
className='text-4xl font-semibold text-center mt-1 mb-7'
>
{children} {children}
</h2> </h2>
) )
}) }

View File

@ -1,5 +1,3 @@
import { forwardRef } from 'react'
import { ShadowContainer } from '../ShadowContainer' import { ShadowContainer } from '../ShadowContainer'
import { SectionHeading } from './SectionHeading' import { SectionHeading } from './SectionHeading'
@ -10,7 +8,7 @@ type SectionProps = React.ComponentPropsWithRef<'section'> & {
withoutShadowContainer?: boolean withoutShadowContainer?: boolean
} }
export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => { export const Section: React.FC<SectionProps> = (props) => {
const { const {
children, children,
heading, heading,
@ -22,11 +20,11 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
if (isMain) { if (isMain) {
return ( return (
<div className='px-3 w-full'> <div className='w-full px-3'>
<ShadowContainer style={{ marginTop: 50 }}> <ShadowContainer style={{ marginTop: 50 }}>
<section ref={ref} {...rest}> <section {...rest}>
{heading != null && <SectionHeading>{heading}</SectionHeading>} {heading != null && <SectionHeading>{heading}</SectionHeading>}
<div className='px-3 w-full'>{children}</div> <div className='w-full px-3'>{children}</div>
</section> </section>
</ShadowContainer> </ShadowContainer>
</div> </div>
@ -35,15 +33,15 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
if (withoutShadowContainer) { if (withoutShadowContainer) {
return ( return (
<section ref={ref} {...rest}> <section {...rest}>
{heading != null && <SectionHeading>{heading}</SectionHeading>} {heading != null && <SectionHeading>{heading}</SectionHeading>}
<div className='px-3 w-full'>{children}</div> <div className='w-full px-3'>{children}</div>
</section> </section>
) )
} }
return ( return (
<section ref={ref} {...rest}> <section {...rest}>
{heading != null && ( {heading != null && (
<SectionHeading style={{ ...(description != null && { margin: 0 }) }}> <SectionHeading style={{ ...(description != null && { margin: 0 }) }}>
{heading} {heading}
@ -54,11 +52,11 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
{description} {description}
</p> </p>
)} )}
<div className='px-3 w-full'> <div className='w-full px-3'>
<ShadowContainer> <ShadowContainer>
<div className='px-16 py-4 leading-8 w-full'>{children}</div> <div className='w-full px-16 py-4 leading-8'>{children}</div>
</ShadowContainer> </ShadowContainer>
</div> </div>
</section> </section>
) )
}) }

View File

@ -1,4 +1,4 @@
import classNames from 'classnames' import classNames from 'clsx'
type ShadowContainerProps = React.ComponentPropsWithRef<'div'> type ShadowContainerProps = React.ComponentPropsWithRef<'div'>
@ -6,27 +6,14 @@ export const ShadowContainer: React.FC<ShadowContainerProps> = (props) => {
const { children, className, ...rest } = props const { children, className, ...rest } = props
return ( return (
<> <div
<div className={classNames(
className={classNames( 'mb-12 h-full max-w-full break-words rounded-2xl border border-solid border-[#000] shadow-light dark:shadow-dark ',
'shadow-container h-full max-w-full break-words', className
className )}
)} {...rest}
{...rest} >
> {children}
{children} </div>
</div>
<style jsx>
{`
.shadow-container {
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin-bottom: 50px;
}
`}
</style>
</>
) )
} }

17
cypress.config.ts Normal file
View File

@ -0,0 +1,17 @@
import { defineConfig } from 'cypress'
export default defineConfig({
fixturesFolder: false,
video: false,
screenshotOnRunFailure: false,
e2e: {
baseUrl: 'http://127.0.0.1:3000',
supportFile: false
},
component: {
devServer: {
framework: 'next',
bundler: 'webpack'
}
}
})

View File

@ -0,0 +1,16 @@
import { Footer } from '@/components/Footer'
describe('<Footer />', () => {
it('should render with appropriate link tag version', () => {
const version = '1.0.0'
cy.mount(<Footer version={version} />)
cy.contains('Divlo')
.get('[data-cy=version-link]')
.should('have.text', version)
.should(
'have.attr',
'href',
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
)
})
})

View File

@ -0,0 +1,17 @@
import { getAge } from '../../../utils/getAge'
describe('utils/getAge', () => {
it('should calculate the right age of a person', () => {
cy.clock(new Date('2018-03-20')).then(() => {
const birthDate = new Date('1980-02-20')
expect(getAge(birthDate)).equal(38)
})
})
it('should calculate the right age of a person (taking into account the months)', () => {
cy.clock(new Date('2018-03-20')).then(() => {
const birthDate = new Date('1980-07-20')
expect(getAge(birthDate)).equal(37)
})
})
})

View File

@ -0,0 +1,62 @@
describe('Common > Header', () => {
beforeEach(() => {
return cy.visit('/')
})
it('should redirect to /blog on click of the blog link', () => {
cy.get('[data-cy=header-blog-link]')
.click()
.location('pathname')
.should('eq', '/blog')
})
it('should always be visible (sticky header)', () => {
cy.scrollTo('bottom').get('header').should('be.visible')
})
describe('Switch theme color (dark/light)', () => {
it('should switch theme from `dark` (default) to `light`', () => {
cy.get('[data-cy=switch-theme-dark]').should('be.visible')
cy.get('[data-cy=switch-theme-light]').should('not.be.visible')
cy.get('body').should(
'not.have.css',
'background-color',
'rgb(255, 255, 255)'
)
cy.get('[data-cy=switch-theme-click]').click()
cy.get('[data-cy=switch-theme-dark]').should('not.be.visible')
cy.get('[data-cy=switch-theme-light]').should('be.visible')
cy.get('body').should(
'have.css',
'background-color',
'rgb(255, 255, 255)'
)
})
})
describe('Switch Language', () => {
it('should switch language from EN (default) to FR', () => {
cy.get('h1').contains('I am Divlo')
cy.get('[data-cy=language-flag-text]').contains('EN')
cy.get('[data-cy=languages-list]').should('not.be.visible')
cy.get('[data-cy=language-click]').click()
cy.get('[data-cy=languages-list]').should('be.visible')
cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click()
cy.get('[data-cy=languages-list]').should('not.be.visible')
cy.get('[data-cy=language-flag-text]').contains('FR')
cy.get('h1').contains('Je suis Divlo')
})
it('should close the language list menu when clicking outside', () => {
cy.get('[data-cy=languages-list]').should('not.be.visible')
cy.get('[data-cy=language-click]').click()
cy.get('[data-cy=languages-list]').should('be.visible')
cy.get('h1').click()
cy.get('[data-cy=languages-list]').should('not.be.visible')
})
})
})
export {}

View File

@ -0,0 +1,11 @@
describe('Page /404', () => {
beforeEach(() => {
return cy.visit('/404', { failOnStatusCode: false })
})
it('should display the statusCode of 404', () => {
cy.get('[data-cy=status-code]').contains('404')
})
})
export {}

View File

@ -0,0 +1,11 @@
describe('Page /500', () => {
beforeEach(() => {
return cy.visit('/500', { failOnStatusCode: false })
})
it('should display the statusCode of 500', () => {
cy.get('[data-cy=status-code]').contains('500')
})
})
export {}

View File

@ -0,0 +1,15 @@
describe('Page /blog/[slug]', () => {
it('should displays the first blog post (`hello-world`)', () => {
cy.visit('/blog/hello-world')
cy.get('[data-cy=language-flag-text]').should('not.exist')
cy.get('h1').should('have.text', '👋 Hello, world!')
cy.get('.prose a').should('have.attr', 'target', '_blank')
})
it("should redirect to /404 if the blog post doesn't exist", () => {
cy.visit('/blog/random-blog-post-not-found', { failOnStatusCode: false })
cy.get('[data-cy=status-code]').contains('404')
})
})
export {}

View File

@ -0,0 +1,24 @@
describe('Page /blog', () => {
it('should displays the blog posts sorted from newest to oldest', () => {
cy.visit('/blog')
cy.get('[data-cy=blog-posts] [data-cy=blog-post-title]')
.last()
.should('have.text', '👋 Hello, world!')
cy.get('[data-cy=blog-posts] [data-cy=blog-post-description]')
.last()
.should(
'have.text',
'First post of the blog, introduction and explanation of how this blog is made.'
)
})
it('should redirect the user to the right blog post', () => {
cy.visit('/blog')
cy.get('[data-cy=hello-world]')
.click()
.location('pathname')
.should('eq', '/blog/hello-world')
})
})
export {}

View File

@ -0,0 +1,18 @@
describe('Page /', () => {
beforeEach(() => {
return cy.visit('/')
})
it('should reveals the sections while scrolling except the about section', () => {
const sectionsReveals = ['#interests', '#skills', '#portfolio']
cy.get('#about').should('be.visible')
for (const section of sectionsReveals) {
cy.get(section)
.should('not.be.visible')
.scrollIntoView()
.should('be.visible')
}
})
})
export {}

View File

@ -0,0 +1,3 @@
/// <reference types="cypress" />
export {}

View File

@ -0,0 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width,initial-scale=1.0" />
<title>Components App</title>
<!-- Used by Next.js to inject CSS. -->
<div id="__next_css__DO_NOT_USE__"></div>
</head>
<body>
<div data-cy-root></div>
</body>
</html>

View File

@ -0,0 +1,14 @@
import { mount } from 'cypress/react'
import './commands'
import '../../styles/global.css'
declare global {
namespace Cypress {
interface Chainable {
mount: typeof mount
}
}
}
Cypress.Commands.add('mount', mount)

View File

@ -1,4 +1,3 @@
version: '3.0'
services: services:
divlo.fr: divlo.fr:
container_name: ${COMPOSE_PROJECT_NAME} container_name: ${COMPOSE_PROJECT_NAME}
@ -6,6 +5,7 @@ services:
build: build:
context: './' context: './'
ports: ports:
- '${PORT}:${PORT}' - '${PORT-3000}:${PORT-3000}'
environment: environment:
PORT: ${PORT} PORT: ${PORT-3000}
env_file: './.env'

View File

@ -1,28 +0,0 @@
module.exports = {
roots: ['<rootDir>'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
},
moduleDirectories: ['node_modules', './'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
'@testing-library/react'
],
collectCoverage: true,
collectCoverageFrom: [
'**/*.{js,jsx,ts,tsx}',
'!**/*.d.ts',
'!**/.next/**',
'!**/node_modules/**',
'!**/next.config.js',
'!**/postcss.config.js',
'!**/tailwind.config.js',
'!**/workbox-*.js',
'!**/sw.js',
'!**/jest.config.js'
],
coverageDirectory: './coverage',
coverageReporters: ['text', 'cobertura']
}

22
jsonresume-theme-custom/.gitignore vendored Normal file
View File

@ -0,0 +1,22 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*
node_modules
dist
dist-ssr
*.local
# Editor directories and files
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

View File

@ -0,0 +1,20 @@
import { fileURLToPath } from 'node:url'
import fs from 'node:fs'
import { build } from 'vite'
const jsonResumeThemeCustom = new URL('./', import.meta.url)
const jsonResumeThemeCustomDist = new URL('./dist', jsonResumeThemeCustom)
const publicResumeOutputURL = new URL(
'../public/curriculum-vitae',
import.meta.url
)
await build({
root: fileURLToPath(jsonResumeThemeCustom),
base: '/curriculum-vitae/'
})
await fs.promises.cp(jsonResumeThemeCustomDist, publicResumeOutputURL, {
recursive: true
})

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M243.4 2.587C251.4-.8625 260.6-.8625 268.6 2.587L492.6 98.59C506.6 104.6 514.4 119.6 511.3 134.4C508.3 149.3 495.2 159.1 479.1 160V168C479.1 181.3 469.3 192 455.1 192H55.1C42.74 192 31.1 181.3 31.1 168V160C16.81 159.1 3.708 149.3 .6528 134.4C-2.402 119.6 5.429 104.6 19.39 98.59L243.4 2.587zM256 128C273.7 128 288 113.7 288 96C288 78.33 273.7 64 256 64C238.3 64 224 78.33 224 96C224 113.7 238.3 128 256 128zM127.1 416H167.1V224H231.1V416H280V224H344V416H384V224H448V420.3C448.6 420.6 449.2 420.1 449.8 421.4L497.8 453.4C509.5 461.2 514.7 475.8 510.6 489.3C506.5 502.8 494.1 512 480 512H31.1C17.9 512 5.458 502.8 1.372 489.3C-2.715 475.8 2.515 461.2 14.25 453.4L62.25 421.4C62.82 420.1 63.41 420.6 63.1 420.3V224H127.1V416z"/></svg>

After

Width:  |  Height:  |  Size: 1015 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M623.1 136.9l-282.7-101.2c-13.73-4.91-28.7-4.91-42.43 0L16.05 136.9C6.438 140.4 0 149.6 0 160s6.438 19.65 16.05 23.09L76.07 204.6c-11.89 15.8-20.26 34.16-24.55 53.95C40.05 263.4 32 274.8 32 288c0 9.953 4.814 18.49 11.94 24.36l-24.83 149C17.48 471.1 25 480 34.89 480H93.11c9.887 0 17.41-8.879 15.78-18.63l-24.83-149C91.19 306.5 96 297.1 96 288c0-10.29-5.174-19.03-12.72-24.89c4.252-17.76 12.88-33.82 24.94-47.03l190.6 68.23c13.73 4.91 28.7 4.91 42.43 0l282.7-101.2C633.6 179.6 640 170.4 640 160S633.6 140.4 623.1 136.9zM351.1 314.4C341.7 318.1 330.9 320 320 320c-10.92 0-21.69-1.867-32-5.555L142.8 262.5L128 405.3C128 446.6 213.1 480 320 480c105.1 0 192-33.4 192-74.67l-14.78-142.9L351.1 314.4z"/></svg>

After

Width:  |  Height:  |  Size: 986 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z"/></svg>

After

Width:  |  Height:  |  Size: 629 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M502.6 182.6l-45.25-45.25C451.4 131.4 443.3 128 434.8 128H384V80C384 53.5 362.5 32 336 32h-160C149.5 32 128 53.5 128 80V128H77.25c-8.5 0-16.62 3.375-22.62 9.375L9.375 182.6C3.375 188.6 0 196.8 0 205.3V304h128v-32C128 263.1 135.1 256 144 256h32C184.9 256 192 263.1 192 272v32h128v-32C320 263.1 327.1 256 336 256h32C376.9 256 384 263.1 384 272v32h128V205.3C512 196.8 508.6 188.6 502.6 182.6zM336 128h-160V80h160V128zM384 368c0 8.875-7.125 16-16 16h-32c-8.875 0-16-7.125-16-16v-32H192v32C192 376.9 184.9 384 176 384h-32C135.1 384 128 376.9 128 368v-32H0V448c0 17.62 14.38 32 32 32h448c17.62 0 32-14.38 32-32v-112h-128V368z"/></svg>

After

Width:  |  Height:  |  Size: 912 B

View File

@ -0,0 +1,2 @@
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM274.7 304H173.3C77.61 304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304z"/></svg>

After

Width:  |  Height:  |  Size: 528 B

View File

@ -0,0 +1,244 @@
<!DOCTYPE html>
<html lang="fr">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title><%= locals.basics.name %></title>
<link rel="icon" type="image/png" href="<%= locals.basics.image %>" />
<link rel="stylesheet" href="./styles/global.css" />
<script defer type="module" src="./scripts/main.js"></script>
</head>
<body>
<div class="container-fluid">
<div class="row main clearfix">
<section class="col-md-3 card-wrapper profile-card-wrapper affix">
<div class="card profile-card">
<div class="profile-pic-container">
<div class="profile-pic">
<img
class="media-object img-circle center-block"
data-src="holder.js/100x100"
alt="<%= locals.basics.name %>"
src="<%= locals.basics.image %>"
/>
</div>
<div class="name-and-profession text-center">
<h3>
<strong><%= locals.basics.name %></strong>
</h3>
<h5 class="text-muted"><%= locals.basics.label %></h5>
<h5 class="text-muted">
<%= locals.basics.age %> (<span id="year-old"></span> ans)
</h5>
<h5 class="text-muted">
<%= locals.basics.location.address %>
</h5>
</div>
</div>
<div class="contact-details clearfix">
<div class="detail">
<span class="info">
<a
class="link-disguise"
href="mailto:<%= locals.basics.email %>"
>
<%= locals.basics.email %>
</a>
</span>
</div>
<div class="detail">
<span class="info">
<a class="link-disguise" href="<%= locals.basics.url %>">
<%= locals.basics.url %>
</a>
</span>
</div>
</div>
<hr />
</div>
<div class="card background-card">
<div class="background-details">
<div class="detail" id="about">
<div class="icon">
<img src="./images/user.svg" alt="user" />
</div>
<div class="info">
<h4 class="title text-uppercase">À propos</h4>
<div class="card card-nested">
<div class="content mop-wrapper">
<p><%- locals.basics.summary %></p>
</div>
</div>
</div>
</div>
<hr />
<section class="section-separated">
<div class="detail" id="education">
<div class="icon">
<img src="./images/graduation-cap.svg" alt="graduation" />
</div>
<div class="info">
<h4 class="title text-uppercase">Formations</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.education.forEach((degree) => { %>
<li class="card card-nested">
<div class="content">
<p class="clear-margin relative">
<strong><%= degree.studyType %></strong>
</p>
<p class="clear-margin relative">
<strong><%= degree.score %></strong>
</p>
<p class="text-muted clear-margin">
<%= degree.institution %>
</p>
<p class="text-muted clear-margin">
<small>
<%= degree.startDate %> <%= degree.endDate !=
null ? " - " + degree.endDate : "" %>
</small>
</p>
<% if (degree.courses != null) { %>
<ul class="education-courses">
<% degree.courses.forEach((course) => { %>
<li><%= course %></li>
<% }) %>
</ul>
<% } %>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
<div class="detail" id="skills">
<div class="icon">
<img src="./images/toolbox.svg" alt="toolbox" />
</div>
<div class="info">
<h4 class="title text-uppercase">Compétences</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.skills.forEach((skill) => { %>
<li class="card card-nested card-skills">
<div class="skill-info">
<strong><%= skill.name %></strong>
<div class="space-top labels">
<% skill.keywords.forEach((keyword) => { %>
<p class="label label-keyword"><%= keyword %></p>
<% }) %>
</div>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</section>
<hr />
<section class="section-separated">
<div class="detail" id="work-experience">
<div class="icon">
<img src="./images/building-columns.svg" alt="work" />
</div>
<div class="info">
<h4 class="title text-uppercase">Expériences</h4>
<ul class="list-unstyled clear-margin">
<% locals.work.filter((experience) =>
experience.description == null).forEach((experience) => {
%>
<li class="card card-nested clearfix">
<div class="content">
<p class="clear-margin relative">
<a href="<%= experience.website %>">
<strong><%= experience.name %></strong>
</a>
</p>
<p class="clear-margin relative">
<strong><%- experience.position %></strong>
</p>
<p class="text-muted">
<small>
<span class="space-right">
<%= date.format(new Date(experience.startDate),
'DD/MM/YYYY') %> - <%= date.format(new
Date(experience.endDate), 'DD/MM/YYYY') %> (<%=
experience.duration %>)
</span>
</small>
</p>
<div class="experience-description">
<p><%- experience.summary %></p>
</div>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
<div class="detail" id="interests">
<div class="icon">
<img src="./images/heart.svg" alt="heart" />
</div>
<div class="info">
<h4 class="title text-uppercase">Intérets</h4>
<div class="content">
<ul class="list-unstyled clear-margin">
<% locals.interests.forEach((interest) => { %>
<li class="card card-nested">
<p><strong><%= interest.name %></strong></p>
</li>
<% }) %>
</ul>
<ul class="list-unstyled clear-margin">
<% locals.work.filter((experience) =>
experience.description != null).forEach((experience) =>
{ %>
<li class="card card-nested clearfix">
<div class="content">
<p class="clear-margin relative">
<a href="<%= experience.website %>">
<strong><%= experience.name %></strong>
</a>
</p>
<p class="clear-margin relative">
<strong><%- experience.position %></strong>
</p>
<p class="text-muted">
<small>
<span class="space-right">
<%= date.format(new
Date(experience.startDate), 'DD/MM/YYYY') %> -
<%= date.format(new Date(experience.endDate),
'DD/MM/YYYY') %> (<%= experience.duration %>)
</span>
</small>
</p>
<div class="experience-description">
<p><%- experience.summary %></p>
</div>
</div>
</li>
<% }) %>
</ul>
</div>
</div>
</div>
</section>
</div>
</div>
</section>
</div>
</div>
</body>
</html>

1530
jsonresume-theme-custom/package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,21 @@
{
"name": "jsonresume-theme-custom",
"private": true,
"version": "1.0.0",
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"jsonc-parser": "3.2.0",
"modern-normalize": "1.1.0"
},
"devDependencies": {
"@types/node": "18.15.11",
"date-and-time": "2.4.3",
"vite": "4.2.1",
"vite-plugin-html": "3.2.0"
}
}

View File

@ -0,0 +1,5 @@
import { DIVLO_BIRTHDAY, getAge } from '../../utils/getAge.ts'
const yearOld = document.getElementById('year-old')
yearOld.textContent = getAge(DIVLO_BIRTHDAY).toString()

View File

@ -0,0 +1,229 @@
@import 'modern-normalize/modern-normalize.css';
body {
font-family: 'Montserrat', 'Arial', 'sans-serif';
background: #f0f0f0;
color: #333;
line-height: 1.42857143;
font-size: 14px;
}
hr {
margin-top: 15px;
margin-bottom: 15px;
border: 0;
border-top: 1px solid #eee;
}
p {
margin: 0;
}
strong {
font-weight: 600;
}
a {
color: #337ab7;
text-decoration: none;
}
a:focus,
a:hover {
color: #23527c;
text-decoration: underline;
}
.link-disguise {
color: inherit;
}
.link-disguise:hover {
color: inherit;
}
.h1,
.h2,
.h3,
h1,
h2,
h3 {
margin-top: 20px;
margin-bottom: 10px;
}
.h4,
.h5,
.h6,
h4,
h5,
h6 {
margin-top: 10px;
margin-bottom: 10px;
}
.h1,
.h2,
.h3,
.h4,
.h5,
.h6,
h1,
h2,
h3,
h4,
h5,
h6 {
font-family: inherit;
font-weight: 500;
line-height: 1.1;
color: inherit;
}
.h3,
h3 {
font-size: 24px;
}
.h4,
h4 {
font-size: 18px;
}
.h5,
h5 {
font-size: 14px;
}
.container-fluid {
padding-right: 15px;
padding-left: 15px;
margin-right: auto;
margin-left: auto;
}
.row {
margin-right: -15px;
margin-left: -15px;
}
.clear-margin {
margin: 0;
}
.relative {
position: relative;
}
.center-block {
display: block;
margin-right: auto;
margin-left: auto;
}
.text-center {
text-align: center;
}
.text-muted {
color: #777;
}
.text-uppercase {
text-transform: uppercase;
}
.list-unstyled {
padding-left: 0;
list-style: none;
}
.main {
padding: 5px;
}
.title {
font-weight: 600;
}
.profile-card-wrapper {
position: relative;
}
.card-wrapper {
float: none !important;
padding: 5px;
}
.profile-card-wrapper .profile-card {
padding: 10px;
}
.card {
background: white;
border-radius: 3px;
padding: 10px 0;
}
.profile-pic {
padding: 10px 0;
}
.profile-pic img {
width: 100px;
height: 100px;
border-radius: 50%;
vertical-align: middle;
border: 0;
}
.contact-details {
display: flex;
justify-content: center;
}
.contact-details .detail {
position: relative;
min-height: 1px;
padding: 10px;
}
.social-links {
line-height: 2.5;
}
.experience-description {
margin-top: 10px;
}
.background-details .detail {
display: table;
}
.background-details .detail .icon,
.background-details .detail .info {
display: table-cell;
}
.background-details .detail .icon {
color: #707070;
}
.background-details .detail .icon {
min-width: 45px;
max-width: 45px;
text-align: center;
}
.icon img {
width: 20px;
height: 20px;
}
.background-details .detail .mobile-title {
display: none;
}
.card-nested {
min-height: 0;
border-width: 1px 0 0 0;
}
.card-skills {
position: relative;
}
.labels {
line-height: 2;
}
.space-top {
margin-top: 10px;
}
.label {
display: inline;
padding: 0.2em 0.6em 0.3em;
font-size: 75%;
font-weight: 600;
line-height: 1;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
border-radius: 0.25em;
}
.label-keyword {
display: inline-block;
font-size: 0.9em;
padding: 5px;
border: 1px solid #357ebd;
margin-right: 5px;
}
.label-keyword p {
margin: 0;
}
.section-separated {
display: flex;
}

View File

@ -0,0 +1,36 @@
import fs from 'node:fs'
import { defineConfig } from 'vite'
import { parse as JSONCParser } from 'jsonc-parser'
import { createHtmlPlugin } from 'vite-plugin-html'
import date from 'date-and-time'
const jsonResumeURL = new URL('../resume.jsonc', import.meta.url)
const dataResumeStringJSON = await fs.promises.readFile(jsonResumeURL, {
encoding: 'utf-8'
})
const resume = JSONCParser(dataResumeStringJSON)
/**
* Documentation: <https://vitejs.dev/config/>
*/
export default defineConfig({
build: {
assetsDir: './'
},
plugins: [
createHtmlPlugin({
inject: {
data: {
date,
locals: {
...resume
}
}
}
})
],
css: {
postcss: {}
}
})

View File

@ -1,6 +1,6 @@
{ {
"english": "English", "english": "English",
"french": "French", "french": "French",
"allRightsReserved": "All rights reserved", "all-rights-reserved": "All rights reserved",
"home": "Home" "home": "Home"
} }

View File

@ -1,6 +1,6 @@
{ {
"returnToHomePage": "Return to the home page?", "return-to-home-page": "Return to the home page?",
"error": "Error", "error": "Error",
"serverError": "Internal Server Error!", "server-error": "Internal Server Error!",
"notFound": "This page doesn't exist!" "not-found": "This page doesn't exist!"
} }

View File

@ -1,35 +1,37 @@
{ {
"about": { "about": {
"IAm": "I am", "i-am": "I am",
"description": "Developer Full Stack Junior • Passionate about High-Tech", "description": "Developer Full Stack • Open-Source enthusiast",
"birthDate": "Birth date", "full-name": "Full name",
"birth-date": "Birth date",
"years-old": "years old",
"nationality": "Nationality", "nationality": "Nationality",
"descriptionBottom": "I am self-taught in Computer Science by following online trainings. <0/> <0/> I put into practice everything I learn and make many projects." "description-bottom": "I am a student in computer science following the French training \"BUT Informatique\" and I am also a self-taught."
}, },
"interests": { "interests": {
"title": "My Interests", "title": "Interests",
"paragraphs": [ "paragraphs": [
{ {
"title": "Developer Full Stack Junior :", "title": "Developer Full Stack",
"description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming some Python and others programming language too." "description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming in others programming language too."
}, },
{ {
"title": "Passionate about High-Tech :", "title": "Open-Source enthusiast",
"description": "For me, everyone should work, solve problems, build things and think together.<br/> The website is open-source on <a class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>GitHub</a>."
},
{
"title": "Passionate about High-Tech",
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas." "description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas."
},
{
"title": "Open-Source enthusiast :",
"description": "For me, everyone should work, solve problems, build things and think together. Long live open source, whenever you can share your work, do it! <br/> The website is open-source on <a class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
} }
] ]
}, },
"skills": { "skills": {
"title": "My skills", "title": "Skills",
"languages": "Programming languages", "languages": "Programming languages",
"softwareTools": "Software and tools" "software-tools": "Software and tools"
}, },
"portfolio": { "portfolio": {
"title": "My Portfolio", "title": "Portfolio",
"items": [ "items": [
{ {
"title": "function.divlo.fr", "title": "function.divlo.fr",
@ -42,7 +44,16 @@
"description": "Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun.", "description": "Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun.",
"link": "https://thream.divlo.fr/", "link": "https://thream.divlo.fr/",
"image": "/images/portfolio/threamdivlofr.png" "image": "/images/portfolio/threamdivlofr.png"
},
{
"title": "Leon",
"description": "Leon is your open-source personal assistant.",
"link": "https://getleon.ai/",
"image": "/images/portfolio/leon.png"
} }
] ]
},
"open-source": {
"description": "List of most famous open source projects I contributed to."
} }
} }

Some files were not shown because too many files have changed in this diff Show More