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

Compare commits

...

59 Commits

Author SHA1 Message Date
a18cec4826 chore(release): 2.7.1 [skip ci] 2023-05-21 16:27:24 +00:00
61e589f0f4 fix: responsive on blog post with code blocks and katex 2023-05-21 18:21:46 +02:00
dc5c3cee41 chore(release): 2.7.0 [skip ci] 2023-05-21 12:49:17 +00:00
20cb0c21d5 feat(posts): add programming-challenges 2023-05-21 14:42:53 +02:00
e5232c1394 build(deps): update latest 2023-05-21 12:15:08 +02:00
fd51609713 chore(release): 2.6.1 [skip ci] 2023-05-13 17:15:54 +00:00
edf16c2562 fix(deps): update latest 2023-05-13 19:09:54 +02:00
94e0d190ae chore(release): 2.6.0 [skip ci] 2023-05-10 18:12:22 +00:00
b1cf7f8517 chore: remove unneeded Lighthouse checking 2023-05-09 23:22:33 +02:00
a1a715d3b9 feat: add Numerize as work experience 2023-05-09 23:06:10 +02:00
eede46fb41 build(deps): update latest 2023-05-09 22:56:42 +02:00
e32c53caa1 chore(release): 2.5.6 [skip ci] 2023-04-02 21:18:14 +00:00
361ea37deb chore: fix CI issues 2023-04-02 23:16:51 +02:00
d49a8a7470 fix: update dependencies to latest 2023-04-02 22:44:09 +02:00
a4996c8251 chore: remove useless runner-dependencies in Dockerfile 2023-01-11 17:42:29 +01:00
b25451e631 chore(release): 2.5.5 [skip ci] 2023-01-10 23:05:24 +00:00
042a861f58 fix: update dependencies to latest 2023-01-10 23:56:46 +01:00
d76db36dbc chore(release): 2.5.4 [skip ci] 2022-12-08 08:54:03 +00:00
99d9dcf334 fix: improve Resume 2022-12-08 09:52:39 +01:00
ece5ded1b4 chore(release): 2.5.3 [skip ci] 2022-11-29 09:33:10 +00:00
1514600998 fix: improve Resume 2022-11-29 10:29:02 +01:00
5f5b328895 chore(release): 2.5.2 [skip ci] 2022-11-19 19:43:26 +00:00
c88887a322 fix: better resume 2022-11-19 20:24:13 +01:00
014044573a chore(release): 2.5.1 [skip ci] 2022-11-10 11:35:11 +00:00
df009c3f7b fix(posts): update broken link in thream-v1.0.0.md 2022-11-10 12:31:48 +01:00
5c85ca2ef1 chore: fix cypress unit tests 2022-11-08 11:00:31 +01:00
07f7942496 chore(release): 2.5.0 [skip ci] 2022-10-27 17:24:30 +00:00
213a3fa182 build(deps): bump Next.js to v13 2022-10-27 19:13:29 +02:00
28d9211583 fix(posts): update git-ultimate-guide 2022-10-23 20:15:07 +02:00
4d085cb148 fix: update biography description 2022-10-23 18:38:37 +02:00
e6c583f2cd ci: fix timeout 2022-10-20 23:57:53 +02:00
232b54588a feat(skills): add PHP and Laravel 2022-10-20 22:44:40 +02:00
c419fb3bb4 chore: remove usage of styled-jsx 2022-10-20 22:44:32 +02:00
03e7e22d74 chore: reduce docker image size 2022-10-20 22:44:32 +02:00
e85c241ed1 feat(posts): add git-ultimate-guide 2022-10-20 22:43:25 +02:00
c1877297f8 refactor: minor changes 2022-08-27 02:30:55 +02:00
83231197dd chore(release): 2.4.1 [skip ci] 2022-08-23 11:33:38 +00:00
a2fe2205bc fix(resume): wrong base path for assets 2022-08-23 13:31:17 +02:00
e1f3dceb07 chore(release): 2.4.0 [skip ci] 2022-08-23 10:33:09 +00:00
0f89fee52f feat: add giscus comments system for blog posts 2022-08-23 12:23:31 +02:00
2fcc7ac384 chore(release): 2.3.2 [skip ci] 2022-07-28 21:06:12 +00:00
9351edf626 chore: use the right resume.json 2022-07-28 23:01:19 +02:00
1f4aa54211 chore: remove jest -> cypress for unit tests 2022-07-28 22:51:12 +02:00
8bc1471cbb chore: easier development for jsonresume-theme-custom thanks to vite 2022-07-28 21:20:41 +02:00
1ebdab18a5 fix: update about, now second year of university 2022-07-23 23:00:58 +02:00
b9b76e839a build(deps): update latest 2022-07-01 23:12:47 +02:00
bc065a2e19 chore(release): 2.3.1 [skip ci] 2022-05-03 08:12:15 +00:00
5d3a287b27 fix(resume): wrong dates 2022-05-03 10:05:11 +02:00
fb689c9bc1 chore(release): 2.3.0 [skip ci] 2022-04-11 10:35:55 +00:00
2c3a70df2a feat(posts): add thream-v1-0-0 2022-04-11 12:31:19 +02:00
bce254a355 chore(release): 2.2.1 [skip ci] 2022-03-24 18:00:10 +00:00
f67d331416 fix: calculate age client side so it updates "automatically" (not only on rebuild) 2022-03-24 18:57:27 +01:00
6abc881e94 chore(release): 2.2.0 [skip ci] 2022-03-24 10:49:45 +00:00
a67d6665ea feat: display age nearby the birth date 2022-03-24 11:45:19 +01:00
1152039663 chore(release): 2.1.0 [skip ci] 2022-03-14 08:15:56 +00:00
919ebd5f3e feat(posts): add mistakes-as-junior-developer 2022-03-14 09:09:46 +01:00
94212f9b5c chore(release): 2.0.2 [skip ci] 2022-02-23 18:52:16 +00:00
bf9347f685 ci: multiple workflows instead of one 2022-02-23 19:46:44 +01:00
896b6051e8 fix: redirect /curriculum-vitae.html to /curriculum-vitae 2022-02-23 19:31:18 +01:00
125 changed files with 15255 additions and 42830 deletions

View File

@ -1,2 +1 @@
ARG VARIANT="16"
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
FROM mcr.microsoft.com/devcontainers/javascript-node:18

View File

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

View File

@ -1,5 +1,3 @@
version: '3.0'
services:
workspace:
build:

View File

@ -1,12 +1,5 @@
.vscode
.git
.env
.*
!.npmrc
build
.next
coverage
node_modules
tmp
temp
.DS_Store
.lighthouseci
.vercel

View File

@ -1,2 +1,2 @@
COMPOSE_PROJECT_NAME=divlo.fr
COMPOSE_PROJECT_NAME=divlo
PORT=3000

View File

@ -1,8 +0,0 @@
.next
.lighthouseci
storybook-static
coverage
node_modules
next-env.d.ts
**/workbox-*.js
**/sw.js

View File

@ -4,13 +4,7 @@
"parserOptions": {
"project": "./tsconfig.json"
},
"env": {
"node": true,
"browser": true,
"jest": true
},
"rules": {
"prettier/prettier": "error",
"unicorn/prefer-node-protocol": "off"
"prettier/prettier": "error"
}
}

