Compare commits
211 Commits
Author | SHA1 | Date | |
---|---|---|---|
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 | |||
cd20e25082 |
@ -1,3 +1,14 @@
|
|||||||
{
|
{
|
||||||
"presets": ["next/babel"]
|
"presets": [
|
||||||
|
[
|
||||||
|
"next/babel",
|
||||||
|
{
|
||||||
|
"preset-env": {
|
||||||
|
"targets": {
|
||||||
|
"browsers": ">1%, not ie 11, not dead"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
7
.devcontainer/Dockerfile
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.163.1/containers/javascript-node/.devcontainer/base.Dockerfile
|
||||||
|
|
||||||
|
ARG VARIANT="14-buster"
|
||||||
|
FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${VARIANT}
|
||||||
|
|
||||||
|
ARG EXTRA_NODE_VERSION=16
|
||||||
|
RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
24
.devcontainer/devcontainer.json
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
{
|
||||||
|
"name": "divlo",
|
||||||
|
"dockerComposeFile": "./docker-compose.yml",
|
||||||
|
"service": "workspace",
|
||||||
|
"workspaceFolder": "/workspace",
|
||||||
|
"settings": {
|
||||||
|
"remote.autoForwardPorts": false
|
||||||
|
},
|
||||||
|
"extensions": [
|
||||||
|
"editorconfig.editorconfig",
|
||||||
|
"esbenp.prettier-vscode",
|
||||||
|
"dbaeumer.vscode-eslint",
|
||||||
|
"divlo.vscode-styled-jsx-syntax",
|
||||||
|
"divlo.vscode-styled-jsx-languageserver",
|
||||||
|
"bradlc.vscode-tailwindcss",
|
||||||
|
"mikestead.dotenv",
|
||||||
|
"coenraads.bracket-pair-colorizer",
|
||||||
|
"davidanson.vscode-markdownlint",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
|
],
|
||||||
|
"forwardPorts": [3000],
|
||||||
|
"postAttachCommand": ["npm", "clean-install"],
|
||||||
|
"remoteUser": "node"
|
||||||
|
}
|
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
|
||||||
|
@ -4,3 +4,4 @@ node_modules
|
|||||||
next-env.d.ts
|
next-env.d.ts
|
||||||
**/workbox-*.js
|
**/workbox-*.js
|
||||||
**/sw.js
|
**/sw.js
|
||||||
|
.vercel
|
||||||
|
@ -1,6 +1,11 @@
|
|||||||
{
|
{
|
||||||
"extends": ["standard-with-typescript", "eslint-config-prettier"],
|
"extends": [
|
||||||
"plugins": ["eslint-plugin-prettier"],
|
"standard-with-typescript",
|
||||||
|
"next",
|
||||||
|
"next/core-web-vitals",
|
||||||
|
"prettier"
|
||||||
|
],
|
||||||
|
"plugins": ["unicorn", "prettier"],
|
||||||
"parserOptions": {
|
"parserOptions": {
|
||||||
"project": "./tsconfig.json"
|
"project": "./tsconfig.json"
|
||||||
},
|
},
|
||||||
@ -10,6 +15,17 @@
|
|||||||
"jest": true
|
"jest": true
|
||||||
},
|
},
|
||||||
"rules": {
|
"rules": {
|
||||||
"prettier/prettier": "error"
|
"prettier/prettier": "error",
|
||||||
|
"unicorn/prefer-node-protocol": "error",
|
||||||
|
"unicorn/prevent-abbreviations": [
|
||||||
|
"error",
|
||||||
|
{
|
||||||
|
"replacements": {
|
||||||
|
"props": {
|
||||||
|
"properties": false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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?
|
||||||
|
|
||||||
|
142
.github/workflows/Divlo.yml
vendored
@ -2,37 +2,145 @@ name: 'Divlo'
|
|||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [master]
|
branches: [master, develop]
|
||||||
pull_request:
|
pull_request:
|
||||||
branches: [master]
|
branches: [master, develop]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
ci:
|
analyze:
|
||||||
runs-on: 'ubuntu-latest'
|
runs-on: 'ubuntu-latest'
|
||||||
|
|
||||||
strategy:
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
node-version: [14.x]
|
language: ['javascript']
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: 'actions/checkout@v2'
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
- name: 'Initialize CodeQL'
|
||||||
uses: 'actions/setup-node@v2.1.5'
|
uses: 'github/codeql-action/init@v1'
|
||||||
with:
|
with:
|
||||||
node-version: ${{ matrix.node-version }}
|
languages: ${{ matrix.language }}
|
||||||
|
|
||||||
- name: 'Cache dependencies'
|
- name: 'Perform CodeQL Analysis'
|
||||||
uses: 'actions/cache@v2.1.5'
|
uses: 'github/codeql-action/analyze@v1'
|
||||||
|
|
||||||
|
lint:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
|
||||||
|
- name: 'Use Node.js'
|
||||||
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
with:
|
with:
|
||||||
path: '.npm'
|
node-version: '16.x'
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
- run: 'npm install --global npm@7'
|
|
||||||
- run: 'npm ci --cache .npm --prefer-offline'
|
|
||||||
- run: 'npm run lint:commit -- --to "${{ github.sha }}"'
|
- run: 'npm run lint:commit -- --to "${{ github.sha }}"'
|
||||||
- run: 'npm run lint:docker'
|
- run: 'npm run lint:docker'
|
||||||
- run: 'npm run lint:editorconfig'
|
- run: 'npm run lint:editorconfig'
|
||||||
- run: 'npm run lint:markdown'
|
- run: 'npm run lint:markdown'
|
||||||
- run: 'npm run lint:typescript'
|
- run: 'npm run lint:typescript'
|
||||||
- run: 'npm run build'
|
|
||||||
- run: 'npm run lighthouse'
|
test-unit:
|
||||||
- run: 'npm run test'
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
|
||||||
|
- name: 'Use Node.js'
|
||||||
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
|
- name: 'Unit Test'
|
||||||
|
run: 'npm run test:unit'
|
||||||
|
|
||||||
|
test-lighthouse:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
|
||||||
|
- name: 'Use Node.js'
|
||||||
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
|
- name: 'Build'
|
||||||
|
run: 'npm run build'
|
||||||
|
|
||||||
|
- name: 'Lighthouse'
|
||||||
|
run: 'npm run test:lighthouse'
|
||||||
|
env:
|
||||||
|
LHCI_GITHUB_APP_TOKEN: ${{ secrets.LHCI_GITHUB_APP_TOKEN }}
|
||||||
|
|
||||||
|
test-e2e:
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
|
||||||
|
- name: 'Use Node.js'
|
||||||
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
|
- name: 'Build'
|
||||||
|
run: 'npm run build'
|
||||||
|
|
||||||
|
- name: 'End To End (e2e) Test'
|
||||||
|
run: 'npm run test:e2e'
|
||||||
|
|
||||||
|
release:
|
||||||
|
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
|
||||||
|
needs: [analyze, lint, test-unit, test-e2e, test-lighthouse]
|
||||||
|
runs-on: 'ubuntu-latest'
|
||||||
|
steps:
|
||||||
|
- uses: 'actions/checkout@v2.3.4'
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
persist-credentials: false
|
||||||
|
|
||||||
|
- name: 'Import GPG key'
|
||||||
|
uses: 'crazy-max/ghaction-import-gpg@v4'
|
||||||
|
with:
|
||||||
|
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||||
|
git_user_signingkey: true
|
||||||
|
git_commit_gpgsign: true
|
||||||
|
|
||||||
|
- name: 'Use Node.js'
|
||||||
|
uses: 'actions/setup-node@v2.4.0'
|
||||||
|
with:
|
||||||
|
node-version: '16.x'
|
||||||
|
cache: 'npm'
|
||||||
|
|
||||||
|
- name: 'Install'
|
||||||
|
run: 'npm install'
|
||||||
|
|
||||||
|
- name: 'Release'
|
||||||
|
run: 'npm run release'
|
||||||
|
env:
|
||||||
|
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }}
|
||||||
|
GIT_COMMITTER_EMAIL: ${{ secrets.GIT_EMAIL }}
|
||||||
|
|
||||||
|
- name: 'Deploy to Vercel'
|
||||||
|
run: 'npm run deploy -- --token="${VERCEL_TOKEN}" --prod'
|
||||||
|
env:
|
||||||
|
VERCEL_TOKEN: ${{ secrets.VERCEL_TOKEN }}
|
||||||
|
VERCEL_PROJECT_ID: ${{ secrets.VERCEL_PROJECT_ID }}
|
||||||
|
VERCEL_ORG_ID: ${{ secrets.VERCEL_ORG_ID }}
|
||||||
|
28
.github/workflows/codeql-analysis.yml
vendored
@ -1,28 +0,0 @@
|
|||||||
name: 'CodeQL'
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [master]
|
|
||||||
pull_request:
|
|
||||||
branches: [master]
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
analyze:
|
|
||||||
name: 'Analyze'
|
|
||||||
runs-on: 'ubuntu-latest'
|
|
||||||
|
|
||||||
strategy:
|
|
||||||
fail-fast: false
|
|
||||||
matrix:
|
|
||||||
language: ['javascript']
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- uses: 'actions/checkout@v2'
|
|
||||||
|
|
||||||
- name: 'Initialize CodeQL'
|
|
||||||
uses: 'github/codeql-action/init@v1'
|
|
||||||
with:
|
|
||||||
languages: ${{ matrix.language }}
|
|
||||||
|
|
||||||
- name: 'Perform CodeQL Analysis'
|
|
||||||
uses: 'github/codeql-action/analyze@v1'
|
|
34
.github/workflows/release.yml
vendored
@ -1,34 +0,0 @@
|
|||||||
name: 'Release'
|
|
||||||
|
|
||||||
on:
|
|
||||||
workflow_run:
|
|
||||||
workflows: [Divlo]
|
|
||||||
branches: [master]
|
|
||||||
types:
|
|
||||||
- 'completed'
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
release:
|
|
||||||
runs-on: 'ubuntu-latest'
|
|
||||||
strategy:
|
|
||||||
matrix:
|
|
||||||
node-version: [14.x]
|
|
||||||
steps:
|
|
||||||
- uses: 'actions/checkout@v2'
|
|
||||||
|
|
||||||
- name: Use Node.js ${{ matrix.node-version }}
|
|
||||||
uses: 'actions/setup-node@v2.1.5'
|
|
||||||
with:
|
|
||||||
node-version: ${{ matrix.node-version }}
|
|
||||||
|
|
||||||
- name: 'Cache dependencies'
|
|
||||||
uses: 'actions/cache@v2.1.5'
|
|
||||||
with:
|
|
||||||
path: '.npm'
|
|
||||||
key: ${{ runner.os }}-${{ hashFiles('**/package-lock.json') }}
|
|
||||||
|
|
||||||
- run: 'npm install --global npm@7'
|
|
||||||
- run: 'npm ci --cache .npm --prefer-offline'
|
|
||||||
- run: 'npm run release'
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
4
.gitignore
vendored
@ -14,6 +14,9 @@ dist
|
|||||||
|
|
||||||
# testing
|
# testing
|
||||||
coverage
|
coverage
|
||||||
|
cypress/screenshots
|
||||||
|
cypress/videos
|
||||||
|
cypress/downloads
|
||||||
|
|
||||||
# PWA
|
# PWA
|
||||||
**/workbox-*.js
|
**/workbox-*.js
|
||||||
@ -45,3 +48,4 @@ npm-debug.log*
|
|||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
.lighthouseci
|
.lighthouseci
|
||||||
|
.vercel
|
||||||
|
1
.husky/.gitignore
vendored
@ -1 +0,0 @@
|
|||||||
_
|
|
@ -10,15 +10,7 @@
|
|||||||
"assert": {
|
"assert": {
|
||||||
"preset": "lighthouse:recommended",
|
"preset": "lighthouse:recommended",
|
||||||
"assertions": {
|
"assertions": {
|
||||||
"legacy-javascript": "off",
|
"csp-xss": "warning"
|
||||||
"unused-javascript": "off",
|
|
||||||
"uses-rel-preload": "off",
|
|
||||||
"canonical": "off",
|
|
||||||
"unsized-images": "off",
|
|
||||||
"uses-responsive-images": "off",
|
|
||||||
"bypass": "warning",
|
|
||||||
"color-contrast": "warning",
|
|
||||||
"preload-lcp-image": "warning"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"upload": {
|
"upload": {
|
||||||
|
@ -6,3 +6,4 @@ package.json
|
|||||||
package-lock.json
|
package-lock.json
|
||||||
**/workbox-*.js
|
**/workbox-*.js
|
||||||
**/sw.js
|
**/sw.js
|
||||||
|
.vercel
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
{
|
{
|
||||||
|
"branches": ["master"],
|
||||||
"plugins": [
|
"plugins": [
|
||||||
[
|
[
|
||||||
"@semantic-release/commit-analyzer",
|
"@semantic-release/commit-analyzer",
|
||||||
@ -6,7 +7,31 @@
|
|||||||
"preset": "conventionalcommits"
|
"preset": "conventionalcommits"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
[
|
||||||
"@semantic-release/release-notes-generator",
|
"@semantic-release/release-notes-generator",
|
||||||
"@semantic-release/github"
|
{
|
||||||
|
"preset": "conventionalcommits"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/npm",
|
||||||
|
{
|
||||||
|
"npmPublish": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
[
|
||||||
|
"@semantic-release/git",
|
||||||
|
{
|
||||||
|
"assets": ["package.json", "package-lock.json"],
|
||||||
|
"message": "chore(release): ${nextRelease.version} [skip ci]"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"@semantic-release/github",
|
||||||
|
[
|
||||||
|
"@saithodev/semantic-release-backmerge",
|
||||||
|
{
|
||||||
|
"backmergeStrategy": "merge"
|
||||||
|
}
|
||||||
|
]
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
3
.vscode/extensions.json
vendored
@ -8,6 +8,7 @@
|
|||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"mikestead.dotenv",
|
"mikestead.dotenv",
|
||||||
"coenraads.bracket-pair-colorizer",
|
"coenraads.bracket-pair-colorizer",
|
||||||
"davidanson.vscode-markdownlint"
|
"davidanson.vscode-markdownlint",
|
||||||
|
"ms-azuretools.vscode-docker"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
41
.vscode/settings.json
vendored
@ -1,9 +1,48 @@
|
|||||||
{
|
{
|
||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"prettier.configPath": ".prettierrc.json",
|
"prettier.configPath": ".prettierrc.json",
|
||||||
"editor.formatOnSave": true,
|
"editor.formatOnSave": true,
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": true
|
"source.fixAll": true
|
||||||
|
},
|
||||||
|
"[css]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[sass]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[scss]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[markdown]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[json]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[jsonc]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescript]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[typescriptreact]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascript]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
|
},
|
||||||
|
"[javascriptreact]": {
|
||||||
|
"editor.autoClosingBrackets": "always",
|
||||||
|
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/) >= 14.0.0
|
||||||
|
- [npm](https://www.npmjs.com/) >= 7.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
|
||||||
|
28
Dockerfile
@ -1,11 +1,23 @@
|
|||||||
FROM node:16.1.0
|
FROM node:16.8.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 clean-install
|
||||||
RUN npm install
|
|
||||||
COPY --chown=node:node ./ ./
|
|
||||||
|
|
||||||
USER node
|
FROM node:16.8.0 AS builder
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
COPY ./ ./
|
||||||
|
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
|
||||||
RUN npm run build
|
RUN npm run build
|
||||||
CMD ["npm", "run", "start", "--", "--port", "${PORT}"]
|
|
||||||
|
FROM node:16.8.0 AS runner
|
||||||
|
WORKDIR /usr/src/app
|
||||||
|
ENV NODE_ENV=production
|
||||||
|
COPY --from=builder /usr/src/app/next.config.js ./next.config.js
|
||||||
|
COPY --from=builder /usr/src/app/public ./public
|
||||||
|
COPY --from=builder /usr/src/app/.next ./.next
|
||||||
|
COPY --from=builder /usr/src/app/i18n.json ./i18n.json
|
||||||
|
COPY --from=builder /usr/src/app/locales ./locales
|
||||||
|
COPY --from=builder /usr/src/app/pages ./pages
|
||||||
|
COPY --from=builder /usr/src/app/node_modules ./node_modules
|
||||||
|
RUN npx next telemetry disable
|
||||||
|
CMD ["node_modules/.bin/next", "start", "--port", "${PORT}"]
|
||||||
|
35
README.md
@ -5,7 +5,7 @@
|
|||||||
</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/Divlo/actions/workflows/Divlo.yml"><img src="https://github.com/Divlo/Divlo/actions/workflows/Divlo.yml/badge.svg?branch=master" alt="Divlo's CI" /></a>
|
||||||
<a href="https://github.com/Divlo"><img alt="GitHub" src="https://img.shields.io/badge/-GitHub-5A5A5A?style=flat&labelColor=5A5A5A&logo=github&logoColor=white"/></a>
|
<a href="https://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,21 +20,22 @@
|
|||||||
|
|
||||||
## 📜 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"],
|
||||||
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": ["Ubuntu", "Visual Studio Code", "Git", "Docker"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
@ -45,5 +46,5 @@ export interface Divlo {
|
|||||||
|
|
||||||
<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()
|
|
||||||
})
|
|
||||||
})
|
|
@ -14,7 +14,12 @@ export const ErrorPage: React.FC<ErrorPageProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
<h1 className='my-6 font-semibold text-4xl'>
|
<h1 className='my-6 font-semibold text-4xl'>
|
||||||
{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}{' '}
|
||||||
|
@ -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='bg-white flex flex-col items-center justify-center py-6 text-lg border-t-2 border-gray-600 dark:border-gray-400 dark:bg-black'>
|
||||||
<p>
|
<p>
|
||||||
<span className='text-yellow dark:text-yellow-dark'>Divlo</span> |{' '}
|
<Link href='/'>
|
||||||
{t('common:allRightsReserved')}
|
<a className='hover:underline text-yellow dark:text-yellow-dark'>
|
||||||
|
Divlo
|
||||||
|
</a>
|
||||||
|
</Link>{' '}
|
||||||
|
| {t('common:allRightsReserved')}
|
||||||
|
</p>
|
||||||
|
<p className='mt-1'>
|
||||||
|
Version{' '}
|
||||||
|
<a
|
||||||
|
className='hover:underline text-yellow dark:text-yellow-dark'
|
||||||
|
href={versionLink}
|
||||||
|
target='_blank'
|
||||||
|
rel='noopener noreferrer'
|
||||||
|
>
|
||||||
|
{version}
|
||||||
|
</a>
|
||||||
</p>
|
</p>
|
||||||
</footer>
|
</footer>
|
||||||
)
|
)
|
||||||
|
@ -15,7 +15,9 @@ export const LanguageFlag: React.FC<LanguageFlagProps> = (props) => {
|
|||||||
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,20 @@
|
|||||||
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 { Arrow } from './Arrow'
|
import { Arrow } from './Arrow'
|
||||||
import { LanguageFlag } from './LanguageFlag'
|
import { LanguageFlag } from './LanguageFlag'
|
||||||
import { locales } from 'i18n.json'
|
import i18n 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,26 +25,32 @@ 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 flex-col justify-center items-center cursor-pointer'>
|
||||||
<div className='flex items-center mr-5' onClick={handleHiddenMenu}>
|
<div
|
||||||
|
data-cy='language-click'
|
||||||
|
className='flex items-center mr-5'
|
||||||
|
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'
|
||||||
|
className={classNames(
|
||||||
|
'flex flex-col justify-center items-center absolute p-0 top-14 z-10 w-24 mt-3 mr-4 rounded-lg list-none shadow-light dark:shadow-dark bg-white dark:bg-black',
|
||||||
|
{ hidden: hiddenMenu }
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{i18n.locales.map((language, index) => {
|
||||||
if (language === currentLanguage) {
|
if (language === currentLanguage) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
@ -54,7 +65,6 @@ export const Language: React.FC = () => {
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</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 cursor-pointer bg-transparent inline-block'>
|
||||||
<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 flex justify-center items-center relative'>
|
||||||
|
🌜
|
||||||
|
</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 flex justify-center items-center relative'>
|
||||||
|
🌞
|
||||||
|
</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;
|
||||||
}
|
}
|
||||||
`}
|
`}
|
||||||
|
@ -16,7 +16,7 @@ export const Header: React.FC = () => {
|
|||||||
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 font-headline font-semibold hidden xs:block text-yellow dark:text-yellow-dark'>
|
||||||
Divlo
|
Divlo
|
||||||
</strong>
|
</strong>
|
||||||
</div>
|
</div>
|
||||||
|
@ -11,11 +11,11 @@ export const InterestParagraph: React.FC<InterestParagraphProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<p className='text-center my-6 text-gray dark:text-gray-dark'>
|
<p className='text-center my-6 text-gray dark:text-gray-dark'>
|
||||||
<strong className='text-yellow font-medium text-lg dark:text-yellow-dark'>
|
<strong className='text-yellow font-semibold text-lg dark:text-yellow-dark'>
|
||||||
{title}
|
{title}
|
||||||
</strong>
|
</strong>
|
||||||
<br />
|
<br />
|
||||||
<span className='paragraph-color'>{htmlParser(description)}</span>
|
<span>{htmlParser(description)}</span>
|
||||||
</p>
|
</p>
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
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='cursor-pointer relative p-6 !mb-4 max-h-32 transition-transform duration-200 ease-in-out hover:-translate-y-2'>
|
||||||
|
<a href={href} target='_blank' rel='noopener noreferrer'>
|
||||||
|
<div className='flex'>
|
||||||
|
<GitHubIcon className='h-6 mr-2' />
|
||||||
|
<span className='text-yellow dark:text-yellow-dark'>{name}</span>
|
||||||
|
</div>
|
||||||
|
<p className='my-4'>{description}</p>
|
||||||
|
</a>
|
||||||
|
</ShadowContainer>
|
||||||
|
)
|
||||||
|
}
|
47
components/OpenSource/index.tsx
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
import useTranslation from 'next-translate/useTranslation'
|
||||||
|
|
||||||
|
import { Repository } from './Repository'
|
||||||
|
|
||||||
|
export const OpenSource: React.FC = () => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<div className='max-w-full mt-0 flex flex-col items-center'>
|
||||||
|
<p className='text-center'>{t('home:open-source.description')}</p>
|
||||||
|
<div className='grid grid-cols-1 md:w-10/12 md:grid-cols-2 gap-6 my-6'>
|
||||||
|
<Repository
|
||||||
|
name='nodejs/node'
|
||||||
|
description='Node.js JavaScript runtime ✨️🐢🚀✨️'
|
||||||
|
href='https://github.com/nodejs/node/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='standard/standard'
|
||||||
|
description='🌟 JavaScript Style Guide, with linter & automatic code fixer'
|
||||||
|
href='https://github.com/standard/standard/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='nrwl/nx'
|
||||||
|
description='Smart, Extensible Build Framework'
|
||||||
|
href='https://github.com/nrwl/nx/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
<Repository
|
||||||
|
name='vercel/styled-jsx'
|
||||||
|
description='Full CSS support for JSX without compromises'
|
||||||
|
href='https://github.com/vercel/styled-jsx/commits?author=Divlo'
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<style jsx global>{`
|
||||||
|
.animation-custom {
|
||||||
|
position: relative;
|
||||||
|
transition: all 0.3s ease 0s;
|
||||||
|
}
|
||||||
|
.animation-custom:hover {
|
||||||
|
transform: translateY(-7px);
|
||||||
|
}
|
||||||
|
`}</style>
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
@ -2,7 +2,7 @@ import Translation from 'next-translate/Trans'
|
|||||||
|
|
||||||
export const ProfileDescriptionBottom: React.FC = () => {
|
export const ProfileDescriptionBottom: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<p className='block mt-8 mb-0 font-normal text-base text-gray dark:text-gray-dark'>
|
<p className='mt-8 mb-8 font-normal text-base text-gray dark:text-gray-dark'>
|
||||||
<Translation
|
<Translation
|
||||||
i18nKey='home:about.descriptionBottom'
|
i18nKey='home:about.descriptionBottom'
|
||||||
components={[<br key='break' />]}
|
components={[<br key='break' />]}
|
||||||
|
@ -1,10 +1,9 @@
|
|||||||
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='pb-2 mb-6 border-b-2 font-headline border-gray-600 dark:border-gray-400'>
|
<div className='pb-2 mb-6 border-b-2 font-headline border-gray-600 dark:border-gray-400'>
|
||||||
<h1 className='text-4xl mb-2'>
|
<h1 className='text-4xl mb-2'>
|
||||||
{t('home:about.IAm')}{' '}
|
{t('home:about.IAm')}{' '}
|
||||||
@ -14,31 +13,5 @@ export const ProfileInfo: React.FC = () => {
|
|||||||
</h1>
|
</h1>
|
||||||
<h2 className='text-base mb-3'>{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>
|
|
||||||
</>
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,10 @@ export const ProfileItem: React.FC<ProfileItemProps> = (props) => {
|
|||||||
</strong>
|
</strong>
|
||||||
<span className='profile-list__item-info text-gray dark:text-gray-dark'>
|
<span className='profile-list__item-info text-gray dark:text-gray-dark'>
|
||||||
{link != null ? (
|
{link != null ? (
|
||||||
<a className='text-gray dark:text-gray-dark' href={link}>
|
<a
|
||||||
|
className='text-gray dark:text-gray-dark hover:underline'
|
||||||
|
href={link}
|
||||||
|
>
|
||||||
{value}
|
{value}
|
||||||
</a>
|
</a>
|
||||||
) : (
|
) : (
|
||||||
|
@ -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='px-2 py-6 max-w-[370px] max-h-[370px]'>
|
||||||
<Image
|
<Image src={DivloLogo} alt='Divlo' priority />
|
||||||
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(
|
||||||
|
'dark:text-white text-black w-8 h-8 fill-current',
|
||||||
|
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>
|
||||||
|
)
|
||||||
|
}
|
@ -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 p-0 list-none text-center mt-2 px-0 py-4'>
|
<ul className='social-media-list m-0 mt-2 py-4 list-none 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 pb-6 md:pt-10 xl:pt-0 md:flex-row'>
|
<div className='flex flex-col justify-center items-center px-10 pt-2 md:pt-10 md:flex-row'>
|
||||||
<ProfileLogo />
|
<ProfileLogo />
|
||||||
<div className='col-sm-24 col-md-14'>
|
<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 width={60} height={60} alt={skill} src={image} />
|
||||||
<p className='mt-1'>{skill}</p>
|
<p className='mt-1'>{skill}</p>
|
||||||
</div>
|
</div>
|
||||||
</a>
|
</a>
|
||||||
|
@ -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,31 @@ 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' />
|
|
||||||
</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.softwareTools')}>
|
||||||
<Skill skill='Ubuntu' />
|
<SkillComponent skill='Ubuntu' />
|
||||||
<Skill skill='Hyper' />
|
<SkillComponent skill='Visual Studio Code' />
|
||||||
<Skill skill='Visual Studio Code' />
|
<SkillComponent skill='Git' />
|
||||||
<Skill skill='Git' />
|
<SkillComponent skill='Docker' />
|
||||||
<Skill 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'
|
||||||
|
@ -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', async () => {
|
||||||
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}`
|
||||||
|
)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
@ -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='text-4xl font-semibold text-center mt-1 mb-3'>
|
||||||
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,
|
||||||
@ -24,7 +22,7 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
return (
|
return (
|
||||||
<div className='px-3 w-full'>
|
<div className='px-3 w-full'>
|
||||||
<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='px-3 w-full'>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
@ -35,7 +33,7 @@ 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='px-3 w-full'>{children}</div>
|
||||||
</section>
|
</section>
|
||||||
@ -43,7 +41,7 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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}
|
||||||
@ -61,4 +59,4 @@ export const Section = forwardRef<HTMLElement, SectionProps>((props, ref) => {
|
|||||||
</div>
|
</div>
|
||||||
</section>
|
</section>
|
||||||
)
|
)
|
||||||
})
|
}
|
||||||
|
@ -9,7 +9,7 @@ export const ShadowContainer: React.FC<ShadowContainerProps> = (props) => {
|
|||||||
<>
|
<>
|
||||||
<div
|
<div
|
||||||
className={classNames(
|
className={classNames(
|
||||||
'shadow-container flex flex-col h-full max-w-full break-words',
|
'shadow-container h-full max-w-full break-words',
|
||||||
className
|
className
|
||||||
)}
|
)}
|
||||||
{...rest}
|
{...rest}
|
||||||
|
8
cypress.json
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
{
|
||||||
|
"baseUrl": "http://localhost:3000",
|
||||||
|
"pluginsFile": false,
|
||||||
|
"supportFile": false,
|
||||||
|
"fixturesFolder": false,
|
||||||
|
"video": false,
|
||||||
|
"screenshotOnRunFailure": false
|
||||||
|
}
|
47
cypress/integration/common/Header.spec.ts
Normal file
@ -0,0 +1,47 @@
|
|||||||
|
describe('Common > Header', () => {
|
||||||
|
beforeEach(() => cy.visit('/'))
|
||||||
|
|
||||||
|
describe('Switch theme color (dark/light)', () => {
|
||||||
|
it('should switch theme from `dark` (default) to `light`', () => {
|
||||||
|
cy.get('[data-cy=switch-theme-dark]').should('be.visible')
|
||||||
|
cy.get('[data-cy=switch-theme-light]').should('not.be.visible')
|
||||||
|
cy.get('body').should(
|
||||||
|
'not.have.css',
|
||||||
|
'background-color',
|
||||||
|
'rgb(255, 255, 255)'
|
||||||
|
)
|
||||||
|
|
||||||
|
cy.get('[data-cy=switch-theme-click]').click()
|
||||||
|
|
||||||
|
cy.get('[data-cy=switch-theme-dark]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=switch-theme-light]').should('be.visible')
|
||||||
|
cy.get('body').should(
|
||||||
|
'have.css',
|
||||||
|
'background-color',
|
||||||
|
'rgb(255, 255, 255)'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('Switch Language', () => {
|
||||||
|
it('should switch language from EN (default) to FR', () => {
|
||||||
|
cy.get('h1').contains('I am Divlo')
|
||||||
|
cy.get('[data-cy=language-flag-text]').contains('EN')
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-click]').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('be.visible')
|
||||||
|
cy.get('[data-cy=languages-list] > li:first-child').contains('FR').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-flag-text]').contains('FR')
|
||||||
|
cy.get('h1').contains('Je suis Divlo')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('should close the language list menu when clicking outside', () => {
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
cy.get('[data-cy=language-click]').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('be.visible')
|
||||||
|
cy.get('h1').click()
|
||||||
|
cy.get('[data-cy=languages-list]').should('not.be.visible')
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
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
@ -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')
|
||||||
|
})
|
||||||
|
})
|
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
@ -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 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
roots: ['<rootDir>'],
|
|
||||||
transform: {
|
|
||||||
'^.+\\.(js|jsx|ts|tsx)$': 'babel-jest'
|
|
||||||
},
|
|
||||||
moduleDirectories: ['node_modules', './'],
|
|
||||||
moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'],
|
|
||||||
setupFilesAfterEnv: [
|
|
||||||
'@testing-library/jest-dom/extend-expect',
|
|
||||||
'@testing-library/react'
|
|
||||||
],
|
|
||||||
collectCoverage: true,
|
|
||||||
collectCoverageFrom: [
|
|
||||||
'**/*.{js,jsx,ts,tsx}',
|
|
||||||
'!**/*.d.ts',
|
|
||||||
'!**/.next/**',
|
|
||||||
'!**/node_modules/**',
|
|
||||||
'!**/next.config.js',
|
|
||||||
'!**/postcss.config.js',
|
|
||||||
'!**/tailwind.config.js',
|
|
||||||
'!**/workbox-*.js',
|
|
||||||
'!**/sw.js',
|
|
||||||
'!**/jest.config.js'
|
|
||||||
],
|
|
||||||
coverageDirectory: './coverage',
|
|
||||||
coverageReporters: ['text', 'cobertura']
|
|
||||||
}
|
|
14
jest.config.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"roots": ["<rootDir>"],
|
||||||
|
"transform": {
|
||||||
|
"^.+\\.(js|jsx|ts|tsx)$": "babel-jest"
|
||||||
|
},
|
||||||
|
"moduleDirectories": ["node_modules", "./"],
|
||||||
|
"modulePathIgnorePatterns": ["<rootDir>/cypress"],
|
||||||
|
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
|
||||||
|
"testEnvironment": "jsdom",
|
||||||
|
"setupFilesAfterEnv": [
|
||||||
|
"@testing-library/jest-dom/extend-expect",
|
||||||
|
"@testing-library/react"
|
||||||
|
]
|
||||||
|
}
|
@ -4,32 +4,32 @@
|
|||||||
"description": "Developer Full Stack Junior • Passionate about High-Tech",
|
"description": "Developer Full Stack Junior • Passionate about High-Tech",
|
||||||
"birthDate": "Birth date",
|
"birthDate": "Birth date",
|
||||||
"nationality": "Nationality",
|
"nationality": "Nationality",
|
||||||
"descriptionBottom": "I'm learning online programming languages to improve my skills in my passion. <0/> <0/> I designed my graphic chart and my website."
|
"descriptionBottom": "I am self-taught in Computer Science by following online trainings and I am also a student at the university following the French training \"BUT Informatique\" (first year). <0/> <0/> I put into practice everything I learn and make many projects."
|
||||||
},
|
},
|
||||||
"interests": {
|
"interests": {
|
||||||
"title": "My Interests",
|
"title": "Interests",
|
||||||
"paragraphs": [
|
"paragraphs": [
|
||||||
{
|
{
|
||||||
"title": "Developer Full Stack Junior :",
|
"title": "Developer Full Stack Junior :",
|
||||||
"description": "Computer programming is my main passion, I love it! <br/> Mostly web development for the moment but I'm programming some Python and others programming language too."
|
"description": "Computer programming is my main hobby, I love it! <br/> Mostly web development for the moment but I'm programming some Python and others programming language too."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Passionate about High-Tech :",
|
"title": "Passionate about High-Tech :",
|
||||||
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and even better than the past. Technologies improve gradually over time, which is very useful in many areas."
|
"description": "I always wondered how the future would be. Every day I want to wake up and think that the future will be great and better than the past. Technologies improve gradually over time, which is very useful in many areas."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Open-Source enthusiast :",
|
"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"
|
"softwareTools": "Software and tools"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"title": "My Portfolio",
|
"title": "Portfolio",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "function.divlo.fr",
|
"title": "function.divlo.fr",
|
||||||
@ -42,7 +42,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."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,32 +4,32 @@
|
|||||||
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
|
"description": "Développeur Full Stack Junior • Passionné de High-Tech",
|
||||||
"birthDate": "Date de naissance",
|
"birthDate": "Date de naissance",
|
||||||
"nationality": "Nationalité",
|
"nationality": "Nationalité",
|
||||||
"descriptionBottom": "J'apprends en ligne l'informatique et les langages de programmation pour m'améliorer dans ma passion. <0/> <0/> J'ai conçu ma charte graphique et mon site internet."
|
"descriptionBottom": "Je me forme en autodidacte dans l'informatique en suivant des formations en ligne et je suis aussi un étudiant à l'université suivant la formation \"BUT Informatique\" (première année). <0/> <0/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets."
|
||||||
},
|
},
|
||||||
"interests": {
|
"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 ma principale passion, j'adore! <br/> Principalement du développement Web pour le moment, mais je programme aussi du Python et d'autres langages de programmation."
|
"description": "La programmation informatique est mon loisir principal, j'adore! <br/> Principalement du développement Web pour le moment, mais je programme aussi du Python et d'autres langages de programmation."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Passionné de High-Tech :",
|
"title": "Passionné de High-Tech :",
|
||||||
"description": "Je me suis toujours demandé comment l'avenir serait. Chaque jour, je veux me réveiller et penser que l'avenir sera formidable et même meilleur que le passé. Les technolgies s'améliorent progressivement avec le temps, ce qui est très utile dans de nombreux domaines."
|
"description": "Je me suis toujours demandé comment l'avenir serait. Chaque jour, je veux me réveiller et penser que l'avenir sera formidable et meilleur que le passé. Les technolgies s'améliorent progressivement avec le temps, ce qui est très utile dans de nombreux domaines."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"title": "Enthousiaste de l'Open-Source :",
|
"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"
|
"softwareTools": "Logiciels et outils"
|
||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"title": "Mon Portfolio",
|
"title": "Portfolio",
|
||||||
"items": [
|
"items": [
|
||||||
{
|
{
|
||||||
"title": "function.divlo.fr",
|
"title": "function.divlo.fr",
|
||||||
@ -42,7 +42,16 @@
|
|||||||
"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 vous amuser.",
|
||||||
"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é."
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
4
next-env.d.ts
vendored
@ -1,2 +1,6 @@
|
|||||||
/// <reference types="next" />
|
/// <reference types="next" />
|
||||||
/// <reference types="next/types/global" />
|
/// <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,35 @@
|
|||||||
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({
|
||||||
pwa: {
|
pwa: {
|
||||||
disable: process.env.NODE_ENV !== 'production',
|
disable: process.env.NODE_ENV !== 'production',
|
||||||
dest: 'public'
|
dest: 'public'
|
||||||
|
},
|
||||||
|
async headers() {
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
source: '/:path*',
|
||||||
|
headers: createSecureHeaders({
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
scriptSrc: ["'self'", "'unsafe-eval'", "'unsafe-inline'"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
imgSrc: ['*', 'data:', 'blob:'],
|
||||||
|
mediaSrc: "'none'",
|
||||||
|
connectSrc: '*',
|
||||||
|
objectSrc: "'none'",
|
||||||
|
fontSrc: "'self'",
|
||||||
|
baseURI: "'none'"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
23936
package-lock.json
generated
98
package.json
@ -1,11 +1,15 @@
|
|||||||
{
|
{
|
||||||
"name": "divlo",
|
"name": "divlo",
|
||||||
"version": "0.0.0-development",
|
"version": "1.3.6",
|
||||||
"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",
|
||||||
@ -14,60 +18,72 @@
|
|||||||
"lint:commit": "commitlint",
|
"lint:commit": "commitlint",
|
||||||
"lint:docker": "dockerfilelint './Dockerfile'",
|
"lint:docker": "dockerfilelint './Dockerfile'",
|
||||||
"lint:editorconfig": "editorconfig-checker",
|
"lint:editorconfig": "editorconfig-checker",
|
||||||
"lint:markdown": "markdownlint '*.md' --dot --ignore node_modules",
|
"lint:markdown": "markdownlint '**/*.md' --dot --ignore node_modules",
|
||||||
"lint:typescript": "eslint '*.{js,ts,jsx,tsx}'",
|
"lint:typescript": "eslint '**/*.{js,ts,jsx,tsx}'",
|
||||||
"lint:staged": "lint-staged",
|
"lint:staged": "lint-staged",
|
||||||
"lighthouse": "lhci autorun",
|
"test:unit": "jest",
|
||||||
"test": "jest",
|
"test:lighthouse": "lhci autorun",
|
||||||
|
"test:e2e": "start-server-and-test 'start' 'http://localhost:3000' 'cypress run'",
|
||||||
|
"test:e2e:dev": "start-server-and-test 'dev' 'http://localhost:3000' 'cypress open'",
|
||||||
"release": "semantic-release",
|
"release": "semantic-release",
|
||||||
|
"deploy": "vercel",
|
||||||
"postinstall": "husky install"
|
"postinstall": "husky install"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@fontsource/montserrat": "4.3.0",
|
"@fontsource/montserrat": "4.5.1",
|
||||||
"@fortawesome/fontawesome-svg-core": "1.2.35",
|
"@fortawesome/fontawesome-svg-core": "1.2.36",
|
||||||
"@fortawesome/free-brands-svg-icons": "5.15.3",
|
"@fortawesome/free-brands-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.3",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/react-fontawesome": "0.1.14",
|
"@fortawesome/react-fontawesome": "0.1.15",
|
||||||
"classnames": "2.3.1",
|
"classnames": "2.3.1",
|
||||||
"html-react-parser": "1.2.6",
|
"html-react-parser": "1.3.0",
|
||||||
"next": "10.2.0",
|
"next": "11.1.2",
|
||||||
"next-pwa": "5.2.21",
|
"next-pwa": "5.3.1",
|
||||||
"next-themes": "0.0.14",
|
"next-themes": "0.0.15",
|
||||||
"next-translate": "1.0.6",
|
"next-translate": "1.0.7",
|
||||||
"react": "17.0.2",
|
"react": "17.0.2",
|
||||||
"react-dom": "17.0.2",
|
"react-dom": "17.0.2",
|
||||||
|
"read-pkg": "7.0.0",
|
||||||
|
"sharp": "0.29.1",
|
||||||
"universal-cookie": "4.0.4"
|
"universal-cookie": "4.0.4"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@commitlint/cli": "12.1.1",
|
"@commitlint/cli": "13.1.0",
|
||||||
"@commitlint/config-conventional": "12.1.1",
|
"@commitlint/config-conventional": "13.1.0",
|
||||||
"@lhci/cli": "0.7.2",
|
"@lhci/cli": "0.8.1",
|
||||||
"@testing-library/jest-dom": "5.12.0",
|
"@saithodev/semantic-release-backmerge": "1.5.3",
|
||||||
"@testing-library/react": "11.2.6",
|
"@semantic-release/git": "9.0.1",
|
||||||
"@types/jest": "26.0.23",
|
"@testing-library/jest-dom": "5.14.1",
|
||||||
"@types/node": "15.0.2",
|
"@testing-library/react": "12.0.0",
|
||||||
"@types/react": "17.0.5",
|
"@types/jest": "27.0.1",
|
||||||
"@types/styled-jsx": "2.2.8",
|
"@types/node": "16.9.0",
|
||||||
"@typescript-eslint/eslint-plugin": "4.22.1",
|
"@types/react": "17.0.20",
|
||||||
"autoprefixer": "10.2.5",
|
"@typescript-eslint/eslint-plugin": "4.31.0",
|
||||||
"babel-jest": "26.6.3",
|
"autoprefixer": "10.3.4",
|
||||||
|
"babel-jest": "27.1.1",
|
||||||
|
"cypress": "8.3.1",
|
||||||
"dockerfilelint": "1.8.0",
|
"dockerfilelint": "1.8.0",
|
||||||
"editorconfig-checker": "4.0.2",
|
"editorconfig-checker": "4.0.2",
|
||||||
"eslint": "7.26.0",
|
"eslint": "7.32.0",
|
||||||
|
"eslint-config-next": "11.1.2",
|
||||||
"eslint-config-prettier": "8.3.0",
|
"eslint-config-prettier": "8.3.0",
|
||||||
"eslint-config-standard-with-typescript": "20.0.0",
|
"eslint-config-standard-with-typescript": "21.0.1",
|
||||||
"eslint-plugin-import": "2.22.1",
|
"eslint-plugin-import": "2.24.2",
|
||||||
"eslint-plugin-node": "11.1.0",
|
"eslint-plugin-node": "11.1.0",
|
||||||
"eslint-plugin-prettier": "3.4.0",
|
"eslint-plugin-prettier": "4.0.0",
|
||||||
"eslint-plugin-promise": "4.3.1",
|
"eslint-plugin-promise": "5.1.0",
|
||||||
"husky": "6.0.0",
|
"eslint-plugin-unicorn": "35.0.0",
|
||||||
"jest": "26.6.3",
|
"husky": "7.0.2",
|
||||||
"lint-staged": "11.0.0",
|
"jest": "27.1.1",
|
||||||
"markdownlint-cli": "0.27.1",
|
"lint-staged": "11.1.2",
|
||||||
"postcss": "8.2.14",
|
"markdownlint-cli": "0.28.1",
|
||||||
"prettier": "2.2.1",
|
"next-secure-headers": "2.2.0",
|
||||||
"semantic-release": "17.4.2",
|
"postcss": "8.3.6",
|
||||||
"tailwindcss": "2.1.2",
|
"prettier": "2.3.2",
|
||||||
"typescript": "4.2.4"
|
"semantic-release": "17.4.7",
|
||||||
|
"start-server-and-test": "1.14.0",
|
||||||
|
"tailwindcss": "2.2.14",
|
||||||
|
"typescript": "4.4.2",
|
||||||
|
"vercel": "23.1.2"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,20 +3,30 @@ 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'
|
||||||
|
|
||||||
const Error404: React.FC = () => {
|
const Error404: React.FC<FooterProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { version } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head title='Divlo - 404' />
|
<Head title='Divlo - 404' />
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
|
||||||
<ErrorPage statusCode={404} message={t('errors:notFound')} />
|
<ErrorPage statusCode={404} message={t('errors:notFound')} />
|
||||||
|
</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()
|
||||||
|
return { props: { version } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Error404
|
export default Error404
|
||||||
|
@ -3,20 +3,30 @@ 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'
|
||||||
|
|
||||||
const Error500: React.FC = () => {
|
const Error500: React.FC<FooterProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { version } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head title='Divlo - 500' />
|
<Head title='Divlo - 500' />
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
|
||||||
<ErrorPage statusCode={500} message={t('errors:serverError')} />
|
<ErrorPage statusCode={500} message={t('errors:serverError')} />
|
||||||
|
</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()
|
||||||
|
return { props: { version } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Error500
|
export default Error500
|
||||||
|
@ -6,19 +6,14 @@ import UniversalCookie from 'universal-cookie'
|
|||||||
|
|
||||||
import 'tailwindcss/tailwind.css'
|
import 'tailwindcss/tailwind.css'
|
||||||
import '@fontsource/montserrat/400.css'
|
import '@fontsource/montserrat/400.css'
|
||||||
import '@fontsource/montserrat/500.css'
|
|
||||||
import '@fontsource/montserrat/600.css'
|
import '@fontsource/montserrat/600.css'
|
||||||
import '@fontsource/montserrat/700.css'
|
|
||||||
|
|
||||||
import { Header } from 'components/Header'
|
|
||||||
import { Footer } from 'components/Footer'
|
|
||||||
|
|
||||||
const universalCookie = new UniversalCookie()
|
const universalCookie = new UniversalCookie()
|
||||||
|
|
||||||
/** how long in seconds, until the cookie expires (10 years) */
|
/** how long in seconds, until the cookie expires (10 years) */
|
||||||
const COOKIE_MAX_AGE = 10 * 365.25 * 24 * 60 * 60
|
const COOKIE_MAX_AGE = 10 * 365.25 * 24 * 60 * 60
|
||||||
|
|
||||||
const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
|
const Application = ({ Component, pageProps }: AppProps): JSX.Element => {
|
||||||
const { lang } = useTranslation()
|
const { lang } = useTranslation()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@ -30,13 +25,9 @@ const MyApp = ({ Component, pageProps }: AppProps): JSX.Element => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider attribute='class' defaultTheme='dark'>
|
<ThemeProvider attribute='class' defaultTheme='dark'>
|
||||||
<Header />
|
|
||||||
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
|
|
||||||
<Component {...pageProps} />
|
<Component {...pageProps} />
|
||||||
</main>
|
|
||||||
<Footer />
|
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export default MyApp
|
export default Application
|
||||||
|
@ -1,21 +1,6 @@
|
|||||||
import Document, {
|
import { Html, Head, Main, NextScript } from 'next/document'
|
||||||
Html,
|
|
||||||
Head,
|
|
||||||
Main,
|
|
||||||
NextScript,
|
|
||||||
DocumentContext,
|
|
||||||
DocumentInitialProps
|
|
||||||
} from 'next/document'
|
|
||||||
|
|
||||||
class MyDocument extends Document {
|
const Document: React.FC = () => {
|
||||||
static async getInitialProps(
|
|
||||||
ctx: DocumentContext
|
|
||||||
): Promise<DocumentInitialProps> {
|
|
||||||
const initialProps = await Document.getInitialProps(ctx)
|
|
||||||
return initialProps
|
|
||||||
}
|
|
||||||
|
|
||||||
render(): JSX.Element {
|
|
||||||
return (
|
return (
|
||||||
<Html>
|
<Html>
|
||||||
<Head />
|
<Head />
|
||||||
@ -26,6 +11,5 @@ class MyDocument extends Document {
|
|||||||
</Html>
|
</Html>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
export default MyDocument
|
export default Document
|
||||||
|
@ -9,14 +9,20 @@ import { Portfolio } from 'components/Portfolio'
|
|||||||
import { Profile } from 'components/Profile'
|
import { Profile } from 'components/Profile'
|
||||||
import { SocialMediaList } from 'components/Profile/SocialMediaList'
|
import { SocialMediaList } from 'components/Profile/SocialMediaList'
|
||||||
import { Skills } from 'components/Skills'
|
import { Skills } from 'components/Skills'
|
||||||
|
import { OpenSource } from 'components/OpenSource'
|
||||||
|
import { Header } from 'components/Header'
|
||||||
|
import { Footer, FooterProps } from 'components/Footer'
|
||||||
|
|
||||||
const Home: React.FC = () => {
|
const Home: React.FC<FooterProps> = (props) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const { version } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Head />
|
<Head />
|
||||||
|
|
||||||
|
<Header />
|
||||||
|
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
|
||||||
<Section isMain id='about'>
|
<Section isMain id='about'>
|
||||||
<Profile />
|
<Profile />
|
||||||
<SocialMediaList />
|
<SocialMediaList />
|
||||||
@ -47,12 +53,26 @@ const Home: React.FC = () => {
|
|||||||
<Portfolio />
|
<Portfolio />
|
||||||
</Section>
|
</Section>
|
||||||
</RevealFade>
|
</RevealFade>
|
||||||
|
|
||||||
|
<RevealFade>
|
||||||
|
<Section
|
||||||
|
id='open-source'
|
||||||
|
heading='Open source'
|
||||||
|
withoutShadowContainer
|
||||||
|
>
|
||||||
|
<OpenSource />
|
||||||
|
</Section>
|
||||||
|
</RevealFade>
|
||||||
|
</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()
|
||||||
|
return { props: { version } }
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Home
|
export default Home
|
||||||
|
Before Width: | Height: | Size: 114 KiB After Width: | Height: | Size: 92 KiB |
BIN
public/images/portfolio/leon.png
Normal file
After Width: | Height: | Size: 189 KiB |
BIN
public/images/skills/C-Cpp.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/images/skills/Fastify-dark.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
public/images/skills/Fastify-light.png
Normal file
After Width: | Height: | Size: 8.7 KiB |
BIN
public/images/skills/PostgreSQL.png
Normal file
After Width: | Height: | Size: 27 KiB |
BIN
public/images/skills/Prisma-dark.png
Normal file
After Width: | Height: | Size: 40 KiB |
BIN
public/images/skills/Prisma-light.png
Normal file
After Width: | Height: | Size: 84 KiB |
BIN
public/images/skills/TailwindCSS.png
Normal file
After Width: | Height: | Size: 13 KiB |
@ -10,6 +10,7 @@
|
|||||||
"removeComments": true,
|
"removeComments": true,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"types": ["jest", "@testing-library/jest-dom", "@testing-library/react"],
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"forceConsistentCasingInFileNames": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
5
vercel.json
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"github": {
|
||||||
|
"enabled": false
|
||||||
|
}
|
||||||
|
}
|