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

Compare commits

..

229 Commits

Author SHA1 Message Date
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
b0a34c6162 fix: link styles with underline on hover 2021-06-10 00:17:52 +02:00
ea04f0f189 build(deps-dev): bump @types/react from 17.0.9 to 17.0.10 (#68) 2021-06-09 11:18:01 +02:00
1403cdf80c build(deps-dev): bump @typescript-eslint/eslint-plugin (#67) 2021-06-08 09:16:29 +02:00
40e676cfc7 build(deps-dev): bump @types/node from 15.12.1 to 15.12.2 (#66) 2021-06-08 09:16:12 +02:00
5f654020d5 build(deps-dev): bump @lhci/cli from 0.7.2 to 0.8.0 (#58) 2021-06-07 14:44:05 +02:00
a3ec87bf52 build(deps-dev): bump @types/node from 15.12.0 to 15.12.1 (#63) 2021-06-07 10:59:08 +02:00
88588355fd build(deps): bump @fontsource/montserrat from 4.4.2 to 4.4.5 (#64) 2021-06-07 10:58:15 +02:00
c329c56094 build(deps-dev): bump prettier from 2.3.0 to 2.3.1 (#65) 2021-06-07 10:57:58 +02:00
08a5454cf4 build(deps-dev): bump eslint from 7.27.0 to 7.28.0 (#62) 2021-06-07 10:57:38 +02:00
8faf47c06e build(deps-dev): bump @testing-library/jest-dom from 5.12.0 to 5.13.0 (#59) 2021-06-04 10:25:13 +02:00
d7f778de28 build(deps-dev): bump jest from 27.0.3 to 27.0.4 (#60) 2021-06-04 10:24:58 +02:00
cd3cc50e00 build(deps-dev): bump @types/node from 15.9.0 to 15.12.0 (#61) 2021-06-04 10:24:39 +02:00
755f2da03a build(deps-dev): bump @types/react from 17.0.8 to 17.0.9 (#54) 2021-06-04 09:29:48 +02:00
7ef9f79b97 build(deps): bump node from 16.2.0 to 16.3.0 (#57) 2021-06-04 09:25:00 +02:00
2c53a1409c build(deps-dev): bump @types/node from 15.6.1 to 15.9.0 (#55) 2021-06-03 10:54:28 +02:00
1e2d5c0f3e build(deps-dev): bump tailwindcss from 2.1.2 to 2.1.4 (#56) 2021-06-03 10:54:11 +02:00
6db7ed2f5e build(deps-dev): bump @typescript-eslint/eslint-plugin (#52) 2021-06-01 18:22:33 +02:00
9b8102cbdc ci: only one workflow (Divlo) 2021-05-31 15:04:17 +02:00
e0bc1fed49 build(deps): bump @fontsource/montserrat from 4.4.0 to 4.4.2 (#51) 2021-05-31 13:43:10 +02:00
c230f5bb51 build(deps-dev): bump eslint-plugin-import from 2.23.3 to 2.23.4 (#50) 2021-05-31 13:42:53 +02:00
6f4819b689 build(deps-dev): bump jest from 27.0.1 to 27.0.3 (#49) 2021-05-31 13:42:30 +02:00
fd67737754 build(deps-dev): bump babel-jest from 27.0.1 to 27.0.2 (#48) 2021-05-31 13:42:14 +02:00
6f94865917 build(deps): bump @fontsource/montserrat from 4.3.0 to 4.4.0 (#47) 2021-05-28 08:46:08 +02:00
1e4167e209 build(deps): bump actions/cache from 2.1.5 to 2.1.6 (#46) 2021-05-28 08:44:01 +02:00
655ed6f6f6 build(deps-dev): bump typescript from 4.2.4 to 4.3.2 (#45) 2021-05-27 10:23:19 +02:00
8fe73be90b build(deps): bump jest to 27.0.1 2021-05-26 18:54:09 +02:00
e925b73606 build(deps): bump next from 10.2.2 to 10.2.3 (#40) 2021-05-25 08:56:30 +02:00
b902b9a122 build(deps-dev): bump @typescript-eslint/eslint-plugin (#41) 2021-05-25 08:55:07 +02:00
1044302118 build(deps-dev): bump @types/node from 15.6.0 to 15.6.1 (#39) 2021-05-25 08:54:46 +02:00
df15232312 build(deps-dev): bump eslint-plugin-import from 2.23.2 to 2.23.3 (#36) 2021-05-24 11:30:29 +02:00
f5d273688d build(deps): bump next-translate from 1.0.6 to 1.0.7 (#37) 2021-05-24 11:30:08 +02:00
993dd1e30e build(deps-dev): bump @types/node from 15.3.1 to 15.6.0 (#38) 2021-05-24 11:29:36 +02:00
83f90e24c7 build(deps-dev): bump eslint from 7.26.0 to 7.27.0 (#35) 2021-05-24 11:29:24 +02:00
c3fd177ff5 build(deps-dev): bump postcss from 8.2.15 to 8.3.0 (#34) 2021-05-21 09:34:40 +02:00
c5f8b4fb13 build(deps): bump node from 16.1.0 to 16.2.0 (#31) 2021-05-20 11:14:07 +02:00
a4e48de57e build(deps-dev): bump @types/node from 15.3.0 to 15.3.1 (#32) 2021-05-20 11:13:37 +02:00
e3aa2a4d50 build(deps): bump next from 10.2.0 to 10.2.2 (#33) 2021-05-20 11:13:11 +02:00
88c44ed31f build(deps-dev): bump @types/react from 17.0.5 to 17.0.6 (#30) 2021-05-19 10:26:33 +02:00
d3d1ca7beb build(deps-dev): bump @typescript-eslint/eslint-plugin (#29) 2021-05-18 09:17:15 +02:00
8d758bc1d7 build(deps-dev): bump @types/node from 15.0.3 to 15.3.0 (#26) 2021-05-17 15:55:05 +02:00
34b5f123b4 build(deps-dev): bump eslint-plugin-import from 2.23.0 to 2.23.2 (#27) 2021-05-17 15:54:48 +02:00
809f4612b5 build(deps-dev): bump @testing-library/react from 11.2.6 to 11.2.7 (#28) 2021-05-17 15:54:32 +02:00
1f48f7a296 build(deps-dev): bump eslint-plugin-import from 2.22.1 to 2.23.0 (#25) 2021-05-14 11:18:23 +02:00
56258dc06b build(deps-dev): bump @commitlint/cli to 12.1.4 (#21) 2021-05-13 13:38:13 +02:00
3fea7d48f6 build(deps-dev): bump @types/node from 15.0.2 to 15.0.3 (#22) 2021-05-13 13:36:12 +02:00
f49ca1f4f2 build(deps-dev): bump @commitlint/config-conventional (#23) 2021-05-13 13:36:00 +02:00
e9d9139263 build(deps-dev): bump semantic-release from 17.4.2 to 17.4.3 (#24) 2021-05-13 13:35:49 +02:00
98e7987b04 build(deps): bump actions/checkout from 2 to 2.3.4 (#20) 2021-05-13 13:35:37 +02:00
4dc145fe75 build(deps-dev): bump prettier from 2.2.1 to 2.3.0 (#17) 2021-05-11 18:33:41 +02:00
c8b12cd618 build(deps-dev): bump @typescript-eslint/eslint-plugin (#18) 2021-05-11 18:25:24 +02:00
97cf63f643 build(deps-dev): bump postcss from 8.2.14 to 8.2.15 (#19) 2021-05-11 18:25:12 +02:00
cd20e25082 fix: update content 2021-05-08 21:09:03 +02:00
c62e66a86a feat: add light mode + rewrite in Tailwind CSS (#15) 2021-05-08 19:52:04 +02:00
26f24329c7 ci(dependabot): temporary disabled npm ecosystem 2021-04-29 16:21:07 +02:00
bb86fb500a build(deps-dev): bump @types/node from 14.14.41 to 15.0.0 (#9) 2021-04-27 10:44:27 +02:00
b78e8b1e02 build(deps-dev): bump @types/jest from 26.0.22 to 26.0.23 (#7) 2021-04-27 10:32:59 +02:00
2e87d6b51f build(deps-dev): bump @types/react from 17.0.3 to 17.0.4 (#8) 2021-04-27 10:32:48 +02:00
4439e73986 build(deps-dev): bump postcss from 8.2.12 to 8.2.13 (#6) 2021-04-27 10:32:37 +02:00
af065afe67 build(deps): bump next-pwa to 5.2.15 2021-04-24 11:29:44 +02:00
088510a62b build(deps-dev): bump postcss from 8.2.10 to 8.2.12 (#5) 2021-04-23 14:19:05 +02:00
e0150361b1 build(deps-dev): bump @testing-library/jest-dom from 5.11.10 to 5.12.0 (#4) 2021-04-23 14:18:52 +02:00
4b7d184c91 fix(pages/api): escape html in send-email 2021-04-20 19:56:02 +02:00
3e18536c2e chore: usage of GitHub flow instead of Git flow 2021-04-20 19:47:57 +02:00
8027e2deed test: improve coverage 2021-04-20 18:23:05 +02:00
c21abdcc81 ci: add codeql-analysis 2021-04-20 17:32:53 +02:00
3382177e27 fix(components): returnToHomePage inside ErrorPage 2021-04-20 15:47:52 +02:00
73352d4414 chore: add vercel-deploy.sh 2021-04-20 15:45:47 +02:00
f11425b2c2 chore: prebuilds Gitpod with everything to true 2021-04-20 03:44:54 +00:00
039f4d172b chore: add prebuilds for Gitpod 2021-04-20 03:31:43 +00:00
b0a608c1fa chore: add Gitpod 2021-04-20 05:18:03 +02:00
122 changed files with 20096 additions and 17085 deletions

View File

@ -3,8 +3,10 @@
[
"next/babel",
{
"styled-jsx": {
"plugins": ["@styled-jsx/plugin-sass"]
"preset-env": {
"targets": {
"browsers": ">1%, not ie 11, not dead"
}
}
}
]

7
.devcontainer/Dockerfile Normal file
View File

@ -0,0 +1,7 @@
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/javascript-node/.devcontainer/base.Dockerfile
ARG VARIANT="14-buster"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
ARG EXTRA_NODE_VERSION=16
RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"

View File

@ -0,0 +1,24 @@
{
"name": "divlo",
"dockerComposeFile": "./docker-compose.yml",
"service": "workspace",
"workspaceFolder": "/workspace",
"settings": {
"remote.autoForwardPorts": false
},
"extensions": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"divlo.vscode-styled-jsx-syntax",
"divlo.vscode-styled-jsx-languageserver",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"coenraads.bracket-pair-colorizer",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker"
],
"forwardPorts": [3000],
"postAttachCommand": ["npm", "clean-install"],
"remoteUser": "node"
}

View File

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

View File

@ -1,11 +1,12 @@
.vscode
.git
.next
.env
build
.next
coverage
dist
node_modules
out
**/workbox-*.js
**/sw.js
**/__test__/**
tmp
temp
.DS_Store
.lighthouseci
.vercel

View File

@ -1,6 +1,2 @@
COMPOSE_PROJECT_NAME=divlo.fr-website
COMPOSE_PROJECT_NAME=divlo.fr
PORT=3000
EMAIL_HOST=divlo.fr-maildev
EMAIL_USER=reply@divlo-website.fr
EMAIL_PASSWORD=password
EMAIL_PORT=25

7
.eslintignore Normal file
View File

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

31
.eslintrc.json Normal file
View File

@ -0,0 +1,31 @@
{
"extends": [
"standard-with-typescript",
"next",
"next/core-web-vitals",
"prettier"
],
"plugins": ["unicorn", "prettier"],
"parserOptions": {
"project": "./tsconfig.json"
},
"env": {
"node": true,
"browser": true,
"jest": true
},
"rules": {
"prettier/prettier": "error",
"unicorn/prefer-node-protocol": "error",
"unicorn/prevent-abbreviations": [
"error",
{
"replacements": {
"props": {
"properties": false
}
}
}
]
}
}

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

View File

@ -2,37 +2,145 @@ name: 'Divlo'
on:
push:
branches: [master, development]
branches: [master, develop]
pull_request:
branches: [master, development]
branches: [master, develop]
jobs:
ci:
analyze:
runs-on: 'ubuntu-latest'
strategy:
fail-fast: false
matrix:
node-version: [14.x]
language: ['javascript']
steps:
- uses: 'actions/checkout@v2'
- uses: 'actions/checkout@v2.3.4'
- name: Use Node.js ${{ matrix.node-version }}
uses: 'actions/setup-node@v2.1.5'
- name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v1'
with:
node-version: ${{ matrix.node-version }}
languages: ${{ matrix.language }}
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.5'
- 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.4.0'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- run: 'npm install --global npm@7'
- 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'
- run: 'npm run build'
- run: 'npm run lighthouse'
- run: 'npm run test'
test-unit:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.4.0'
with:
node-version: '16.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@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.4.0'
with:
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'
- 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@v2.3.4'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.4.0'
with:
node-version: '16.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'
release:
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
needs: [analyze, lint, test-unit, test-e2e, test-lighthouse]
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.3.4'
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@v2.4.0'
with:
node-version: '16.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 }}

View File

@ -1,34 +0,0 @@
name: 'release'
on:
workflow_run:
workflows: [Divlo]
branches: [master]
types:
- 'completed'
jobs:
release:
runs-on: 'ubuntu-latest'
strategy:
matrix:
node-version: [14.x]
steps:
- uses: 'actions/checkout@v2'
- name: Use Node.js ${{ matrix.node-version }}
uses: 'actions/setup-node@v2.1.5'
with:
node-version: ${{ matrix.node-version }}
- name: 'Cache dependencies'
uses: 'actions/cache@v2.1.5'
with:
path: '.npm'
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
- run: 'npm install --global npm@7'
- run: 'npm ci --cache .npm --prefer-offline'
- run: 'npm run release'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

23
.gitignore vendored
View File

@ -14,6 +14,9 @@ dist
# testing
coverage
cypress/screenshots
cypress/videos
cypress/downloads
# PWA
**/workbox-*.js
@ -26,11 +29,23 @@ coverage
# debug
npm-debug.log*
# editors
.vscode
.theia
.idea
# IDEs and editors
/.idea
.project
.classpath
.c9/
*.launch
.settings/
*.sublime-workspace
# IDE - VSCode
.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json
# misc
.DS_Store
.lighthouseci
.vercel

20
.gitpod.yml Normal file
View File

@ -0,0 +1,20 @@
image: 'gitpod/workspace-full'
tasks:
- before: 'cp .env.example .env && npm install --global npm@7'
init: 'npm clean-install'
command: 'npm run dev'
ports:
- port: 3000
onOpen: 'open-preview'
github:
prebuilds:
master: true
branches: true
pullRequests: true
pullRequestsFromForks: true
addComment: true
addBadge: true
addLabel: true

1
.husky/.gitignore vendored
View File

@ -1 +0,0 @@
_

View File

@ -1,7 +1,4 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run lint:docker
npm run lint:editorconfig
npm run lint:markdown
npm run lint:typescript
npm run lint:staged

View File

@ -10,15 +10,7 @@
"assert": {
"preset": "lighthouse:recommended",
"assertions": {
"legacy-javascript": "off",
"unused-javascript": "off",
"uses-rel-preload": "off",
"canonical": "off",
"unsized-images": "off",
"uses-responsive-images": "off",
"bypass": "warning",
"color-contrast": "warning",
"preload-lcp-image": "warning"
"csp-xss": "warning"
}
},
"upload": {

11
.lintstagedrc.json Normal file
View File

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

9
.prettierignore Normal file
View File

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

6
.prettierrc.json Normal file
View File

@ -0,0 +1,6 @@
{
"singleQuote": true,
"jsxSingleQuote": true,
"semi": false,
"trailingComma": "none"
}

View File

@ -1,12 +1,37 @@
{
"release": {
"branches": ["master"]
},
"branches": ["master"],
"plugins": [
["@semantic-release/commit-analyzer", {
"preset": "conventionalcommits"
}],
"@semantic-release/release-notes-generator",
"@semantic-release/github"
[
"@semantic-release/commit-analyzer",
{
"preset": "conventionalcommits"
}
],
[
"@semantic-release/release-notes-generator",
{
"preset": "conventionalcommits"
}
],
[
"@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",
{
"backmergeStrategy": "merge"
}
]
]
}

14
.vscode/extensions.json vendored Normal file
View File

@ -0,0 +1,14 @@
{
"recommendations": [
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"divlo.vscode-styled-jsx-syntax",
"divlo.vscode-styled-jsx-languageserver",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"coenraads.bracket-pair-colorizer",
"davidanson.vscode-markdownlint",
"ms-azuretools.vscode-docker"
]
}

48
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,48 @@
{
"typescript.tsdk": "node_modules/typescript/lib",
"prettier.configPath": ".prettierrc.json",
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
},
"[css]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[sass]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[scss]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[markdown]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[json]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[jsonc]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescript]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[typescriptreact]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascript]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
},
"[javascriptreact]": {
"editor.autoClosingBrackets": "always",
"editor.defaultFormatter": "esbenp.prettier-vscode"
}
}

View File

@ -47,6 +47,13 @@ Scopes define what part of the code changed.
## Getting Started
[![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/) >= 14.0.0
- [npm](https://www.npmjs.com/) >= 7.0.0
### Installation
```sh
@ -58,9 +65,19 @@ cd Divlo
# Configure environment variables
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
# Setup and run all the services for you
@ -70,4 +87,3 @@ docker-compose up --build
### Services started
- website : `http://localhost:3000`
- [MailDev](https://maildev.github.io/maildev/) : `http://localhost:1080`

View File

@ -1,10 +1,23 @@
FROM node:14.16.1
RUN npm install --global npm@7
WORKDIR /app
FROM node:16.8.0 AS dependencies
WORKDIR /usr/src/app
COPY ./package*.json ./
RUN npm install
COPY ./ ./
RUN npm clean-install
CMD ["npm", "run", "dev", "--", "--port", "${PORT}"]
FROM node:16.8.0 AS builder
WORKDIR /usr/src/app
COPY ./ ./
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
RUN npm run build
FROM node:16.8.0 AS runner
WORKDIR /usr/src/app
ENV NODE_ENV=production
COPY --from=builder /usr/src/app/next.config.js ./next.config.js
COPY --from=builder /usr/src/app/public ./public
COPY --from=builder /usr/src/app/.next ./.next
COPY --from=builder /usr/src/app/i18n.json ./i18n.json
COPY --from=builder /usr/src/app/locales ./locales
COPY --from=builder /usr/src/app/pages ./pages
COPY --from=builder /usr/src/app/node_modules ./node_modules
RUN npx next telemetry disable
CMD ["node_modules/.bin/next", "start", "--port", "${PORT}"]

View File

@ -5,7 +5,7 @@
</p>
<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/Divlo/actions/workflows/Divlo.yml"><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://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>
@ -20,21 +20,22 @@
## 📜 About
```typescript
export interface Divlo {
pronouns: 'He' | 'Him'
birthDate: '31/03/2003'
nationality: 'Alsace, France'
interests: [
'Developer Full Stack Junior',
'Passionate about High-Tech',
'Open-Source enthusiast'
]
skills: {
languages: ['JavaScript', 'TypeScript', 'Python', 'Dart']
frontEnd: ['HTML', 'CSS', 'SASS', 'React.js (+ Next.js)', 'Flutter']
backEnd: ['Node.js', 'Strapi', 'MySQL']
tools: ['Ubuntu', 'Hyper Terminal', 'VSCode', 'Git', 'Docker']
```json
{
"name": "Divlo",
"pronouns": "He/Him",
"birthDate": "31/03/2003",
"nationality": "Alsace, France",
"interests": [
"Developer Full Stack Junior",
"Passionate about High-Tech",
"Open-Source enthusiast"
],
"skills": {
"programmingLanguages": ["JavaScript", "TypeScript", "Python"],
"frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
"backEnd": ["Node.js", "Fastify", "Prisma", "PostgreSQL", "MySQL"],
"tools": ["Ubuntu", "Visual Studio Code", "Git", "Docker"]
}
}
```
@ -45,5 +46,5 @@ export interface Divlo {
<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/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>

View File

@ -1,70 +0,0 @@
import { createMocks } from 'node-mocks-http'
import handleSendEmail from 'pages/api/send-email'
jest.mock('nodemailer', () => ({
createTransport: () => {
return {
sendMail: jest.fn(async () => {})
}
}
}))
describe('POST /api/send-email', () => {
it('succeeds and send the email', async () => {
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'Divlo',
email: 'contact@divlo.fr',
subject: 'Subject',
message: 'Hello world!'
}
})
await handleSendEmail(req, res)
expect(res._getStatusCode()).toBe(201)
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
type: 'success'
})
)
})
it('fails with empty values', async () => {
const { req, res } = createMocks({
method: 'POST',
body: {
name: '',
email: '',
subject: '',
message: ''
}
})
await handleSendEmail(req, res)
expect(res._getStatusCode()).toBe(400)
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
type: 'requiredFields'
})
)
})
it('fails with invalid email', async () => {
const { req, res } = createMocks({
method: 'POST',
body: {
name: 'Name',
email: 'random wrong email',
subject: 'Subject',
message: 'Message'
}
})
await handleSendEmail(req, res)
expect(res._getStatusCode()).toBe(400)
expect(JSON.parse(res._getData())).toEqual(
expect.objectContaining({
type: 'invalidEmail'
})
)
})
})

View File

@ -1,31 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
import { FormState } from './FormState'
import { ResultState } from './index'
export interface FormResultProps {
state: ResultState
}
export const FormResult: React.FC<FormResultProps> = (props) => {
const { state } = props
const { t } = useTranslation()
if (state === 'idle') {
return null
}
if (state === 'loading' || state === 'success') {
return (
<FormState state={state}>
{t(`home:contact.result.${state}`)}
</FormState>
)
}
return (
<FormState state='error'>
{t(`home:contact.result.${state}`)}
</FormState>
)
}

View File

@ -1,39 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
export interface FormStateProps extends React.ComponentPropsWithRef<'p'> {
state: 'success' | 'error' | 'loading'
children: string
}
export const FormState: React.FC<FormStateProps> = props => {
const { state, children, ...rest } = props
const { t } = useTranslation()
return (
<>
<div className='form-result text-center'>
<p className={state} {...rest}>
{['error', 'success'].includes(state) && (
<b>
{state === 'error' ? t('home:contact.error') : t('home:contact.success')}:
</b>
)}{' '}
{children}
</p>
</div>
<style jsx>{`
.form-result {
margin: 30px;
}
.success {
color: #90ee90;
}
.error {
color: #ff7f7f;
}
`}
</style>
</>
)
}

View File

@ -1,89 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
import { useState } from 'react'
import Form, { HandleForm } from 'react-component-form'
import axios from 'axios'
import { Input } from 'components/design/Input'
import { Button } from 'components/design/Button'
import { Textarea } from 'components/design/Textarea'
import { FormResult } from './FormResult'
export const resultState = [
'idle',
'success',
'loading',
'requiredFields',
'invalidEmail',
'serverError'
] as const
export type ResultState = typeof resultState[number]
export const Contact: React.FC = () => {
const { t } = useTranslation()
const [state, setState] = useState<ResultState>('idle')
const handleSubmit: HandleForm = async (formData, formElement) => {
setState('loading')
try {
const { data } = await axios.post<{ type: ResultState }>(
'/api/send-email',
formData
)
if (data.type === 'success') {
setState('success')
return formElement.reset()
}
return setState('serverError')
} catch (error) {
const type = error.response.data.type
if (resultState.includes(type)) {
return setState(type)
}
return setState('serverError')
}
}
return (
<>
<div className='col-24'>
<Form onSubmit={handleSubmit}>
<Input
label={`${t('home:contact.nameField')} :`}
type='text'
name='name'
autoComplete='off'
required
/>
<Input
label='Email :'
type='email'
name='email'
autoComplete='off'
required
/>
<Input
label={`${t('home:contact.subjectField')} :`}
type='text'
name='subject'
autoComplete='off'
required
/>
<Textarea
label='Message :'
name='message'
autoComplete='off'
required
/>
<div className='text-center' style={{ marginBottom: 20 }}>
<Button type='submit'>{t('home:contact.sendEmail')}</Button>
</div>
</Form>
<FormResult state={state} />
</div>
</>
)
}

View File

@ -6,32 +6,47 @@ export interface ErrorPageProps {
message: string
}
export const ErrorPage: React.FC<ErrorPageProps> = props => {
export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
const { message, statusCode } = props
const { t } = useTranslation()
return (
<>
<h1>
{t('errors:error')} <span className='important'>{statusCode}</span>
<h1 className='my-6 font-semibold text-4xl'>
{t('errors:error')}{' '}
<span
className='text-yellow dark:text-yellow-dark'
data-cy='status-code'
>
{statusCode}
</span>
</h1>
<p className='text-center'>
{message} <Link href='/'>{t('returnToHomePage')}</Link>
<p className='text-center text-lg'>
{message}{' '}
<Link href='/'>
<a className='text-yellow dark:text-yellow-dark hover:underline'>
{t('errors:returnToHomePage')}
</a>
</Link>
</p>
<style jsx global>{`
.content {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-width: 100vw;
min-height: 100%;
}
#__next {
padding-top: 0;
}
`}
<style jsx global>
{`
main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
min-width: 100vw;
flex: 1;
}
#__next {
display: flex;
flex-direction: column;
padding-top: 0;
height: 100vh;
}
`}
</style>
</>
)

40
components/Footer.tsx Normal file
View File

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

View File

@ -1,28 +0,0 @@
import useTranslation from 'next-translate/useTranslation'
export const Footer: React.FC = () => {
const { t } = useTranslation()
return (
<>
<footer className='Footer text-center'>
<p>
<span className='important'>Divlo</span> | {t('common:allRightsReserved')}
</p>
</footer>
<style jsx>
{`
.Footer {
border-top: var(--border-header-footer);
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 10px;
}
`}
</style>
</>
)
}

View File

@ -7,12 +7,12 @@ interface HeadProps {
url?: string
}
export const Head: React.FC<HeadProps> = props => {
export const Head: React.FC<HeadProps> = (props) => {
const {
title = 'Divlo',
image = '/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",
url = 'https://divlo.divlo.fr/'
url = 'https://divlo.fr/'
} = props
return (
@ -21,7 +21,7 @@ export const Head: React.FC<HeadProps> = props => {
<link rel='icon' type='image/png' href={image} />
{/* Meta Tag */}
<meta name='viewport' content='width=device-width, initial-scale=1' />
<meta name='viewport' content='width=device-width, initial-scale=1.0' />
<meta name='description' content={description} />
<meta name='Language' content='fr, en' />
<meta name='theme-color' content='#ffd800' />

View File

@ -8,8 +8,8 @@ export const Arrow: React.FC = () => {
xmlns='http://www.w3.org/2000/svg'
>
<path
className='fill-current text-black dark:text-white'
d='M9.8024 0.292969L5.61855 4.58597L1.43469 0.292969L0.0566406 1.70697L5.61855 7.41397L11.1805 1.70697L9.8024 0.292969Z'
fill='#fff'
/>
</svg>
)

View File

@ -15,17 +15,9 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
src={`/images/languages/${language}.svg`}
alt={language}
/>
<p className='language-title'>{language.toUpperCase()}</p>
<style jsx>
{`
.language-title {
margin: 0 8px 0 10px;
font-size: 16px;
font-family: 'Arial', 'sans-serif';
}
`}
</style>
<p data-cy='language-flag-text' className='mx-2 text-base'>
{language.toUpperCase()}
</p>
</>
)
}

View File

@ -1,15 +1,20 @@
import { useEffect, useState } from 'react'
import { useCallback, useEffect, useState } from 'react'
import useTranslation from 'next-translate/useTranslation'
import setLanguage from 'next-translate/setLanguage'
import classNames from 'classnames'
import { Arrow } from './Arrow'
import { LanguageFlag } from './LanguageFlag'
import { locales } from 'i18n.json'
import i18n from 'i18n.json'
export const Language: React.FC = () => {
const { lang: currentLanguage } = useTranslation()
const [hiddenMenu, setHiddenMenu] = useState(true)
const handleHiddenMenu = useCallback(() => {
setHiddenMenu(!hiddenMenu)
}, [hiddenMenu])
useEffect(() => {
if (!hiddenMenu) {
window.document.addEventListener('click', handleHiddenMenu)
@ -20,86 +25,46 @@ export const Language: React.FC = () => {
return () => {
window.document.removeEventListener('click', handleHiddenMenu)
}
}, [hiddenMenu])
}, [hiddenMenu, handleHiddenMenu])
const handleLanguage = async (language: string): Promise<void> => {
await setLanguage(language)
handleHiddenMenu()
}
const handleHiddenMenu = (): void => {
setHiddenMenu(!hiddenMenu)
}
return (
<>
<div className='language-menu'>
<div className='selected-language' onClick={handleHiddenMenu}>
<LanguageFlag language={currentLanguage} />
<Arrow />
</div>
{!hiddenMenu && (
<ul>
{locales.map((language, index) => {
if (language === currentLanguage) {
return null
}
return (
<li
key={index}
onClick={async () => await handleLanguage(language)}
>
<LanguageFlag language={language} />
</li>
)
})}
</ul>
)}
<div className='flex flex-col justify-center items-center cursor-pointer'>
<div
data-cy='language-click'
className='flex items-center mr-5'
onClick={handleHiddenMenu}
>
<LanguageFlag language={currentLanguage} />
<Arrow />
</div>
<style jsx>
{`
.language-menu {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
cursor: pointer;
<ul
data-cy='languages-list'
className={classNames(
'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',
{ hidden: hiddenMenu }
)}
>
{i18n.locales.map((language, index) => {
if (language === currentLanguage) {
return null
}
.selected-language {
display: flex;
align-items: center;
margin-right: 15px;
}
ul {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
position: absolute;
top: 60px;
width: 100px;
padding: 10px;
margin: 10px 15px 0 0px;
border-radius: 15%;
padding: 0;
box-shadow: 0px 1px 10px var(--color-shadow);
background-color: var(--color-background-primary);
z-index: 10;
}
ul > li {
list-style: none;
display: flex;
align-items: center;
justify-content: center;
height: 50px;
width: 100%;
}
ul > li:hover {
background-color: rgba(79, 84, 92, 0.16);
}
`}
</style>
</>
return (
<li
key={index}
className='flex items-center justify-center w-full h-12 hover:bg-[#4f545c] hover:bg-opacity-20 pl-2'
onClick={async () => await handleLanguage(language)}
>
<LanguageFlag language={language} />
</li>
)
})}
</ul>
</div>
)
}

View File

@ -0,0 +1,126 @@
import { useEffect, useState } from 'react'
import { useTheme } from 'next-themes'
export const SwitchTheme: React.FC = () => {
const [mounted, setMounted] = useState(false)
const { theme, setTheme } = useTheme()
useEffect(() => {
setMounted(true)
}, [])
if (!mounted) {
return null
}
const handleClick = (): void => {
setTheme(theme === 'dark' ? 'light' : 'dark')
}
return (
<>
<div
className='flex items-center'
data-cy='switch-theme-click'
onClick={handleClick}
>
<div className='toggle-theme-button relative cursor-pointer bg-transparent inline-block'>
<div className='toggle-track'>
<div
data-cy='switch-theme-dark'
className='toggle-track-check absolute'
>
<span className='toggle_Dark flex justify-center items-center relative'>
🌜
</span>
</div>
<div
data-cy='switch-theme-light'
className='toggle-track-x absolute'
>
<span className='toggle_Light flex justify-center items-center relative'>
🌞
</span>
</div>
</div>
<div className='toggle-thumb absolute' />
<input
data-cy='switch-theme-input'
type='checkbox'
aria-label='Dark mode toggle'
className='toggle-screenreader-only absolute overflow-hidden'
defaultChecked
/>
</div>
</div>
<style jsx>
{`
.toggle-theme-button {
touch-action: pan-x;
border: 0;
padding: 0;
user-select: none;
}
.toggle-track {
width: 50px;
height: 24px;
padding: 0;
border-radius: 30px;
background-color: #4d4d4d;
transition: all 0.2s ease;
color: #fff;
}
.toggle-track-check {
width: 14px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
left: 8px;
opacity: ${theme === 'dark' ? 1 : 0};
transition: opacity 0.25s ease;
}
.toggle-track-x {
width: 10px;
height: 10px;
top: 0;
bottom: 0;
margin-top: auto;
margin-bottom: auto;
line-height: 0;
right: 10px;
opacity: ${theme === 'dark' ? 0 : 1};
}
.toggle_Dark,
.toggle_Light {
height: 10px;
width: 10px;
}
.toggle-thumb {
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;
padding: 0;
width: 1px;
}
`}
</style>
</>
)
}

View File

@ -0,0 +1,10 @@
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

@ -2,87 +2,30 @@ import Link from 'next/link'
import Image from 'next/image'
import { Language } from './Language'
import { SwitchTheme } from './SwitchTheme'
export const Header: React.FC = () => {
return (
<>
<header className='header'>
<div className='container'>
<nav className='navbar navbar-fixed-top'>
<Link href='/'>
<a className='navbar__brand-link'>
<div className='navbar__brand'>
<Image
width={60}
height={60}
src='/images/divlo_icon_small.png'
alt='Divlo'
/>
<strong className='navbar__brand-title'>Divlo</strong>
</div>
</a>
</Link>
<div className='navbar__buttons'>
<Language />
</div>
</nav>
</div>
</header>
<style jsx>
{`
.header {
background-color: var(--color-background);
border-bottom: var(--border-header-footer);
padding: 0.5rem 1rem;
position: fixed;
width: 100%;
top: 0;
left: 0;
right: 0;
z-index: 100;
height: var(--header-height);
}
.container {
max-width: 1280px;
width: 100%;
margin: auto;
}
.navbar {
display: flex;
justify-content: space-between;
align-items: center;
}
.navbar-fixed-top {
position: sticky;
top: 0;
z-index: 200;
}
.navbar__brand-link {
color: var(--color-text-1);
text-decoration: none;
font-size: 16px;
}
.navbar__brand {
display: flex;
align-items: center;
justify-content: space-between;
}
.navbar__brand-title {
font-weight: 600;
margin-left: 10px;
}
.navbar__buttons {
display: flex;
justify-content: space-between;
}
@media (max-width: 320px) {
.navbar__brand-title {
display: none;
}
}
`}
</style>
</>
<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'>
<Link href='/'>
<a>
<div className='flex items-center justify-center'>
<Image
width={60}
height={60}
src='/images/divlo_icon_small.png'
alt='Divlo'
/>
<strong className='ml-1 font-headline font-semibold hidden xs:block text-yellow dark:text-yellow-dark'>
Divlo
</strong>
</div>
</a>
</Link>
<div className='flex justify-between'>
<Language />
<SwitchTheme />
</div>
</header>
)
}

View File

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

View File

@ -1,41 +1,20 @@
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
import { IconDefinition } from '@fortawesome/free-solid-svg-icons'
import { Tooltip } from 'components/design/Tooltip'
interface InterestItemProps {
title: string
fontAwesomeIcon: IconDefinition
}
export const InterestItem: React.FC<InterestItemProps> = props => {
export const InterestItem: React.FC<InterestItemProps> = (props) => {
const { fontAwesomeIcon, title } = props
return (
<>
<li className='interest-item'>
<Tooltip title={title}>
<FontAwesomeIcon
className='color-primary'
style={{
cursor: 'pointer',
height: '100%',
width: '100%',
display: 'block'
}}
icon={fontAwesomeIcon}
/>
</Tooltip>
</li>
<style jsx>
{`
.interest-item {
margin: 7px 5px;
width: 34px;
height: 34px;
}
`}
</style>
</>
<li className='interest-item my-2 mx-2 w-8 h-8' title={title}>
<FontAwesomeIcon
className='text-yellow cursor-pointer h-full w-full block dark:text-yellow-dark'
icon={fontAwesomeIcon}
/>
</li>
)
}

View File

@ -5,41 +5,18 @@ import { InterestItem } from './InterestItem'
export const InterestsList: React.FC = () => {
return (
<>
<div className='container-list'>
<ul className='interests-list'>
<InterestItem
title='Developer Full Stack Junior'
fontAwesomeIcon={faCode}
/>
<InterestItem
title='Passionate about High-Tech'
fontAwesomeIcon={faMicrochip}
/>
<InterestItem
title='Open-Source enthusiast'
fontAwesomeIcon={faGit}
/>
</ul>
</div>
<style jsx>
{`
.container-list {
display: flex;
justify-content: center;
margin: 15px 0 15px 0;
}
.interests-list {
display: flex;
justify-content: space-around;
padding: 0;
margin: 0;
width: 60%;
list-style: none;
}
`}
</style>
</>
<div className='flex justify-center my-4'>
<ul className='flex justify-around p-0 m-0 list-none w-96'>
<InterestItem
title='Developer Full Stack Junior'
fontAwesomeIcon={faCode}
/>
<InterestItem
title='Passionate about High-Tech'
fontAwesomeIcon={faMicrochip}
/>
<InterestItem title='Open-Source enthusiast' fontAwesomeIcon={faGit} />
</ul>
</div>
)
}

View File

@ -6,13 +6,17 @@ import { InterestsList } from './InterestsList'
export const Interests: React.FC = () => {
const { t } = useTranslation()
const paragraphs: InterestParagraphProps[] = t('home:interests.paragraphs', {}, {
returnObjects: true
})
const paragraphs: InterestParagraphProps[] = t(
'home:interests.paragraphs',
{},
{
returnObjects: true
}
)
return (
<>
<div className='col-24'>
<div className='max-w-full'>
{paragraphs.map((paragraph, index) => {
return <InterestParagraph key={index} {...paragraph} />
})}

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='cursor-pointer relative p-6 !mb-4 max-h-32 transition-transform duration-200 ease-in-out hover:-translate-y-2'>
<a href={href} target='_blank' rel='noopener noreferrer'>
<div className='flex'>
<GitHubIcon className='h-6 mr-2' />
<span className='text-yellow dark:text-yellow-dark'>{name}</span>
</div>
<p className='my-4'>{description}</p>
</a>
</ShadowContainer>
)
}

View File

@ -0,0 +1,47 @@
import useTranslation from 'next-translate/useTranslation'
import { Repository } from './Repository'
export const OpenSource: React.FC = () => {
const { t } = useTranslation()
return (
<>
<div className='max-w-full mt-0 flex flex-col items-center'>
<p className='text-center'>{t('home:open-source.description')}</p>
<div className='grid grid-cols-1 md:w-10/12 md:grid-cols-2 gap-6 my-6'>
<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/styled-jsx'
description='Full CSS support for JSX without compromises'
href='https://github.com/vercel/styled-jsx/commits?author=Divlo'
/>
</div>
</div>
<style jsx global>{`
.animation-custom {
position: relative;
transition: all 0.3s ease 0s;
}
.animation-custom:hover {
transform: translateY(-7px);
}
`}</style>
</>
)
}

View File

@ -1,3 +1,4 @@
import { ShadowContainer } from 'components/design/ShadowContainer'
import Image from 'next/image'
export interface PortfolioItemProps {
@ -7,96 +8,34 @@ export interface PortfolioItemProps {
image: string
}
export const PortfolioItem: React.FC<PortfolioItemProps> = props => {
export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
const { title, description, link, image } = props
return (
<>
<div className='col-sm-24 col-md-10 col-xl-7 portfolio-grid'>
<a
className='portfolio-link'
target='_blank'
rel='noopener noreferrer'
href={link}
aria-label={title}
>
<div className='portfolio-figure'>
<Image width={300} height={300} src={image} alt={title} />
</div>
<div className='portfolio-caption'>
<h3 className='portfolio-title important'>{title}</h3>
<p className='portfolio-description'>{description}</p>
</div>
</a>
</div>
<style jsx global>
{`
.portfolio-figure img[alt='${title}'] {
max-height: 300px;
max-width: 300px;
transition: opacity 0.5s ease;
}
.portfolio-grid:hover img[alt='${title}'] {
opacity: 0.05;
}
`}
</style>
<style jsx>
{`
.portfolio-grid {
display: flex;
align-items: center;
position: relative;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
margin: 0 0 50px 0;
cursor: pointer;
}
/* col-md */
@media (min-width: 768px) {
.portfolio-grid {
margin: 0 30px 50px 30px;
}
}
/* col-xl */
@media (min-width: 1200px) {
.portfolio-grid {
margin: 0 20px 50px 20px;
}
}
.portfolio-figure {
display: flex;
justify-content: center;
}
.portfolio-caption {
transition: opacity 0.5s ease;
opacity: 0;
height: 0;
overflow: hidden;
}
.portfolio-description {
font-size: 16px;
}
.portfolio-grid:hover .portfolio-caption {
opacity: 1;
height: auto;
position: absolute;
bottom: 0;
text-align: center;
width: 80%;
}
.portfolio-grid:hover .portfolio-link {
color: var(--text-color);
display: flex;
justify-content: center;
}
`}
</style>
</>
<ShadowContainer className='cursor-pointer relative items-center sm:ml-10'>
<a
className='group inline-flex justify-center'
target='_blank'
rel='noopener noreferrer'
href={link}
aria-label={title}
>
<div className='flex justify-center'>
<Image
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
width={300}
height={300}
src={image}
alt={title}
/>
</div>
<div className='opacity-0 transition-opacity duration-500 h-auto absolute text-center overflow-hidden bottom-0 group-hover:opacity-100'>
<h3 className='text-yellow text-xl font-semibold my-6 dark:text-yellow-dark'>
{title}
</h3>
<p className='my-6'>{description}</p>
</div>
</a>
</ShadowContainer>
)
}

View File

@ -5,19 +5,19 @@ import { PortfolioItem, PortfolioItemProps } from './PortfolioItem'
export const Portfolio: React.FC = () => {
const { t } = useTranslation('home')
const items: PortfolioItemProps[] = t('home:portfolio.items', {}, {
returnObjects: true
})
const items: PortfolioItemProps[] = t(
'home:portfolio.items',
{},
{
returnObjects: true
}
)
return (
<>
<div className='container-fluid'>
<div className='row justify-content-center'>
{items.map((item, index) => {
return <PortfolioItem key={index} {...item} />
})}
</div>
</div>
</>
<div className='flex flex-wrap justify-center px-3 w-full'>
{items.map((item, index) => {
return <PortfolioItem key={index} {...item} />
})}
</div>
)
}

View File

@ -2,27 +2,11 @@ import Translation from 'next-translate/Trans'
export const ProfileDescriptionBottom: React.FC = () => {
return (
<>
<p className='profile-description-bottom'>
<Translation
i18nKey='home:about.descriptionBottom'
components={[<br key='break' />]}
/>
</p>
<style jsx>
{`
.profile-description-bottom {
font-size: 16px;
display: block;
font-weight: 400;
line-height: 25px;
color: #b2bac2;
margin-top: 30px;
margin-bottom: 0;
}
`}
</style>
</>
<p className='mt-8 mb-8 font-normal text-base text-gray dark:text-gray-dark'>
<Translation
i18nKey='home:about.descriptionBottom'
components={[<br key='break' />]}
/>
</p>
)
}

View File

@ -1,41 +1,17 @@
import useTranslation from 'next-translate/useTranslation'
export const ProfileInfo: React.FC = () => {
export const ProfileInformation: React.FC = () => {
const { t } = useTranslation()
return (
<>
<div className='profile-info'>
<h1 className='profile-title'>
{t('home:about.IAm')} <strong className='important'>Divlo</strong>
</h1>
<h2 className='profile-description'>{t('home:about.description')}</h2>
</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>
</>
<div className='pb-2 mb-6 border-b-2 font-headline border-gray-600 dark:border-gray-400'>
<h1 className='text-4xl mb-2'>
{t('home:about.IAm')}{' '}
<strong className='font-semibold text-yellow dark:text-yellow-dark'>
Divlo
</strong>
</h1>
<h2 className='text-base mb-3'>{t('home:about.description')}</h2>
</div>
)
}

View File

@ -4,16 +4,21 @@ interface ProfileItemProps {
link?: string
}
export const ProfileItem: React.FC<ProfileItemProps> = props => {
export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
const { title, value, link } = props
return (
<>
<li className='profile-list__item'>
<strong className='profile-list__item-title'>{title}</strong>
<span className='profile-list__item-info'>
<strong className='profile-list__item-title text-black dark:text-white'>
{title}
</strong>
<span className='profile-list__item-info text-gray dark:text-gray-dark'>
{link != null ? (
<a className='profile-list__link' href={link}>
<a
className='text-gray dark:text-gray-dark hover:underline'
href={link}
>
{value}
</a>
) : (
@ -39,7 +44,6 @@ export const ProfileItem: React.FC<ProfileItemProps> = props => {
display: block;
width: 120px;
float: left;
color: #d4d4d5;
font-size: 12px;
font-weight: 700;
line-height: 20px;
@ -51,10 +55,6 @@ export const ProfileItem: React.FC<ProfileItemProps> = props => {
font-size: 15px;
font-weight: 400;
line-height: 20px;
color: #84898e;
}
.profile-list__link {
color: #84898e;
}
@media (max-width: 576px) {

View File

@ -6,32 +6,14 @@ export const ProfileList: React.FC = () => {
const { t } = useTranslation('home')
return (
<>
<ul className='profile-list'>
<ProfileItem
title={t('home:about.birthDate')}
value='31/03/2003'
/>
<ProfileItem
title={t('home:about.nationality')}
value='Alsace, France'
/>
<ProfileItem
title='Email'
value='contact@divlo.fr'
link='mailto:contact@divlo.fr'
/>
</ul>
<style jsx>
{`
.profile-list {
margin: 0;
padding: 0;
list-style: none;
}
`}
</style>
</>
<ul className='m-0 p-0 list-none'>
<ProfileItem title={t('home:about.birthDate')} value='31/03/2003' />
<ProfileItem title={t('home:about.nationality')} value='Alsace, France' />
<ProfileItem
title='Email'
value='contact@divlo.fr'
link='mailto:contact@divlo.fr'
/>
</ul>
)
}

View File

@ -1,26 +1,11 @@
import Image from 'next/image'
import DivloLogo from 'public/images/divlo_logo.png'
export const ProfileLogo: React.FC = () => {
return (
<>
<div className='col-sm-24 col-md-10'>
<div className='profile-logo'>
<Image
width={800}
height={800}
src='/images/divlo_logo.png'
alt='Divlo'
/>
</div>
</div>
<style jsx>{`
.profile-logo {
margin-right: 10px;
margin-left: 10px;
}
`}
</style>
</>
<div className='px-2 py-6 max-w-[370px] max-h-[370px]'>
<Image src={DivloLogo} alt='Divlo' priority />
</div>
)
}

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const EmailIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>Email</title>
<path d='M15.61 12c0 1.99-1.62 3.61-3.61 3.61-1.99 0-3.61-1.62-3.61-3.61 0-1.99 1.62-3.61 3.61-3.61 1.99 0 3.61 1.62 3.61 3.61M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12c2.424 0 4.761-.722 6.76-2.087l.034-.024-1.617-1.879-.027.017A9.494 9.494 0 0112 21.54c-5.26 0-9.54-4.28-9.54-9.54 0-5.26 4.28-9.54 9.54-9.54 5.26 0 9.54 4.28 9.54 9.54a9.63 9.63 0 01-.225 2.05c-.301 1.239-1.169 1.618-1.82 1.568-.654-.053-1.42-.52-1.426-1.661V12A6.076 6.076 0 0012 5.93 6.076 6.076 0 005.93 12 6.076 6.076 0 0012 18.07a6.02 6.02 0 004.3-1.792 3.9 3.9 0 003.32 1.805c.874 0 1.74-.292 2.437-.821.719-.547 1.256-1.336 1.553-2.285.047-.154.135-.504.135-.507l.002-.013c.175-.76.253-1.52.253-2.457 0-6.617-5.383-12-12-12' />
</Icon>
)
}

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const GitHubIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>GitHub</title>
<path d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12' />
</Icon>
)
}

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

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

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

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const TwitchIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>Twitch</title>
<path d='M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714z' />
</Icon>
)
}

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const TwitterIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>Twitter</title>
<path d='M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z' />
</Icon>
)
}

View File

@ -0,0 +1,10 @@
import { Icon } from './Icon'
export const YouTubeIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
return (
<Icon {...props}>
<title>YouTube</title>
<path d='M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z' />
</Icon>
)
}

View File

@ -1,50 +1,22 @@
import { Tooltip } from 'components/design/Tooltip'
import Image from 'next/image'
interface SocialMediaItemProps {
link: string
socialMedia: 'Email' | 'GitHub' | 'Twitch' | 'Twitter' | 'YouTube'
ariaLabel: string
}
export const SocialMediaItem: React.FC<SocialMediaItemProps> = props => {
const { link, socialMedia } = props
export const SocialMediaItem: React.FC<SocialMediaItemProps> = (props) => {
const { link, ariaLabel, children } = props
return (
<>
<li className='social-media-list__item'>
<a
href={link}
aria-label={socialMedia}
target='_blank'
rel='noopener noreferrer'
className='social-media-list__link'
>
<Tooltip title={socialMedia}>
<Image
width={45}
height={45}
alt={socialMedia}
src={`/images/web/${socialMedia}.png`}
/>
</Tooltip>
</a>
</li>
<style jsx>
{`
.social-media-list__item {
display: inline-block;
margin: 5px 15px;
}
.social-media-list__link {
width: 45px;
height: 45px;
position: relative;
display: inline-block;
background-color: transparent;
}
`}
</style>
</>
<li className='inline-block mx-4 my-1'>
<a
href={link}
aria-label={ariaLabel}
target='_blank'
rel='noopener noreferrer'
className='relative inline-block bg-transparent'
>
{children}
</a>
</li>
)
}

View File

@ -1,41 +1,39 @@
import { SocialMediaItem } from './SocialMediaItem'
import { TwitterIcon } from './SocialMediaIcons/TwitterIcon'
import { GitHubIcon } from './SocialMediaIcons/GitHubIcon'
import { GitLabIcon } from './SocialMediaIcons/GitLabIcon'
import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon'
import { TwitchIcon } from './SocialMediaIcons/TwitchIcon'
import { EmailIcon } from './SocialMediaIcons/EmailIcon'
import { NPMIcon } from './SocialMediaIcons/NPMIcon'
export const SocialMediaList: React.FC = () => {
return (
<>
<div className='row justify-content-center'>
<ul className='social-media-list'>
<SocialMediaItem
socialMedia='Twitter'
link='https://twitter.com/Divlo_FR'
/>
<SocialMediaItem
socialMedia='GitHub'
link='https://github.com/Divlo'
/>
<SocialMediaItem
socialMedia='YouTube'
link='https://www.youtube.com/c/Divlo'
/>
<SocialMediaItem
socialMedia='Twitch'
link='https://www.twitch.tv/divlo'
/>
<SocialMediaItem socialMedia='Email' link='mailto:contact@divlo.fr' />
</ul>
</div>
<style jsx>{`
.social-media-list {
margin: 0;
padding: 0;
list-style: none;
text-align: center;
padding: 15px 0;
margin-top: 10px;
}
`}
</style>
</>
<ul className='social-media-list m-0 mt-2 py-4 list-none text-center'>
<SocialMediaItem link='https://github.com/Divlo' ariaLabel='GitHub'>
<GitHubIcon />
</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
link='https://www.youtube.com/c/Divlo'
ariaLabel='YouTube'
>
<YouTubeIcon />
</SocialMediaItem>
<SocialMediaItem link='https://www.twitch.tv/divlo' ariaLabel='Twitch'>
<TwitchIcon />
</SocialMediaItem>
<SocialMediaItem link='mailto:contact@divlo.fr' ariaLabel='Email'>
<EmailIcon />
</SocialMediaItem>
</ul>
)
}

View File

@ -1,33 +1,17 @@
import { ProfileDescriptionBottom } from './ProfileDescriptionBottom'
import { ProfileInfo } from './ProfileInfo'
import { ProfileInformation } from './ProfileInfo'
import { ProfileList } from './ProfileList'
import { ProfileLogo } from './ProfileLogo'
export const Profile: React.FC = () => {
return (
<>
<div className='row profile'>
<ProfileLogo />
<div className='col-sm-24 col-md-14'>
<ProfileInfo />
<ProfileList />
<ProfileDescriptionBottom />
</div>
<div className='flex flex-col justify-center items-center px-10 pt-2 md:pt-10 md:flex-row'>
<ProfileLogo />
<div>
<ProfileInformation />
<ProfileList />
<ProfileDescriptionBottom />
</div>
<style jsx>
{`
.profile {
padding: 40px 50px 15px 50px;
}
@media (max-width: 576px) {
.profile {
padding: 40px 10px 0 10px;
}
}
`}
</style>
</>
</div>
)
}

View File

@ -1,44 +1,36 @@
import { useTheme } from 'next-themes'
import Image from 'next/image'
import { useMemo } from 'react'
import { skills } from './skills'
export interface SkillProps {
skill: keyof typeof skills
export interface SkillComponentProps {
skill: string
}
export const Skill: React.FC<SkillProps> = props => {
export const SkillComponent: React.FC<SkillComponentProps> = (props) => {
const { skill } = props
const skillProperties = skills[skill]
const { theme } = useTheme()
const image = useMemo(() => {
if (typeof skillProperties.image !== 'string') {
return skillProperties.image[theme ?? 'light']
}
return skillProperties.image
}, [skillProperties, theme])
return (
<>
<a
href={skillProperties.link}
className='skills-link'
target='_blank'
rel='noopener noreferrer'
>
<div className='skills-content text-center'>
<Image
width={60}
height={60}
alt={skill}
src={skillProperties.image}
/>
<p className='skills-text'>{skill}</p>
</div>
</a>
<style jsx>{`
.skills-link {
max-width: 120px;
margin: 0px 10px 0 10px;
}
.skills-text {
margin-top: 5px;
}
`}
</style>
</>
<a
href={skillProperties.link}
className='mx-2 max-w-xl text-yellow hover:underline dark:text-yellow-dark'
target='_blank'
rel='noopener noreferrer'
>
<div className='text-center'>
<Image width={60} height={60} alt={skill} src={image} />
<p className='mt-1'>{skill}</p>
</div>
</a>
)
}

View File

@ -5,40 +5,23 @@ export interface SkillsSectionProps {
children: React.ReactNode
}
export const SkillsSection: React.FC<SkillsSectionProps> = props => {
export const SkillsSection: React.FC<SkillsSectionProps> = (props) => {
const { title, children } = props
return (
<>
<ShadowContainer>
<div className='container-fluid'>
<div className='row row-padding'>
<div className='col-24'>
<div className='skills-header'>
<h3 className='important'>{title}</h3>
</div>
<div className='skills-body'>{children}</div>
<ShadowContainer>
<div className='w-full px-4 mx-auto'>
<div className='flex flex-wrap px-4 py-6'>
<div className='flex-1'>
<div className='mb-8 border-b border-gray-600 dark:border-opacity-10 dark:border-white'>
<h3 className='text-yellow font-semibold text-xl my-3 dark:text-yellow-dark'>
{title}
</h3>
</div>
<div className='flex justify-around flex-wrap'>{children}</div>
</div>
</div>
</ShadowContainer>
<style jsx>{`
.skills-header {
border-bottom: 1px solid rgba(255, 255, 255, 0.1);
margin-bottom: 15px;
}
.skills-header > h3 {
margin-bottom: 15px;
}
.skills-body {
display: flex;
justify-content: space-around;
flex-flow: row wrap;
padding-top: 1.5rem;
}
`}
</style>
</>
</div>
</ShadowContainer>
)
}

View File

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

View File

@ -1,4 +1,13 @@
export const skills = {
export interface Skill {
link: string
image: string | { [key: string]: string }
}
export interface Skills {
[key: string]: Skill
}
export const skills: Skills = {
JavaScript: {
link: 'https://developer.mozilla.org/docs/Web/JavaScript',
image: '/images/skills/JavaScript.png'
@ -11,6 +20,10 @@ export const skills = {
link: 'https://www.python.org/',
image: '/images/skills/Python.png'
},
'C/C++': {
link: 'https://isocpp.org/',
image: '/images/skills/C-Cpp.png'
},
Dart: {
link: 'https://dart.dev/',
image: '/images/skills/Dart.png'
@ -27,6 +40,10 @@ export const skills = {
link: 'https://developer.mozilla.org/docs/Web/CSS',
image: '/images/skills/CSS.png'
},
'Tailwind CSS': {
link: 'https://tailwindcss.com/',
image: '/images/skills/TailwindCSS.png'
},
SASS: {
link: 'https://sass-lang.com/',
image: '/images/skills/SASS.svg'
@ -39,6 +56,24 @@ export const skills = {
link: 'https://nodejs.org/',
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: {
link: 'https://www.mysql.com/',
image: '/images/skills/MySQL.png'

View File

@ -0,0 +1,15 @@
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

@ -0,0 +1,16 @@
import { render } from '@testing-library/react'
import { Footer } from '../Footer'
describe('<Footer />', () => {
it('should render with appropriate link tag version', async () => {
const version = '1.0.0'
const { getByText } = render(<Footer version={version} />)
const versionLink = getByText(version) as HTMLAnchorElement
expect(getByText('Divlo')).toBeInTheDocument()
expect(versionLink).toBeInTheDocument()
expect(versionLink.href).toEqual(
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
)
})
})

View File

@ -1,43 +0,0 @@
import { forwardRef } from 'react'
type ButtonProps = React.ComponentPropsWithRef<'button'>
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
(props, ref) => {
const { children, ...rest } = props
return (
<>
<button ref={ref} {...rest} className='btn btn-dark'>
{children}
</button>
<style jsx>
{`
.btn {
cursor: pointer;
border: 1px solid transparent;
padding: 0.375rem 0.75rem;
font-size: 1rem;
line-height: 1.5;
border-radius: 0.25rem;
transition: color 0.15s ease-in-out,
background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out, box-shadow 0.15s ease-in-out;
}
.btn-dark {
color: #fff;
background-color: #343a40;
border-color: #343a40;
}
.btn-dark:hover {
color: #fff;
background-color: #23272b;
border-color: #1d2124;
}
`}
</style>
</>
)
}
)

View File

@ -1,75 +0,0 @@
import { forwardRef } from 'react'
interface InputProps extends React.HTMLProps<HTMLInputElement> {
label: string
}
export const Input = forwardRef<HTMLInputElement, InputProps>((props, ref) => {
const { label, name, ...rest } = props
return (
<>
<div className='form-group-animation'>
<input ref={ref} {...rest} id={name} name={name} />
<label htmlFor={name} className='label'>
<span className='label-content'>{label}</span>
</label>
</div>
<style jsx>{`
.form-group-animation {
position: relative;
margin-top: 10px;
margin-bottom: 30px;
overflow: hidden;
}
.form-group-animation input {
width: 100%;
height: 100%;
padding-top: 35px;
color: var(--color-text-1);
border: none;
background: transparent;
outline: none;
}
.form-group-animation label {
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
border-bottom: 1px solid #fff;
}
.form-group-animation label::after {
content: '';
position: absolute;
left: 0;
bottom: -1px;
height: 100%;
width: 100%;
border-bottom: 3px solid var(--color-primary);
transform: translateX(-100%);
transition: transform 0.2s ease;
}
.label-content {
position: absolute;
bottom: 5px;
left: 0px;
transition: all 0.3s ease;
}
.form-group-animation input:focus + .label .label-content,
.form-group-animation input:valid + .label .label-content {
transform: translateY(-150%);
font-size: 14px;
color: var(--color-primary);
}
.form-group-animation input:focus + .label::after,
.form-group-animation input:valid + .label::after {
transform: translateX(0%);
}
`}
</style>
</>
)
})

View File

@ -1,6 +1,6 @@
import { useEffect, useRef } from 'react'
export const RevealFade: React.FC = props => {
export const RevealFade: React.FC = (props) => {
const { children } = props
const htmlElement = useRef<HTMLDivElement>(null)
@ -8,7 +8,7 @@ export const RevealFade: React.FC = props => {
useEffect(() => {
const observer = new window.IntersectionObserver(
(entries, observer) => {
entries.forEach(entry => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
entry.target.classList.add('reveal-visible')
observer.unobserve(entry.target)
@ -30,19 +30,20 @@ export const RevealFade: React.FC = props => {
{children}
</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 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,28 +1,11 @@
import { forwardRef } from 'react'
type SectionHeadingProps = React.ComponentPropsWithRef<'h2'>
export const SectionHeading = forwardRef<
HTMLHeadingElement,
SectionHeadingProps
>((props, ref) => {
export const SectionHeading: React.FC<SectionHeadingProps> = (props) => {
const { children, ...rest } = props
return (
<>
<h2 ref={ref} {...rest} className='Section__title'>
{children}
</h2>
<style jsx>
{`
.Section__title {
font-size: 34px;
margin-top: 10px;
text-align: center;
}
`}
</style>
</>
<h2 {...rest} className='text-4xl font-semibold text-center mt-1 mb-3'>
{children}
</h2>
)
})
}

View File

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

View File

@ -1,12 +1,17 @@
import classNames from 'classnames'
type ShadowContainerProps = React.ComponentPropsWithRef<'div'>
export const ShadowContainer: React.FC<ShadowContainerProps> = props => {
export const ShadowContainer: React.FC<ShadowContainerProps> = (props) => {
const { children, className, ...rest } = props
return (
<>
<div
className={`shadow-container ${className != null ? className : ''}`}
className={classNames(
'shadow-container h-full max-w-full break-words',
className
)}
{...rest}
>
{children}
@ -15,14 +20,9 @@ export const ShadowContainer: React.FC<ShadowContainerProps> = props => {
<style jsx>
{`
.shadow-container {
display: flex;
flex-direction: column;
word-wrap: break-word;
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
border: 1px solid black;
border-radius: 1rem;
height: 100%;
max-width: 100%;
margin-bottom: 50px;
}
`}

View File

@ -1,39 +0,0 @@
import { forwardRef } from 'react'
interface TextareaProps extends React.HTMLProps<HTMLTextAreaElement> {
label: string
}
export const Textarea = forwardRef<HTMLTextAreaElement, TextareaProps>(
(props, ref) => {
const { label, name, ...rest } = props
return (
<>
<div className='form-group'>
<label htmlFor={name}>{label}</label>
<br />
<textarea id={name} name={name} ref={ref} {...rest} />
</div>
<style jsx>{`
.form-group {
padding-top: 15px;
margin-bottom: 30px;
}
.form-group textarea {
background: transparent;
color: var(--color-text);
outline: none;
width: 100%;
height: auto;
padding: 10px;
resize: vertical;
margin-top: 8px;
}
`}
</style>
</>
)
}
)

View File

@ -1,49 +0,0 @@
interface TooltipProps extends React.ComponentPropsWithRef<'div'> {
title: string
children: React.ReactNode
}
export const Tooltip: React.FC<TooltipProps> = props => {
const { title, children, ...rest } = props
return (
<>
<span className='tooltip' {...rest}>
{children}
<span className='title'>{title}</span>
</span>
<style jsx>{`
.title {
color: #fff;
font-size: 11px;
font-weight: 400;
line-height: 1;
display: inline-block;
background-color: #222222;
padding: 5px 8px;
white-space: nowrap;
position: absolute;
top: 100%;
margin-top: 10px;
z-index: 1;
opacity: 0;
visibility: hidden;
border-radius: 3px;
transition: all 0.15s ease-in;
transform: translate3d(0, -15px, 0);
backface-visibility: hidden;
}
.tooltip ~ .tooltip:hover .title,
.tooltip:first-child:hover .title {
opacity: 1;
visibility: visible;
transition: all 0.35s ease-out;
transform: translate3d(0, 0, 0);
margin: 0;
backface-visibility: hidden;
}
`}
</style>
</>
)
}

View File

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

8
cypress.json Normal file
View File

@ -0,0 +1,8 @@
{
"baseUrl": "http://localhost:3000",
"pluginsFile": false,
"supportFile": false,
"fixturesFolder": false,
"video": false,
"screenshotOnRunFailure": false
}

View File

@ -0,0 +1,47 @@
describe('Common > Header', () => {
beforeEach(() => cy.visit('/'))
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')
})
})
})

View File

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

View File

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

View File

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

9
cypress/tsconfig.json Normal file
View File

@ -0,0 +1,9 @@
{
"extends": "../tsconfig.json",
"compilerOptions": {
"noEmit": true,
"types": ["cypress"],
"isolatedModules": false
},
"include": ["../node_modules/cypress", "./**/*.ts"]
}

View File

@ -1,18 +1,12 @@
version: '3.0'
services:
divlo.fr-website:
divlo.fr:
container_name: ${COMPOSE_PROJECT_NAME}
image: 'divlo.fr'
build:
context: './'
ports:
- '${PORT}:${PORT}'
environment:
PORT: ${PORT}
volumes:
- './:/app'
divlo.fr-maildev:
image: 'maildev/maildev:1.1.0'
ports:
- '1080:80'
container_name: 'divlo.fr-maildev'
env_file: './.env'

View File

@ -1,25 +0,0 @@
module.exports = {
roots: ['<rootDir>'],
transform: {
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
},
moduleDirectories: ['node_modules', './'],
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
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',
'!**/workbox-*.js',
'!**/sw.js'
],
coverageDirectory: './coverage',
coverageReporters: ['text', 'cobertura']
}

14
jest.config.json Normal file
View File

@ -0,0 +1,14 @@
{
"roots": ["<rootDir>"],
"transform": {
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
},
"moduleDirectories": ["node_modules", "./"],
"modulePathIgnorePatterns": ["<rootDir>/cypress"],
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"testEnvironment": "jsdom",
"setupFilesAfterEnv": [
"@testing-library/jest-dom/extend-expect",
"@testing-library/react"
]
}

View File

@ -4,32 +4,32 @@
"description": "Developer Full Stack Junior • Passionate about High-Tech",
"birthDate": "Birth date",
"nationality": "Nationality",
"descriptionBottom": "I'm learning online programming languages to improve my skills in my passion. <0/> <0/> I designed my graphic chart and my website."
"descriptionBottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (first year). <0/> <0/> I put into practice everything I learn and make many projects."
},
"interests": {
"title": "My Interests",
"title": "Interests",
"paragraphs": [
{
"title": "Developer Full Stack Junior :",
"description": "Computer programming is my main passion, 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 some Python and others programming language too."
},
{
"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 even 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 href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
"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": {
"title": "My skills",
"title": "Skills",
"languages": "Programming languages",
"softwareTools": "Software and tools"
},
"portfolio": {
"title": "My Portfolio",
"title": "Portfolio",
"items": [
{
"title": "function.divlo.fr",
@ -42,22 +42,16 @@
"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/",
"image": "/images/portfolio/threamdivlofr.png"
},
{
"title": "Leon",
"description": "Leon is your open-source personal assistant.",
"link": "https://getleon.ai/",
"image": "/images/portfolio/leon.png"
}
]
},
"contact": {
"title": "Contact-Me",
"nameField": "Name",
"subjectField": "Subject",
"sendEmail": "Send email",
"result": {
"loading": "Loading...",
"success": "Your email has been sent!",
"requiredFields": "You must fill all the fields...",
"invalidEmail": "Please enter a valid email address...",
"serverError": "The server could not process your request..."
},
"error": "Error",
"success": "Success"
"open-source": {
"description": "List of most famous open source projects I contributed to."
}
}

View File

@ -4,32 +4,32 @@
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
"birthDate": "Date de naissance",
"nationality": "Nationalité",
"descriptionBottom": "J'apprends en ligne l'informatique et les langages de programmation pour m'améliorer dans ma passion. <0/> <0/> J'ai conçu ma charte graphique et mon site internet."
"descriptionBottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (première année). <0/> <0/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets."
},
"interests": {
"title": "Mes intérêts",
"title": "Intérêts",
"paragraphs": [
{
"title": "Développeur Full Stack Junior :",
"description": "La programmation informatique est ma principale passion, j'adore! <br/> Principalement du développement Web pour le moment, mais je programme aussi du Python et d'autres langages de programmation."
"description": "La programmation informatique est mon loisir principal, j'adore! <br/> Principalement du développement Web pour le moment, mais je programme aussi du Python et d'autres langages de programmation."
},
{
"title": "Passionné de High-Tech :",
"description": "Je me suis toujours demandé comment l'avenir serait. Chaque jour, je veux me réveiller et penser que l'avenir sera formidable et même meilleur que le passé. Les technolgies s'améliorent progressivement avec le temps, ce qui est très utile dans de nombreux domaines."
"description": "Je me suis toujours demandé comment l'avenir serait. Chaque jour, je veux me réveiller et penser que l'avenir sera formidable et meilleur que le passé. Les technolgies s'améliorent progressivement avec le temps, ce qui est très utile dans de nombreux domaines."
},
{
"title": "Enthousiaste de l'Open-Source :",
"description": "Pour moi, tout le monde devrait travailler, résoudre des problèmes, construire des choses et réfléchir ensemble. Longue vie à l'open-source, chaque fois que vous pouvez partagez votre travail, faites-le! <br/> Le site est open-source sur <a href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
"description": "Pour moi, tout le monde devrait travailler, résoudre des problèmes, construire des choses et réfléchir ensemble. Longue vie à l'open-source, chaque fois que vous pouvez partagez votre travail, faites-le! <br/> Le site est open-source sur <a class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
}
]
},
"skills": {
"title": "Mes compétences",
"title": "Compétences",
"languages": "Langages de programmation",
"softwareTools": "Logiciels et outils"
},
"portfolio": {
"title": "Mon Portfolio",
"title": "Portfolio",
"items": [
{
"title": "function.divlo.fr",
@ -42,22 +42,16 @@
"description": "Votre plateforme open source pour rester proche de vos amis et communautés, parler, discuter, collaborer, partager et vous amuser.",
"link": "https://thream.divlo.fr/",
"image": "/images/portfolio/threamdivlofr.png"
},
{
"title": "Leon",
"description": "Leon est votre assistant personnel open source.",
"link": "https://getleon.ai/",
"image": "/images/portfolio/leon.png"
}
]
},
"contact": {
"title": "Contactez-Moi",
"nameField": "Nom",
"subjectField": "Objet",
"sendEmail": "Envoyer l'email",
"result": {
"loading": "Chargement...",
"success": "Votre email a été envoyé!",
"requiredFields": "Vous devez remplir tous les champs...",
"invalidEmail": "Veuillez entrer une adresse mail valide...",
"serverError": "Le serveur n'a pas pu traiter votre requête..."
},
"error": "Erreur",
"success": "Succès"
"open-source": {
"description": "Liste des projets open source les plus célèbres auxquels j'ai contribué."
}
}

4
next-env.d.ts vendored
View File

@ -1,2 +1,6 @@
/// <reference types="next" />
/// <reference types="next/types/global" />
/// <reference types="next/image-types/global" />
// NOTE: This file should not be edited
// see https://nextjs.org/docs/basic-features/typescript for more information.

View File

@ -1,11 +1,35 @@
const nextPWA = require('next-pwa')
const nextTranslate = require('next-translate')
const { createSecureHeaders } = require('next-secure-headers')
/** @type {import("next").NextConfig} */
module.exports = nextTranslate(
nextPWA({
pwa: {
disable: process.env.NODE_ENV !== 'production',
dest: 'public'
},
async headers() {
return [
{
source: '/:path*',
headers: createSecureHeaders({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ['*', 'data:', 'blob:'],
mediaSrc: "'none'",
connectSrc: '*',
objectSrc: "'none'",
fontSrc: "'self'",
baseURI: "'none'"
}
}
})
}
]
}
})
)

33122
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,26 +1,14 @@
{
"name": "divlo",
"version": "0.0.0-development",
"version": "1.3.6",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/Divlo/Divlo"
},
"ts-standard": {
"ignore": [
".next",
".lighthouseci",
"node_modules",
"next-env.d.ts",
"**/workbox-*.js",
"**/sw.js"
],
"envs": [
"node",
"browser",
"jest"
],
"report": "stylish"
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
},
"scripts": {
"dev": "next dev",
@ -31,59 +19,71 @@
"lint:docker": "dockerfilelint './Dockerfile'",
"lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint '**/*.md' --dot --ignore node_modules",
"lint:typescript": "ts-standard",
"lighthouse": "lhci autorun",
"test": "jest",
"lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'",
"lint:staged": "lint-staged",
"test:unit": "jest",
"test:lighthouse": "lhci autorun",
"test:e2e": "start-server-and-test 'start' 'http://localhost:3000' 'cypress run'",
"test:e2e:dev": "start-server-and-test 'dev' 'http://localhost:3000' 'cypress open'",
"release": "semantic-release",
"deploy": "vercel",
"postinstall": "husky install"
},
"dependencies": {
"@fontsource/montserrat": "4.2.2",
"@fortawesome/fontawesome-svg-core": "1.2.35",
"@fortawesome/free-brands-svg-icons": "5.15.3",
"@fortawesome/free-solid-svg-icons": "5.15.3",
"@fortawesome/react-fontawesome": "0.1.14",
"axios": "0.21.1",
"@fontsource/montserrat": "4.5.1",
"@fortawesome/fontawesome-svg-core": "1.2.36",
"@fortawesome/free-brands-svg-icons": "5.15.4",
"@fortawesome/free-solid-svg-icons": "5.15.4",
"@fortawesome/react-fontawesome": "0.1.15",
"classnames": "2.3.1",
"html-react-parser": "1.2.6",
"next": "10.1.3",
"next-pwa": "5.2.14",
"next-translate": "1.0.6",
"nodemailer": "6.5.0",
"normalize.css": "8.0.1",
"nprogress": "0.2.0",
"html-react-parser": "1.3.0",
"next": "11.1.2",
"next-pwa": "5.3.1",
"next-themes": "0.0.15",
"next-translate": "1.0.7",
"react": "17.0.2",
"react-component-form": "1.3.0",
"react-dom": "17.0.2",
"universal-cookie": "4.0.4",
"validator": "13.5.2"
"read-pkg": "7.0.0",
"sharp": "0.29.1",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@commitlint/cli": "12.1.1",
"@commitlint/config-conventional": "12.1.1",
"@fullhuman/postcss-purgecss": "4.0.3",
"@lhci/cli": "0.7.1",
"@styled-jsx/plugin-sass": "3.0.0",
"@testing-library/jest-dom": "5.11.10",
"@testing-library/react": "11.2.6",
"@types/jest": "26.0.22",
"@types/node": "14.14.41",
"@types/nodemailer": "6.4.1",
"@types/nprogress": "0.2.0",
"@types/react": "17.0.3",
"@types/styled-jsx": "2.2.8",
"@types/validator": "13.1.3",
"babel-jest": "26.6.3",
"@commitlint/cli": "13.1.0",
"@commitlint/config-conventional": "13.1.0",
"@lhci/cli": "0.8.1",
"@saithodev/semantic-release-backmerge": "1.5.3",
"@semantic-release/git": "9.0.1",
"@testing-library/jest-dom": "5.14.1",
"@testing-library/react": "12.0.0",
"@types/jest": "27.0.1",
"@types/node": "16.9.0",
"@types/react": "17.0.20",
"@typescript-eslint/eslint-plugin": "4.31.0",
"autoprefixer": "10.3.4",
"babel-jest": "27.1.1",
"cypress": "8.3.1",
"dockerfilelint": "1.8.0",
"editorconfig-checker": "4.0.2",
"husky": "6.0.0",
"jest": "26.6.3",
"markdownlint-cli": "0.27.1",
"node-mocks-http": "1.10.1",
"postcss": "8.2.10",
"sass": "1.32.11",
"semantic-release": "17.4.2",
"ts-standard": "10.0.0",
"typescript": "4.2.4"
"eslint": "7.32.0",
"eslint-config-next": "11.1.2",
"eslint-config-prettier": "8.3.0",
"eslint-config-standard-with-typescript": "21.0.1",
"eslint-plugin-import": "2.24.2",
"eslint-plugin-node": "11.1.0",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-promise": "5.1.0",
"eslint-plugin-unicorn": "35.0.0",
"husky": "7.0.2",
"jest": "27.1.1",
"lint-staged": "11.1.2",
"markdownlint-cli": "0.28.1",
"next-secure-headers": "2.2.0",
"postcss": "8.3.6",
"prettier": "2.3.2",
"semantic-release": "17.4.7",
"start-server-and-test": "1.14.0",
"tailwindcss": "2.2.14",
"typescript": "4.4.2",
"vercel": "23.1.2"
}
}

View File

@ -3,21 +3,30 @@ import useTranslation from 'next-translate/useTranslation'
import { ErrorPage } from 'components/ErrorPage'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { Footer, FooterProps } from 'components/Footer'
const Error404: React.FC = () => {
const Error404: React.FC<FooterProps> = (props) => {
const { t } = useTranslation()
const { version } = props
return (
<>
<Head title='Divlo - 404' />
<ErrorPage statusCode={404} message={t('errors:notFound')} />
<Header />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
<ErrorPage statusCode={404} message={t('errors:notFound')} />
</main>
<Footer version={version} />
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
return { props: { version } }
}
export default Error404

View File

@ -3,21 +3,30 @@ import useTranslation from 'next-translate/useTranslation'
import { ErrorPage } from 'components/ErrorPage'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { Footer, FooterProps } from 'components/Footer'
const Error500: React.FC = () => {
const Error500: React.FC<FooterProps> = (props) => {
const { t } = useTranslation()
const { version } = props
return (
<>
<Head title='Divlo - 500' />
<ErrorPage statusCode={500} message={t('errors:serverError')} />
<Header />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
<ErrorPage statusCode={500} message={t('errors:serverError')} />
</main>
<Footer version={version} />
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
return { props: { version } }
}
export default Error500

View File

@ -1,33 +1,19 @@
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import Router from 'next/router'
import NProgress from 'nprogress'
import { ThemeProvider } from 'next-themes'
import useTranslation from 'next-translate/useTranslation'
import UniversalCookie from 'universal-cookie'
import 'normalize.css/normalize.css'
import 'tailwindcss/tailwind.css'
import '@fontsource/montserrat/400.css'
import '@fontsource/montserrat/500.css'
import '@fontsource/montserrat/600.css'
import '@fontsource/montserrat/700.css'
import 'styles/grid.scss'
import 'styles/general.scss'
import 'styles/nprogress.scss'
import { Header } from 'components/Header'
import { Footer } from 'components/Footer'
import { useEffect } from 'react'
const universalCookie = new UniversalCookie()
/** how long in seconds, until the cookie expires (10 years) */
const COOKIE_MAX_AGE = 10 * 365.25 * 24 * 60 * 60
Router.events.on('routeChangeStart', () => NProgress.start())
Router.events.on('routeChangeComplete', () => NProgress.done())
Router.events.on('routeChangeError', () => NProgress.done())
const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
const Application = ({ Component, pageProps }: AppProps): JSX.Element => {
const { lang } = useTranslation()
useEffect(() => {
@ -38,14 +24,10 @@ const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
}, [lang])
return (
<>
<Header />
<main className='content container'>
<Component {...pageProps} />
</main>
<Footer />
</>
<ThemeProvider attribute='class' defaultTheme='dark'>
<Component {...pageProps} />
</ThemeProvider>
)
}
export default MyApp
export default Application

15
pages/_document.tsx Normal file
View File

@ -0,0 +1,15 @@
import { Html, Head, Main, NextScript } from 'next/document'
const Document: React.FC = () => {
return (
<Html>
<Head />
<body className='bg-white dark:bg-black text-black dark:text-white font-headline'>
<Main />
<NextScript />
</body>
</Html>
)
}
export default Document

View File

@ -1,69 +0,0 @@
import { NextApiRequest, NextApiResponse } from 'next'
import nodemailer from 'nodemailer'
import validator from 'validator'
const EMAIL_PORT = parseInt(process.env.EMAIL_PORT ?? '465', 10)
const emailTransporter = nodemailer.createTransport({
host: process.env.EMAIL_HOST,
port: EMAIL_PORT,
secure: EMAIL_PORT === 465,
auth: {
user: process.env.EMAIL_USER,
pass: process.env.EMAIL_PASSWORD
},
tls: {
rejectUnauthorized: false
}
})
export default async (
request: NextApiRequest,
response: NextApiResponse
): Promise<any> => {
if (request.method !== 'POST') {
return response.redirect('/404')
}
let { name, email, subject, message } = request.body as {
name: string
email: string
subject: string
message: string
}
if (
validator.isEmpty(name) ||
validator.isEmpty(email) ||
validator.isEmpty(subject) ||
validator.isEmpty(message)
) {
return response.status(400).json({ type: 'requiredFields' })
}
if (!validator.isEmail(email)) {
return response.status(400).json({ type: 'invalidEmail' })
}
email = validator.normalizeEmail(email) as string
message = validator.trim(message)
message = validator.escape(message)
subject = validator.trim(subject)
subject = validator.escape(subject)
try {
await emailTransporter.sendMail({
from: '"Divlo" <contact@divlo.fr>',
to: email,
subject: `Contact - ${subject}`,
html: `
<b>Name:</b> ${name} <br/>
<b>Email:</b> ${email} <br/>
<b>Message:</b> ${message}
`
})
return response.status(201).json({ type: 'success' })
} catch {
return response.status(500).json({ type: 'serverError' })
}
}

View File

@ -1,7 +1,6 @@
import { GetStaticProps } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { Contact } from 'components/Contact'
import { RevealFade } from 'components/design/RevealFade'
import { Section } from 'components/design/Section'
import { Head } from 'components/Head'
@ -10,52 +9,70 @@ import { Portfolio } from 'components/Portfolio'
import { Profile } from 'components/Profile'
import { SocialMediaList } from 'components/Profile/SocialMediaList'
import { Skills } from 'components/Skills'
import { OpenSource } from 'components/OpenSource'
import { Header } from 'components/Header'
import { Footer, FooterProps } from 'components/Footer'
const Home: React.FC = () => {
const Home: React.FC<FooterProps> = (props) => {
const { t } = useTranslation()
const { version } = props
return (
<>
<Head />
<Section isMain id='about'>
<Profile />
<SocialMediaList />
</Section>
<RevealFade>
<Section id='interests' heading={t('home:interests.title')}>
<Interests />
<Header />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
<Section isMain id='about'>
<Profile />
<SocialMediaList />
</Section>
</RevealFade>
<RevealFade>
<Section id='skills' heading={t('home:skills.title')} withoutShadowContainer>
<Skills />
</Section>
</RevealFade>
<RevealFade>
<Section id='interests' heading={t('home:interests.title')}>
<Interests />
</Section>
</RevealFade>
<RevealFade>
<Section
id='portfolio'
heading={t('home:portfolio.title')}
withoutShadowContainer
>
<Portfolio />
</Section>
</RevealFade>
<RevealFade>
<Section
id='skills'
heading={t('home:skills.title')}
withoutShadowContainer
>
<Skills />
</Section>
</RevealFade>
<RevealFade>
<Section id='contact' heading={t('home:contact.title')}>
<Contact />
</Section>
</RevealFade>
<RevealFade>
<Section
id='portfolio'
heading={t('home:portfolio.title')}
withoutShadowContainer
>
<Portfolio />
</Section>
</RevealFade>
<RevealFade>
<Section
id='open-source'
heading='Open source'
withoutShadowContainer
>
<OpenSource />
</Section>
</RevealFade>
</main>
<Footer version={version} />
</>
)
}
export const getStaticProps: GetStaticProps = async () => {
return { props: {} }
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
return { props: { version } }
}
export default Home

View File

@ -1,15 +1,6 @@
module.exports = {
plugins: [
[
'@fullhuman/postcss-purgecss',
{
content: [
'./pages/**/*.{js,jsx,ts,tsx}',
'./components/**/*.{js,jsx,ts,tsx}'
],
defaultExtractor: (content) => content.match(/[\w-/:]+(?<!:)/g) || [],
safelist: ['html', 'body']
}
]
]
plugins: {
tailwindcss: {},
autoprefixer: {}
}
}

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