View File

@ -1,6 +1,6 @@
<!-- 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?
# What changes this PR introduce?
## List any relevant issue numbers

View File

@ -1,188 +0,0 @@
name: 'Divlo'
on:
push:
branches: [master, develop]
pull_request:
branches: [master, develop]
jobs:
analyze:
runs-on: 'ubuntu-latest'
strategy:
fail-fast: false
matrix:
language: ['javascript']
steps:
- uses: 'actions/checkout@v2.4.0'
- name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v1'
with:
languages: ${{ matrix.language }}
- name: 'Perform CodeQL Analysis'
uses: 'github/codeql-action/analyze@v1'
build:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.4.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
with:
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'
lint:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.4.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
with:
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'lint:commit'
run: 'npm run lint:commit -- --to "${{ github.sha }}"'
- name: 'lint:editorconfig'
run: 'npm run lint:editorconfig'
- name: 'lint:markdown'
run: 'npm run lint:markdown'
- name: 'lint:typescript'
run: 'npm run lint:typescript'
- name: 'lint:prettier'
run: 'npm run lint:prettier'
- name: 'resume:validate'
run: 'npm run resume:validate'
- name: 'lint:dotenv'
uses: 'dotenv-linter/action-dotenv-linter@v2'
with:
github_token: ${{ secrets.github_token }}
- name: 'lint:docker'
uses: 'hadolint/hadolint-action@v1.6.0'
with:
dockerfile: './Dockerfile'
test-unit:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.4.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
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.4.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
with:
node-version: '16.x'
cache: 'npm'
- name: 'Install'
run: 'npm install'
- name: 'Build'
run: 'npm run build'
- name: 'html-w3c-validator'
run: 'npm run test:html-w3c-validator'
- name: 'Lighthouse'
run: 'npm run test:lighthouse'
env:
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
test-e2e:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.4.0'
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
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, build, lint, test-unit, test-lighthouse, test-e2e]
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v2.4.0'
with:
fetch-depth: 0
persist-credentials: false
- name: 'Import GPG key'
uses: 'crazy-max/ghaction-import-gpg@v4'
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: 'Use Node.js'
uses: 'actions/setup-node@v2.5.1'
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 }}

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

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

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

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

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

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

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

@ -0,0 +1,44 @@
name: 'Release'
on:
push:
branches: [master]
jobs:
release:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.2'
with:
fetch-depth: 0
persist-credentials: false
- name: 'Import GPG key'
uses: 'crazy-max/ghaction-import-gpg@v5.3.0'
with:
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
git_user_signingkey: true
git_commit_gpgsign: true
- name: 'Setup Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm clean-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 }}

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

@ -0,0 +1,48 @@
name: 'Test'
on:
push:
branches: [develop]
pull_request:
branches: [master, develop]
jobs:
test-unit:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.2'
- name: 'Setup Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm clean-install'
- name: 'Unit Test'
run: 'npm run test:unit'
test-e2e:
runs-on: 'ubuntu-latest'
steps:
- uses: 'actions/checkout@v3.5.0'
- name: 'Setup Node.js'
uses: 'actions/setup-node@v3.6.0'
with:
node-version: '18.x'
cache: 'npm'
- name: 'Install dependencies'
run: 'npm clean-install'
- name: 'Build'
run: 'npm run build'
- name: 'html-w3c-validator'
run: 'npm run test:html-w3c-validator'
- name: 'End To End (e2e) Test'
run: 'npm run test:e2e'

6
.gitignore vendored
View File

@ -11,7 +11,7 @@ out
# production
build
dist
public/*.html
public/curriculum-vitae
# PWA
public/workbox-*.js
public/sw.js
@ -49,3 +49,7 @@ npm-debug.log*
.DS_Store
.lighthouseci
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

View File

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

View File

@ -1,30 +0,0 @@
{
"ci": {
"collect": {
"startServerCommand": "npm run start",
"startServerReadyPattern": "ready on",
"startServerReadyTimeout": 20000,
"url": [
"http://localhost:3000/",
"http://localhost:3000/blog",
"http://localhost:3000/blog/hello-world"
],
"numberOfRuns": 1
},
"assert": {
"preset": "lighthouse:recommended",
"assertions": {
"csp-xss": "warning",
"non-composited-animations": "warning",
"unused-javascript": "warning",
"image-size-responsive": "warning",
"unsized-images": "warning",
"color-contrast": "warning"
}
},
"upload": {
"target": "temporary-public-storage"
},
"server": {}
}
}

View File

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

12
.markdownlint-cli2.jsonc Normal file
View File

@ -0,0 +1,12 @@
{
"config": {
"default": true,
"relative-links": true,
"extends": "markdownlint/style/prettier",
"MD024": false,
"MD033": false
},
"globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
}

View File

@ -1,7 +0,0 @@
{
"default": true,
"MD013": false,
"MD024": false,
"MD033": false,
"MD041": false
}

View File

@ -1,9 +0,0 @@
.next
.lighthouseci
storybook-static
coverage
node_modules
next-env.d.ts
**/workbox-*.js
**/sw.js
*.hbs

View File

@ -30,6 +30,7 @@
[
"@saithodev/semantic-release-backmerge",
{
"branches": [{ "from": "master", "to": "develop" }],
"backmergeStrategy": "merge"
}
]

View File

@ -3,8 +3,6 @@
"editorconfig.editorconfig",
"esbenp.prettier-vscode",
"dbaeumer.vscode-eslint",
"divlo.vscode-styled-jsx-syntax",
"divlo.vscode-styled-jsx-languageserver",
"bradlc.vscode-tailwindcss",
"mikestead.dotenv",
"davidanson.vscode-markdownlint",

View File

@ -6,5 +6,9 @@
"editor.formatOnSave": true,
"editor.codeActionsOnSave": {
"source.fixAll": true
}
},
"eslint.options": {
"ignorePath": ".gitignore"
},
"prettier.ignorePath": ".gitignore"
}

View File

@ -60,7 +60,7 @@ representative at an online or offline event.
Instances of abusive, harassing, or otherwise unacceptable behavior may be
reported to the community leaders responsible for enforcement at
contact@divlo.fr.
<contact@divlo.fr>.
All complaints will be reviewed and investigated promptly and fairly.
All community leaders are obligated to respect the privacy and security of the

View File

