mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
Compare commits
229 Commits
Author | SHA1 | Date | |
---|---|---|---|
94212f9b5c | |||
bf9347f685 | |||
896b6051e8 | |||
b5f3552c07 | |||
5fbae8601f | |||
48d35776a9 | |||
8b9e58c47c | |||
33078ece66 | |||
a2da9618af | |||
a467ea7aff | |||
0e0036b737 | |||
729e540d04 | |||
e5f4615f7f | |||
0bf89f4df5 | |||
bcb184e49c | |||
1505b81233 | |||
a30355582e | |||
a4effb52f9 | |||
52bba0ef9c | |||
8ecfeca50d | |||
fd0740d12a | |||
bd2dc9c9af | |||
a53888ab42 | |||
624e79eecd | |||
049ec367fc | |||
56f22d0c9b | |||
9adb67662e | |||
010087088f | |||
35d4396e80 | |||
934118737a | |||
b692dac926 | |||
dd582652ab | |||
337352de0c | |||
c513268cbb | |||
4fdcb2b667 | |||
377b8e91a6 | |||
fce29c9d4a | |||
c198f47aa9 | |||
8e051332cd | |||
9f3436e1df | |||
2f2373e62f | |||
c6b455dd10 | |||
4e089b41f2 | |||
6c102b1b35 | |||
52b10944b7 | |||
db36eb3e7a | |||
c739ad951d | |||
2802ff029f | |||
1a7457b44b | |||
ff210f879d | |||
607454b360 | |||
d1522fbf44 | |||
b82eae7499 | |||
73527ce8fe | |||
0cd885ee70 | |||
2cb2df975f | |||
37f5843adb | |||
d794d38f14 | |||
fc5ba28b8a | |||
b5945150b8 | |||
aa12d626d2 | |||
6ac4782b7d | |||
0aa998d593 | |||
56f975e53c | |||
5a16d24ea1 | |||
52267005ec | |||
99b9b12ac9 | |||
2cae77481f | |||
e98b47a459 | |||
4cc87758c1 | |||
1bb0f31223 | |||
af2dd0bd60 | |||
63d7485c8d | |||
74fde0ea40 | |||
0d2b318818 | |||
266b3f8589 | |||
f7d304ca80 | |||
63017953d7 | |||
20600eb976 | |||
7f920b77aa | |||
4f5dfc63ea | |||
712805df93 | |||
cd68f597c9 | |||
7ec3fe8ced | |||
90d22b2c7f | |||
4b06fd0522 | |||
b4427f36c2 | |||
b758c64e02 | |||
04469b83ea | |||
36d54666a0 | |||
a34cefec6e | |||
5c343395df | |||
028815a7b6 | |||
a2ad591d6d | |||
7087911756 | |||
35b1c4169f | |||
4c351b8179 | |||
701dccc018 | |||
5133765f94 | |||
3b208c6614 | |||
52870fd6a4 | |||
3a278fec10 | |||
669f592a9f | |||
9c0a3ea1af | |||
fa8d70bf82 | |||
3293fd488e | |||
426bee09da | |||
dbc6c84895 | |||
fab539c9d7 | |||
176ab64a37 | |||
1b56bbc694 | |||
0f9a968081 | |||
6b9ff4100d | |||
870bc3d26b | |||
41e4b93427 | |||
72ae4ef01d | |||
748259b57c | |||
fafd606c18 | |||
b8c3022532 | |||
46adaee53f | |||
508114152c | |||
b2852d172c | |||
16e3b1e465 | |||
ae610ff816 | |||
7c001f3c30 | |||
7eada755e1 | |||
6909304f15 | |||
25b2f05170 | |||
0cc83a811c | |||
78b14c2620 | |||
eebdf0edd2 | |||
62e8005081 | |||
6473e9da7d | |||
1805997f59 | |||
fb25c12883 | |||
849b758fab | |||
ccf5d42c19 | |||
2d68ce59ca | |||
4e6531e341 | |||
8f2d0817ce | |||
7674401e7c | |||
61983dfc4a | |||
ed47407b7d | |||
0a79754978 | |||
725afecbf3 | |||
1bf79e55e1 | |||
3a369c49fa | |||
e78ccf3db4 | |||
acafe71f31 | |||
3ef876b737 | |||
b30bbc99e9 | |||
235c072c21 | |||
f5bdd85b73 | |||
b81ae5a9a6 | |||
1ea5e3f323 | |||
f6eaef54b9 | |||
5b14361d74 | |||
d1f9c0eb2f | |||
95b27abec1 | |||
228e987d8b | |||
7c44102afd | |||
b8410e5628 | |||
d6f0b12b17 | |||
b02e31c373 | |||
e012d41929 | |||
4bd77b45e4 | |||
e43f572588 | |||
9aecb3cab9 | |||
f1256ab23f | |||
892bf0e87a | |||
61ef6c5525 | |||
38405d658e | |||
f3b7c315f0 | |||
6950286eec | |||
60f966c493 | |||
7af4d3c512 | |||
d9b53480be | |||
a574a8ffd1 | |||
b0a34c6162 | |||
ea04f0f189 | |||
1403cdf80c | |||
40e676cfc7 | |||
5f654020d5 | |||
a3ec87bf52 | |||
88588355fd | |||
c329c56094 | |||
08a5454cf4 | |||
8faf47c06e | |||
d7f778de28 | |||
cd3cc50e00 | |||
755f2da03a | |||
7ef9f79b97 | |||
2c53a1409c | |||
1e2d5c0f3e | |||
6db7ed2f5e | |||
9b8102cbdc | |||
e0bc1fed49 | |||
c230f5bb51 | |||
6f4819b689 | |||
fd67737754 | |||
6f94865917 | |||
1e4167e209 | |||
655ed6f6f6 | |||
8fe73be90b | |||
e925b73606 | |||
b902b9a122 | |||
1044302118 | |||
df15232312 | |||
f5d273688d | |||
993dd1e30e | |||
83f90e24c7 | |||
c3fd177ff5 | |||
c5f8b4fb13 | |||
a4e48de57e | |||
e3aa2a4d50 | |||
88c44ed31f | |||
d3d1ca7beb | |||
8d758bc1d7 | |||
34b5f123b4 | |||
809f4612b5 | |||
1f48f7a296 | |||
56258dc06b | |||
3fea7d48f6 | |||
f49ca1f4f2 | |||
e9d9139263 | |||
98e7987b04 | |||
4dc145fe75 | |||
c8b12cd618 | |||
97cf63f643 |
@ -1,3 +0,0 @@
|
|||||||
{
|
|
||||||
"presets": ["next/babel"]
|
|
||||||
}
|
|
2
.devcontainer/Dockerfile
Normal file
2
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ARG VARIANT="16"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
23
.devcontainer/devcontainer.json
Normal file
23
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"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",
|
||||||
|
"davidanson.vscode-markdownlint",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
|
],
|
||||||
|
"forwardPorts": [3000],
|
||||||
|
"postAttachCommand": ["npm", "install"],
|
||||||
|
"remoteUser": "node"
|
||||||
|
}
|
10
.devcontainer/docker-compose.yml
Normal file
10
.devcontainer/docker-compose.yml
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
version: '3.0'
|
||||||
|
|
||||||
|
services:
|
||||||
|
workspace:
|
||||||
|
build:
|
||||||
|
context: './'
|
||||||
|
dockerfile: './Dockerfile'
|
||||||
|
volumes:
|
||||||
|
- '..:/workspace:cached'
|
||||||
|
command: 'sleep infinity'
|
@ -1,11 +1,12 @@
|
|||||||
.vscode
|
.vscode
|
||||||
.git
|
.git
|
||||||
.next
|
.env
|
||||||
build
|
build
|
||||||
|
.next
|
||||||
coverage
|
coverage
|
||||||
dist
|
|
||||||
node_modules
|
node_modules
|
||||||
out
|
tmp
|
||||||
**/workbox-*.js
|
temp
|
||||||
**/sw.js
|
.DS_Store
|
||||||
**/__test__/**
|
.lighthouseci
|
||||||
|
.vercel
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
.next
|
.next
|
||||||
.lighthouseci
|
.lighthouseci
|
||||||
|
storybook-static
|
||||||
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
**/workbox-*.js
|
**/workbox-*.js
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard-with-typescript", "eslint-config-prettier"],
|
"extends": ["conventions", "next/core-web-vitals", "prettier"],
|
||||||
"plugins": ["eslint-plugin-prettier"],
|
"plugins": ["prettier", "unicorn"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
@ -10,6 +10,7 @@
|
|||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": "error"
|
"prettier/prettier": "error",
|
||||||
|
"unicorn/prefer-node-protocol": "off"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
1
.gitattributes
vendored
Normal file
1
.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
* text=auto eol=lf
|
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
9
.github/PULL_REQUEST_TEMPLATE.md
vendored
@ -1,11 +1,4 @@
|
|||||||
<!--
|
<!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. -->
|
||||||
|
|
||||||
Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time.
|
|
||||||
|
|
||||||
Before submitting your contribution, please take a moment to review this document:
|
|
||||||
https://github.com/Divlo/Divlo/blob/master/.github/CONTRIBUTING.md
|
|
||||||
|
|
||||||
-->
|
|
||||||
|
|
||||||
## What changes this PR introduce?
|
## What changes this PR introduce?
|
||||||
|
|
||||||
|
16
.github/dependabot.yml
vendored
16
.github/dependabot.yml
vendored
@ -1,16 +0,0 @@
|
|||||||
version: 2
|
|
||||||
updates:
|
|
||||||
- package-ecosystem: 'github-actions'
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: 'daily'
|
|
||||||
|
|
||||||
- package-ecosystem: 'docker'
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: 'daily'
|
|
||||||
|
|
||||||
- package-ecosystem: 'npm'
|
|
||||||
directory: '/'
|
|
||||||
schedule:
|
|
||||||
interval: 'daily'
|
|
38
.github/workflows/Divlo.yml
vendored
38
.github/workflows/Divlo.yml
vendored
@ -1,38 +0,0 @@
|
|||||||
name: 'Divlo'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
ci:
|
|
||||||
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 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'
|
|
@ -1,14 +1,13 @@
|
|||||||
name: 'CodeQL'
|
name: 'Analyze'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [develop]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master, develop]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
analyze:
|
analyze:
|
||||||
name: 'Analyze'
|
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
@ -17,7 +16,7 @@ jobs:
|
|||||||
language: ['javascript']
|
language: ['javascript']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2.4.0'
|
||||||
|
|
||||||
- name: 'Initialize CodeQL'
|
- name: 'Initialize CodeQL'
|
||||||
uses: 'github/codeql-action/init@v1'
|
uses: 'github/codeql-action/init@v1'
|
25
.github/workflows/build.yml
vendored
Normal file
25
.github/workflows/build.yml
vendored
Normal 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@v2'
|
||||||
|
|
||||||
|
- 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'
|
50
.github/workflows/lint.yml
vendored
Normal file
50
.github/workflows/lint.yml
vendored
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
name: 'Lint'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2'
|
||||||
|
|
||||||
|
- 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'
|
50
.github/workflows/release.yml
vendored
50
.github/workflows/release.yml
vendored
@ -1,34 +1,44 @@
|
|||||||
name: 'Release'
|
name: 'Release'
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_run:
|
push:
|
||||||
workflows: [Divlo]
|
|
||||||
branches: [master]
|
branches: [master]
|
||||||
types:
|
|
||||||
- 'completed'
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
release:
|
release:
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2.4.0'
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: 'actions/setup-node@v2.1.5'
|
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
- name: 'Cache dependencies'
|
- name: 'Import GPG key'
|
||||||
uses: 'actions/cache@v2.1.5'
|
uses: 'crazy-max/ghaction-import-gpg@v4'
|
||||||
with:
|
with:
|
||||||
path: '.npm'
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
git_user_signingkey: true
|
||||||
|
git_commit_gpgsign: true
|
||||||
|
|
||||||
- run: 'npm install --global npm@7'
|
- name: 'Use Node.js'
|
||||||
- run: 'npm ci --cache .npm --prefer-offline'
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
- run: 'npm run release'
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
|
- name: 'Release'
|
||||||
|
run: 'npm run release'
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }}
|
||||||
|
GIT_COMMITTER_EMAIL: ${{ secrets.GIT_EMAIL }}
|
||||||
|
|
||||||
|
- name: 'Deploy to Vercel'
|
||||||
|
run: 'npm run deploy -- --token="${VERCEL_TOKEN}" --prod'
|
||||||
|
env:
|
||||||
|
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
70
.github/workflows/test.yml
vendored
Normal file
70
.github/workflows/test.yml
vendored
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
name: 'Test'
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [develop]
|
||||||
|
pull_request:
|
||||||
|
branches: [master, develop]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test-unit:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@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'
|
12
.gitignore
vendored
12
.gitignore
vendored
@ -11,13 +11,16 @@ out
|
|||||||
# production
|
# production
|
||||||
build
|
build
|
||||||
dist
|
dist
|
||||||
|
public/*.html
|
||||||
|
# PWA
|
||||||
|
public/workbox-*.js
|
||||||
|
public/sw.js
|
||||||
|
|
||||||
# testing
|
# testing
|
||||||
coverage
|
coverage
|
||||||
|
cypress/screenshots
|
||||||
# PWA
|
cypress/videos
|
||||||
**/workbox-*.js
|
cypress/downloads
|
||||||
**/sw.js
|
|
||||||
|
|
||||||
# envs
|
# envs
|
||||||
.env
|
.env
|
||||||
@ -45,3 +48,4 @@ npm-debug.log*
|
|||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.lighthouseci
|
.lighthouseci
|
||||||
|
.vercel
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
image: 'gitpod/workspace-full'
|
image: 'gitpod/workspace-full'
|
||||||
|
|
||||||
tasks:
|
tasks:
|
||||||
- before: 'cp .env.example .env && npm install --global npm@7'
|
- before: 'cp .env.example .env'
|
||||||
init: 'npm clean-install'
|
init: 'npm install'
|
||||||
command: 'npm run dev'
|
command: 'npm run dev'
|
||||||
|
|
||||||
ports:
|
ports:
|
||||||
|
7
.html-w3c-validatorrc.json
Normal file
7
.html-w3c-validatorrc.json
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"urls": [
|
||||||
|
"http://localhost:3000/",
|
||||||
|
"http://localhost:3000/blog",
|
||||||
|
"http://localhost:3000/blog/hello-world"
|
||||||
|
]
|
||||||
|
}
|
1
.husky/.gitignore
vendored
1
.husky/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
_
|
|
@ -4,21 +4,22 @@
|
|||||||
"startServerCommand": "npm run start",
|
"startServerCommand": "npm run start",
|
||||||
"startServerReadyPattern": "ready on",
|
"startServerReadyPattern": "ready on",
|
||||||
"startServerReadyTimeout": 20000,
|
"startServerReadyTimeout": 20000,
|
||||||
"url": ["http://localhost:3000/"],
|
"url": [
|
||||||
"numberOfRuns": 3
|
"http://localhost:3000/",
|
||||||
|
"http://localhost:3000/blog",
|
||||||
|
"http://localhost:3000/blog/hello-world"
|
||||||
|
],
|
||||||
|
"numberOfRuns": 1
|
||||||
},
|
},
|
||||||
"assert": {
|
"assert": {
|
||||||
"preset": "lighthouse:recommended",
|
"preset": "lighthouse:recommended",
|
||||||
"assertions": {
|
"assertions": {
|
||||||
"legacy-javascript": "off",
|
"csp-xss": "warning",
|
||||||
"unused-javascript": "off",
|
"non-composited-animations": "warning",
|
||||||
"uses-rel-preload": "off",
|
"unused-javascript": "warning",
|
||||||
"canonical": "off",
|
"image-size-responsive": "warning",
|
||||||
"unsized-images": "off",
|
"unsized-images": "warning",
|
||||||
"uses-responsive-images": "off",
|
"color-contrast": "warning"
|
||||||
"bypass": "warning",
|
|
||||||
"color-contrast": "warning",
|
|
||||||
"preload-lcp-image": "warning"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"*": ["editorconfig-checker"],
|
"*": ["editorconfig-checker"],
|
||||||
"*.{js,ts,jsx,tsx}": [
|
"*.{js,jsx,ts,tsx}": [
|
||||||
"prettier --write",
|
"prettier --write",
|
||||||
"eslint --fix",
|
"eslint --fix",
|
||||||
"jest --findRelatedTests"
|
"jest --findRelatedTests"
|
||||||
],
|
],
|
||||||
"*.{css,yml,json}": ["prettier --write"],
|
"*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"],
|
||||||
"*.{md}": ["prettier --write", "markdownlint --dot --fix"],
|
"*.{md,mdx}": ["prettier --write", "markdownlint --dot --fix"],
|
||||||
"./Dockerfile": ["dockerfilelint"]
|
"resume.json": ["resume validate"]
|
||||||
}
|
}
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
.next
|
.next
|
||||||
.lighthouseci
|
.lighthouseci
|
||||||
|
storybook-static
|
||||||
|
coverage
|
||||||
node_modules
|
node_modules
|
||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
package.json
|
|
||||||
package-lock.json
|
|
||||||
**/workbox-*.js
|
**/workbox-*.js
|
||||||
**/sw.js
|
**/sw.js
|
||||||
|
*.hbs
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"branches": ["master"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[
|
[
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
@ -6,7 +7,32 @@
|
|||||||
"preset": "conventionalcommits"
|
"preset": "conventionalcommits"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"@semantic-release/release-notes-generator",
|
[
|
||||||
"@semantic-release/github"
|
"@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",
|
||||||
|
{
|
||||||
|
"branches": [{ "from": "master", "to": "develop" }],
|
||||||
|
"backmergeStrategy": "merge"
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
4
.vscode/extensions.json
vendored
4
.vscode/extensions.json
vendored
@ -7,7 +7,7 @@
|
|||||||
"divlo.vscode-styled-jsx-languageserver",
|
"divlo.vscode-styled-jsx-languageserver",
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
"coenraads.bracket-pair-colorizer",
|
"davidanson.vscode-markdownlint",
|
||||||
"davidanson.vscode-markdownlint"
|
"ms-azuretools.vscode-docker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
3
.vscode/settings.json
vendored
3
.vscode/settings.json
vendored
@ -1,7 +1,8 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
"editor.bracketPairColorization.enabled": true,
|
||||||
"prettier.configPath": ".prettierrc.json",
|
"prettier.configPath": ".prettierrc.json",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": true
|
||||||
|
@ -13,7 +13,7 @@ Thanks a lot for your interest in contributing to **divlo.fr**! 🎉
|
|||||||
|
|
||||||
- **Please first discuss** the change you wish to make via [issue](https://github.com/Divlo/Divlo/issues) before making a change. It might avoid a waste of your time.
|
- **Please first discuss** the change you wish to make via [issue](https://github.com/Divlo/Divlo/issues) before making a change. It might avoid a waste of your time.
|
||||||
|
|
||||||
- Ensure your code respect [Typescript Standard Style](https://www.npmjs.com/package/ts-standard).
|
- Ensure your code respect linting.
|
||||||
|
|
||||||
- Make sure your **code passes the tests**.
|
- Make sure your **code passes the tests**.
|
||||||
|
|
||||||
@ -49,6 +49,11 @@ Scopes define what part of the code changed.
|
|||||||
|
|
||||||
[](https://gitpod.io/#https://github.com/Divlo/Divlo)
|
[](https://gitpod.io/#https://github.com/Divlo/Divlo)
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- [Node.js](https://nodejs.org/) >= 16.0.0
|
||||||
|
- [npm](https://www.npmjs.com/) >= 8.0.0
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
@ -60,9 +65,19 @@ cd Divlo
|
|||||||
|
|
||||||
# Configure environment variables
|
# Configure environment variables
|
||||||
cp .env.example .env
|
cp .env.example .env
|
||||||
|
|
||||||
|
# Install
|
||||||
|
npm install
|
||||||
```
|
```
|
||||||
|
|
||||||
### Development environment with [Docker](https://www.docker.com/)
|
### Local Development environment
|
||||||
|
|
||||||
|
```sh
|
||||||
|
# Run website
|
||||||
|
npm run dev
|
||||||
|
```
|
||||||
|
|
||||||
|
### Production environment with [Docker](https://www.docker.com/)
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
# Setup and run all the services for you
|
# Setup and run all the services for you
|
||||||
|
26
Dockerfile
26
Dockerfile
@ -1,11 +1,23 @@
|
|||||||
FROM node:16.1.0
|
FROM node:16.14.0 AS dependencies
|
||||||
|
|
||||||
WORKDIR /usr/src/app
|
WORKDIR /usr/src/app
|
||||||
RUN chown --recursive node:node /usr/src/app
|
COPY ./package*.json ./
|
||||||
COPY --chown=node:node ./package*.json ./
|
|
||||||
RUN npm install
|
RUN npm install
|
||||||
COPY --chown=node:node ./ ./
|
|
||||||
|
|
||||||
USER node
|
FROM node:16.14.0 AS builder
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY ./ ./
|
||||||
|
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
CMD ["npm", "run", "start", "--", "--port", "${PORT}"]
|
|
||||||
|
FROM node:16.14.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}"]
|
||||||
|
36
README.md
36
README.md
@ -5,7 +5,6 @@
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://github.com/Divlo/Divlo/actions?query=workflow%3A%22Divlo%22"><img src="https://github.com/Divlo/Divlo/actions/workflows/Divlo.yml/badge.svg?branch=master" alt="Divlo's CI" /></a>
|
|
||||||
<a href="https://github.com/Divlo"><img alt="GitHub" src="https://img.shields.io/badge/-GitHub-5A5A5A?style=flat&labelColor=5A5A5A&logo=github&logoColor=white"/></a>
|
<a href="https://github.com/Divlo"><img alt="GitHub" src="https://img.shields.io/badge/-GitHub-5A5A5A?style=flat&labelColor=5A5A5A&logo=github&logoColor=white"/></a>
|
||||||
<a href="https://gitlab.com/Divlo"><img alt="GitLab" src="https://img.shields.io/badge/-GitLab-303030?style=flat&labelColor=303030&logo=gitlab&logoColor=white"/></a>
|
<a href="https://gitlab.com/Divlo"><img alt="GitLab" src="https://img.shields.io/badge/-GitLab-303030?style=flat&labelColor=303030&logo=gitlab&logoColor=white"/></a>
|
||||||
<a href="https://www.npmjs.com/~divlo"><img alt="npm" src="https://img.shields.io/badge/-npm-c4302b?style=flat&labelColor=c4302b&logo=npm&logoColor=white"/></a>
|
<a href="https://www.npmjs.com/~divlo"><img alt="npm" src="https://img.shields.io/badge/-npm-c4302b?style=flat&labelColor=c4302b&logo=npm&logoColor=white"/></a>
|
||||||
@ -20,30 +19,31 @@
|
|||||||
|
|
||||||
## 📜 About
|
## 📜 About
|
||||||
|
|
||||||
```typescript
|
```json
|
||||||
export interface Divlo {
|
{
|
||||||
pronouns: 'He' | 'Him'
|
"name": "Divlo",
|
||||||
birthDate: '31/03/2003'
|
"pronouns": "He/Him",
|
||||||
nationality: 'Alsace, France'
|
"birthDate": "31/03/2003",
|
||||||
interests: [
|
"nationality": "Alsace, France",
|
||||||
'Developer Full Stack Junior',
|
"interests": [
|
||||||
'Passionate about High-Tech',
|
"Developer Full Stack Junior",
|
||||||
'Open-Source enthusiast'
|
"Passionate about High-Tech",
|
||||||
]
|
"Open-Source enthusiast"
|
||||||
skills: {
|
],
|
||||||
languages: ['JavaScript', 'TypeScript', 'Python', 'Dart']
|
"skills": {
|
||||||
frontEnd: ['HTML', 'CSS', 'SASS', 'React.js (+ Next.js)', 'Flutter']
|
"programmingLanguages": ["JavaScript", "TypeScript", "Python", "C/C++"],
|
||||||
backEnd: ['Node.js', 'Strapi', 'MySQL']
|
"frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
|
||||||
tools: ['Ubuntu', 'Hyper Terminal', 'VSCode', 'Git', 'Docker']
|
"backEnd": ["Node.js", "Fastify", "Prisma", "PostgreSQL", "MySQL"],
|
||||||
|
"tools": ["GNU/Linux", "Ubuntu", "Visual Studio Code", "Git", "Docker"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
## 📈 Stats
|
## 📈 Statistics
|
||||||
|
|
||||||
<p align=center>
|
<p align=center>
|
||||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=Divlo&show_icons=true&theme=dark" />
|
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=Divlo&show_icons=true&theme=dark" />
|
||||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=Divlo&hide=html,css&langs_count=8&layout=compact&theme=dark" />
|
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=Divlo&hide=html,css,javascript&langs_count=8&layout=compact&theme=dark" />
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,10 +0,0 @@
|
|||||||
import { render } from '@testing-library/react'
|
|
||||||
|
|
||||||
import Error404 from 'pages/404'
|
|
||||||
|
|
||||||
describe('GET /404', () => {
|
|
||||||
it('should render', async () => {
|
|
||||||
const { getByText } = render(<Error404 />)
|
|
||||||
expect(getByText('404')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
@ -1,10 +0,0 @@
|
|||||||
import { render } from '@testing-library/react'
|
|
||||||
|
|
||||||
import Error500 from 'pages/500'
|
|
||||||
|
|
||||||
describe('GET /500', () => {
|
|
||||||
it('should render', async () => {
|
|
||||||
const { getByText } = render(<Error500 />)
|
|
||||||
expect(getByText('500')).toBeInTheDocument()
|
|
||||||
})
|
|
||||||
})
|
|
@ -12,15 +12,20 @@ export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<h1 className='my-6 font-semibold text-4xl'>
|
<h1 className='my-6 text-4xl font-semibold'>
|
||||||
{t('errors:error')}{' '}
|
{t('errors:error')}{' '}
|
||||||
<span className='text-yellow dark:text-yellow-dark'>{statusCode}</span>
|
<span
|
||||||
|
className='text-yellow dark:text-yellow-dark'
|
||||||
|
data-cy='status-code'
|
||||||
|
>
|
||||||
|
{statusCode}
|
||||||
|
</span>
|
||||||
</h1>
|
</h1>
|
||||||
<p className='text-center text-lg'>
|
<p className='text-center text-lg'>
|
||||||
{message}{' '}
|
{message}{' '}
|
||||||
<Link href='/'>
|
<Link href='/'>
|
||||||
<a className='text-yellow dark:text-yellow-dark hover:underline'>
|
<a className='text-yellow hover:underline dark:text-yellow-dark'>
|
||||||
{t('errors:returnToHomePage')}
|
{t('errors:return-to-home-page')}
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
</p>
|
</p>
|
||||||
|
@ -1,13 +1,39 @@
|
|||||||
|
import { useMemo } from 'react'
|
||||||
|
import Link from 'next/link'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
export const Footer: React.FC = () => {
|
export interface FooterProps {
|
||||||
|
version: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Footer: React.FC<FooterProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { version } = props
|
||||||
|
|
||||||
|
const versionLink = useMemo(() => {
|
||||||
|
return `https://github.com/Divlo/Divlo/releases/tag/v${version}`
|
||||||
|
}, [version])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<footer className='bg-white flex justify-center py-6 text-lg border-t-2 border-gray-600 dark:border-gray-400 dark:bg-black'>
|
<footer className='flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black'>
|
||||||
<p>
|
<p>
|
||||||
<span className='text-yellow dark:text-yellow-dark'>Divlo</span> |{' '}
|
<Link href='/'>
|
||||||
{t('common:allRightsReserved')}
|
<a className='text-yellow hover:underline dark:text-yellow-dark'>
|
||||||
|
Divlo
|
||||||
|
</a>
|
||||||
|
</Link>{' '}
|
||||||
|
| {t('common:all-rights-reserved')}
|
||||||
|
</p>
|
||||||
|
<p className='mt-1'>
|
||||||
|
Version{' '}
|
||||||
|
<a
|
||||||
|
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||||
|
href={versionLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
{version}
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
@ -3,7 +3,7 @@ import NextHead from 'next/head'
|
|||||||
interface HeadProps {
|
interface HeadProps {
|
||||||
title?: string
|
title?: string
|
||||||
image?: string
|
image?: string
|
||||||
description?: string
|
description: string
|
||||||
url?: string
|
url?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -11,7 +11,7 @@ export const Head: React.FC<HeadProps> = (props) => {
|
|||||||
const {
|
const {
|
||||||
title = 'Divlo',
|
title = 'Divlo',
|
||||||
image = '/images/icons/icon-96x96.png',
|
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",
|
description,
|
||||||
url = 'https://divlo.fr/'
|
url = 'https://divlo.fr/'
|
||||||
} = props
|
} = props
|
||||||
|
|
||||||
@ -39,7 +39,7 @@ export const Head: React.FC<HeadProps> = (props) => {
|
|||||||
<meta name='twitter:card' content='summary' />
|
<meta name='twitter:card' content='summary' />
|
||||||
<meta name='twitter:description' content={description} />
|
<meta name='twitter:description' content={description} />
|
||||||
<meta name='twitter:title' content={title} />
|
<meta name='twitter:title' content={title} />
|
||||||
<meta name='twitter:image:src' content={image} />
|
<meta name='twitter:image' content={image} />
|
||||||
|
|
||||||
{/* Google Verification */}
|
{/* Google Verification */}
|
||||||
<meta
|
<meta
|
||||||
|
@ -10,12 +10,15 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Image
|
<Image
|
||||||
|
quality={100}
|
||||||
width={35}
|
width={35}
|
||||||
height={35}
|
height={35}
|
||||||
src={`/images/languages/${language}.svg`}
|
src={`/images/languages/${language}.svg`}
|
||||||
alt={language}
|
alt={language}
|
||||||
/>
|
/>
|
||||||
<p className='mx-2 text-base'>{language.toUpperCase()}</p>
|
<p data-cy='language-flag-text' className='mx-2 text-base'>
|
||||||
|
{language.toUpperCase()}
|
||||||
|
</p>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,15 +1,21 @@
|
|||||||
import { useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
import setLanguage from 'next-translate/setLanguage'
|
import setLanguage from 'next-translate/setLanguage'
|
||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
|
import i18n from 'i18n.json'
|
||||||
|
|
||||||
import { Arrow } from './Arrow'
|
import { Arrow } from './Arrow'
|
||||||
import { LanguageFlag } from './LanguageFlag'
|
import { LanguageFlag } from './LanguageFlag'
|
||||||
import { locales } from 'i18n.json'
|
|
||||||
|
|
||||||
export const Language: React.FC = () => {
|
export const Language: React.FC = () => {
|
||||||
const { lang: currentLanguage } = useTranslation()
|
const { lang: currentLanguage } = useTranslation()
|
||||||
const [hiddenMenu, setHiddenMenu] = useState(true)
|
const [hiddenMenu, setHiddenMenu] = useState(true)
|
||||||
|
|
||||||
|
const handleHiddenMenu = useCallback(() => {
|
||||||
|
setHiddenMenu(!hiddenMenu)
|
||||||
|
}, [hiddenMenu])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!hiddenMenu) {
|
if (!hiddenMenu) {
|
||||||
window.document.addEventListener('click', handleHiddenMenu)
|
window.document.addEventListener('click', handleHiddenMenu)
|
||||||
@ -20,41 +26,46 @@ export const Language: React.FC = () => {
|
|||||||
return () => {
|
return () => {
|
||||||
window.document.removeEventListener('click', handleHiddenMenu)
|
window.document.removeEventListener('click', handleHiddenMenu)
|
||||||
}
|
}
|
||||||
}, [hiddenMenu])
|
}, [hiddenMenu, handleHiddenMenu])
|
||||||
|
|
||||||
const handleLanguage = async (language: string): Promise<void> => {
|
const handleLanguage = async (language: string): Promise<void> => {
|
||||||
await setLanguage(language)
|
await setLanguage(language)
|
||||||
handleHiddenMenu()
|
handleHiddenMenu()
|
||||||
}
|
}
|
||||||
|
|
||||||
const handleHiddenMenu = (): void => {
|
|
||||||
setHiddenMenu(!hiddenMenu)
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-center items-center cursor-pointer'>
|
<div className='flex cursor-pointer flex-col items-center justify-center'>
|
||||||
<div className='flex items-center mr-5' onClick={handleHiddenMenu}>
|
<div
|
||||||
|
data-cy='language-click'
|
||||||
|
className='mr-5 flex items-center'
|
||||||
|
onClick={handleHiddenMenu}
|
||||||
|
>
|
||||||
<LanguageFlag language={currentLanguage} />
|
<LanguageFlag language={currentLanguage} />
|
||||||
<Arrow />
|
<Arrow />
|
||||||
</div>
|
</div>
|
||||||
{!hiddenMenu && (
|
|
||||||
<ul className='flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black'>
|
<ul
|
||||||
{locales.map((language, index) => {
|
data-cy='languages-list'
|
||||||
if (language === currentLanguage) {
|
className={classNames(
|
||||||
return null
|
'absolute top-14 z-10 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',
|
||||||
}
|
{ hidden: hiddenMenu }
|
||||||
return (
|
)}
|
||||||
<li
|
>
|
||||||
key={index}
|
{i18n.locales.map((language, index) => {
|
||||||
className='flex items-center justify-center w-full h-12 hover:bg-[#4f545c] hover:bg-opacity-20 pl-2'
|
if (language === currentLanguage) {
|
||||||
onClick={async () => await handleLanguage(language)}
|
return null
|
||||||
>
|
}
|
||||||
<LanguageFlag language={language} />
|
return (
|
||||||
</li>
|
<li
|
||||||
)
|
key={index}
|
||||||
})}
|
className='flex h-12 w-full items-center justify-center pl-2 hover:bg-[#4f545c] hover:bg-opacity-20'
|
||||||
</ul>
|
onClick={async () => await handleLanguage(language)}
|
||||||
)}
|
>
|
||||||
|
<LanguageFlag language={language} />
|
||||||
|
</li>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -13,26 +13,42 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const handleClick = (): void => {
|
||||||
|
setTheme(theme === 'dark' ? 'light' : 'dark')
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className='toggle-button'
|
className='flex items-center'
|
||||||
onClick={() => setTheme(theme === 'dark' ? 'light' : 'dark')}
|
data-cy='switch-theme-click'
|
||||||
|
onClick={handleClick}
|
||||||
>
|
>
|
||||||
<div className='toggle-theme-button'>
|
<div className='toggle-theme-button relative inline-block cursor-pointer bg-transparent'>
|
||||||
<div className='toggle-track'>
|
<div className='toggle-track'>
|
||||||
<div className='toggle-track-check'>
|
<div
|
||||||
<span className='toggle_Dark'>🌜</span>
|
data-cy='switch-theme-dark'
|
||||||
|
className='toggle-track-check absolute'
|
||||||
|
>
|
||||||
|
<span className='toggle_Dark relative flex items-center justify-center'>
|
||||||
|
🌜
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div className='toggle-track-x'>
|
<div
|
||||||
<span className='toggle_Light'>🌞</span>
|
data-cy='switch-theme-light'
|
||||||
|
className='toggle-track-x absolute'
|
||||||
|
>
|
||||||
|
<span className='toggle_Light relative flex items-center justify-center'>
|
||||||
|
🌞
|
||||||
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className='toggle-thumb' />
|
<div className='toggle-thumb absolute' />
|
||||||
<input
|
<input
|
||||||
|
data-cy='switch-theme-input'
|
||||||
type='checkbox'
|
type='checkbox'
|
||||||
aria-label='Dark mode toggle'
|
aria-label='Dark mode toggle'
|
||||||
className='toggle-screenreader-only'
|
className='toggle-screenreader-only absolute overflow-hidden'
|
||||||
defaultChecked
|
defaultChecked
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@ -40,16 +56,8 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
|
|
||||||
<style jsx>
|
<style jsx>
|
||||||
{`
|
{`
|
||||||
.toggle-button {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
.toggle-theme-button {
|
.toggle-theme-button {
|
||||||
touch-action: pan-x;
|
touch-action: pan-x;
|
||||||
display: inline-block;
|
|
||||||
position: relative;
|
|
||||||
cursor: pointer;
|
|
||||||
background-color: transparent;
|
|
||||||
border: 0;
|
border: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
user-select: none;
|
user-select: none;
|
||||||
@ -64,7 +72,6 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
color: #fff;
|
color: #fff;
|
||||||
}
|
}
|
||||||
.toggle-track-check {
|
.toggle-track-check {
|
||||||
position: absolute;
|
|
||||||
width: 14px;
|
width: 14px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -77,7 +84,6 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
transition: opacity 0.25s ease;
|
transition: opacity 0.25s ease;
|
||||||
}
|
}
|
||||||
.toggle-track-x {
|
.toggle-track-x {
|
||||||
position: absolute;
|
|
||||||
width: 10px;
|
width: 10px;
|
||||||
height: 10px;
|
height: 10px;
|
||||||
top: 0;
|
top: 0;
|
||||||
@ -90,15 +96,10 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
}
|
}
|
||||||
.toggle_Dark,
|
.toggle_Dark,
|
||||||
.toggle_Light {
|
.toggle_Light {
|
||||||
align-items: center;
|
|
||||||
display: flex;
|
|
||||||
height: 10px;
|
height: 10px;
|
||||||
justify-content: center;
|
|
||||||
position: relative;
|
|
||||||
width: 10px;
|
width: 10px;
|
||||||
}
|
}
|
||||||
.toggle-thumb {
|
.toggle-thumb {
|
||||||
position: absolute;
|
|
||||||
left: ${theme === 'dark' ? '27px' : '0px'};
|
left: ${theme === 'dark' ? '27px' : '0px'};
|
||||||
width: 22px;
|
width: 22px;
|
||||||
height: 22px;
|
height: 22px;
|
||||||
@ -115,9 +116,7 @@ export const SwitchTheme: React.FC = () => {
|
|||||||
clip: rect(0 0 0 0);
|
clip: rect(0 0 0 0);
|
||||||
height: 1px;
|
height: 1px;
|
||||||
margin: -1px;
|
margin: -1px;
|
||||||
overflow: hidden;
|
|
||||||
padding: 0;
|
padding: 0;
|
||||||
position: absolute;
|
|
||||||
width: 1px;
|
width: 1px;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
@ -3,7 +3,7 @@ import { render } from '@testing-library/react'
|
|||||||
import { Header } from '..'
|
import { Header } from '..'
|
||||||
|
|
||||||
describe('<Header />', () => {
|
describe('<Header />', () => {
|
||||||
it('should render', async () => {
|
it('should render', () => {
|
||||||
const { getByText } = render(<Header />)
|
const { getByText } = render(<Header />)
|
||||||
expect(getByText('Divlo')).toBeInTheDocument()
|
expect(getByText('Divlo')).toBeInTheDocument()
|
||||||
})
|
})
|
||||||
|
@ -4,26 +4,43 @@ import Image from 'next/image'
|
|||||||
import { Language } from './Language'
|
import { Language } from './Language'
|
||||||
import { SwitchTheme } from './SwitchTheme'
|
import { SwitchTheme } from './SwitchTheme'
|
||||||
|
|
||||||
export const Header: React.FC = () => {
|
export interface HeaderProps {
|
||||||
|
showLanguage?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Header: React.FC<HeaderProps> = (props) => {
|
||||||
|
const { showLanguage = false } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<header className='bg-white sticky top-0 z-50 flex w-full justify-between px-6 py-2 border-b-2 border-gray-600 dark:border-gray-400 dark:bg-black'>
|
<header className='sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black'>
|
||||||
<Link href='/'>
|
<Link href='/'>
|
||||||
<a>
|
<a>
|
||||||
<div className='flex items-center justify-center'>
|
<div className='flex items-center justify-center'>
|
||||||
<Image
|
<Image
|
||||||
|
quality={100}
|
||||||
width={60}
|
width={60}
|
||||||
height={60}
|
height={60}
|
||||||
src='/images/divlo_icon_small.png'
|
src='/images/divlo_icon_small.png'
|
||||||
alt='Divlo'
|
alt='Divlo'
|
||||||
/>
|
/>
|
||||||
<strong className='ml-1 font-headline font-semibold hidden xs:block'>
|
<strong className='ml-1 hidden font-headline font-semibold text-yellow dark:text-yellow-dark xs:block'>
|
||||||
Divlo
|
Divlo
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
</Link>
|
</Link>
|
||||||
<div className='flex justify-between'>
|
<div className='flex justify-between'>
|
||||||
<Language />
|
<div className='flex flex-col items-center justify-center px-6'>
|
||||||
|
<Link href='/blog'>
|
||||||
|
<a
|
||||||
|
data-cy='header-blog-link'
|
||||||
|
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||||
|
>
|
||||||
|
Blog
|
||||||
|
</a>
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
{showLanguage && <Language />}
|
||||||
<SwitchTheme />
|
<SwitchTheme />
|
||||||
</div>
|
</div>
|
||||||
</header>
|
</header>
|
||||||
|
@ -10,12 +10,12 @@ export const InterestParagraph: React.FC<InterestParagraphProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className='text-center my-6 text-gray dark:text-gray-dark'>
|
<p className='my-6 text-center text-gray dark:text-gray-dark'>
|
||||||
<strong className='text-yellow font-medium text-lg dark:text-yellow-dark'>
|
<strong className='text-lg font-semibold text-yellow dark:text-yellow-dark'>
|
||||||
{title}
|
{title}
|
||||||
</strong>
|
</strong>
|
||||||
<br />
|
<br />
|
||||||
<span className='paragraph-color'>{htmlParser(description)}</span>
|
<span>{htmlParser(description)}</span>
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -10,9 +10,9 @@ export const InterestItem: React.FC<InterestItemProps> = (props) => {
|
|||||||
const { fontAwesomeIcon, title } = props
|
const { fontAwesomeIcon, title } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='interest-item my-2 mx-2 w-8 h-8' title={title}>
|
<li className='interest-item my-2 mx-2 h-8 w-8' title={title}>
|
||||||
<FontAwesomeIcon
|
<FontAwesomeIcon
|
||||||
className='text-yellow cursor-pointer h-full w-full block dark:text-yellow-dark'
|
className='block h-full w-full text-yellow dark:text-yellow-dark'
|
||||||
icon={fontAwesomeIcon}
|
icon={fontAwesomeIcon}
|
||||||
/>
|
/>
|
||||||
</li>
|
</li>
|
||||||
|
@ -5,8 +5,8 @@ import { InterestItem } from './InterestItem'
|
|||||||
|
|
||||||
export const InterestsList: React.FC = () => {
|
export const InterestsList: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className='flex justify-center my-4'>
|
<div className='my-4 flex justify-center'>
|
||||||
<ul className='flex justify-around p-0 m-0 list-none w-96'>
|
<ul className='m-0 flex w-96 list-none justify-around p-0'>
|
||||||
<InterestItem
|
<InterestItem
|
||||||
title='Developer Full Stack Junior'
|
title='Developer Full Stack Junior'
|
||||||
fontAwesomeIcon={faCode}
|
fontAwesomeIcon={faCode}
|
||||||
|
@ -15,13 +15,11 @@ export const Interests: React.FC = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='max-w-full'>
|
||||||
<div className='max-w-full'>
|
{paragraphs.map((paragraph, index) => {
|
||||||
{paragraphs.map((paragraph, index) => {
|
return <InterestParagraph key={index} {...paragraph} />
|
||||||
return <InterestParagraph key={index} {...paragraph} />
|
})}
|
||||||
})}
|
<InterestsList />
|
||||||
<InterestsList />
|
</div>
|
||||||
</div>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
24
components/OpenSource/Repository.tsx
Normal file
24
components/OpenSource/Repository.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { ShadowContainer } from 'components/design/ShadowContainer'
|
||||||
|
import { GitHubIcon } from 'components/Profile/SocialMediaList/SocialMediaIcons/GitHubIcon'
|
||||||
|
|
||||||
|
export interface RepositoryProps {
|
||||||
|
name: string
|
||||||
|
description: string
|
||||||
|
href: string
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Repository: React.FC<RepositoryProps> = (props) => {
|
||||||
|
const { name, description, href } = props
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ShadowContainer className='relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2'>
|
||||||
|
<a href={href} target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div className='flex'>
|
||||||
|
<GitHubIcon className='mr-2 h-6' />
|
||||||
|
<span className='text-yellow dark:text-yellow-dark'>{name}</span>
|
||||||
|
</div>
|
||||||
|
<p className='my-4'>{description}</p>
|
||||||
|
</a>
|
||||||
|
</ShadowContainer>
|
||||||
|
)
|
||||||
|
}
|
35
components/OpenSource/index.tsx
Normal file
35
components/OpenSource/index.tsx
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
|
import { Repository } from './Repository'
|
||||||
|
|
||||||
|
export const OpenSource: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='mt-0 flex max-w-full flex-col items-center'>
|
||||||
|
<p className='text-center'>{t('home:open-source.description')}</p>
|
||||||
|
<div className='my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2'>
|
||||||
|
<Repository
|
||||||
|
name='nodejs/node'
|
||||||
|
description='Node.js JavaScript runtime 🐢🚀'
|
||||||
|
href='https://github.com/nodejs/node/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='standard/standard'
|
||||||
|
description='🌟 JavaScript Style Guide, with linter & automatic code fixer'
|
||||||
|
href='https://github.com/standard/standard/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='nrwl/nx'
|
||||||
|
description='Smart, Extensible Build Framework'
|
||||||
|
href='https://github.com/nrwl/nx/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='vercel/next.js'
|
||||||
|
description='The React Framework for Production'
|
||||||
|
href='https://github.com/vercel/next.js/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { ShadowContainer } from 'components/design/ShadowContainer'
|
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
import { ShadowContainer } from 'components/design/ShadowContainer'
|
||||||
|
|
||||||
export interface PortfolioItemProps {
|
export interface PortfolioItemProps {
|
||||||
title: string
|
title: string
|
||||||
description: string
|
description: string
|
||||||
@ -12,7 +13,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
|
|||||||
const { title, description, link, image } = props
|
const { title, description, link, image } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ShadowContainer className='cursor-pointer relative items-center sm:ml-10'>
|
<ShadowContainer className='relative cursor-pointer items-center sm:ml-10'>
|
||||||
<a
|
<a
|
||||||
className='group inline-flex justify-center'
|
className='group inline-flex justify-center'
|
||||||
target='_blank'
|
target='_blank'
|
||||||
@ -22,6 +23,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
|
|||||||
>
|
>
|
||||||
<div className='flex justify-center'>
|
<div className='flex justify-center'>
|
||||||
<Image
|
<Image
|
||||||
|
quality={100}
|
||||||
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
|
className='transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
|
||||||
width={300}
|
width={300}
|
||||||
height={300}
|
height={300}
|
||||||
@ -29,8 +31,8 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
|
|||||||
alt={title}
|
alt={title}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className='opacity-0 transition-opacity duration-500 h-auto absolute text-center overflow-hidden bottom-0 group-hover:opacity-100'>
|
<div className='absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100'>
|
||||||
<h3 className='text-yellow text-xl font-semibold my-6 dark:text-yellow-dark'>
|
<h3 className='my-6 text-xl font-semibold text-yellow dark:text-yellow-dark'>
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
<p className='my-6'>{description}</p>
|
<p className='my-6'>{description}</p>
|
||||||
|
@ -14,7 +14,7 @@ export const Portfolio: React.FC = () => {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-wrap justify-center px-3 w-full'>
|
<div className='flex w-full flex-wrap justify-center px-3'>
|
||||||
{items.map((item, index) => {
|
{items.map((item, index) => {
|
||||||
return <PortfolioItem key={index} {...item} />
|
return <PortfolioItem key={index} {...item} />
|
||||||
})}
|
})}
|
||||||
|
@ -1,12 +1,23 @@
|
|||||||
import Translation from 'next-translate/Trans'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
export const ProfileDescriptionBottom: React.FC = () => {
|
export const ProfileDescriptionBottom: React.FC = () => {
|
||||||
|
const { t, lang } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<p className='mt-8 mb-8 font-normal text-base text-gray dark:text-gray-dark'>
|
<p className='mt-8 mb-8 text-base font-normal text-gray dark:text-gray-dark'>
|
||||||
<Translation
|
{t('home:about.description-bottom')}
|
||||||
i18nKey='home:about.descriptionBottom'
|
{lang === 'fr' && (
|
||||||
components={[<br key='break' />]}
|
<>
|
||||||
/>
|
<br />
|
||||||
|
<br />
|
||||||
|
<a
|
||||||
|
href='/curriculum-vitae'
|
||||||
|
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||||
|
>
|
||||||
|
Curriculum vitæ
|
||||||
|
</a>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,44 +1,17 @@
|
|||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
export const ProfileInfo: React.FC = () => {
|
export const ProfileInformation: React.FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div className='mb-6 border-b-2 border-gray-600 pb-2 font-headline dark:border-gray-400'>
|
||||||
<div className='pb-2 mb-6 border-b-2 font-headline border-gray-600 dark:border-gray-400'>
|
<h1 className='mb-2 text-4xl'>
|
||||||
<h1 className='text-4xl mb-2'>
|
{t('home:about.i-am')}{' '}
|
||||||
{t('home:about.IAm')}{' '}
|
<strong className='font-semibold text-yellow dark:text-yellow-dark'>
|
||||||
<strong className='font-semibold text-yellow dark:text-yellow-dark'>
|
Divlo
|
||||||
Divlo
|
</strong>
|
||||||
</strong>
|
</h1>
|
||||||
</h1>
|
<h2 className='mb-3 text-base'>{t('home:about.description')}</h2>
|
||||||
<h2 className='text-base mb-3'>{t('home:about.description')}</h2>
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
.profile-info {
|
|
||||||
padding-bottom: 25px;
|
|
||||||
margin-bottom: 25px;
|
|
||||||
border-bottom: 1px solid #dedede;
|
|
||||||
}
|
|
||||||
.profile-title {
|
|
||||||
font-size: 36px;
|
|
||||||
line-height: 1.1;
|
|
||||||
font-weight: 300;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
.profile-title > strong {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
.profile-description {
|
|
||||||
font-size: 17.4px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 1.1;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -8,69 +8,22 @@ export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
|
|||||||
const { title, value, link } = props
|
const { title, value, link } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<li className='mb-3 before:table after:clear-both after:table'>
|
||||||
<li className='profile-list__item'>
|
<strong className='float-left block w-28 text-sm font-bold text-black dark:text-white'>
|
||||||
<strong className='profile-list__item-title text-black dark:text-white'>
|
{title}
|
||||||
{title}
|
</strong>
|
||||||
</strong>
|
<span className='ml-0 mb-4 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
|
||||||
<span className='profile-list__item-info text-gray dark:text-gray-dark'>
|
{link != null ? (
|
||||||
{link != null ? (
|
<a
|
||||||
<a className='text-gray dark:text-gray-dark' href={link}>
|
className='text-gray hover:underline dark:text-gray-dark'
|
||||||
{value}
|
href={link}
|
||||||
</a>
|
>
|
||||||
) : (
|
{value}
|
||||||
value
|
</a>
|
||||||
)}
|
) : (
|
||||||
</span>
|
value
|
||||||
</li>
|
)}
|
||||||
|
</span>
|
||||||
<style jsx>
|
</li>
|
||||||
{`
|
|
||||||
.profile-list__item {
|
|
||||||
margin-bottom: 13px;
|
|
||||||
}
|
|
||||||
.profile-list__item::after,
|
|
||||||
.profile-list__item::before {
|
|
||||||
content: ' ';
|
|
||||||
display: table;
|
|
||||||
}
|
|
||||||
.profile-list__item::after {
|
|
||||||
clear: both;
|
|
||||||
}
|
|
||||||
.profile-list__item-title {
|
|
||||||
display: block;
|
|
||||||
width: 120px;
|
|
||||||
float: left;
|
|
||||||
font-size: 12px;
|
|
||||||
font-weight: 700;
|
|
||||||
line-height: 20px;
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
.profile-list__item-info {
|
|
||||||
display: block;
|
|
||||||
margin-left: 125px;
|
|
||||||
font-size: 15px;
|
|
||||||
font-weight: 400;
|
|
||||||
line-height: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 576px) {
|
|
||||||
.profile-list__item-title {
|
|
||||||
margin-bottom: 3px;
|
|
||||||
}
|
|
||||||
.profile-list__item-info {
|
|
||||||
margin-left: 0;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.profile-list__item-info,
|
|
||||||
.profile-list__item-title {
|
|
||||||
width: 100%;
|
|
||||||
float: none;
|
|
||||||
line-height: 1.2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -6,8 +6,9 @@ export const ProfileList: React.FC = () => {
|
|||||||
const { t } = useTranslation('home')
|
const { t } = useTranslation('home')
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ul className='m-0 p-0 list-none'>
|
<ul className='m-0 list-none p-0'>
|
||||||
<ProfileItem title={t('home:about.birthDate')} value='31/03/2003' />
|
<ProfileItem title={t('home:about.full-name')} value='Théo LUDWIG' />
|
||||||
|
<ProfileItem title={t('home:about.birth-date')} value='31/03/2003' />
|
||||||
<ProfileItem title={t('home:about.nationality')} value='Alsace, France' />
|
<ProfileItem title={t('home:about.nationality')} value='Alsace, France' />
|
||||||
<ProfileItem
|
<ProfileItem
|
||||||
title='Email'
|
title='Email'
|
||||||
|
@ -1,14 +1,11 @@
|
|||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
|
||||||
|
import DivloLogo from 'public/images/divlo_logo.png'
|
||||||
|
|
||||||
export const ProfileLogo: React.FC = () => {
|
export const ProfileLogo: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className='px-2 py-6'>
|
<div className='max-h-[370px] max-w-[370px] px-2 py-6'>
|
||||||
<Image
|
<Image quality={100} src={DivloLogo} alt='Divlo' />
|
||||||
width={370}
|
|
||||||
height={370}
|
|
||||||
src='/images/divlo_logo.png'
|
|
||||||
alt='Divlo'
|
|
||||||
/>
|
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
@ -1,11 +1,16 @@
|
|||||||
|
import classNames from 'classnames'
|
||||||
|
|
||||||
export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
export const Icon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||||
const { children, ...rest } = props
|
const { children, className, ...rest } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<svg
|
<svg
|
||||||
xmlns='http://www.w3.org/2000/svg'
|
xmlns='http://www.w3.org/2000/svg'
|
||||||
viewBox='0 0 24 24'
|
viewBox='0 0 24 24'
|
||||||
className='dark:text-white text-black w-8 h-8 fill-current'
|
className={classNames(
|
||||||
|
'h-8 w-8 fill-current text-black dark:text-white',
|
||||||
|
className
|
||||||
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{children}
|
{children}
|
||||||
|
@ -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>
|
||||||
|
)
|
||||||
|
}
|
@ -7,7 +7,7 @@ export const SocialMediaItem: React.FC<SocialMediaItemProps> = (props) => {
|
|||||||
const { link, ariaLabel, children } = props
|
const { link, ariaLabel, children } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li className='inline-block mx-4 my-1'>
|
<li className='mx-4 my-1 inline-block'>
|
||||||
<a
|
<a
|
||||||
href={link}
|
href={link}
|
||||||
aria-label={ariaLabel}
|
aria-label={ariaLabel}
|
||||||
|
@ -1,19 +1,27 @@
|
|||||||
import { SocialMediaItem } from './SocialMediaItem'
|
import { SocialMediaItem } from './SocialMediaItem'
|
||||||
import { TwitterIcon } from './SocialMediaIcons/TwitterIcon'
|
import { TwitterIcon } from './SocialMediaIcons/TwitterIcon'
|
||||||
import { GitHubIcon } from './SocialMediaIcons/GitHubIcon'
|
import { GitHubIcon } from './SocialMediaIcons/GitHubIcon'
|
||||||
|
import { GitLabIcon } from './SocialMediaIcons/GitLabIcon'
|
||||||
import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon'
|
import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon'
|
||||||
import { TwitchIcon } from './SocialMediaIcons/TwitchIcon'
|
import { TwitchIcon } from './SocialMediaIcons/TwitchIcon'
|
||||||
import { EmailIcon } from './SocialMediaIcons/EmailIcon'
|
import { EmailIcon } from './SocialMediaIcons/EmailIcon'
|
||||||
|
import { NPMIcon } from './SocialMediaIcons/NPMIcon'
|
||||||
|
|
||||||
export const SocialMediaList: React.FC = () => {
|
export const SocialMediaList: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<ul className='social-media-list m-0 mt-2 py-4 list-none text-center'>
|
<ul className='social-media-list m-0 mt-2 list-none py-4 text-center'>
|
||||||
<SocialMediaItem link='https://twitter.com/Divlo_FR' ariaLabel='Twitter'>
|
|
||||||
<TwitterIcon />
|
|
||||||
</SocialMediaItem>
|
|
||||||
<SocialMediaItem link='https://github.com/Divlo' ariaLabel='GitHub'>
|
<SocialMediaItem link='https://github.com/Divlo' ariaLabel='GitHub'>
|
||||||
<GitHubIcon />
|
<GitHubIcon />
|
||||||
</SocialMediaItem>
|
</SocialMediaItem>
|
||||||
|
<SocialMediaItem link='https://gitlab.com/Divlo' ariaLabel='GitLab'>
|
||||||
|
<GitLabIcon />
|
||||||
|
</SocialMediaItem>
|
||||||
|
<SocialMediaItem link='https://www.npmjs.com/~divlo' ariaLabel='NPM'>
|
||||||
|
<NPMIcon />
|
||||||
|
</SocialMediaItem>
|
||||||
|
<SocialMediaItem link='https://twitter.com/Divlo_FR' ariaLabel='Twitter'>
|
||||||
|
<TwitterIcon />
|
||||||
|
</SocialMediaItem>
|
||||||
<SocialMediaItem
|
<SocialMediaItem
|
||||||
link='https://www.youtube.com/c/Divlo'
|
link='https://www.youtube.com/c/Divlo'
|
||||||
ariaLabel='YouTube'
|
ariaLabel='YouTube'
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import { ProfileDescriptionBottom } from './ProfileDescriptionBottom'
|
import { ProfileDescriptionBottom } from './ProfileDescriptionBottom'
|
||||||
import { ProfileInfo } from './ProfileInfo'
|
import { ProfileInformation } from './ProfileInfo'
|
||||||
import { ProfileList } from './ProfileList'
|
import { ProfileList } from './ProfileList'
|
||||||
import { ProfileLogo } from './ProfileLogo'
|
import { ProfileLogo } from './ProfileLogo'
|
||||||
|
|
||||||
export const Profile: React.FC = () => {
|
export const Profile: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<div className='flex flex-col justify-center items-center px-10 pt-2 md:pt-10 xl:pt-0 md:flex-row'>
|
<div className='flex flex-col items-center justify-center px-10 pt-2 md:flex-row md:pt-10'>
|
||||||
<ProfileLogo />
|
<ProfileLogo />
|
||||||
<div>
|
<div>
|
||||||
<ProfileInfo />
|
<ProfileInformation />
|
||||||
<ProfileList />
|
<ProfileList />
|
||||||
<ProfileDescriptionBottom />
|
<ProfileDescriptionBottom />
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,14 +1,24 @@
|
|||||||
|
import { useTheme } from 'next-themes'
|
||||||
import Image from 'next/image'
|
import Image from 'next/image'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
|
||||||
import { skills } from './skills'
|
import { skills } from './skills'
|
||||||
|
|
||||||
export interface SkillProps {
|
export interface SkillComponentProps {
|
||||||
skill: keyof typeof skills
|
skill: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Skill: React.FC<SkillProps> = (props) => {
|
export const SkillComponent: React.FC<SkillComponentProps> = (props) => {
|
||||||
const { skill } = props
|
const { skill } = props
|
||||||
const skillProperties = skills[skill]
|
const skillProperties = skills[skill]
|
||||||
|
const { theme } = useTheme()
|
||||||
|
|
||||||
|
const image = useMemo(() => {
|
||||||
|
if (typeof skillProperties.image !== 'string') {
|
||||||
|
return skillProperties.image[theme ?? 'light']
|
||||||
|
}
|
||||||
|
return skillProperties.image
|
||||||
|
}, [skillProperties, theme])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<a
|
<a
|
||||||
@ -18,7 +28,7 @@ export const Skill: React.FC<SkillProps> = (props) => {
|
|||||||
rel='noopener noreferrer'
|
rel='noopener noreferrer'
|
||||||
>
|
>
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<Image width={60} height={60} alt={skill} src={skillProperties.image} />
|
<Image quality={100} width={60} height={60} alt={skill} src={image} />
|
||||||
<p className='mt-1'>{skill}</p>
|
<p className='mt-1'>{skill}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -10,15 +10,15 @@ export const SkillsSection: React.FC<SkillsSectionProps> = (props) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ShadowContainer>
|
<ShadowContainer>
|
||||||
<div className='w-full px-4 mx-auto'>
|
<div className='mx-auto w-full px-4'>
|
||||||
<div className='flex flex-wrap px-4 py-6'>
|
<div className='flex flex-wrap px-4 py-6'>
|
||||||
<div className='flex-1'>
|
<div className='flex-1'>
|
||||||
<div className='mb-8 border-b border-gray-600 dark:border-opacity-10 dark:border-white'>
|
<div className='mb-8 border-b border-gray-600 dark:border-white dark:border-opacity-10'>
|
||||||
<h3 className='text-yellow font-semibold text-xl my-3 dark:text-yellow-dark'>
|
<h3 className='my-3 text-xl font-semibold text-yellow dark:text-yellow-dark'>
|
||||||
{title}
|
{title}
|
||||||
</h3>
|
</h3>
|
||||||
</div>
|
</div>
|
||||||
<div className='flex justify-around flex-wrap'>{children}</div>
|
<div className='flex flex-wrap justify-around'>{children}</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
import { Skill } from './Skill'
|
import { SkillComponent } from './Skill'
|
||||||
import { SkillsSection } from './SkillsSection'
|
import { SkillsSection } from './SkillsSection'
|
||||||
|
|
||||||
export const Skills: React.FC = () => {
|
export const Skills: React.FC = () => {
|
||||||
@ -9,32 +9,33 @@ export const Skills: React.FC = () => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<SkillsSection title={t('home:skills.languages')}>
|
<SkillsSection title={t('home:skills.languages')}>
|
||||||
<Skill skill='JavaScript' />
|
<SkillComponent skill='JavaScript' />
|
||||||
<Skill skill='TypeScript' />
|
<SkillComponent skill='TypeScript' />
|
||||||
<Skill skill='Python' />
|
<SkillComponent skill='Python' />
|
||||||
<Skill skill='Dart' />
|
<SkillComponent skill='C/C++' />
|
||||||
</SkillsSection>
|
</SkillsSection>
|
||||||
|
|
||||||
<SkillsSection title='Front-end'>
|
<SkillsSection title='Front-end'>
|
||||||
<Skill skill='HTML' />
|
<SkillComponent skill='HTML' />
|
||||||
<Skill skill='CSS' />
|
<SkillComponent skill='CSS' />
|
||||||
<Skill skill='SASS' />
|
<SkillComponent skill='Tailwind CSS' />
|
||||||
<Skill skill='React.js (+ Next.js)' />
|
<SkillComponent skill='React.js (+ Next.js)' />
|
||||||
<Skill skill='Flutter' />
|
|
||||||
</SkillsSection>
|
</SkillsSection>
|
||||||
|
|
||||||
<SkillsSection title='Back-end'>
|
<SkillsSection title='Back-end'>
|
||||||
<Skill skill='Node.js' />
|
<SkillComponent skill='Node.js' />
|
||||||
<Skill skill='Strapi' />
|
<SkillComponent skill='Fastify' />
|
||||||
<Skill skill='MySQL' />
|
<SkillComponent skill='Prisma' />
|
||||||
|
<SkillComponent skill='PostgreSQL' />
|
||||||
|
<SkillComponent skill='MySQL' />
|
||||||
</SkillsSection>
|
</SkillsSection>
|
||||||
|
|
||||||
<SkillsSection title={t('home:skills.softwareTools')}>
|
<SkillsSection title={t('home:skills.software-tools')}>
|
||||||
<Skill skill='Ubuntu' />
|
<SkillComponent skill='GNU/Linux' />
|
||||||
<Skill skill='Hyper' />
|
<SkillComponent skill='Ubuntu' />
|
||||||
<Skill skill='Visual Studio Code' />
|
<SkillComponent skill='Visual Studio Code' />
|
||||||
<Skill skill='Git' />
|
<SkillComponent skill='Git' />
|
||||||
<Skill skill='Docker' />
|
<SkillComponent skill='Docker' />
|
||||||
</SkillsSection>
|
</SkillsSection>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -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: {
|
JavaScript: {
|
||||||
link: 'https://developer.mozilla.org/docs/Web/JavaScript',
|
link: 'https://developer.mozilla.org/docs/Web/JavaScript',
|
||||||
image: '/images/skills/JavaScript.png'
|
image: '/images/skills/JavaScript.png'
|
||||||
@ -11,6 +20,10 @@ export const skills = {
|
|||||||
link: 'https://www.python.org/',
|
link: 'https://www.python.org/',
|
||||||
image: '/images/skills/Python.png'
|
image: '/images/skills/Python.png'
|
||||||
},
|
},
|
||||||
|
'C/C++': {
|
||||||
|
link: 'https://isocpp.org/',
|
||||||
|
image: '/images/skills/C-Cpp.png'
|
||||||
|
},
|
||||||
Dart: {
|
Dart: {
|
||||||
link: 'https://dart.dev/',
|
link: 'https://dart.dev/',
|
||||||
image: '/images/skills/Dart.png'
|
image: '/images/skills/Dart.png'
|
||||||
@ -27,6 +40,10 @@ export const skills = {
|
|||||||
link: 'https://developer.mozilla.org/docs/Web/CSS',
|
link: 'https://developer.mozilla.org/docs/Web/CSS',
|
||||||
image: '/images/skills/CSS.png'
|
image: '/images/skills/CSS.png'
|
||||||
},
|
},
|
||||||
|
'Tailwind CSS': {
|
||||||
|
link: 'https://tailwindcss.com/',
|
||||||
|
image: '/images/skills/TailwindCSS.png'
|
||||||
|
},
|
||||||
SASS: {
|
SASS: {
|
||||||
link: 'https://sass-lang.com/',
|
link: 'https://sass-lang.com/',
|
||||||
image: '/images/skills/SASS.svg'
|
image: '/images/skills/SASS.svg'
|
||||||
@ -39,6 +56,24 @@ export const skills = {
|
|||||||
link: 'https://nodejs.org/',
|
link: 'https://nodejs.org/',
|
||||||
image: '/images/skills/NodeJS.png'
|
image: '/images/skills/NodeJS.png'
|
||||||
},
|
},
|
||||||
|
Fastify: {
|
||||||
|
link: 'https://www.fastify.io/',
|
||||||
|
image: {
|
||||||
|
light: '/images/skills/Fastify-light.png',
|
||||||
|
dark: '/images/skills/Fastify-dark.png'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Prisma: {
|
||||||
|
link: 'https://www.prisma.io/',
|
||||||
|
image: {
|
||||||
|
light: '/images/skills/Prisma-light.png',
|
||||||
|
dark: '/images/skills/Prisma-dark.png'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
PostgreSQL: {
|
||||||
|
link: 'https://www.postgresql.org/',
|
||||||
|
image: '/images/skills/PostgreSQL.png'
|
||||||
|
},
|
||||||
MySQL: {
|
MySQL: {
|
||||||
link: 'https://www.mysql.com/',
|
link: 'https://www.mysql.com/',
|
||||||
image: '/images/skills/MySQL.png'
|
image: '/images/skills/MySQL.png'
|
||||||
@ -63,6 +98,10 @@ export const skills = {
|
|||||||
link: 'https://ubuntu.com/',
|
link: 'https://ubuntu.com/',
|
||||||
image: '/images/skills/Ubuntu.png'
|
image: '/images/skills/Ubuntu.png'
|
||||||
},
|
},
|
||||||
|
'GNU/Linux': {
|
||||||
|
link: 'https://www.gnu.org/',
|
||||||
|
image: '/images/skills/GNU-Linux.png'
|
||||||
|
},
|
||||||
Docker: {
|
Docker: {
|
||||||
link: 'https://www.docker.com/',
|
link: 'https://www.docker.com/',
|
||||||
image: '/images/skills/Docker.png'
|
image: '/images/skills/Docker.png'
|
||||||
|
@ -3,7 +3,7 @@ import { render } from '@testing-library/react'
|
|||||||
import { ErrorPage } from '../ErrorPage'
|
import { ErrorPage } from '../ErrorPage'
|
||||||
|
|
||||||
describe('<ErrorPage />', () => {
|
describe('<ErrorPage />', () => {
|
||||||
it('should render the message and statusCode', async () => {
|
it('should render the message and statusCode', () => {
|
||||||
const messageContent = 'message content'
|
const messageContent = 'message content'
|
||||||
const statusCode = 404
|
const statusCode = 404
|
||||||
const { getByText } = render(
|
const { getByText } = render(
|
||||||
|
@ -3,8 +3,14 @@ import { render } from '@testing-library/react'
|
|||||||
import { Footer } from '../Footer'
|
import { Footer } from '../Footer'
|
||||||
|
|
||||||
describe('<Footer />', () => {
|
describe('<Footer />', () => {
|
||||||
it('should render', async () => {
|
it('should render with appropriate link tag version', () => {
|
||||||
const { getByText } = render(<Footer />)
|
const version = '1.0.0'
|
||||||
|
const { getByText } = render(<Footer version={version} />)
|
||||||
|
const versionLink = getByText(version) as HTMLAnchorElement
|
||||||
expect(getByText('Divlo')).toBeInTheDocument()
|
expect(getByText('Divlo')).toBeInTheDocument()
|
||||||
|
expect(versionLink).toBeInTheDocument()
|
||||||
|
expect(versionLink.href).toEqual(
|
||||||
|
`https://github.com/Divlo/Divlo/releases/tag/v${version}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -10,7 +10,8 @@ export const RevealFade: React.FC = (props) => {
|
|||||||
(entries, observer) => {
|
(entries, observer) => {
|
||||||
entries.forEach((entry) => {
|
entries.forEach((entry) => {
|
||||||
if (entry.isIntersecting) {
|
if (entry.isIntersecting) {
|
||||||
entry.target.classList.add('reveal-visible')
|
entry.target.className =
|
||||||
|
'opacity-100 visible translate-y-0 transition-all duration-700 ease-in-out'
|
||||||
observer.unobserve(entry.target)
|
observer.unobserve(entry.target)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -25,26 +26,8 @@ export const RevealFade: React.FC = (props) => {
|
|||||||
}, [])
|
}, [])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div ref={htmlElement} className='invisible -translate-y-7 opacity-0'>
|
||||||
<div ref={htmlElement} className='reveal'>
|
{children}
|
||||||
{children}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
.reveal {
|
|
||||||
opacity: 0;
|
|
||||||
visibility: hidden;
|
|
||||||
transform: translateY(-30px);
|
|
||||||
}
|
|
||||||
.reveal-visible {
|
|
||||||
opacity: 1;
|
|
||||||
visibility: visible;
|
|
||||||
transform: translateY(0);
|
|
||||||
transition: all 500ms ease-out 100ms;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -1,20 +1,11 @@
|
|||||||
import { forwardRef } from 'react'
|
|
||||||
|
|
||||||
type SectionHeadingProps = React.ComponentPropsWithRef<'h2'>
|
type SectionHeadingProps = React.ComponentPropsWithRef<'h2'>
|
||||||
|
|
||||||
export const SectionHeading = forwardRef<
|
export const SectionHeading: React.FC<SectionHeadingProps> = (props) => {
|
||||||
HTMLHeadingElement,
|
|
||||||
SectionHeadingProps
|
|
||||||
>((props, ref) => {
|
|
||||||
const { children, ...rest } = props
|
const { children, ...rest } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<h2
|
<h2 {...rest} className='mt-1 mb-3 text-center text-4xl font-semibold'>
|
||||||
ref={ref}
|
|
||||||
{...rest}
|
|
||||||
className='text-4xl font-semibold text-center mt-1 mb-7'
|
|
||||||
>
|
|
||||||
{children}
|
{children}
|
||||||
</h2>
|
</h2>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { forwardRef } from 'react'
|
|
||||||
|
|
||||||
import { ShadowContainer } from '../ShadowContainer'
|
import { ShadowContainer } from '../ShadowContainer'
|
||||||
import { SectionHeading } from './SectionHeading'
|
import { SectionHeading } from './SectionHeading'
|
||||||
|
|
||||||
@ -10,7 +8,7 @@ type SectionProps = React.ComponentPropsWithRef<'section'> & {
|
|||||||
withoutShadowContainer?: boolean
|
withoutShadowContainer?: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
export const Section: React.FC<SectionProps> = (props) => {
|
||||||
const {
|
const {
|
||||||
children,
|
children,
|
||||||
heading,
|
heading,
|
||||||
@ -22,11 +20,11 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
|
|
||||||
if (isMain) {
|
if (isMain) {
|
||||||
return (
|
return (
|
||||||
<div className='px-3 w-full'>
|
<div className='w-full px-3'>
|
||||||
<ShadowContainer style={{ marginTop: 50 }}>
|
<ShadowContainer style={{ marginTop: 50 }}>
|
||||||
<section ref={ref} {...rest}>
|
<section {...rest}>
|
||||||
{heading != null && <SectionHeading>{heading}</SectionHeading>}
|
{heading != null && <SectionHeading>{heading}</SectionHeading>}
|
||||||
<div className='px-3 w-full'>{children}</div>
|
<div className='w-full px-3'>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
</ShadowContainer>
|
</ShadowContainer>
|
||||||
</div>
|
</div>
|
||||||
@ -35,15 +33,15 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
|
|
||||||
if (withoutShadowContainer) {
|
if (withoutShadowContainer) {
|
||||||
return (
|
return (
|
||||||
<section ref={ref} {...rest}>
|
<section {...rest}>
|
||||||
{heading != null && <SectionHeading>{heading}</SectionHeading>}
|
{heading != null && <SectionHeading>{heading}</SectionHeading>}
|
||||||
<div className='px-3 w-full'>{children}</div>
|
<div className='w-full px-3'>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section ref={ref} {...rest}>
|
<section {...rest}>
|
||||||
{heading != null && (
|
{heading != null && (
|
||||||
<SectionHeading style={{ ...(description != null && { margin: 0 }) }}>
|
<SectionHeading style={{ ...(description != null && { margin: 0 }) }}>
|
||||||
{heading}
|
{heading}
|
||||||
@ -54,11 +52,11 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
{description}
|
{description}
|
||||||
</p>
|
</p>
|
||||||
)}
|
)}
|
||||||
<div className='px-3 w-full'>
|
<div className='w-full px-3'>
|
||||||
<ShadowContainer>
|
<ShadowContainer>
|
||||||
<div className='px-16 py-4 leading-8 w-full'>{children}</div>
|
<div className='w-full px-16 py-4 leading-8'>{children}</div>
|
||||||
</ShadowContainer>
|
</ShadowContainer>
|
||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
@ -6,27 +6,14 @@ export const ShadowContainer: React.FC<ShadowContainerProps> = (props) => {
|
|||||||
const { children, className, ...rest } = props
|
const { children, className, ...rest } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<div
|
||||||
<div
|
className={classNames(
|
||||||
className={classNames(
|
'mb-12 h-full max-w-full break-words rounded-2xl border border-solid border-[#000] shadow-light dark:shadow-dark ',
|
||||||
'shadow-container h-full max-w-full break-words',
|
className
|
||||||
className
|
)}
|
||||||
)}
|
{...rest}
|
||||||
{...rest}
|
>
|
||||||
>
|
{children}
|
||||||
{children}
|
</div>
|
||||||
</div>
|
|
||||||
|
|
||||||
<style jsx>
|
|
||||||
{`
|
|
||||||
.shadow-container {
|
|
||||||
box-shadow: 0px 0px 6px 6px rgba(0, 0, 0, 0.25);
|
|
||||||
border: 1px solid black;
|
|
||||||
border-radius: 1rem;
|
|
||||||
margin-bottom: 50px;
|
|
||||||
}
|
|
||||||
`}
|
|
||||||
</style>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
8
cypress.json
Normal file
8
cypress.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "http://localhost:3000",
|
||||||
|
"pluginsFile": false,
|
||||||
|
"supportFile": false,
|
||||||
|
"fixturesFolder": false,
|
||||||
|
"video": false,
|
||||||
|
"screenshotOnRunFailure": false
|
||||||
|
}
|
58
cypress/integration/common/Header.spec.ts
Normal file
58
cypress/integration/common/Header.spec.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
describe('Common > Header', () => {
|
||||||
|
beforeEach(() => cy.visit('/'))
|
||||||
|
|
||||||
|
it('should redirect to /blog on click of the blog link', () => {
|
||||||
|
cy.get('[data-cy=header-blog-link]')
|
||||||
|
.click()
|
||||||
|
.location('pathname')
|
||||||
|
.should('eq', '/blog')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should always be visible (sticky header)', () => {
|
||||||
|
cy.scrollTo('bottom').get('header').should('be.visible')
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Switch theme color (dark/light)', () => {
|
||||||
|
it('should switch theme from `dark` (default) to `light`', () => {
|
||||||
|
cy.get('[data-cy=switch-theme-dark]').should('be.visible')
|
||||||
|
cy.get('[data-cy=switch-theme-light]').should('not.be.visible')
|
||||||
|
cy.get('body').should(
|
||||||
|
'not.have.css',
|
||||||
|
'background-color',
|
||||||
|
'rgb(255, 255, 255)'
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('[data-cy=switch-theme-click]').click()
|
||||||
|
|
||||||
|
cy.get('[data-cy=switch-theme-dark]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=switch-theme-light]').should('be.visible')
|
||||||
|
cy.get('body').should(
|
||||||
|
'have.css',
|
||||||
|
'background-color',
|
||||||
|
'rgb(255, 255, 255)'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Switch Language', () => {
|
||||||
|
it('should switch language from EN (default) to FR', () => {
|
||||||
|
cy.get('h1').contains('I am Divlo')
|
||||||
|
cy.get('[data-cy=language-flag-text]').contains('EN')
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-click]').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('be.visible')
|
||||||
|
cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-flag-text]').contains('FR')
|
||||||
|
cy.get('h1').contains('Je suis Divlo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should close the language list menu when clicking outside', () => {
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-click]').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('be.visible')
|
||||||
|
cy.get('h1').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
7
cypress/integration/pages/404.spec.ts
Normal file
7
cypress/integration/pages/404.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
7
cypress/integration/pages/500.spec.ts
Normal file
7
cypress/integration/pages/500.spec.ts
Normal 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')
|
||||||
|
})
|
||||||
|
})
|
13
cypress/integration/pages/blog/[slug].spec.ts
Normal file
13
cypress/integration/pages/blog/[slug].spec.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
describe('Page /blog/[slug]', () => {
|
||||||
|
it('should displays the first blog post (`hello-world`)', () => {
|
||||||
|
cy.visit('/blog/hello-world')
|
||||||
|
cy.get('[data-cy=language-flag-text]').should('not.exist')
|
||||||
|
cy.get('h1').should('have.text', '👋 Hello, world!')
|
||||||
|
cy.get('.prose a').should('have.attr', 'target', '_blank')
|
||||||
|
})
|
||||||
|
|
||||||
|
it("should redirect to /404 if the blog post doesn't exist", () => {
|
||||||
|
cy.visit('/blog/random-blog-post-not-found', { failOnStatusCode: false })
|
||||||
|
cy.get('[data-cy=status-code]').contains('404')
|
||||||
|
})
|
||||||
|
})
|
22
cypress/integration/pages/blog/index.spec.ts
Normal file
22
cypress/integration/pages/blog/index.spec.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
describe('Page /blog', () => {
|
||||||
|
it('should displays the blog posts sorted from newest to oldest', () => {
|
||||||
|
cy.visit('/blog')
|
||||||
|
cy.get('[data-cy=blog-posts] [data-cy=blog-post-title]')
|
||||||
|
.last()
|
||||||
|
.should('have.text', '👋 Hello, world!')
|
||||||
|
cy.get('[data-cy=blog-posts] [data-cy=blog-post-description]')
|
||||||
|
.last()
|
||||||
|
.should(
|
||||||
|
'have.text',
|
||||||
|
'First post of the blog, introduction and explanation of how this blog is made.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should redirect the user to the right blog post', () => {
|
||||||
|
cy.visit('/blog')
|
||||||
|
cy.get('[data-cy=hello-world]')
|
||||||
|
.click()
|
||||||
|
.location('pathname')
|
||||||
|
.should('eq', '/blog/hello-world')
|
||||||
|
})
|
||||||
|
})
|
19
cypress/integration/pages/index.spec.ts
Normal file
19
cypress/integration/pages/index.spec.ts
Normal 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
9
cypress/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"noEmit": true,
|
||||||
|
"types": ["cypress"],
|
||||||
|
"isolatedModules": false
|
||||||
|
},
|
||||||
|
"include": ["../node_modules/cypress", "./**/*.ts"]
|
||||||
|
}
|
@ -9,3 +9,4 @@ services:
|
|||||||
- '${PORT}:${PORT}'
|
- '${PORT}:${PORT}'
|
||||||
environment:
|
environment:
|
||||||
PORT: ${PORT}
|
PORT: ${PORT}
|
||||||
|
env_file: './.env'
|
||||||
|
@ -1,27 +1,14 @@
|
|||||||
module.exports = {
|
const nextJest = require('next/jest')
|
||||||
roots: ['<rootDir>'],
|
|
||||||
transform: {
|
const createJestConfig = nextJest()
|
||||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
|
const customJestConfig = {
|
||||||
},
|
|
||||||
moduleDirectories: ['node_modules', './'],
|
moduleDirectories: ['node_modules', './'],
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
modulePathIgnorePatterns: ['<rootDir>/cypress'],
|
||||||
|
testEnvironment: 'jsdom',
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
'@testing-library/jest-dom/extend-expect',
|
'@testing-library/jest-dom/extend-expect',
|
||||||
'@testing-library/react'
|
'@testing-library/react'
|
||||||
],
|
]
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'**/*.{js,jsx,ts,tsx}',
|
|
||||||
'!**/*.d.ts',
|
|
||||||
'!**/.next/**',
|
|
||||||
'!**/node_modules/**',
|
|
||||||
'!**/next.config.js',
|
|
||||||
'!**/postcss.config.js',
|
|
||||||
'!**/tailwind.config.js',
|
|
||||||
'!**/workbox-*.js',
|
|
||||||
'!**/sw.js',
|
|
||||||
'!**/jest.config.js'
|
|
||||||
],
|
|
||||||
coverageDirectory: './coverage',
|
|
||||||
coverageReporters: ['text', 'cobertura']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
module.exports = createJestConfig(customJestConfig)
|
||||||
|
4
jsonresume-theme-custom/.gitignore
vendored
Normal file
4
jsonresume-theme-custom/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
node_modules
|
||||||
|
theme/index.html
|
||||||
|
dist
|
||||||
|
.parcel-cache
|
32
jsonresume-theme-custom/index.js
Normal file
32
jsonresume-theme-custom/index.js
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
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
|
||||||
|
}
|
4953
jsonresume-theme-custom/package-lock.json
generated
Normal file
4953
jsonresume-theme-custom/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
18
jsonresume-theme-custom/package.json
Normal file
18
jsonresume-theme-custom/package.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"name": "jsonresume-theme-custom",
|
||||||
|
"private": true,
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {},
|
||||||
|
"dependencies": {
|
||||||
|
"date-and-time": "2.1.2",
|
||||||
|
"ejs": "3.1.6",
|
||||||
|
"modern-normalize": "1.1.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"
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,2 @@
|
|||||||
|
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M243.4 2.587C251.4-.8625 260.6-.8625 268.6 2.587L492.6 98.59C506.6 104.6 514.4 119.6 511.3 134.4C508.3 149.3 495.2 159.1 479.1 160V168C479.1 181.3 469.3 192 455.1 192H55.1C42.74 192 31.1 181.3 31.1 168V160C16.81 159.1 3.708 149.3 .6528 134.4C-2.402 119.6 5.429 104.6 19.39 98.59L243.4 2.587zM256 128C273.7 128 288 113.7 288 96C288 78.33 273.7 64 256 64C238.3 64 224 78.33 224 96C224 113.7 238.3 128 256 128zM127.1 416H167.1V224H231.1V416H280V224H344V416H384V224H448V420.3C448.6 420.6 449.2 420.1 449.8 421.4L497.8 453.4C509.5 461.2 514.7 475.8 510.6 489.3C506.5 502.8 494.1 512 480 512H31.1C17.9 512 5.458 502.8 1.372 489.3C-2.715 475.8 2.515 461.2 14.25 453.4L62.25 421.4C62.82 420.1 63.41 420.6 63.1 420.3V224H127.1V416z"/></svg>
|
After Width: | Height: | Size: 1015 B |
2
jsonresume-theme-custom/theme/images/graduation-cap.svg
Normal file
2
jsonresume-theme-custom/theme/images/graduation-cap.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 640 512"><path d="M623.1 136.9l-282.7-101.2c-13.73-4.91-28.7-4.91-42.43 0L16.05 136.9C6.438 140.4 0 149.6 0 160s6.438 19.65 16.05 23.09L76.07 204.6c-11.89 15.8-20.26 34.16-24.55 53.95C40.05 263.4 32 274.8 32 288c0 9.953 4.814 18.49 11.94 24.36l-24.83 149C17.48 471.1 25 480 34.89 480H93.11c9.887 0 17.41-8.879 15.78-18.63l-24.83-149C91.19 306.5 96 297.1 96 288c0-10.29-5.174-19.03-12.72-24.89c4.252-17.76 12.88-33.82 24.94-47.03l190.6 68.23c13.73 4.91 28.7 4.91 42.43 0l282.7-101.2C633.6 179.6 640 170.4 640 160S633.6 140.4 623.1 136.9zM351.1 314.4C341.7 318.1 330.9 320 320 320c-10.92 0-21.69-1.867-32-5.555L142.8 262.5L128 405.3C128 446.6 213.1 480 320 480c105.1 0 192-33.4 192-74.67l-14.78-142.9L351.1 314.4z"/></svg>
|
After Width: | Height: | Size: 986 B |
2
jsonresume-theme-custom/theme/images/heart.svg
Normal file
2
jsonresume-theme-custom/theme/images/heart.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M0 190.9V185.1C0 115.2 50.52 55.58 119.4 44.1C164.1 36.51 211.4 51.37 244 84.02L256 96L267.1 84.02C300.6 51.37 347 36.51 392.6 44.1C461.5 55.58 512 115.2 512 185.1V190.9C512 232.4 494.8 272.1 464.4 300.4L283.7 469.1C276.2 476.1 266.3 480 256 480C245.7 480 235.8 476.1 228.3 469.1L47.59 300.4C17.23 272.1 .0003 232.4 .0003 190.9L0 190.9z"/></svg>
|
After Width: | Height: | Size: 629 B |
2
jsonresume-theme-custom/theme/images/toolbox.svg
Normal file
2
jsonresume-theme-custom/theme/images/toolbox.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><path d="M502.6 182.6l-45.25-45.25C451.4 131.4 443.3 128 434.8 128H384V80C384 53.5 362.5 32 336 32h-160C149.5 32 128 53.5 128 80V128H77.25c-8.5 0-16.62 3.375-22.62 9.375L9.375 182.6C3.375 188.6 0 196.8 0 205.3V304h128v-32C128 263.1 135.1 256 144 256h32C184.9 256 192 263.1 192 272v32h128v-32C320 263.1 327.1 256 336 256h32C376.9 256 384 263.1 384 272v32h128V205.3C512 196.8 508.6 188.6 502.6 182.6zM336 128h-160V80h160V128zM384 368c0 8.875-7.125 16-16 16h-32c-8.875 0-16-7.125-16-16v-32H192v32C192 376.9 184.9 384 176 384h-32C135.1 384 128 376.9 128 368v-32H0V448c0 17.62 14.38 32 32 32h448c17.62 0 32-14.38 32-32v-112h-128V368z"/></svg>
|
After Width: | Height: | Size: 912 B |
2
jsonresume-theme-custom/theme/images/user.svg
Normal file
2
jsonresume-theme-custom/theme/images/user.svg
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
<!--! Font Awesome Free 6.0.0 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free (Icons: CC BY 4.0, Fonts: SIL OFL 1.1, Code: MIT License) Copyright 2022 Fonticons, Inc. -->
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><path d="M224 256c70.7 0 128-57.31 128-128s-57.3-128-128-128C153.3 0 96 57.31 96 128S153.3 256 224 256zM274.7 304H173.3C77.61 304 0 381.6 0 477.3c0 19.14 15.52 34.67 34.66 34.67h378.7C432.5 512 448 496.5 448 477.3C448 381.6 370.4 304 274.7 304z"/></svg>
|
After Width: | Height: | Size: 528 B |
206
jsonresume-theme-custom/theme/index.ejs
Normal file
206
jsonresume-theme-custom/theme/index.ejs
Normal file
@ -0,0 +1,206 @@
|
|||||||
|
<!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>
|
229
jsonresume-theme-custom/theme/styles/global.css
Normal file
229
jsonresume-theme-custom/theme/styles/global.css
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
@import 'npm:modern-normalize/modern-normalize.css';
|
||||||
|
|
||||||
|
body {
|
||||||
|
font-family: 'Montserrat', 'Arial', 'sans-serif';
|
||||||
|
background: #f0f0f0;
|
||||||
|
color: #333;
|
||||||
|
line-height: 1.42857143;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
hr {
|
||||||
|
margin-top: 15px;
|
||||||
|
margin-bottom: 15px;
|
||||||
|
border: 0;
|
||||||
|
border-top: 1px solid #eee;
|
||||||
|
}
|
||||||
|
p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
strong {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
a {
|
||||||
|
color: #337ab7;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
a:focus,
|
||||||
|
a:hover {
|
||||||
|
color: #23527c;
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
.link-disguise {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.link-disguise:hover {
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.h1,
|
||||||
|
.h2,
|
||||||
|
.h3,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3 {
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.h4,
|
||||||
|
.h5,
|
||||||
|
.h6,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
.h1,
|
||||||
|
.h2,
|
||||||
|
.h3,
|
||||||
|
.h4,
|
||||||
|
.h5,
|
||||||
|
.h6,
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
font-family: inherit;
|
||||||
|
font-weight: 500;
|
||||||
|
line-height: 1.1;
|
||||||
|
color: inherit;
|
||||||
|
}
|
||||||
|
.h3,
|
||||||
|
h3 {
|
||||||
|
font-size: 24px;
|
||||||
|
}
|
||||||
|
.h4,
|
||||||
|
h4 {
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
.h5,
|
||||||
|
h5 {
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
.container-fluid {
|
||||||
|
padding-right: 15px;
|
||||||
|
padding-left: 15px;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.row {
|
||||||
|
margin-right: -15px;
|
||||||
|
margin-left: -15px;
|
||||||
|
}
|
||||||
|
.clear-margin {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
.relative {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.center-block {
|
||||||
|
display: block;
|
||||||
|
margin-right: auto;
|
||||||
|
margin-left: auto;
|
||||||
|
}
|
||||||
|
.text-center {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.text-muted {
|
||||||
|
color: #777;
|
||||||
|
}
|
||||||
|
.text-uppercase {
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.list-unstyled {
|
||||||
|
padding-left: 0;
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.main {
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
.title {
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-wrapper {
|
||||||
|
float: none !important;
|
||||||
|
padding: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.profile-card-wrapper .profile-card {
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 3px;
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.profile-pic {
|
||||||
|
padding: 10px 0;
|
||||||
|
}
|
||||||
|
.profile-pic img {
|
||||||
|
width: 100px;
|
||||||
|
height: 100px;
|
||||||
|
border-radius: 50%;
|
||||||
|
vertical-align: middle;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
.contact-details {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
.contact-details .detail {
|
||||||
|
position: relative;
|
||||||
|
min-height: 1px;
|
||||||
|
padding: 10px;
|
||||||
|
}
|
||||||
|
.social-links {
|
||||||
|
line-height: 2.5;
|
||||||
|
}
|
||||||
|
.experience-description {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.background-details .detail {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
.background-details .detail .icon,
|
||||||
|
.background-details .detail .info {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
.background-details .detail .icon {
|
||||||
|
color: #707070;
|
||||||
|
}
|
||||||
|
.background-details .detail .icon {
|
||||||
|
min-width: 45px;
|
||||||
|
max-width: 45px;
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.icon img {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
.background-details .detail .mobile-title {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
.card-nested {
|
||||||
|
min-height: 0;
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-skills {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
.labels {
|
||||||
|
line-height: 2;
|
||||||
|
}
|
||||||
|
.space-top {
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
display: inline;
|
||||||
|
padding: 0.2em 0.6em 0.3em;
|
||||||
|
font-size: 75%;
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
color: #fff;
|
||||||
|
text-align: center;
|
||||||
|
white-space: nowrap;
|
||||||
|
vertical-align: baseline;
|
||||||
|
border-radius: 0.25em;
|
||||||
|
}
|
||||||
|
.label-keyword {
|
||||||
|
display: inline-block;
|
||||||
|
background: #7eb0db;
|
||||||
|
color: white;
|
||||||
|
font-size: 0.9em;
|
||||||
|
padding: 5px;
|
||||||
|
border: 1px solid #357ebd;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
.label-keyword p {
|
||||||
|
margin: 0;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"english": "English",
|
"english": "English",
|
||||||
"french": "French",
|
"french": "French",
|
||||||
"allRightsReserved": "All rights reserved",
|
"all-rights-reserved": "All rights reserved",
|
||||||
"home": "Home"
|
"home": "Home"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"returnToHomePage": "Return to the home page?",
|
"return-to-home-page": "Return to the home page?",
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
"serverError": "Internal Server Error!",
|
"server-error": "Internal Server Error!",
|
||||||
"notFound": "This page doesn't exist!"
|
"not-found": "This page doesn't exist!"
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,36 @@
|
|||||||
{
|
{
|
||||||
"about": {
|
"about": {
|
||||||
"IAm": "I am",
|
"i-am": "I am",
|
||||||
"description": "Developer Full Stack Junior • Passionate about High-Tech",
|
"description": "Developer Full Stack Junior • Passionate about High-Tech",
|
||||||
"birthDate": "Birth date",
|
"full-name": "Full name",
|
||||||
|
"birth-date": "Birth date",
|
||||||
"nationality": "Nationality",
|
"nationality": "Nationality",
|
||||||
"descriptionBottom": "I am self-taught in Computer Science by following online trainings. <0/> <0/> I put into practice everything I learn and make many projects."
|
"description-bottom": "I am 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)."
|
||||||
},
|
},
|
||||||
"interests": {
|
"interests": {
|
||||||
"title": "My Interests",
|
"title": "Interests",
|
||||||
"paragraphs": [
|
"paragraphs": [
|
||||||
{
|
{
|
||||||
"title": "Developer Full Stack Junior :",
|
"title": "Developer Full Stack Junior",
|
||||||
"description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming some Python and others programming language too."
|
"description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming in others programming language too."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Passionate about High-Tech :",
|
"title": "Passionate about High-Tech",
|
||||||
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas."
|
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Open-Source enthusiast :",
|
"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": {
|
"skills": {
|
||||||
"title": "My skills",
|
"title": "Skills",
|
||||||
"languages": "Programming languages",
|
"languages": "Programming languages",
|
||||||
"softwareTools": "Software and tools"
|
"software-tools": "Software and tools"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"title": "My Portfolio",
|
"title": "Portfolio",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "function.divlo.fr",
|
"title": "function.divlo.fr",
|
||||||
@ -42,7 +43,16 @@
|
|||||||
"description": "Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun.",
|
"description": "Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun.",
|
||||||
"link": "https://thream.divlo.fr/",
|
"link": "https://thream.divlo.fr/",
|
||||||
"image": "/images/portfolio/threamdivlofr.png"
|
"image": "/images/portfolio/threamdivlofr.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Leon",
|
||||||
|
"description": "Leon is your open-source personal assistant.",
|
||||||
|
"link": "https://getleon.ai/",
|
||||||
|
"image": "/images/portfolio/leon.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"open-source": {
|
||||||
|
"description": "List of most famous open source projects I contributed to."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"english": "Anglais",
|
"english": "Anglais",
|
||||||
"french": "Français",
|
"french": "Français",
|
||||||
"allRightsReserved": "Tous droits réservés",
|
"all-rights-reserved": "Tous droits réservés",
|
||||||
"home": "Accueil"
|
"home": "Accueil"
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"returnToHomePage": "Revenir à la page d'accueil ?",
|
"return-to-home-page": "Revenir à la page d'accueil ?",
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
"serverError": "Erreur Interne du Serveur !",
|
"server-error": "Erreur Interne du Serveur !",
|
||||||
"notFound": "Cette page n'existe pas!"
|
"not-found": "Cette page n'existe pas!"
|
||||||
}
|
}
|
||||||
|
@ -1,35 +1,36 @@
|
|||||||
{
|
{
|
||||||
"about": {
|
"about": {
|
||||||
"IAm": "Je suis",
|
"i-am": "Je suis",
|
||||||
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
|
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
|
||||||
"birthDate": "Date de naissance",
|
"full-name": "Prénom NOM",
|
||||||
|
"birth-date": "Date de naissance",
|
||||||
"nationality": "Nationalité",
|
"nationality": "Nationalité",
|
||||||
"descriptionBottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne. <0/> <0/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets."
|
"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)."
|
||||||
},
|
},
|
||||||
"interests": {
|
"interests": {
|
||||||
"title": "Mes intérêts",
|
"title": "Intérêts",
|
||||||
"paragraphs": [
|
"paragraphs": [
|
||||||
{
|
{
|
||||||
"title": "Développeur Full Stack Junior :",
|
"title": "Développeur Full Stack Junior",
|
||||||
"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."
|
"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": "Passionné de High-Tech :",
|
"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."
|
"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 :",
|
"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": {
|
"skills": {
|
||||||
"title": "Mes compétences",
|
"title": "Compétences",
|
||||||
"languages": "Langages de programmation",
|
"languages": "Langages de programmation",
|
||||||
"softwareTools": "Logiciels et outils"
|
"software-tools": "Logiciels et outils"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"title": "Mon Portfolio",
|
"title": "Portfolio",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "function.divlo.fr",
|
"title": "function.divlo.fr",
|
||||||
@ -39,10 +40,19 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "thream.divlo.fr",
|
"title": "thream.divlo.fr",
|
||||||
"description": "Votre plateforme open source pour rester proche de vos amis et communautés, parler, discuter, collaborer, partager et vous amuser.",
|
"description": "Votre plateforme open source pour rester proche de vos amis et communautés, parler, discuter, collaborer, partager et amusez-vous.",
|
||||||
"link": "https://thream.divlo.fr/",
|
"link": "https://thream.divlo.fr/",
|
||||||
"image": "/images/portfolio/threamdivlofr.png"
|
"image": "/images/portfolio/threamdivlofr.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"title": "Leon",
|
||||||
|
"description": "Leon est votre assistant personnel open source.",
|
||||||
|
"link": "https://getleon.ai/",
|
||||||
|
"image": "/images/portfolio/leon.png"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
"open-source": {
|
||||||
|
"description": "Liste des projets open source les plus célèbres auxquels j'ai contribué."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
5
next-env.d.ts
vendored
5
next-env.d.ts
vendored
@ -1,2 +1,5 @@
|
|||||||
/// <reference types="next" />
|
/// <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.
|
||||||
|
@ -1,11 +1,41 @@
|
|||||||
const nextPWA = require('next-pwa')
|
const nextPWA = require('next-pwa')
|
||||||
const nextTranslate = require('next-translate')
|
const nextTranslate = require('next-translate')
|
||||||
|
const { createSecureHeaders } = require('next-secure-headers')
|
||||||
|
|
||||||
|
/** @type {import("next").NextConfig} */
|
||||||
module.exports = nextTranslate(
|
module.exports = nextTranslate(
|
||||||
nextPWA({
|
nextPWA({
|
||||||
|
reactStrictMode: true,
|
||||||
pwa: {
|
pwa: {
|
||||||
disable: process.env.NODE_ENV !== 'production',
|
disable: process.env.NODE_ENV !== 'production',
|
||||||
dest: 'public'
|
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'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
47156
package-lock.json
generated
47156
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
125
package.json
125
package.json
@ -1,73 +1,106 @@
|
|||||||
{
|
{
|
||||||
"name": "divlo",
|
"name": "divlo",
|
||||||
"version": "0.0.0-development",
|
"version": "2.0.2",
|
||||||
"private": true,
|
"private": true,
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/Divlo/Divlo"
|
"url": "https://github.com/Divlo/Divlo"
|
||||||
},
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.0.0",
|
||||||
|
"npm": ">=7.0.0"
|
||||||
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev",
|
"dev": "next dev",
|
||||||
"start": "next start",
|
"start": "next start",
|
||||||
"build": "next build",
|
"build": "npm run resume:export && next build",
|
||||||
"export": "next export",
|
"export": "next export",
|
||||||
"lint:commit": "commitlint",
|
"lint:commit": "commitlint",
|
||||||
"lint:docker": "dockerfilelint './Dockerfile'",
|
|
||||||
"lint:editorconfig": "editorconfig-checker",
|
"lint:editorconfig": "editorconfig-checker",
|
||||||
"lint:markdown": "markdownlint '*.md' --dot --ignore node_modules",
|
"lint:markdown": "markdownlint \"**/*.{md,mdx}\" --dot --ignore-path \".gitignore\"",
|
||||||
"lint:typescript": "eslint '*.{js,ts,jsx,tsx}'",
|
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\"",
|
||||||
|
"lint:prettier": "prettier \".\" --check",
|
||||||
"lint:staged": "lint-staged",
|
"lint:staged": "lint-staged",
|
||||||
"lighthouse": "lhci autorun",
|
"test:unit": "jest",
|
||||||
"test": "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\"",
|
||||||
"release": "semantic-release",
|
"release": "semantic-release",
|
||||||
|
"deploy": "vercel",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/montserrat": "4.3.0",
|
"@fontsource/montserrat": "4.5.5",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
"@fortawesome/fontawesome-svg-core": "1.3.0",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.3",
|
"@fortawesome/free-brands-svg-icons": "6.0.0",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
"@fortawesome/free-solid-svg-icons": "6.0.0",
|
||||||
"@fortawesome/react-fontawesome": "0.1.14",
|
"@fortawesome/react-fontawesome": "0.1.17",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"html-react-parser": "1.2.6",
|
"date-and-time": "2.1.2",
|
||||||
"next": "10.2.0",
|
"gray-matter": "4.0.3",
|
||||||
"next-pwa": "5.2.21",
|
"html-react-parser": "1.4.8",
|
||||||
"next-themes": "0.0.14",
|
"next": "12.1.0",
|
||||||
"next-translate": "1.0.6",
|
"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": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"read-pkg": "7.1.0",
|
||||||
|
"rehype-raw": "6.1.1",
|
||||||
|
"rehype-slug": "5.0.1",
|
||||||
|
"remark-gfm": "3.0.1",
|
||||||
|
"sharp": "0.30.1",
|
||||||
|
"shiki": "0.10.1",
|
||||||
|
"unified": "10.1.1",
|
||||||
|
"unist-util-visit": "4.1.0",
|
||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "12.1.1",
|
"@commitlint/cli": "16.2.1",
|
||||||
"@commitlint/config-conventional": "12.1.1",
|
"@commitlint/config-conventional": "16.2.1",
|
||||||
"@lhci/cli": "0.7.2",
|
"@lhci/cli": "0.9.0",
|
||||||
"@testing-library/jest-dom": "5.12.0",
|
"@saithodev/semantic-release-backmerge": "2.1.1",
|
||||||
"@testing-library/react": "11.2.6",
|
"@semantic-release/git": "10.0.1",
|
||||||
"@types/jest": "26.0.23",
|
"@tailwindcss/typography": "0.5.2",
|
||||||
"@types/node": "15.0.2",
|
"@testing-library/jest-dom": "5.16.2",
|
||||||
"@types/react": "17.0.5",
|
"@testing-library/react": "12.1.3",
|
||||||
"@types/styled-jsx": "2.2.8",
|
"@types/date-and-time": "0.13.0",
|
||||||
"@typescript-eslint/eslint-plugin": "4.22.1",
|
"@types/jest": "27.4.0",
|
||||||
"autoprefixer": "10.2.5",
|
"@types/node": "17.0.19",
|
||||||
"babel-jest": "26.6.3",
|
"@types/react": "17.0.39",
|
||||||
"dockerfilelint": "1.8.0",
|
"@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",
|
"editorconfig-checker": "4.0.2",
|
||||||
"eslint": "7.26.0",
|
"eslint": "8.9.0",
|
||||||
"eslint-config-prettier": "8.3.0",
|
"eslint-config-conventions": "1.1.0",
|
||||||
"eslint-config-standard-with-typescript": "20.0.0",
|
"eslint-config-next": "12.1.0",
|
||||||
"eslint-plugin-import": "2.22.1",
|
"eslint-config-prettier": "8.4.0",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-import": "2.25.4",
|
||||||
"eslint-plugin-prettier": "3.4.0",
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
"eslint-plugin-promise": "4.3.1",
|
"eslint-plugin-promise": "6.0.0",
|
||||||
"husky": "6.0.0",
|
"eslint-plugin-unicorn": "41.0.0",
|
||||||
"jest": "26.6.3",
|
"html-w3c-validator": "1.0.0",
|
||||||
"lint-staged": "11.0.0",
|
"husky": "7.0.4",
|
||||||
"markdownlint-cli": "0.27.1",
|
"jest": "27.5.1",
|
||||||
"postcss": "8.2.14",
|
"jsonresume-theme-custom": "file:./jsonresume-theme-custom",
|
||||||
"prettier": "2.2.1",
|
"lint-staged": "12.3.4",
|
||||||
"semantic-release": "17.4.2",
|
"markdownlint-cli": "0.31.1",
|
||||||
"tailwindcss": "2.1.2",
|
"next-secure-headers": "2.2.0",
|
||||||
"typescript": "4.2.4"
|
"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"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,22 +1,38 @@
|
|||||||
import { GetStaticProps } from 'next'
|
import { GetStaticProps, NextPage } from 'next'
|
||||||
import useTranslation from 'next-translate/useTranslation'
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
import { ErrorPage } from 'components/ErrorPage'
|
import { ErrorPage } from 'components/ErrorPage'
|
||||||
import { Head } from 'components/Head'
|
import { Head } from 'components/Head'
|
||||||
|
import { Header } from 'components/Header'
|
||||||
|
import { Footer, FooterProps } from 'components/Footer'
|
||||||
|
import { getDefaultDescription } from 'utils/getDefaultDescription'
|
||||||
|
|
||||||
const Error404: React.FC = () => {
|
interface Error404Props extends FooterProps {
|
||||||
|
description: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const Error404: NextPage<Error404Props> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { version, description } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head title='Divlo - 404' />
|
<Head title='404 | Divlo' description={description} />
|
||||||
<ErrorPage statusCode={404} message={t('errors:notFound')} />
|
|
||||||
|
<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} />
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getStaticProps: GetStaticProps = async () => {
|
export const getStaticProps: GetStaticProps<FooterProps> = async () => {
|
||||||
return { props: {} }
|
const { readPackage } = await import('read-pkg')
|
||||||
|
const { version } = await readPackage()
|
||||||
|
const description = getDefaultDescription()
|
||||||
|
return { props: { version, description } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Error404
|
export default Error404
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user