@ -2,6 +2,10 @@
Thanks a lot for your interest in contributing to **divlo.fr**! 🎉
## Code of Conduct
**divlo.fr** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
## Types of contributions
- Reporting a bug.
@ -21,29 +25,7 @@ If you're adding new features to **divlo.fr**, please include tests.
## Commits
The commit message guidelines respect [@commitlint/config-conventional](https://github.com/conventional-changelog/commitlint/tree/master/%40commitlint/config-conventional) and [Semantic Versioning](https://semver.org/) for releases.
### Types
Types define which kind of changes you made to the project.
| Types | Description |
| -------- | ------------------------------------------------------------------------------------------------------------ |
| feat | A new feature. |
| fix | A bug fix. |
| docs | Documentation only changes. |
| style | Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc). |
| refactor | A code change that neither fixes a bug nor adds a feature. |
| perf | A code change that improves performance. |
| test | Adding missing tests or correcting existing tests. |
| build | Changes that affect the build system or external dependencies (example scopes: gulp, broccoli, npm). |
| ci | Changes to our CI configuration files and scripts (example scopes: Travis, Circle, BrowserStack, SauceLabs). |
| chore | Other changes that don't modify src or test files. |
| revert | Reverts a previous commit. |
### Scopes
Scopes define what part of the code changed.
The commit message guidelines adheres to [Conventional Commits](https://www.conventionalcommits.org/) and [Semantic Versioning](https://semver.org/) for releases.
## Getting Started
@ -81,9 +63,9 @@ npm run dev
```sh
# Setup and run all the services for you
docker-compose up --build
docker compose up --build
```
### Services started
- website : `http://localhost:3000`
- website: `http://127.0.0.1:3000`

View File

@ -1,23 +1,21 @@
FROM node:16.14.0 AS dependencies
WORKDIR /usr/src/app
FROM node:18.16.0 AS builder-dependencies
WORKDIR /usr/src/application
COPY ./package*.json ./
RUN npm install
RUN npm clean-install
FROM node:16.14.0 AS builder
WORKDIR /usr/src/app
FROM node:18.16.0 AS builder
WORKDIR /usr/src/application
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
COPY ./ ./
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
RUN npm run build
FROM node:16.14.0 AS runner
WORKDIR /usr/src/app
FROM gcr.io/distroless/nodejs18-debian11:latest AS runner
WORKDIR /usr/src/application
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}"]
ENV NEXT_TELEMETRY_DISABLED=1
COPY --from=builder /usr/src/application/.next/standalone ./
COPY --from=builder /usr/src/application/.next/static ./.next/static
COPY --from=builder /usr/src/application/public ./public
COPY --from=builder /usr/src/application/locales ./locales
COPY --from=builder /usr/src/application/next.config.js ./next.config.js
CMD ["./server.js"]

View File

@ -1,11 +1,10 @@
<h1 align="center"><a href="https://divlo.fr/">Divlo</a></h1>
<p align="center">
<strong>Developer Full Stack Junior • Passionate about High-Tech</strong>
<strong>Developer Full Stack • Open-Source enthusiast</strong>
</p>
<p align="center">
<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>
@ -26,15 +25,11 @@
"pronouns": "He/Him",
"birthDate": "31/03/2003",
"nationality": "Alsace, France",
"interests": [
"Developer Full Stack Junior",
"Passionate about High-Tech",
"Open-Source enthusiast"
],
"interests": ["Open-Source enthusiast", "Passionate about High-Tech"],
"skills": {
"programmingLanguages": ["JavaScript", "TypeScript", "Python", "C/C++"],
"frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
"backEnd": ["Node.js", "Fastify", "Prisma", "PostgreSQL", "MySQL"],
"programmingLanguages": ["JavaScript/TypeScript", "Python", "C/C++", "PHP"],
"frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js/Next.js"],
"backEnd": ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
"tools": ["GNU/Linux", "Ubuntu", "Visual Studio Code", "Git", "Docker"]
}
}

View File

@ -1,53 +1,45 @@
import useTranslation from 'next-translate/useTranslation'
import Link from 'next/link'
export interface ErrorPageProps {
import type { FooterProps } from './Footer'
import { Footer } from './Footer'
import { Header } from './Header'
export interface ErrorPageProps extends FooterProps {
statusCode: number
message: string
}
export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
const { message, statusCode } = props
const { message, statusCode, version } = props
const { t } = useTranslation()
return (
<>
<h1 className='my-6 text-4xl font-semibold'>
{t('errors:error')}{' '}
<span
className='text-yellow dark:text-yellow-dark'
data-cy='status-code'
>
{statusCode}
</span>
</h1>
<p className='text-center text-lg'>
{message}{' '}
<Link href='/'>
<a className='text-yellow hover:underline dark:text-yellow-dark'>
{t('errors:return-to-home-page')}
</a>
</Link>
</p>
<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>
<div className='flex h-screen flex-col pt-0'>
<Header showLanguage />
<main className='flex min-w-full flex-1 flex-col items-center justify-center'>
<h1 className='my-6 text-4xl font-semibold'>
{t('errors:error')}{' '}
<span
className='text-yellow dark:text-yellow-dark'
data-cy='status-code'
>
{statusCode}
</span>
</h1>
<p className='text-center text-lg'>
{message}{' '}
<Link
href='/'
className='text-yellow hover:underline dark:text-yellow-dark'
>
{t('errors:return-to-home-page')}
</Link>
</p>
</main>
<Footer version={version} />
</div>
</>
)
}

View File

@ -17,16 +17,18 @@ export const Footer: React.FC<FooterProps> = (props) => {
return (
<footer className='flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black'>
<p>
<Link href='/'>
<a className='text-yellow hover:underline dark:text-yellow-dark'>
Divlo
</a>
<Link
href='/'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Divlo
</Link>{' '}
| {t('common:all-rights-reserved')}
</p>
<p className='mt-1'>
Version{' '}
<a
data-cy='version-link'
className='text-yellow hover:underline dark:text-yellow-dark'
href={versionLink}
target='_blank'

View File

@ -3,15 +3,15 @@ import NextHead from 'next/head'
interface HeadProps {
title?: string
image?: string
description: string
description?: string
url?: string
}
export const Head: React.FC<HeadProps> = (props) => {
const {
title = 'Divlo',
image = '/images/icons/icon-96x96.png',
description,
image = 'https://divlo.fr/images/icons/icon-96x96.png',
description = 'Divlo - Developer Full Stack • Passionate about High-Tech',
url = 'https://divlo.fr/'
} = props

View File

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

View File

@ -1,4 +1,5 @@
import { useEffect, useState } from 'react'
import classNames from 'clsx'
import { useTheme } from 'next-themes'
export const SwitchTheme: React.FC = () => {
@ -18,109 +19,60 @@ export const SwitchTheme: React.FC = () => {
}
return (
<>
<div
className='flex items-center'
data-cy='switch-theme-click'
onClick={handleClick}
>
<div className='toggle-theme-button relative inline-block cursor-pointer bg-transparent'>
<div className='toggle-track'>
<div
data-cy='switch-theme-dark'
className='toggle-track-check absolute'
>
<span className='toggle_Dark relative flex items-center justify-center'>
🌜
</span>
</div>
<div
data-cy='switch-theme-light'
className='toggle-track-x absolute'
>
<span className='toggle_Light relative flex items-center justify-center'>
🌞
</span>
</div>
<div
className='flex items-center'
data-cy='switch-theme-click'
onClick={handleClick}
>
<div className='relative inline-block cursor-pointer touch-pan-x select-none border-0 bg-transparent p-0'>
<div className='h-[24px] w-[50px] rounded-[30px] bg-[#4d4d4d] p-0 text-white transition-all duration-200 ease-in-out'>
<div
data-cy='switch-theme-dark'
className={classNames(
'absolute bottom-0 left-[8px] top-0 mb-auto mt-auto h-[10px] w-[14px] leading-[0] transition-opacity duration-[250ms] ease-in-out',
{
'opacity-100': theme === 'dark',
'opacity-0': theme === 'light'
}
)}
>
<span className='relative flex h-[10px] w-[10px] items-center justify-center'>
🌜
</span>
</div>
<div
data-cy='switch-theme-light'
className={classNames(
'absolute bottom-0 right-[10px] top-0 mb-auto mt-auto h-[10px] w-[10px] leading-[0]',
{
'opacity-100': theme === 'light',
'opacity-0': theme === 'dark'
}
)}
>
<span className='relative flex h-[10px] w-[10px] items-center justify-center'>
🌞
</span>
</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
className={classNames(
'absolute top-[1px] box-border h-[22px] w-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out',
{
'left-[27px]': theme === 'dark',
'left-0': theme === 'light'
}
)}
style={{ border: '1px solid #4d4d4d' }}
/>
<input
data-cy='switch-theme-input'
type='checkbox'
aria-label='Dark mode toggle'
className='absolute m-[-1px] h-[1px] w-[1px] overflow-hidden border-0 p-0'
defaultChecked
/>
</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>
</>
</div>
)
}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import useTranslation from 'next-translate/useTranslation'
import { InterestParagraph, InterestParagraphProps } from './InterestParagraph'
import type { InterestParagraphProps } from './InterestParagraph'
import { InterestParagraph } from './InterestParagraph'
import { InterestsList } from './InterestsList'
export const Interests: React.FC = () => {

View File

@ -24,7 +24,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
<div className='flex justify-center'>
<Image
quality={100}
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
className='h-auto w-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
width={300}
height={300}
src={image}

View File

@ -1,6 +1,7 @@
import useTranslation from 'next-translate/useTranslation'
import { PortfolioItem, PortfolioItemProps } from './PortfolioItem'
import type { PortfolioItemProps } from './PortfolioItem'
import { PortfolioItem } from './PortfolioItem'
export const Portfolio: React.FC = () => {
const { t } = useTranslation('home')

View File

@ -4,20 +4,20 @@ export const ProfileDescriptionBottom: React.FC = () => {
const { t, lang } = useTranslation()
return (
<p className='mt-8 mb-8 text-base font-normal text-gray dark:text-gray-dark'>
<p className='mb-8 mt-8 text-base font-normal text-gray dark:text-gray-dark'>
{t('home:about.description-bottom')}
{lang === 'fr' && (
{lang === 'fr' ? (
<>
<br />
<br />
<a
href='/curriculum-vitae.html'
href='/curriculum-vitae'
className='text-yellow hover:underline dark:text-yellow-dark'
>
Curriculum vitæ
</a>
</>
)}
) : null}
</p>
)
}

View File

@ -12,7 +12,7 @@ export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
<strong className='float-left block w-28 text-sm font-bold text-black dark:text-white'>
{title}
</strong>
<span className='ml-0 mb-4 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
<span className='mb-4 ml-0 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
{link != null ? (
<a
className='text-gray hover:underline dark:text-gray-dark'

View File

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

View File

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

View File

@ -1,4 +1,4 @@
import classNames from 'classnames'
import classNames from 'clsx'
export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
const { children, className, ...rest } = props

View File

@ -3,7 +3,9 @@ interface SocialMediaItemProps {
ariaLabel: string
}
export const SocialMediaItem: React.FC<SocialMediaItemProps> = (props) => {
export const SocialMediaItem: React.FC<
React.PropsWithChildren<SocialMediaItemProps>
> = (props) => {
const { link, ariaLabel, children } = props
return (

View File

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

View File

@ -13,6 +13,7 @@ export const Skills: React.FC = () => {
<SkillComponent skill='TypeScript' />
<SkillComponent skill='Python' />
<SkillComponent skill='C/C++' />
<SkillComponent skill='PHP' />
</SkillsSection>
<SkillsSection title='Front-end'>
@ -23,11 +24,10 @@ export const Skills: React.FC = () => {
</SkillsSection>
<SkillsSection title='Back-end'>
<SkillComponent skill='Laravel' />
<SkillComponent skill='Node.js' />
<SkillComponent skill='Fastify' />
<SkillComponent skill='Prisma' />
<SkillComponent skill='PostgreSQL' />
<SkillComponent skill='MySQL' />
</SkillsSection>
<SkillsSection title={t('home:skills.software-tools')}>

View File

@ -3,11 +3,7 @@ export interface Skill {
image: string | { [key: string]: string }
}
export interface Skills {
[key: string]: Skill
}
export const skills: Skills = {
export const skills = {
JavaScript: {
link: 'https://developer.mozilla.org/docs/Web/JavaScript',
image: '/images/skills/JavaScript.png'
@ -24,6 +20,14 @@ export const skills: Skills = {
link: 'https://isocpp.org/',
image: '/images/skills/C-Cpp.png'
},
PHP: {
link: 'https://www.php.net/',
image: '/images/skills/PHP.png'
},
Laravel: {
link: 'https://laravel.com/',
image: '/images/skills/Laravel.png'
},
Dart: {
link: 'https://dart.dev/',
image: '/images/skills/Dart.png'
@ -107,3 +111,5 @@ export const skills: Skills = {
image: '/images/skills/Docker.png'
}
} as const
export type SkillName = keyof typeof skills

View File

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

View File

@ -1,16 +0,0 @@
import { render } from '@testing-library/react'
import { Footer } from '../Footer'
describe('<Footer />', () => {
it('should render with appropriate link tag version', () => {
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,6 +1,6 @@
import { useEffect, useRef } from 'react'
export const RevealFade: React.FC = (props) => {
export const RevealFade: React.FC<React.PropsWithChildren> = (props) => {
const { children } = props
const htmlElement = useRef<HTMLDivElement>(null)

View File

@ -4,7 +4,7 @@ export const SectionHeading: React.FC<SectionHeadingProps> = (props) => {
const { children, ...rest } = props
return (
<h2 {...rest} className='mt-1 mb-3 text-center text-4xl font-semibold'>
<h2 {...rest} className='mb-3 mt-1 text-center text-4xl font-semibold'>
{children}
</h2>
)

View File

@ -23,7 +23,9 @@ export const Section: React.FC<SectionProps> = (props) => {
<div className='w-full px-3'>
<ShadowContainer style={{ marginTop: 50 }}>
<section {...rest}>
{heading != null && <SectionHeading>{heading}</SectionHeading>}
{heading != null ? (
<SectionHeading>{heading}</SectionHeading>
) : null}
<div className='w-full px-3'>{children}</div>
</section>
</ShadowContainer>
@ -34,7 +36,7 @@ export const Section: React.FC<SectionProps> = (props) => {
if (withoutShadowContainer) {
return (
<section {...rest}>
{heading != null && <SectionHeading>{heading}</SectionHeading>}
{heading != null ? <SectionHeading>{heading}</SectionHeading> : null}
<div className='w-full px-3'>{children}</div>
</section>
)
@ -42,16 +44,18 @@ export const Section: React.FC<SectionProps> = (props) => {
return (
<section {...rest}>
{heading != null && (
<SectionHeading style={{ ...(description != null && { margin: 0 }) }}>
{heading != null ? (
<SectionHeading
style={{ ...(description != null ? { margin: 0 } : {}) }}
>
{heading}
</SectionHeading>
)}
{description != null && (
) : null}
{description != null ? (
<p style={{ marginTop: 7 }} className='text-center'>
{description}
</p>
)}
) : null}
<div className='w-full px-3'>
<ShadowContainer>
<div className='w-full px-16 py-4 leading-8'>{children}</div>

View File

@ -1,4 +1,4 @@
import classNames from 'classnames'
import classNames from 'clsx'
type ShadowContainerProps = React.ComponentPropsWithRef<'div'>

17
cypress.config.ts Normal file
View File

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

View File

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

View File

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

View File

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

View File

@ -1,5 +1,7 @@
describe('Common > Header', () => {
beforeEach(() => cy.visit('/'))
beforeEach(() => {
return cy.visit('/')
})
it('should redirect to /blog on click of the blog link', () => {
cy.get('[data-cy=header-blog-link]')
@ -56,3 +58,5 @@ describe('Common > Header', () => {
})
})
})
export {}

View File

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

View File

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

View File

@ -11,3 +11,5 @@ describe('Page /blog/[slug]', () => {
cy.get('[data-cy=status-code]').contains('404')
})
})
export {}

View File

@ -20,3 +20,5 @@ describe('Page /blog', () => {
.should('eq', '/blog/hello-world')
})
})
export {}

View File

@ -1,13 +1,10 @@
describe('Page /', () => {
beforeEach(() => cy.visit('/'))
beforeEach(() => {
return cy.visit('/')
})
it('should reveals the sections while scrolling except the about section', () => {
const sectionsReveals = [
'#interests',
'#skills',
'#portfolio',
'#open-source'
]
const sectionsReveals = ['#interests', '#skills', '#portfolio']
cy.get('#about').should('be.visible')
for (const section of sectionsReveals) {
cy.get(section)
@ -17,3 +14,5 @@ describe('Page /', () => {
}
})
})
export {}

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
version: '3.0'
services:
divlo.fr:
divlo:
container_name: ${COMPOSE_PROJECT_NAME}
image: 'divlo.fr'
image: 'divlo'
build:
context: './'
ports:
- '${PORT}:${PORT}'
- '${PORT-3000}:${PORT-3000}'
environment:
PORT: ${PORT}
env_file: './.env'
PORT: ${PORT-3000}
env_file: '.env'

View File

@ -1,14 +0,0 @@
const nextJest = require('next/jest')
const createJestConfig = nextJest()
const customJestConfig = {
moduleDirectories: ['node_modules', './'],
modulePathIgnorePatterns: ['<rootDir>/cypress'],
testEnvironment: 'jsdom',
setupFilesAfterEnv: [
'@testing-library/jest-dom/extend-expect',
'@testing-library/react'
]
}
module.exports = createJestConfig(customJestConfig)

View File

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

View File

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

View File

Before

Width:  |  Height:  |  Size: 1015 B

After

Width:  |  Height:  |  Size: 1015 B

View File

Before

Width:  |  Height:  |  Size: 986 B

After

Width:  |  Height:  |  Size: 986 B

View File

Before

Width:  |  Height:  |  Size: 629 B

After

Width:  |  Height:  |  Size: 629 B

View File

Before

Width:  |  Height:  |  Size: 912 B

After

Width:  |  Height:  |  Size: 912 B

View File

Before

Width:  |  Height:  |  Size: 528 B

After

Width:  |  Height:  |  Size: 528 B

View File

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

View File

@ -1,32 +0,0 @@
const path = require('path')
const fs = require('fs')
const ejs = require('ejs')
const date = require('date-and-time')
const { Parcel } = require('@parcel/core')
const render = async (resume) => {
const themeIndexPath = path.join(__dirname, 'theme', 'index.ejs')
const themeBuildPath = path.join(__dirname, 'theme', 'index.html')
const indexHTMLPath = path.join(__dirname, 'dist', 'index.html')
const html = await ejs.renderFile(themeIndexPath, {
date,
locals: {
...resume
}
})
await fs.promises.writeFile(themeBuildPath, html, { encoding: 'utf-8' })
const bundler = new Parcel({
entries: themeBuildPath,
source: themeBuildPath,
mode: 'production',
defaultConfig: '@parcel/config-default'
})
await bundler.run()
return await fs.promises.readFile(indexHTMLPath, { encoding: 'utf-8' })
}
module.exports = {
render
}

File diff suppressed because it is too large Load Diff

View File

@ -2,17 +2,20 @@
"name": "jsonresume-theme-custom",
"private": true,
"version": "1.0.0",
"scripts": {},
"type": "module",
"scripts": {
"dev": "vite",
"build": "vite build",
"preview": "vite preview"
},
"dependencies": {
"date-and-time": "2.1.2",
"ejs": "3.1.6",
"modern-normalize": "1.1.0"
"jsonc-parser": "3.2.0",
"modern-normalize": "2.0.0"
},
"devDependencies": {
"@parcel/config-default": "2.3.2",
"@parcel/core": "2.3.2",
"@parcel/optimizer-data-url": "^2.3.2",
"@parcel/transformer-inline-string": "^2.3.2",
"parcel": "2.3.2"
"@types/node": "20.2.1",
"date-and-time": "3.0.0",
"vite": "4.3.8",
"vite-plugin-html": "3.2.0"
}
}

View File

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

View File

@ -1,4 +1,4 @@
@import 'npm:modern-normalize/modern-normalize.css';
@import 'modern-normalize/modern-normalize.css';
body {
font-family: 'Montserrat', 'Arial', 'sans-serif';
@ -209,7 +209,6 @@ h5 {
font-size: 75%;
font-weight: 600;
line-height: 1;
color: #fff;
text-align: center;
white-space: nowrap;
vertical-align: baseline;
@ -217,8 +216,6 @@ h5 {
}
.label-keyword {
display: inline-block;
background: #7eb0db;
color: white;
font-size: 0.9em;
padding: 5px;
border: 1px solid #357ebd;
@ -227,3 +224,6 @@ h5 {
.label-keyword p {
margin: 0;
}
.section-separated {
display: flex;
}

View File

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

View File

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

View File

@ -1,26 +1,27 @@
{
"about": {
"i-am": "I am",
"description": "Developer Full Stack Junior • Passionate about High-Tech",
"description": "Developer Full Stack • Open-Source enthusiast",
"full-name": "Full name",
"birth-date": "Birth date",
"years-old": "years old",
"nationality": "Nationality",
"description-bottom": "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)."
"description-bottom": "I am a student in computer science following the French training \"BUT Informatique\" and I am also a self-taught."
},
"interests": {
"title": "Interests",
"paragraphs": [
{
"title": "Developer Full Stack Junior",
"title": "Developer Full Stack",
"description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming in others programming language too."
},
{
"title": "Open-Source enthusiast",
"description": "For me, everyone should work, solve problems, build things and think together.<br/> The website is open-source on <a class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>GitHub</a>."
},
{
"title": "Passionate about High-Tech",
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas."
},
{
"title": "Open-Source enthusiast",
"description": "For me, everyone should work, solve problems, build things and think together. Long live open source, whenever you can share your work, do it! <br/> The website is open-source on <a class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
}
]
},

View File

@ -2,5 +2,5 @@
"return-to-home-page": "Revenir à la page d'accueil ?",
"error": "Erreur",
"server-error": "Erreur Interne du Serveur !",
"not-found": "Cette page n'existe pas!"
"not-found": "Cette page n'existe pas !"
}

View File

@ -1,26 +1,27 @@
{
"about": {
"i-am": "Je suis",
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
"description": "Développeur Full Stack • Enthousiaste de l'Open-Source",
"full-name": "Prénom NOM",
"birth-date": "Date de naissance",
"years-old": "ans",
"nationality": "Nationalité",
"description-bottom": "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)."
"description-bottom": "Je suis étudiant à l'université suivant la formation \"BUT Informatique\" et me forme en autodidacte dans l'informatique en suivant des formations en ligne."
},
"interests": {
"title": "Intérêts",
"paragraphs": [
{
"title": "Développeur Full Stack Junior",
"title": "Développeur Full Stack",
"description": "La programmation informatique est mon loisir principal, j'adore! <br/> Principalement du développement Web pour le moment, mais je programme aussi dans d'autres langages de programmation."
},
{
"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. <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>."
},
{
"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 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 class='text-yellow dark:text-yellow-dark hover:underline' href='https://github.com/Divlo/Divlo' target='_blank' rel='noopener noreferrer'>github</a>."
}
]
},

5
next-env.d.ts vendored
View File

@ -1,5 +0,0 @@
/// <reference types="next" />
/// <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,41 +1,13 @@
const nextPWA = require('next-pwa')
const nextTranslate = require('next-translate')
const { createSecureHeaders } = require('next-secure-headers')
const nextPWA = require('next-pwa')({
disable: process.env.NODE_ENV !== 'production',
dest: 'public'
})
const nextTranslate = require('next-translate-plugin')
/** @type {import("next").NextConfig} */
module.exports = nextTranslate(
nextPWA({
reactStrictMode: true,
pwa: {
disable: process.env.NODE_ENV !== 'production',
dest: 'public'
},
headers() {
return [
{
source: '/:path*',
headers: createSecureHeaders({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
scriptSrc: [
"'self'",
'data:',
"'unsafe-eval'",
"'unsafe-inline'"
],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ['*', 'data:', 'blob:'],
mediaSrc: "'none'",
connectSrc: '*',
objectSrc: "'none'",
fontSrc: "'self'",
baseURI: "'none'"
}
}
})
}
]
}
})
)
const nextConfig = {
reactStrictMode: true,
output: 'standalone'
}
module.exports = nextTranslate(nextPWA(nextConfig))

49126
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,106 +1,101 @@
{
"name": "divlo",
"version": "2.0.1",
"version": "2.7.1",
"private": true,
"repository": {
"type": "git",
"url": "https://github.com/Divlo/Divlo"
},
"engines": {
"node": ">=14.0.0",
"npm": ">=7.0.0"
"node": ">=16.0.0",
"npm": ">=8.0.0"
},
"scripts": {
"dev": "next dev",
"start": "next start",
"build": "npm run resume:export && next build",
"export": "next export",
"build": "npm run resume:build && next build",
"lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint \"**/*.{md,mdx}\" --dot --ignore-path \".gitignore\"",
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\"",
"lint:prettier": "prettier \".\" --check",
"lint:markdown": "markdownlint-cli2",
"lint:eslint": "eslint \".\" --ignore-path \".gitignore\"",
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"",
"lint:staged": "lint-staged",
"test:unit": "jest",
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://localhost:3000\" \"html-w3c-validator\"",
"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\"",
"resume:validate": "resume validate",
"resume:serve": "resume serve --theme \"custom\"",
"resume:export": "resume export \"./public/curriculum-vitae.html\" --format \"html\" --theme \"custom\"",
"test:unit": "cypress run --component",
"test:html-w3c-validator": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"html-w3c-validator\"",
"test:e2e": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"cypress run\"",
"test:dev": "start-server-and-test \"dev\" \"http://127.0.0.1:3000\" \"cypress open\"",
"resume:build": "node ./jsonresume-theme-custom/build.js",
"release": "semantic-release",
"deploy": "vercel",
"postinstall": "husky install"
},
"dependencies": {
"@fontsource/montserrat": "4.5.5",
"@fortawesome/fontawesome-svg-core": "1.3.0",
"@fortawesome/free-brands-svg-icons": "6.0.0",
"@fortawesome/free-solid-svg-icons": "6.0.0",
"@fortawesome/react-fontawesome": "0.1.17",
"classnames": "2.3.1",
"date-and-time": "2.1.2",
"@fontsource/montserrat": "5.0.0",
"@fortawesome/fontawesome-svg-core": "6.4.0",
"@fortawesome/free-brands-svg-icons": "6.4.0",
"@fortawesome/free-solid-svg-icons": "6.4.0",
"@fortawesome/react-fontawesome": "0.2.0",
"@giscus/react": "2.2.8",
"clsx": "1.2.1",
"date-and-time": "3.0.0",
"gray-matter": "4.0.3",
"html-react-parser": "1.4.8",
"next": "12.1.0",
"next-mdx-remote": "4.0.0",
"next-pwa": "5.4.4",
"next-themes": "0.1.1",
"next-translate": "1.3.4",
"react": "17.0.2",
"react-dom": "17.0.2",
"read-pkg": "7.1.0",
"html-react-parser": "3.0.16",
"katex": "0.16.7",
"next": "13.4.3",
"next-mdx-remote": "4.4.1",
"next-pwa": "5.6.0",
"next-themes": "0.2.1",
"next-translate": "2.0.5",
"react": "18.2.0",
"react-dom": "18.2.0",
"read-pkg": "8.0.0",
"rehype-katex": "6.0.3",
"rehype-raw": "6.1.1",
"rehype-slug": "5.0.1",
"rehype-slug": "5.1.0",
"remark-gfm": "3.0.1",
"sharp": "0.30.1",
"shiki": "0.10.1",
"unified": "10.1.1",
"unist-util-visit": "4.1.0",
"remark-math": "5.1.1",
"sharp": "0.32.1",
"shiki": "0.14.2",
"unified": "10.1.2",
"unist-util-visit": "4.1.2",
"universal-cookie": "4.0.4"
},
"devDependencies": {
"@commitlint/cli": "16.2.1",
"@commitlint/config-conventional": "16.2.1",
"@lhci/cli": "0.9.0",
"@saithodev/semantic-release-backmerge": "2.1.1",
"@commitlint/cli": "17.6.3",
"@commitlint/config-conventional": "17.6.3",
"@saithodev/semantic-release-backmerge": "3.2.0",
"@semantic-release/git": "10.0.1",
"@tailwindcss/typography": "0.5.2",
"@testing-library/jest-dom": "5.16.2",
"@testing-library/react": "12.1.3",
"@types/date-and-time": "0.13.0",
"@types/jest": "27.4.0",
"@types/node": "17.0.19",
"@types/react": "17.0.39",
"@tailwindcss/typography": "0.5.9",
"@tsconfig/strictest": "2.0.1",
"@types/node": "20.2.1",
"@types/react": "18.2.6",
"@types/unist": "2.0.6",
"@typescript-eslint/eslint-plugin": "5.12.1",
"autoprefixer": "10.4.2",
"cypress": "9.5.0",
"editorconfig-checker": "4.0.2",
"eslint": "8.9.0",
"eslint-config-conventions": "1.1.0",
"eslint-config-next": "12.1.0",
"eslint-config-prettier": "8.4.0",
"eslint-plugin-import": "2.25.4",
"eslint-plugin-prettier": "4.0.0",
"eslint-plugin-promise": "6.0.0",
"eslint-plugin-unicorn": "41.0.0",
"html-w3c-validator": "1.0.0",
"husky": "7.0.4",
"jest": "27.5.1",
"@typescript-eslint/eslint-plugin": "5.59.6",
"autoprefixer": "10.4.14",
"cypress": "12.12.0",
"editorconfig-checker": "5.0.1",
"eslint": "8.41.0",
"eslint-config-conventions": "9.0.0",
"eslint-config-next": "13.4.3",
"eslint-config-prettier": "8.8.0",
"eslint-plugin-import": "2.27.5",
"eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "47.0.0",
"html-w3c-validator": "1.3.0",
"husky": "8.0.3",
"jsonresume-theme-custom": "file:./jsonresume-theme-custom",
"lint-staged": "12.3.4",
"markdownlint-cli": "0.31.1",
"next-secure-headers": "2.2.0",
"postcss": "8.4.6",
"prettier": "2.5.1",
"prettier-plugin-tailwindcss": "0.1.7",
"resume-cli": "3.0.6",
"semantic-release": "19.0.2",
"start-server-and-test": "1.14.0",
"tailwindcss": "3.0.23",
"typescript": "4.4.4",
"vercel": "24.0.0"
"lint-staged": "13.2.2",
"markdownlint-cli2": "0.7.1",
"markdownlint-rule-relative-links": "1.2.0",
"next-translate-plugin": "2.0.5",
"postcss": "8.4.23",
"prettier": "2.8.8",
"prettier-plugin-tailwindcss": "0.3.0",
"semantic-release": "21.0.2",
"start-server-and-test": "2.0.0",
"tailwindcss": "3.3.2",
"typescript": "5.0.4",
"vercel": "29.3.6"
}
}

View File

@ -1,38 +1,32 @@
import { GetStaticProps, NextPage } from 'next'
import type { GetStaticProps, NextPage } from 'next'
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'
import { getDefaultDescription } from 'utils/getDefaultDescription'
import type { FooterProps } from 'components/Footer'
interface Error404Props extends FooterProps {
description: string
}
interface Error404Props extends FooterProps {}
const Error404: NextPage<Error404Props> = (props) => {
const { t } = useTranslation()
const { version, description } = props
const { version } = props
return (
<>
<Head title='404 | Divlo' description={description} />
<Header showLanguage />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
<ErrorPage statusCode={404} message={t('errors:not-found')} />
</main>
<Footer version={version} />
<Head title='404 | Divlo' />
<ErrorPage
statusCode={404}
message={t('errors:not-found')}
version={version}
/>
</>
)
}
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
export const getStaticProps: GetStaticProps<Error404Props> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
const description = getDefaultDescription()
return { props: { version, description } }
return { props: { version } }
}
export default Error404

View File

@ -1,38 +1,32 @@
import { GetStaticProps, NextPage } from 'next'
import type { GetStaticProps, NextPage } from 'next'
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'
import { getDefaultDescription } from 'utils/getDefaultDescription'
import type { FooterProps } from 'components/Footer'
interface Error500Props extends FooterProps {
description: string
}
interface Error500Props extends FooterProps {}
const Error500: NextPage<Error500Props> = (props) => {
const { t } = useTranslation()
const { version, description } = props
const { version } = props
return (
<>
<Head title='500 | Divlo' description={description} />
<Header showLanguage />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
<ErrorPage statusCode={500} message={t('errors:server-error')} />
</main>
<Footer version={version} />
<Head title='500 | Divlo' />
<ErrorPage
statusCode={500}
message={t('errors:server-error')}
version={version}
/>
</>
)
}
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
export const getStaticProps: GetStaticProps<Error500Props> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
const description = getDefaultDescription()
return { props: { version, description } }
return { props: { version } }
}
export default Error500

View File

@ -1,5 +1,5 @@
import { useEffect } from 'react'
import { AppProps } from 'next/app'
import type { AppType } from 'next/app'
import { ThemeProvider } from 'next-themes'
import useTranslation from 'next-translate/useTranslation'
import UniversalCookie from 'universal-cookie'
@ -13,7 +13,7 @@ const universalCookie = new UniversalCookie()
/** how long in seconds, until the cookie expires (10 years) */
const COOKIE_MAX_AGE = 10 * 365.25 * 24 * 60 * 60
const Application = ({ Component, pageProps }: AppProps): JSX.Element => {
const Application: AppType = ({ Component, pageProps }) => {
const { lang } = useTranslation()
useEffect(() => {

View File

@ -1,10 +1,16 @@
import { GetStaticProps, GetStaticPaths, NextPage } from 'next'
import type { GetStaticProps, GetStaticPaths, NextPage } from 'next'
import Image from 'next/image'
import { MDXRemote } from 'next-mdx-remote'
import date from 'date-and-time'
import Giscus from '@giscus/react'
import { useTheme } from 'next-themes'
import 'katex/dist/katex.min.css'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { Footer, FooterProps } from 'components/Footer'
import type { FooterProps } from 'components/Footer'
import { Footer } from 'components/Footer'
import type { Post } from 'utils/blog'
interface BlogPostPageProps extends FooterProps {
@ -14,6 +20,8 @@ interface BlogPostPageProps extends FooterProps {
const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
const { version, post } = props
const { theme = 'dark' } = useTheme()
return (
<>
<Head
@ -22,27 +30,58 @@ const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
/>
<Header />
<main className='flex flex-1 flex-col flex-wrap items-center'>
<div className='my-10 flex flex-col items-center'>
<main className='break-wrap-words flex flex-1 flex-col flex-wrap items-center'>
<div className='my-10 flex flex-col items-center text-center'>
<h1 className='text-3xl font-semibold'>{post.frontmatter.title}</h1>
<p className='mt-2' data-cy='blog-post-date'>
{date.format(new Date(post.frontmatter.publishedOn), 'DD/MM/YYYY')}
</p>
</div>
<div className='prose mb-10 px-8'>
<MDXRemote
{...post.source}
components={{
a: (props: React.ComponentPropsWithoutRef<'a'>) => {
if (props.href?.startsWith('#') ?? false) {
return <a {...props} />
<div className='prose mb-10'>
<div className='px-8'>
<MDXRemote
{...post.source}
components={{
img: (properties) => {
const { src = '', alt = 'Blog Image' } = properties
const source = src.replace('../public/', '/')
return (
<span className='flex flex-col items-center justify-center'>
<Image
src={source}
alt={alt}
width={1000}
height={1000}
className='h-auto w-auto'
/>
</span>
)
},
a: (props) => {
if (props.href?.startsWith('#') ?? false) {
return <a {...props} />
}
return (
<a target='_blank' rel='noopener noreferrer' {...props} />
)
}
return (
<a target='_blank' rel='noopener noreferrer' {...props} />
)
}
}}
/>
}}
/>
<Giscus
id='comments'
repo='Divlo/Divlo'
repoId='MDEwOlJlcG9zaXRvcnkzNTg5NDg1NDQ='
category='General'
categoryId='DIC_kwDOFWUewM4CQ_WK'
mapping='pathname'
reactionsEnabled='1'
emitMetadata='0'
inputPosition='top'
theme={theme}
lang='en'
loading='lazy'
/>
</div>
</div>
</main>
<Footer version={version} />
@ -53,7 +92,7 @@ const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
export const getStaticProps: GetStaticProps<BlogPostPageProps> = async (
context
) => {
const slug = context?.params?.slug
const slug = context?.params?.['slug']
const { getPostBySlug } = await import('utils/blog')
const post = await getPostBySlug(slug)
if (post == null || (post != null && !post.frontmatter.isPublished)) {

View File

@ -1,10 +1,11 @@
import { GetStaticProps, NextPage } from 'next'
import type { GetStaticProps, NextPage } from 'next'
import Link from 'next/link'
import date from 'date-and-time'
import { Head } from 'components/Head'
import { Header } from 'components/Header'
import { Footer, FooterProps } from 'components/Footer'
import type { FooterProps } from 'components/Footer'
import { Footer } from 'components/Footer'
import { ShadowContainer } from 'components/design/ShadowContainer'
import type { PostMetadata } from 'utils/blog'
@ -38,23 +39,26 @@ const BlogPage: NextPage<BlogPageProps> = (props) => {
'DD/MM/YYYY'
)
return (
<Link href={`/blog/${post.slug}`} key={index} locale='en'>
<a data-cy={post.slug}>
<ShadowContainer className='cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2'>
<h2
data-cy='blog-post-title'
className='text-xl font-semibold'
>
{post.frontmatter.title}
</h2>
<p data-cy='blog-post-date' className='mt-2'>
{postPublishedOn}
</p>
<p data-cy='blog-post-description' className='mt-3'>
{post.frontmatter.description}
</p>
</ShadowContainer>
</a>
<Link
href={`/blog/${post.slug}`}
key={index}
locale='en'
data-cy={post.slug}
>
<ShadowContainer className='cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2'>
<h2
data-cy='blog-post-title'
className='text-xl font-semibold'
>
{post.frontmatter.title}
</h2>
<p data-cy='blog-post-date' className='mt-2'>
{postPublishedOn}
</p>
<p data-cy='blog-post-description' className='mt-3'>
{post.frontmatter.description}
</p>
</ShadowContainer>
</Link>
)
})}

View File

@ -1,4 +1,4 @@
import { GetStaticProps, NextPage } from 'next'
import type { GetStaticProps, NextPage } from 'next'
import useTranslation from 'next-translate/useTranslation'
import { RevealFade } from 'components/design/RevealFade'
@ -11,20 +11,18 @@ 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'
import { getDefaultDescription } from 'utils/getDefaultDescription'
import type { FooterProps } from 'components/Footer'
import { Footer } from 'components/Footer'
interface HomeProps extends FooterProps {
description: string
}
interface HomeProps extends FooterProps {}
const Home: NextPage<HomeProps> = (props) => {
const { t } = useTranslation()
const { version, description } = props
const { version } = props
return (
<>
<Head description={description} />
<Head />
<Header showLanguage />
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
@ -74,11 +72,10 @@ const Home: NextPage<HomeProps> = (props) => {
)
}
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
export const getStaticProps: GetStaticProps<HomeProps> = async () => {
const { readPackage } = await import('read-pkg')
const { version } = await readPackage()
const description = getDefaultDescription()
return { props: { version, description } }
return { props: { version } }
}
export default Home

View File

@ -7,13 +7,13 @@ publishedOn: '2022-02-23T08:00:18.758Z'
Hello! 👋
Have you already heard of "**Clean Code**" or "**Design Patterns**" ?
Have you already heard of "**Clean Code**" or "**Design Patterns**"?
Even if you know what it is about, this blog post will probably still be useful to you, I will share some tips and tricks to make your code more readable and maintainable in the long term.
**Note:** Sources used to write this blog post are available at the [end of this post](#sources).
## Definition : Clean Code
## Definition: Clean Code
A clean code is a code that is **easy** to **read** and easy to **understand**.
@ -23,7 +23,7 @@ We could ask ourselves, what is **easy** to **read** and easy to **understand**
It depends of many factors, and is somewhat relative to each one of us. The **perfect** Clean code **doesn't exist**, but we can try to be **as perfect as possible**.
## Why is it so important ?
## Why is it so important?
Code like that works great, but it is not enough, even if the code will be read by the computer and understood by the machine, we should not forget that the code is **written by human** and will be also **read by human** not only a machine.
@ -31,7 +31,7 @@ For example the [Linux kernel](https://www.kernel.org/), is one of the biggest o
With a project of this magnitude, we can't let everyone do what they want and however they want, **we must set rules and conventions** to get everyone to agree, this allows to add features faster and will reduce possible bugs as **developers** will not struggle as much to understand the code.
## Definition : Design Patterns
## Definition: Design Patterns
These **rules** and **conventions** are so called **Design Patterns**.
@ -77,8 +77,8 @@ setTimeout(restart, 86400000)
##### Example (good way)
```typescript
const MILLISECONDS_IN_A_DAY = 24 * 60 * 60 * 1000
setTimeout(restart, MILLISECONDS_IN_A_DAY)
const MILLISECONDS_IN_ONE_DAY = 24 * 60 * 60 * 1000
setTimeout(restart, MILLISECONDS_IN_ONE_DAY)
```
---
@ -131,7 +131,9 @@ const printCar = (car: Car): void => {
---
#### Boolean names (Prefix: is, has, can)
#### Boolean names
The name of a boolean variable should be a question, and the answer should be true or false. We can use prefixes like `is`, `has`, `can` to make it more explicit and we should avoid negation.
##### Example (bad way)
@ -171,7 +173,10 @@ We have to keep it as simple as possible, not to implement features that are not
import fs from 'node:fs'
import path from 'node:path'
const createFile = async (name: string, isTemporary: boolean = false) => {
const createFile = async (
name: string,
isTemporary: boolean = false
): Promise<void> => {
if (isTemporary) {
return await fs.promises.writeFile(path.join('temporary', name), '')
}
@ -187,11 +192,11 @@ const createFile = async (name: string, isTemporary: boolean = false) => {
import fs from 'node:fs'
import path from 'node:path'
const createFile = async (name: string) => {
const createFile = async (name: string): Promise<void> => {
await fs.promises.writeFile(name, '')
}
const createTemporaryFile = async (name: string) => {
const createTemporaryFile = async (name: string): Promise<void> => {
await createFile(path.join('temporary', name))
}
```
@ -210,7 +215,7 @@ The End To End (e2e) and Unit tests should document what is the behavior intende
### Avoid comments
One of the most important rule of "Clean Code" : If you need to add **comments**, it's because your code is **not clean**.
One of the most important rule of "Clean Code": If you need to add **comments**, it's because your code is **not clean**.
I know that might be counter intuitive at first, as most developers will advice you to add comments to your code, to document what it does.

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