Compare commits
24 Commits
v4.3.0
...
42c79ef49e
| Author | SHA1 | Date | |
|---|---|---|---|
|
42c79ef49e
|
|||
|
74a9e039d4
|
|||
|
d0cde4538c
|
|||
|
57026d652c
|
|||
|
dbe7a73d1f
|
|||
|
203fee5416
|
|||
|
3c92889f12
|
|||
|
c86a039c1f
|
|||
|
338741d252
|
|||
|
be738a1c8d
|
|||
|
9c20844d89
|
|||
|
eceeca8b69
|
|||
|
92eca82127
|
|||
|
4502e32250
|
|||
|
d6f2d9e3a6
|
|||
|
eeb5e8872f
|
|||
|
7d22880905
|
|||
|
49896bbaeb
|
|||
|
b3140dd977
|
|||
|
39ef3e80ce
|
|||
|
64a5ca73ad
|
|||
|
b16699f5aa
|
|||
|
e044b4caea
|
|||
|
d795025860
|
20
.github/ISSUE_TEMPLATE/BUG.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🐛 Bug Report"
|
|
||||||
about: "Report an unexpected problem or unintended behavior."
|
|
||||||
title: "[Bug]"
|
|
||||||
labels: "bug"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!--
|
|
||||||
Please provide a clear and concise description of what the bug is. Include
|
|
||||||
screenshots if needed. Please make sure your issue has not already been fixed.
|
|
||||||
-->
|
|
||||||
|
|
||||||
## Steps To Reproduce
|
|
||||||
|
|
||||||
1. Step 1
|
|
||||||
2. Step 2
|
|
||||||
|
|
||||||
## The current behavior
|
|
||||||
|
|
||||||
## The expected behavior
|
|
||||||
18
.github/ISSUE_TEMPLATE/DOCUMENTATION.md
vendored
@@ -1,18 +0,0 @@
|
|||||||
---
|
|
||||||
name: "📜 Documentation"
|
|
||||||
about: "Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...)."
|
|
||||||
title: "[Documentation]"
|
|
||||||
labels: "documentation"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Please make sure your issue has not already been fixed. -->
|
|
||||||
|
|
||||||
## Documentation
|
|
||||||
|
|
||||||
<!-- Please uncomment the type of documentation problem this issue address -->
|
|
||||||
|
|
||||||
<!-- Documentation is Missing -->
|
|
||||||
<!-- Documentation is Confusing -->
|
|
||||||
<!-- Documentation has Typo errors -->
|
|
||||||
|
|
||||||
## Proposal
|
|
||||||
20
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: "✨ Feature Request"
|
|
||||||
about: "Suggest a new feature idea."
|
|
||||||
title: "[Feature]"
|
|
||||||
labels: "feature request"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Please make sure your issue has not already been fixed. -->
|
|
||||||
|
|
||||||
## Description
|
|
||||||
|
|
||||||
<!-- A clear and concise description of the problem or missing capability... -->
|
|
||||||
|
|
||||||
## Describe the solution you'd like
|
|
||||||
|
|
||||||
<!-- If you have a solution in mind, please describe it. -->
|
|
||||||
|
|
||||||
## Describe alternatives you've considered
|
|
||||||
|
|
||||||
<!-- Have you considered any alternative solutions or workarounds? -->
|
|
||||||
20
.github/ISSUE_TEMPLATE/IMPROVEMENT.md
vendored
@@ -1,20 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🔧 Improvement"
|
|
||||||
about: "Improve structure/format/performance/refactor/tests of the code."
|
|
||||||
title: "[Improvement]"
|
|
||||||
labels: "improvement"
|
|
||||||
---
|
|
||||||
|
|
||||||
<!-- Please make sure your issue has not already been fixed. -->
|
|
||||||
|
|
||||||
## Type of Improvement
|
|
||||||
|
|
||||||
<!-- Please uncomment the type of improvements this issue address -->
|
|
||||||
|
|
||||||
<!-- Files and Folders Structure -->
|
|
||||||
<!-- Performance -->
|
|
||||||
<!-- Refactoring code -->
|
|
||||||
<!-- Tests -->
|
|
||||||
<!-- Not Sure? -->
|
|
||||||
|
|
||||||
## Proposal
|
|
||||||
8
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
@@ -1,8 +0,0 @@
|
|||||||
---
|
|
||||||
name: "🙋 Question"
|
|
||||||
about: "Further information is requested."
|
|
||||||
title: "[Question]"
|
|
||||||
labels: "question"
|
|
||||||
---
|
|
||||||
|
|
||||||
### Question
|
|
||||||
7
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -1,7 +0,0 @@
|
|||||||
<!-- Please first discuss the change you wish to make via issue before making a change. It might avoid a waste of your time. -->
|
|
||||||
|
|
||||||
# What changes this PR introduce?
|
|
||||||
|
|
||||||
## List any relevant issue numbers
|
|
||||||
|
|
||||||
## Is there anything you'd like reviewers to focus on?
|
|
||||||
15
.github/workflows/chromatic.yml
vendored
@@ -11,27 +11,32 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
env:
|
env:
|
||||||
|
CI: "1"
|
||||||
|
TZ: "Europe/Paris"
|
||||||
DO_NOT_TRACK: "1"
|
DO_NOT_TRACK: "1"
|
||||||
TURBO_TELEMETRY_DISABLED: "1"
|
TURBO_TELEMETRY_DISABLED: "1"
|
||||||
NEXT_TELEMETRY_DISABLED: "1"
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
STORYBOOK_DISABLE_TELEMETRY: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v4.2.2"
|
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
|
|
||||||
- uses: "pnpm/action-setup@v4.1.0"
|
- uses: "pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061" # v4.2.0
|
||||||
|
|
||||||
|
- uses: "pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061" # v4.2.0
|
||||||
|
|
||||||
- name: "Setup Node.js"
|
- name: "Setup Node.js"
|
||||||
uses: "actions/setup-node@v4.2.0"
|
uses: "actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238" # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.x"
|
node-version: "24.13.1"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
run: "pnpm install --frozen-lockfile"
|
run: "pnpm install --frozen-lockfile"
|
||||||
|
|
||||||
- name: "Run Chromatic"
|
- name: "Run Chromatic"
|
||||||
uses: "chromaui/action@latest"
|
uses: "chromaui/action@07791f8243f4cb2698bf4d00426baf4b2d1cb7e0" # latest
|
||||||
with:
|
with:
|
||||||
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }}
|
||||||
workingDir: "apps/storybook"
|
workingDir: "apps/storybook"
|
||||||
|
|||||||
25
.github/workflows/ci.yml
vendored
@@ -11,18 +11,21 @@ jobs:
|
|||||||
timeout-minutes: 30
|
timeout-minutes: 30
|
||||||
runs-on: "ubuntu-latest"
|
runs-on: "ubuntu-latest"
|
||||||
env:
|
env:
|
||||||
|
CI: "1"
|
||||||
|
TZ: "Europe/Paris"
|
||||||
DO_NOT_TRACK: "1"
|
DO_NOT_TRACK: "1"
|
||||||
TURBO_TELEMETRY_DISABLED: "1"
|
TURBO_TELEMETRY_DISABLED: "1"
|
||||||
NEXT_TELEMETRY_DISABLED: "1"
|
NEXT_TELEMETRY_DISABLED: "1"
|
||||||
|
STORYBOOK_DISABLE_TELEMETRY: "1"
|
||||||
steps:
|
steps:
|
||||||
- uses: "actions/checkout@v4.2.2"
|
- uses: "actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd" # v6.0.2
|
||||||
|
|
||||||
- uses: "pnpm/action-setup@v4.1.0"
|
- uses: "pnpm/action-setup@41ff72655975bd51cab0327fa583b6e92b6d3061" # v4.2.0
|
||||||
|
|
||||||
- name: "Setup Node.js"
|
- name: "Setup Node.js"
|
||||||
uses: "actions/setup-node@v4.2.0"
|
uses: "actions/setup-node@6044e13b5dc448c55e2357c09f80417699197238" # v6.2.0
|
||||||
with:
|
with:
|
||||||
node-version: "24.x"
|
node-version: "24.13.1"
|
||||||
cache: "pnpm"
|
cache: "pnpm"
|
||||||
|
|
||||||
- name: "Install dependencies"
|
- name: "Install dependencies"
|
||||||
@@ -33,15 +36,9 @@ jobs:
|
|||||||
|
|
||||||
- run: "node --run lint:editorconfig"
|
- run: "node --run lint:editorconfig"
|
||||||
- run: "node --run lint:markdown"
|
- run: "node --run lint:markdown"
|
||||||
- run: "node --run lint:typescript"
|
- run: "node --run lint:turbo"
|
||||||
- run: "node --run lint:eslint"
|
# - run: "node --run lint:typescript" # already covered by oxlint
|
||||||
- run: "node --run lint:prettier"
|
- run: "node --run lint:oxlint"
|
||||||
|
- run: "node --run lint:oxfmt"
|
||||||
- run: "node --run test"
|
- run: "node --run test"
|
||||||
- run: "node --run build"
|
- run: "node --run build"
|
||||||
|
|
||||||
commitlint:
|
|
||||||
runs-on: "ubuntu-latest"
|
|
||||||
steps:
|
|
||||||
- uses: "actions/checkout@v4.2.2"
|
|
||||||
|
|
||||||
- uses: "wagoid/commitlint-github-action@v6.1.2"
|
|
||||||
|
|||||||
11
.gitignore
vendored
@@ -1,8 +1,10 @@
|
|||||||
# dependencies
|
# dependencies
|
||||||
node_modules
|
node_modules
|
||||||
.npm
|
.npm
|
||||||
|
.cache
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.pnpm-store
|
.pnpm-store
|
||||||
|
.apt
|
||||||
.pnp
|
.pnp
|
||||||
.pnp.js
|
.pnp.js
|
||||||
.yarn/install-state.gz
|
.yarn/install-state.gz
|
||||||
@@ -15,12 +17,16 @@ coverage
|
|||||||
out/
|
out/
|
||||||
dist/
|
dist/
|
||||||
build/
|
build/
|
||||||
|
.swc/
|
||||||
|
.react-email/
|
||||||
|
|
||||||
# misc
|
# misc
|
||||||
.DS_Store
|
.DS_Store
|
||||||
*.pem
|
*.pem
|
||||||
|
*.bak
|
||||||
.turbo
|
.turbo
|
||||||
bin/
|
bin/
|
||||||
|
dump_*
|
||||||
|
|
||||||
# debug
|
# debug
|
||||||
npm-debug.log*
|
npm-debug.log*
|
||||||
@@ -30,6 +36,7 @@ yarn-error.log*
|
|||||||
# Storybook
|
# Storybook
|
||||||
*storybook.log
|
*storybook.log
|
||||||
storybook-static
|
storybook-static
|
||||||
|
/public
|
||||||
|
|
||||||
# IDEs and editors
|
# IDEs and editors
|
||||||
.idea
|
.idea
|
||||||
@@ -42,6 +49,7 @@ storybook-static
|
|||||||
|
|
||||||
# local env files
|
# local env files
|
||||||
.env
|
.env
|
||||||
|
.env.dev
|
||||||
.env.production
|
.env.production
|
||||||
.env*.local
|
.env*.local
|
||||||
|
|
||||||
@@ -50,4 +58,5 @@ storybook-static
|
|||||||
|
|
||||||
# typescript
|
# typescript
|
||||||
*.tsbuildinfo
|
*.tsbuildinfo
|
||||||
# next-env.d.ts
|
next-env.d.ts
|
||||||
|
packages/i18n/src/translations/*.d.json.ts
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import relativeLinksRule, { markdownIt } from "markdownlint-rule-relative-links"
|
import relativeLinksRule from "markdownlint-rule-relative-links"
|
||||||
|
|
||||||
const config = {
|
const config = {
|
||||||
config: {
|
config: {
|
||||||
@@ -11,9 +11,6 @@ const config = {
|
|||||||
globs: ["**/*.md"],
|
globs: ["**/*.md"],
|
||||||
ignores: ["**/node_modules"],
|
ignores: ["**/node_modules"],
|
||||||
customRules: [relativeLinksRule],
|
customRules: [relativeLinksRule],
|
||||||
markdownItFactory: () => {
|
|
||||||
return markdownIt
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default config
|
export default config
|
||||||
|
|||||||
9
.oxfmtrc.json
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxfmt/configuration_schema.json",
|
||||||
|
"ignorePatterns": ["pnpm-lock.yaml"],
|
||||||
|
"semi": false,
|
||||||
|
"experimentalTailwindcss": {
|
||||||
|
"stylesheet": "./configs/config-tailwind/styles.css",
|
||||||
|
"functions": ["classNames"]
|
||||||
|
}
|
||||||
|
}
|
||||||
16
.oxlintrc.json
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"$schema": "./node_modules/oxlint/configuration_schema.json",
|
||||||
|
"extends": ["node_modules/eslint-config-conventions/.oxlintrc.json"],
|
||||||
|
"rules": {
|
||||||
|
"import-x/extensions": [
|
||||||
|
"error",
|
||||||
|
"ignorePackages",
|
||||||
|
{
|
||||||
|
"ts": "always",
|
||||||
|
"tsx": "always",
|
||||||
|
"js": "never",
|
||||||
|
"jsx": "never"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"semi": false,
|
|
||||||
"plugins": ["prettier-plugin-tailwindcss"],
|
|
||||||
"tailwindFunctions": ["classNames", "cva"],
|
|
||||||
"overrides": [
|
|
||||||
{
|
|
||||||
"files": "pnpm-lock.yaml",
|
|
||||||
"options": {
|
|
||||||
"rangeEnd": 0
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
4
.vscode/extensions.json
vendored
@@ -1,11 +1,9 @@
|
|||||||
{
|
{
|
||||||
"recommendations": [
|
"recommendations": [
|
||||||
"editorconfig.editorconfig",
|
"editorconfig.editorconfig",
|
||||||
"esbenp.prettier-vscode",
|
"oxc.oxc-vscode",
|
||||||
"dbaeumer.vscode-eslint",
|
|
||||||
"davidanson.vscode-markdownlint",
|
"davidanson.vscode-markdownlint",
|
||||||
"bradlc.vscode-tailwindcss",
|
"bradlc.vscode-tailwindcss",
|
||||||
"mikestead.dotenv",
|
|
||||||
"antfu.pnpm-catalog-lens",
|
"antfu.pnpm-catalog-lens",
|
||||||
"Lokalise.i18n-ally"
|
"Lokalise.i18n-ally"
|
||||||
]
|
]
|
||||||
|
|||||||
3
.vscode/react.code-snippets
vendored
@@ -4,7 +4,6 @@
|
|||||||
"prefix": "rfc",
|
"prefix": "rfc",
|
||||||
"body": [
|
"body": [
|
||||||
"export interface ${1:ComponentName}Props {}",
|
"export interface ${1:ComponentName}Props {}",
|
||||||
"",
|
|
||||||
"export const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = () => {",
|
"export const ${1:ComponentName}: React.FC<${1:ComponentName}Props> = () => {",
|
||||||
" return (",
|
" return (",
|
||||||
" <div>",
|
" <div>",
|
||||||
@@ -20,7 +19,7 @@
|
|||||||
"scope": "typescriptreact",
|
"scope": "typescriptreact",
|
||||||
"prefix": "rfcs",
|
"prefix": "rfcs",
|
||||||
"body": [
|
"body": [
|
||||||
"import type { Meta, StoryObj } from \"@storybook/react\"",
|
"import type { Meta, StoryObj } from \"@storybook/nextjs\"",
|
||||||
"",
|
"",
|
||||||
"import { ${1:ComponentName} as ${1:ComponentName}Component } from \"./${1:ComponentName}.tsx\"",
|
"import { ${1:ComponentName} as ${1:ComponentName}Component } from \"./${1:ComponentName}.tsx\"",
|
||||||
"",
|
"",
|
||||||
|
|||||||
27
.vscode/settings.json
vendored
@@ -2,22 +2,29 @@
|
|||||||
"typescript.tsdk": "node_modules/typescript/lib",
|
"typescript.tsdk": "node_modules/typescript/lib",
|
||||||
"editor.bracketPairColorization.enabled": true,
|
"editor.bracketPairColorization.enabled": true,
|
||||||
"editor.wordWrap": "on",
|
"editor.wordWrap": "on",
|
||||||
"prettier.configPath": ".prettierrc.json",
|
|
||||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
|
||||||
"editor.formatOnSave": true,
|
|
||||||
"editor.codeActionsOnSave": {
|
"editor.codeActionsOnSave": {
|
||||||
"source.fixAll": "explicit",
|
"source.fixAll": "never",
|
||||||
"source.organizeImports": "never"
|
"source.organizeImports": "never"
|
||||||
},
|
},
|
||||||
"tailwindCSS.experimental.classRegex": [
|
"oxc.enable": true,
|
||||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
"oxc.typeAware": true,
|
||||||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
"oxc.unusedDisableDirectives": "deny",
|
||||||
],
|
"editor.defaultFormatter": "oxc.oxc-vscode",
|
||||||
|
"editor.formatOnSave": true,
|
||||||
|
"typescript.experimental.useTsgo": false,
|
||||||
|
"editor.quickSuggestions": {
|
||||||
|
"strings": "on"
|
||||||
|
},
|
||||||
|
"files.associations": {
|
||||||
|
"*.css": "tailwindcss"
|
||||||
|
},
|
||||||
|
"tailwindCSS.classFunctions": ["classNames"],
|
||||||
|
"tailwindCSS.experimental.configFile": "./configs/config-tailwind/styles.css",
|
||||||
"i18n-ally.localesPaths": ["./packages/i18n/src/translations/"],
|
"i18n-ally.localesPaths": ["./packages/i18n/src/translations/"],
|
||||||
"i18n-ally.keystyle": "nested",
|
"i18n-ally.keystyle": "nested",
|
||||||
"i18n-ally.sortKeys": false,
|
"i18n-ally.sortKeys": false,
|
||||||
"i18n-ally.sourceLanguage": "en-US",
|
"i18n-ally.sourceLanguage": "fr-FR",
|
||||||
"i18n-ally.displayLanguage": "en-US",
|
"i18n-ally.displayLanguage": "fr-FR",
|
||||||
"i18n-ally.enabledFrameworks": ["next-intl", "general"],
|
"i18n-ally.enabledFrameworks": ["next-intl", "general"],
|
||||||
"i18n-ally.extract.autoDetect": true
|
"i18n-ally.extract.autoDetect": true
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,127 +0,0 @@
|
|||||||
# Contributor Covenant Code of Conduct
|
|
||||||
|
|
||||||
## Our Pledge
|
|
||||||
|
|
||||||
We as members, contributors, and leaders pledge to make participation in our
|
|
||||||
community a harassment-free experience for everyone, regardless of age, body
|
|
||||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
|
||||||
identity and expression, level of experience, education, socio-economic status,
|
|
||||||
nationality, personal appearance, race, religion, or sexual identity
|
|
||||||
and orientation.
|
|
||||||
|
|
||||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
|
||||||
diverse, inclusive, and healthy community.
|
|
||||||
|
|
||||||
## Our Standards
|
|
||||||
|
|
||||||
Examples of behavior that contributes to a positive environment for our
|
|
||||||
community include:
|
|
||||||
|
|
||||||
- Demonstrating empathy and kindness toward other people
|
|
||||||
- Being respectful of differing opinions, viewpoints, and experiences
|
|
||||||
- Giving and gracefully accepting constructive feedback
|
|
||||||
- Accepting responsibility and apologizing to those affected by our mistakes, and learning from the experience
|
|
||||||
- Focusing on what is best not just for us as individuals, but for the overall community
|
|
||||||
|
|
||||||
Examples of unacceptable behavior include:
|
|
||||||
|
|
||||||
- The use of sexualized language or imagery, and sexual attention or advances of any kind
|
|
||||||
- Trolling, insulting or derogatory comments, and personal or political attacks
|
|
||||||
- Public or private harassment
|
|
||||||
- Publishing others' private information, such as a physical or email address, without their explicit permission
|
|
||||||
- Other conduct which could reasonably be considered inappropriate in a professional setting
|
|
||||||
|
|
||||||
## Enforcement Responsibilities
|
|
||||||
|
|
||||||
Community leaders are responsible for clarifying and enforcing our standards of
|
|
||||||
acceptable behavior and will take appropriate and fair corrective action in
|
|
||||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
|
||||||
or harmful.
|
|
||||||
|
|
||||||
Community leaders have the right and responsibility to remove, edit, or reject
|
|
||||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
|
||||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
|
||||||
decisions when appropriate.
|
|
||||||
|
|
||||||
## Scope
|
|
||||||
|
|
||||||
This Code of Conduct applies within all community spaces, and also applies when
|
|
||||||
an individual is officially representing the community in public spaces.
|
|
||||||
Examples of representing our community include using an official e-mail address,
|
|
||||||
posting via an official social media account, or acting as an appointed
|
|
||||||
representative at an online or offline event.
|
|
||||||
|
|
||||||
## Enforcement
|
|
||||||
|
|
||||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
|
||||||
reported to the community leaders responsible for enforcement at
|
|
||||||
<contact@theoludwig.fr>.
|
|
||||||
All complaints will be reviewed and investigated promptly and fairly.
|
|
||||||
|
|
||||||
All community leaders are obligated to respect the privacy and security of the
|
|
||||||
reporter of any incident.
|
|
||||||
|
|
||||||
## Enforcement Guidelines
|
|
||||||
|
|
||||||
Community leaders will follow these Community Impact Guidelines in determining
|
|
||||||
the consequences for any action they deem in violation of this Code of Conduct:
|
|
||||||
|
|
||||||
### 1. Correction
|
|
||||||
|
|
||||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
|
||||||
unprofessional or unwelcome in the community.
|
|
||||||
|
|
||||||
**Consequence**: A private, written warning from community leaders, providing
|
|
||||||
clarity around the nature of the violation and an explanation of why the
|
|
||||||
behavior was inappropriate. A public apology may be requested.
|
|
||||||
|
|
||||||
### 2. Warning
|
|
||||||
|
|
||||||
**Community Impact**: A violation through a single incident or series
|
|
||||||
of actions.
|
|
||||||
|
|
||||||
**Consequence**: A warning with consequences for continued behavior. No
|
|
||||||
interaction with the people involved, including unsolicited interaction with
|
|
||||||
those enforcing the Code of Conduct, for a specified period of time. This
|
|
||||||
includes avoiding interactions in community spaces as well as external channels
|
|
||||||
like social media. Violating these terms may lead to a temporary or
|
|
||||||
permanent ban.
|
|
||||||
|
|
||||||
### 3. Temporary Ban
|
|
||||||
|
|
||||||
**Community Impact**: A serious violation of community standards, including
|
|
||||||
sustained inappropriate behavior.
|
|
||||||
|
|
||||||
**Consequence**: A temporary ban from any sort of interaction or public
|
|
||||||
communication with the community for a specified period of time. No public or
|
|
||||||
private interaction with the people involved, including unsolicited interaction
|
|
||||||
with those enforcing the Code of Conduct, is allowed during this period.
|
|
||||||
Violating these terms may lead to a permanent ban.
|
|
||||||
|
|
||||||
### 4. Permanent Ban
|
|
||||||
|
|
||||||
**Community Impact**: Demonstrating a pattern of violation of community
|
|
||||||
standards, including sustained inappropriate behavior, harassment of an
|
|
||||||
individual, or aggression toward or disparagement of classes of individuals.
|
|
||||||
|
|
||||||
**Consequence**: A permanent ban from any sort of public interaction within
|
|
||||||
the community.
|
|
||||||
|
|
||||||
## Attribution
|
|
||||||
|
|
||||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
|
||||||
version 2.0, available at
|
|
||||||
[https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0].
|
|
||||||
|
|
||||||
Community Impact Guidelines were inspired by
|
|
||||||
[Mozilla's code of conduct enforcement ladder][mozilla coc].
|
|
||||||
|
|
||||||
For answers to common questions about this code of conduct, see the FAQ at
|
|
||||||
[https://www.contributor-covenant.org/faq][faq]. Translations are available
|
|
||||||
at [https://www.contributor-covenant.org/translations][translations].
|
|
||||||
|
|
||||||
[homepage]: https://www.contributor-covenant.org
|
|
||||||
[v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html
|
|
||||||
[mozilla coc]: https://github.com/mozilla/diversity
|
|
||||||
[faq]: https://www.contributor-covenant.org/faq
|
|
||||||
[translations]: https://www.contributor-covenant.org/translations
|
|
||||||
@@ -1,10 +1,6 @@
|
|||||||
# 💡 Contributing
|
# Contributing
|
||||||
|
|
||||||
Thanks a lot for your interest in contributing to **theoludwig.fr**! 🎉
|
Thanks a lot for your interest in contributing to **theoludwig.fr**!
|
||||||
|
|
||||||
## Code of Conduct
|
|
||||||
|
|
||||||
**theoludwig.fr** adopted the [Contributor Covenant](https://www.contributor-covenant.org/) as its Code of Conduct, and we expect project participants to adhere to it. Please read [the full text](./CODE_OF_CONDUCT.md) so that you can understand what actions will and will not be tolerated.
|
|
||||||
|
|
||||||
## Types of contributions
|
## Types of contributions
|
||||||
|
|
||||||
@@ -32,7 +28,7 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv
|
|||||||
### Prerequisites
|
### Prerequisites
|
||||||
|
|
||||||
- [Node.js](https://nodejs.org/) >= v24.0.0 [(`nvm install 24`)](https://nvm.sh)
|
- [Node.js](https://nodejs.org/) >= v24.0.0 [(`nvm install 24`)](https://nvm.sh)
|
||||||
- [pnpm](https://pnpm.io/) v10.10.0 [(`npm install --global corepack@0.32.0 && corepack enable`)](https://github.com/nodejs/corepack)
|
- [pnpm](https://pnpm.io/) [(`npm install --global corepack@0.34.6 && corepack enable`)](https://github.com/nodejs/corepack)
|
||||||
- [Docker](https://www.docker.com/)
|
- [Docker](https://www.docker.com/)
|
||||||
|
|
||||||
### Installation
|
### Installation
|
||||||
@@ -64,9 +60,10 @@ node --run dev
|
|||||||
# Lint
|
# Lint
|
||||||
node --run lint:editorconfig
|
node --run lint:editorconfig
|
||||||
node --run lint:markdown
|
node --run lint:markdown
|
||||||
node --run lint:typescript
|
node --run lint:turbo
|
||||||
node --run lint:eslint
|
# node --run lint:typescript # already covered by oxlint
|
||||||
node --run lint:prettier
|
node --run lint:oxlint
|
||||||
|
node --run lint:oxfmt
|
||||||
|
|
||||||
# Tests
|
# Tests
|
||||||
node --run test
|
node --run test
|
||||||
|
|||||||
21
LICENSE
@@ -1,21 +0,0 @@
|
|||||||
# MIT License
|
|
||||||
|
|
||||||
Copyright (c) Théo LUDWIG <contact@theoludwig.fr>
|
|
||||||
|
|
||||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
||||||
of this software and associated documentation files (the "Software"), to deal
|
|
||||||
in the Software without restriction, including without limitation the rights
|
|
||||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
||||||
copies of the Software, and to permit persons to whom the Software is
|
|
||||||
furnished to do so, subject to the following conditions:
|
|
||||||
|
|
||||||
The above copyright notice and this permission notice shall be included in all
|
|
||||||
copies or substantial portions of the Software.
|
|
||||||
|
|
||||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
||||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
||||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
||||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
||||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
||||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
||||||
SOFTWARE.
|
|
||||||
37
README.md
@@ -8,7 +8,7 @@
|
|||||||
<a href="https://github.com/theoludwig"><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/theoludwig"><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/theoludwig"><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/theoludwig"><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/~theoludwig"><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/~theoludwig"><img alt="npm" src="https://img.shields.io/badge/-npm-c4302b?style=flat&labelColor=c4302b&logo=npm&logoColor=white"/></a>
|
||||||
<a href="https://twitter.com/theoludwig_"><img alt="Twitter" src="https://img.shields.io/badge/-Twitter-1ca0f1?style=flat&labelColor=1ca0f1&logo=x&logoColor=white"/></a>
|
<a href="https://twitter.com/theoludwig_"><img alt="X/Twitter" src="https://img.shields.io/badge/-Twitter-1ca0f1?style=flat&labelColor=1ca0f1&logo=x&logoColor=white"/></a>
|
||||||
<a href="https://www.youtube.com/@theo_ludwig"><img alt="YouTube" src="https://img.shields.io/badge/-YouTube-c4302b?style=flat&labelColor=c4302b&logo=youtube&logoColor=white"/></a>
|
<a href="https://www.youtube.com/@theo_ludwig"><img alt="YouTube" src="https://img.shields.io/badge/-YouTube-c4302b?style=flat&labelColor=c4302b&logo=youtube&logoColor=white"/></a>
|
||||||
<a href="https://www.twitch.tv/theoludwig"><img alt="Twitch" src="https://img.shields.io/badge/-Twitch-9147FF?style=flat&labelColor=9147FF&logo=twitch&logoColor=white"/></a>
|
<a href="https://www.twitch.tv/theoludwig"><img alt="Twitch" src="https://img.shields.io/badge/-Twitch-9147FF?style=flat&labelColor=9147FF&logo=twitch&logoColor=white"/></a>
|
||||||
<a href="https://theoludwig.fr/"><img alt="Website" src="https://img.shields.io/badge/-Website-181818?style=flat&labelColor=181818&logo=Google-Chrome&logoColor=white"/></a>
|
<a href="https://theoludwig.fr/"><img alt="Website" src="https://img.shields.io/badge/-Website-181818?style=flat&labelColor=181818&logo=Google-Chrome&logoColor=white"/></a>
|
||||||
@@ -17,7 +17,7 @@
|
|||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
## 📜 About
|
## About
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
@@ -27,30 +27,17 @@
|
|||||||
"nationality": "Alsace, France",
|
"nationality": "Alsace, France",
|
||||||
"interests": ["Developer Full Stack", "Open-Source Enthusiast"],
|
"interests": ["Developer Full Stack", "Open-Source Enthusiast"],
|
||||||
"skills": {
|
"skills": {
|
||||||
"programmingLanguages": [
|
"software-development": [
|
||||||
"JavaScript/TypeScript",
|
"TypeScript",
|
||||||
"Python",
|
"React.js (+ Next.js)",
|
||||||
"C/C++",
|
"Tailwind CSS",
|
||||||
"PHP"
|
"Node.js",
|
||||||
|
"tRPC/oRPC",
|
||||||
|
"PostgreSQL"
|
||||||
],
|
],
|
||||||
"frontend": ["HTML/CSS", "Tailwind CSS", "React.js/Next.js"],
|
"sys-admin": ["Docker", "Proxmox", "Caddy", "GitHub Actions", "GitLab CI/CD"],
|
||||||
"backend": ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
|
"software-tools": ["GNU/Linux", "Arch Linux", "Visual Studio Code", "Git"],
|
||||||
"tools": [
|
"systems-programming": ["C/C++", "Rust", "Go"]
|
||||||
"GNU/Linux",
|
|
||||||
"Arch Linux",
|
|
||||||
"Visual Studio Code",
|
|
||||||
"Git",
|
|
||||||
"Docker"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
## 📈 Statistics
|
|
||||||
|
|
||||||
<p align=center>
|
|
||||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=theoludwig&show_icons=true&theme=dark" alt="Théo LUDWIG's GitHub Stats" />
|
|
||||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=theoludwig&hide=html,css,javascript&langs_count=8&layout=compact&theme=dark" alt="Théo LUDWIG's Programming Languages" />
|
|
||||||
</p>
|
|
||||||
|
|||||||
@@ -12,14 +12,7 @@ const config: StorybookConfig = {
|
|||||||
"../../../packages/blog/src/**/*.stories.tsx",
|
"../../../packages/blog/src/**/*.stories.tsx",
|
||||||
"../stories/*.mdx",
|
"../stories/*.mdx",
|
||||||
],
|
],
|
||||||
addons: [
|
addons: ["@chromatic-com/storybook", "@storybook/addon-docs", "@storybook/addon-a11y"],
|
||||||
"@chromatic-com/storybook",
|
|
||||||
"@storybook/addon-essentials",
|
|
||||||
"@storybook/addon-storysource",
|
|
||||||
"@storybook/addon-a11y",
|
|
||||||
"@storybook/addon-interactions",
|
|
||||||
"storybook-dark-mode",
|
|
||||||
],
|
|
||||||
framework: {
|
framework: {
|
||||||
name: "@storybook/nextjs",
|
name: "@storybook/nextjs",
|
||||||
options: {},
|
options: {},
|
||||||
|
|||||||
@@ -2,18 +2,23 @@ import "@repo/config-tailwind/styles.css"
|
|||||||
import "./storybook-css-overrides.css"
|
import "./storybook-css-overrides.css"
|
||||||
import i18nMessages from "@repo/i18n/translations/en-US.json"
|
import i18nMessages from "@repo/i18n/translations/en-US.json"
|
||||||
import { LOCALE_DEFAULT, TIMEZONE } from "@repo/utils/constants"
|
import { LOCALE_DEFAULT, TIMEZONE } from "@repo/utils/constants"
|
||||||
import type { Preview } from "@storybook/react"
|
import type { Preview } from "@storybook/nextjs"
|
||||||
import { NextIntlClientProvider } from "next-intl"
|
import { NextIntlClientProvider } from "next-intl"
|
||||||
import { ThemeProvider as NextThemeProvider } from "next-themes"
|
import { ThemeProvider as NextThemeProvider } from "next-themes"
|
||||||
import React from "react"
|
|
||||||
|
|
||||||
const preview: Preview = {
|
const preview: Preview = {
|
||||||
globals: {
|
initialGlobals: {
|
||||||
a11y: {
|
a11y: {
|
||||||
manual: true,
|
manual: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
parameters: {
|
parameters: {
|
||||||
|
a11y: {
|
||||||
|
test: "error",
|
||||||
|
},
|
||||||
|
docs: {
|
||||||
|
codePanel: true,
|
||||||
|
},
|
||||||
nextjs: {
|
nextjs: {
|
||||||
appDirectory: true,
|
appDirectory: true,
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import type { TestRunnerConfig } from "@storybook/test-runner"
|
|
||||||
import { getStoryContext } from "@storybook/test-runner"
|
|
||||||
|
|
||||||
import { checkA11y, configureAxe, injectAxe } from "axe-playwright"
|
|
||||||
|
|
||||||
/*
|
|
||||||
* See https://storybook.js.org/docs/writing-tests/test-runner#test-hook-api
|
|
||||||
*/
|
|
||||||
const config: TestRunnerConfig = {
|
|
||||||
async preVisit(page) {
|
|
||||||
await injectAxe(page)
|
|
||||||
},
|
|
||||||
async postVisit(page, context) {
|
|
||||||
const storyContext = await getStoryContext(page, context)
|
|
||||||
|
|
||||||
const isA11yDisabled = storyContext.parameters?.a11y?.disable as boolean
|
|
||||||
if (isA11yDisabled) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
await configureAxe(page, {
|
|
||||||
rules: storyContext.parameters?.a11y?.config?.rules,
|
|
||||||
})
|
|
||||||
|
|
||||||
await checkA11y(page, "#storybook-root", {
|
|
||||||
verbose: false,
|
|
||||||
detailedReport: true,
|
|
||||||
detailedReportOptions: {
|
|
||||||
html: true,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
export default config
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import config from "@repo/config-eslint"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...config, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -76,9 +76,7 @@ server.listen(
|
|||||||
port,
|
port,
|
||||||
},
|
},
|
||||||
() => {
|
() => {
|
||||||
console.log(
|
console.log(`HTTP Server is listening at ${util.styleText("cyan", serverURL)}`)
|
||||||
`HTTP Server is listening at ${util.styleText("cyan", serverURL)}`,
|
|
||||||
)
|
|
||||||
console.log(`Serving files from: \`${basePath}\``)
|
console.log(`Serving files from: \`${basePath}\``)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,53 +6,43 @@
|
|||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "storybook build",
|
"build": "storybook build",
|
||||||
"dev": "storybook dev --port 6006 --no-open",
|
"dev": "storybook dev --port 6006 --no-open",
|
||||||
"start": "node --experimental-strip-types http-server.ts --path=storybook-static --port=6006",
|
"start": "node http-server.ts --path=storybook-static --port=6006",
|
||||||
"test": "start-server-and-test \"start\" http://localhost:6006 \"test:storybook\"",
|
"test": "start-server-and-test \"start\" http://localhost:6006 \"test:storybook\"",
|
||||||
"test:dev": "start-server-and-test \"dev\" http://localhost:6006 \"test:storybook\"",
|
"test:dev": "start-server-and-test \"dev\" http://localhost:6006 \"test:storybook\"",
|
||||||
"test:storybook": "test-storybook --testTimeout=60000 --maxWorkers=2",
|
"test:storybook": "test-storybook --testTimeout=60000 --maxWorkers=2",
|
||||||
"chromatic": "chromatic"
|
"chromatic": "chromatic"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@repo/blog": "workspace:*",
|
||||||
"@repo/config-tailwind": "workspace:*",
|
"@repo/config-tailwind": "workspace:*",
|
||||||
"@repo/i18n": "workspace:*",
|
"@repo/i18n": "workspace:*",
|
||||||
"@repo/ui": "workspace:*",
|
"@repo/ui": "workspace:*",
|
||||||
"@repo/utils": "workspace:*",
|
"@repo/utils": "workspace:*",
|
||||||
"@repo/blog": "workspace:*",
|
"mime": "catalog:",
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-intl": "catalog:",
|
"next-intl": "catalog:",
|
||||||
"next-themes": "catalog:",
|
"next-themes": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:",
|
"react-dom": "catalog:"
|
||||||
"mime": "catalog:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
|
||||||
"@chromatic-com/storybook": "catalog:",
|
"@chromatic-com/storybook": "catalog:",
|
||||||
"@playwright/test": "catalog:",
|
"@playwright/test": "catalog:",
|
||||||
"@storybook/addon-essentials": "catalog:",
|
"@repo/config-typescript": "workspace:*",
|
||||||
"@storybook/addon-storysource": "catalog:",
|
|
||||||
"@storybook/addon-a11y": "catalog:",
|
"@storybook/addon-a11y": "catalog:",
|
||||||
"@storybook/addon-interactions": "catalog:",
|
"@storybook/addon-docs": "catalog:",
|
||||||
"@storybook/addon-themes": "catalog:",
|
"@storybook/addon-themes": "catalog:",
|
||||||
"@storybook/blocks": "catalog:",
|
|
||||||
"@storybook/nextjs": "catalog:",
|
"@storybook/nextjs": "catalog:",
|
||||||
"@storybook/react": "catalog:",
|
|
||||||
"@storybook/test": "catalog:",
|
|
||||||
"@storybook/test-runner": "catalog:",
|
"@storybook/test-runner": "catalog:",
|
||||||
|
"@tailwindcss/postcss": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"axe-playwright": "catalog:",
|
|
||||||
"chromatic": "catalog:",
|
"chromatic": "catalog:",
|
||||||
"eslint": "catalog:",
|
"postcss": "catalog:",
|
||||||
"start-server-and-test": "catalog:",
|
"start-server-and-test": "catalog:",
|
||||||
"storybook": "catalog:",
|
"storybook": "catalog:",
|
||||||
"storybook-dark-mode": "catalog:",
|
|
||||||
"postcss": "catalog:",
|
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:",
|
||||||
"@tailwindcss/postcss": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { Meta, Title, ColorPalette, ColorItem } from "@storybook/blocks"
|
import { Meta, Title, ColorPalette, ColorItem } from "@storybook/addon-docs/blocks"
|
||||||
import tailwindConfig from "@repo/config-tailwind"
|
import tailwindConfig from "@repo/config-tailwind"
|
||||||
|
|
||||||
<Meta title="Design System/Colors" />
|
<Meta title="Design System/Colors" />
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
FROM node:24.0.0-slim AS node-pnpm
|
FROM node:24.13.1-slim@sha256:a81a03dd965b4052269a57fac857004022b522a4bf06e7a739e25e18bce45af2 AS node-pnpm
|
||||||
ENV PNPM_HOME="/pnpm"
|
ENV PNPM_HOME="/pnpm"
|
||||||
ENV PATH="$PNPM_HOME:$PATH"
|
ENV PATH="$PNPM_HOME:$PATH"
|
||||||
RUN npm install --global corepack@0.32.0 && corepack enable
|
RUN npm install --global corepack@0.34.6 && corepack enable
|
||||||
ENV TURBO_TELEMETRY_DISABLED=1
|
ENV TURBO_TELEMETRY_DISABLED=1
|
||||||
ENV NEXT_TELEMETRY_DISABLED=1
|
ENV NEXT_TELEMETRY_DISABLED=1
|
||||||
ENV DO_NOT_TRACK=1
|
ENV DO_NOT_TRACK=1
|
||||||
@@ -9,7 +9,7 @@ WORKDIR /usr/src/app
|
|||||||
|
|
||||||
FROM node-pnpm AS builder
|
FROM node-pnpm AS builder
|
||||||
COPY ./ ./
|
COPY ./ ./
|
||||||
RUN pnpm install --global turbo@2.5.3
|
RUN pnpm install --global turbo@2.8.9
|
||||||
RUN turbo prune @repo/website --docker
|
RUN turbo prune @repo/website --docker
|
||||||
|
|
||||||
FROM node-pnpm AS installer
|
FROM node-pnpm AS installer
|
||||||
@@ -23,7 +23,7 @@ COPY --from=builder /usr/src/app/out/full/ ./
|
|||||||
COPY turbo.json turbo.json
|
COPY turbo.json turbo.json
|
||||||
|
|
||||||
ARG VERSION="0.0.0-develop"
|
ARG VERSION="0.0.0-develop"
|
||||||
RUN pnpm install --global replace-in-files-cli@3.0.0
|
RUN pnpm install --global replace-in-files-cli@4.0.0
|
||||||
RUN VERSION_STRIPPED=${VERSION#v} && replace-in-files --regex='version": *"[^"]*' --replacement='"version": "'"$VERSION_STRIPPED"'"' '**/package.json' '!**/node_modules/**'
|
RUN VERSION_STRIPPED=${VERSION#v} && replace-in-files --regex='version": *"[^"]*' --replacement='"version": "'"$VERSION_STRIPPED"'"' '**/package.json' '!**/node_modules/**'
|
||||||
RUN pnpm --filter=@repo/website... exec turbo run build
|
RUN pnpm --filter=@repo/website... exec turbo run build
|
||||||
|
|
||||||
@@ -34,7 +34,7 @@ ENV IS_STANDALONE=true
|
|||||||
|
|
||||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 applicationrunner
|
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 applicationrunner
|
||||||
USER applicationrunner
|
USER applicationrunner
|
||||||
COPY --from=installer /usr/src/app/apps/website/next.config.js ./
|
COPY --from=installer /usr/src/app/apps/website/next.config.ts ./
|
||||||
COPY --from=installer /usr/src/app/apps/website/package.json ./
|
COPY --from=installer /usr/src/app/apps/website/package.json ./
|
||||||
COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/standalone ./
|
COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/standalone ./
|
||||||
COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/static ./apps/website/.next/static
|
COPY --from=installer --chown=applicationrunner:nodejs /usr/src/app/apps/website/.next/static ./apps/website/.next/static
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ interface BlogPostPageProps {
|
|||||||
}>
|
}>
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateMetadata = async (
|
export const generateMetadata = async (props: BlogPostPageProps): Promise<Metadata> => {
|
||||||
props: BlogPostPageProps,
|
|
||||||
): Promise<Metadata> => {
|
|
||||||
const { slug } = await props.params
|
const { slug } = await props.params
|
||||||
const blogPost = await getBlogPostBySlug(slug)
|
const blogPost = await getBlogPostBySlug(slug)
|
||||||
if (blogPost == null) {
|
if (blogPost == null) {
|
||||||
@@ -37,9 +35,7 @@ export const generateMetadata = async (
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export const generateStaticParams = async (): Promise<
|
export const generateStaticParams = async (): Promise<Array<{ slug: string }>> => {
|
||||||
Array<{ slug: string }>
|
|
||||||
> => {
|
|
||||||
const posts = await getBlogPosts()
|
const posts = await getBlogPosts()
|
||||||
return posts.map((post) => {
|
return posts.map((post) => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -2,18 +2,13 @@ import { getBlogPosts } from "@repo/blog"
|
|||||||
import { BlogPosts } from "@repo/blog/BlogPosts"
|
import { BlogPosts } from "@repo/blog/BlogPosts"
|
||||||
import type { LocaleProps } from "@repo/i18n/routing"
|
import type { LocaleProps } from "@repo/i18n/routing"
|
||||||
import { MainLayout } from "@repo/ui/Layout/MainLayout"
|
import { MainLayout } from "@repo/ui/Layout/MainLayout"
|
||||||
import {
|
import { Section, SectionDescription, SectionTitle } from "@repo/ui/Layout/Section"
|
||||||
Section,
|
|
||||||
SectionDescription,
|
|
||||||
SectionTitle,
|
|
||||||
} from "@repo/ui/Layout/Section"
|
|
||||||
import { LOCALE_DEFAULT } from "@repo/utils/constants"
|
import { LOCALE_DEFAULT } from "@repo/utils/constants"
|
||||||
import type { Metadata } from "next"
|
import type { Metadata } from "next"
|
||||||
import { setRequestLocale } from "next-intl/server"
|
import { setRequestLocale } from "next-intl/server"
|
||||||
|
|
||||||
const title = "Blog | Théo LUDWIG"
|
const title = "Blog | Théo LUDWIG"
|
||||||
const description =
|
const description = "The latest news about my journey of learning computer science."
|
||||||
"The latest news about my journey of learning computer science."
|
|
||||||
|
|
||||||
export const generateMetadata = async (): Promise<Metadata> => {
|
export const generateMetadata = async (): Promise<Metadata> => {
|
||||||
return {
|
return {
|
||||||
|
|||||||
@@ -1,19 +1,23 @@
|
|||||||
import "@repo/config-tailwind/styles.css"
|
import "@repo/config-tailwind/styles.css"
|
||||||
import type { LocaleProps } from "@repo/i18n/routing"
|
import type { Locale } from "@repo/utils/constants"
|
||||||
import { Footer } from "@repo/ui/Layout/Footer"
|
import { Footer } from "@repo/ui/Layout/Footer"
|
||||||
import { Header } from "@repo/ui/Layout/Header"
|
import { Header } from "@repo/ui/Layout/Header"
|
||||||
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
||||||
import { VERSION } from "@repo/utils/constants"
|
import { VERSION } from "@repo/utils/constants"
|
||||||
import { setRequestLocale } from "next-intl/server"
|
import { setRequestLocale } from "next-intl/server"
|
||||||
|
|
||||||
interface MainLayoutProps extends React.PropsWithChildren, LocaleProps {}
|
interface MainLayoutProps extends React.PropsWithChildren {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
const MainLayout: React.FC<MainLayoutProps> = async (props) => {
|
const MainLayout: React.FC<MainLayoutProps> = async (props) => {
|
||||||
const { children, params } = props
|
const { children, params } = props
|
||||||
|
|
||||||
const { locale } = await params
|
const { locale } = await params
|
||||||
// Enable static rendering
|
// Enable static rendering
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale as Locale)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ThemeProvider>
|
<ThemeProvider>
|
||||||
|
|||||||
@@ -21,21 +21,21 @@ const HomePage: React.FC<HomePageProps> = async (props) => {
|
|||||||
<MainLayout>
|
<MainLayout>
|
||||||
<About />
|
<About />
|
||||||
|
|
||||||
<RevealFade>
|
|
||||||
<Interests />
|
|
||||||
</RevealFade>
|
|
||||||
|
|
||||||
<RevealFade>
|
|
||||||
<Skills />
|
|
||||||
</RevealFade>
|
|
||||||
|
|
||||||
<RevealFade>
|
<RevealFade>
|
||||||
<Portfolio />
|
<Portfolio />
|
||||||
</RevealFade>
|
</RevealFade>
|
||||||
|
|
||||||
|
<RevealFade>
|
||||||
|
<Interests />
|
||||||
|
</RevealFade>
|
||||||
|
|
||||||
<RevealFade>
|
<RevealFade>
|
||||||
<OpenSource />
|
<OpenSource />
|
||||||
</RevealFade>
|
</RevealFade>
|
||||||
|
|
||||||
|
<RevealFade>
|
||||||
|
<Skills />
|
||||||
|
</RevealFade>
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,20 +1,20 @@
|
|||||||
import "@repo/config-tailwind/styles.css"
|
import "@repo/config-tailwind/styles.css"
|
||||||
import type { LocaleProps } from "@repo/i18n/routing"
|
import type { Locale } from "@repo/utils/constants"
|
||||||
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
||||||
import { setRequestLocale } from "next-intl/server"
|
import { setRequestLocale } from "next-intl/server"
|
||||||
|
|
||||||
interface CurriculumVitaeLayoutProps
|
interface CurriculumVitaeLayoutProps extends React.PropsWithChildren {
|
||||||
extends React.PropsWithChildren,
|
params: Promise<{
|
||||||
LocaleProps {}
|
locale: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
const CurriculumVitaeLayout: React.FC<CurriculumVitaeLayoutProps> = async (
|
const CurriculumVitaeLayout: React.FC<CurriculumVitaeLayoutProps> = async (props) => {
|
||||||
props,
|
|
||||||
) => {
|
|
||||||
const { children, params } = props
|
const { children, params } = props
|
||||||
|
|
||||||
const { locale } = await params
|
const { locale } = await params
|
||||||
// Enable static rendering
|
// Enable static rendering
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale as Locale)
|
||||||
|
|
||||||
return <ThemeProvider forcedTheme="light">{children}</ThemeProvider>
|
return <ThemeProvider forcedTheme="light">{children}</ThemeProvider>
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,14 +1,9 @@
|
|||||||
import "@repo/config-tailwind/styles.css"
|
import "@repo/config-tailwind/styles.css"
|
||||||
import type { LocaleProps } from "@repo/i18n/routing"
|
|
||||||
import type { Locale } from "@repo/utils/constants"
|
import type { Locale } from "@repo/utils/constants"
|
||||||
import { LOCALES } from "@repo/utils/constants"
|
import { LOCALES } from "@repo/utils/constants"
|
||||||
import type { Metadata, Viewport } from "next"
|
import type { Metadata, Viewport } from "next"
|
||||||
import { NextIntlClientProvider } from "next-intl"
|
import { NextIntlClientProvider } from "next-intl"
|
||||||
import {
|
import { getMessages, getTranslations, setRequestLocale } from "next-intl/server"
|
||||||
getMessages,
|
|
||||||
getTranslations,
|
|
||||||
setRequestLocale,
|
|
||||||
} from "next-intl/server"
|
|
||||||
import Script from "next/script"
|
import Script from "next/script"
|
||||||
|
|
||||||
const DOMAIN = "theoludwig.fr"
|
const DOMAIN = "theoludwig.fr"
|
||||||
@@ -19,9 +14,13 @@ export const viewport: Viewport = {
|
|||||||
|
|
||||||
export const generateMetadata = async ({
|
export const generateMetadata = async ({
|
||||||
params,
|
params,
|
||||||
}: LocaleProps): Promise<Metadata> => {
|
}: {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string
|
||||||
|
}>
|
||||||
|
}): Promise<Metadata> => {
|
||||||
const { locale } = await params
|
const { locale } = await params
|
||||||
const t = await getTranslations({ locale })
|
const t = await getTranslations({ locale: locale as Locale })
|
||||||
const title = t("meta.title")
|
const title = t("meta.title")
|
||||||
const description = `${title} - ${t("meta.description")}`
|
const description = `${title} - ${t("meta.description")}`
|
||||||
const image = "/images/logo.webp"
|
const image = "/images/logo.webp"
|
||||||
@@ -64,29 +63,32 @@ export const generateStaticParams = (): Array<{ locale: Locale }> => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
interface LocaleLayoutProps extends React.PropsWithChildren, LocaleProps {}
|
interface LocaleLayoutProps extends React.PropsWithChildren {
|
||||||
|
params: Promise<{
|
||||||
|
locale: string
|
||||||
|
}>
|
||||||
|
}
|
||||||
|
|
||||||
const LocaleLayout: React.FC<LocaleLayoutProps> = async (props) => {
|
const LocaleLayout: React.FC<LocaleLayoutProps> = async (props) => {
|
||||||
const { children, params } = props
|
const { children, params } = props
|
||||||
|
|
||||||
const { locale } = await params
|
const { locale } = await params
|
||||||
// Enable static rendering
|
// Enable static rendering
|
||||||
setRequestLocale(locale)
|
setRequestLocale(locale as Locale)
|
||||||
|
|
||||||
const messages = await getMessages()
|
const messages = await getMessages()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<html lang={locale} suppressHydrationWarning>
|
<html lang={locale} suppressHydrationWarning>
|
||||||
<body>
|
<body>
|
||||||
<NextIntlClientProvider messages={messages}>
|
<NextIntlClientProvider messages={messages}>{children}</NextIntlClientProvider>
|
||||||
{children}
|
|
||||||
</NextIntlClientProvider>
|
|
||||||
|
|
||||||
<Script
|
<Script async src="https://analytics.theoludwig.fr/js/pa-MToGPBn2iXgorPLkh6f_s.js" />
|
||||||
defer
|
<Script id="analytics-init">
|
||||||
data-domain={DOMAIN}
|
{`
|
||||||
src="https://plausible.theoludwig.fr/js/script.js"
|
window.plausible=window.plausible||function(){(plausible.q=plausible.q||[]).push(arguments)},plausible.init=plausible.init||function(i){plausible.o=i||{}};
|
||||||
/>
|
plausible.init()`}
|
||||||
|
</Script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import configNextjs from "@repo/config-eslint/nextjs"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...configNextjs, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
5
apps/website/next-env.d.ts
vendored
@@ -1,5 +0,0 @@
|
|||||||
/// <reference types="next" />
|
|
||||||
/// <reference types="next/image-types/global" />
|
|
||||||
|
|
||||||
// NOTE: This file should not be edited
|
|
||||||
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
import createNextIntlPlugin from "next-intl/plugin"
|
|
||||||
|
|
||||||
const IS_STANDALONE = process.env.IS_STANDALONE === "true"
|
|
||||||
|
|
||||||
/** @type {import('next').NextConfig} */
|
|
||||||
const nextConfig = {
|
|
||||||
output: IS_STANDALONE ? "standalone" : undefined,
|
|
||||||
images: {
|
|
||||||
unoptimized: true,
|
|
||||||
},
|
|
||||||
compress: false,
|
|
||||||
eslint: {
|
|
||||||
ignoreDuringBuilds: true,
|
|
||||||
},
|
|
||||||
typescript: {
|
|
||||||
ignoreBuildErrors: true,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
const withNextIntl = createNextIntlPlugin()
|
|
||||||
|
|
||||||
export default withNextIntl(nextConfig)
|
|
||||||
34
apps/website/next.config.ts
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
import { LOCALE_DEFAULT } from "@repo/utils/constants"
|
||||||
|
import type { NextConfig } from "next"
|
||||||
|
import createNextIntlPlugin from "next-intl/plugin"
|
||||||
|
import path from "node:path"
|
||||||
|
|
||||||
|
const IS_STANDALONE = process.env.IS_STANDALONE === "true"
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
output: IS_STANDALONE ? "standalone" : undefined,
|
||||||
|
typedRoutes: true,
|
||||||
|
images: {
|
||||||
|
unoptimized: true,
|
||||||
|
},
|
||||||
|
typescript: {
|
||||||
|
ignoreBuildErrors: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
const withNextIntl = createNextIntlPlugin({
|
||||||
|
experimental: {
|
||||||
|
createMessagesDeclaration: path.join(
|
||||||
|
process.cwd(),
|
||||||
|
"..",
|
||||||
|
"..",
|
||||||
|
"packages",
|
||||||
|
"i18n",
|
||||||
|
"src",
|
||||||
|
"translations",
|
||||||
|
`${LOCALE_DEFAULT}.json`,
|
||||||
|
),
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export default withNextIntl(nextConfig)
|
||||||
@@ -7,38 +7,35 @@
|
|||||||
"#*": "./*"
|
"#*": "./*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --port 3000 --turbopack",
|
"dev": "next dev --port 3000",
|
||||||
"build": "next build",
|
"build": "next build",
|
||||||
"start": "next start --port 3000",
|
"start": "next start --port 3000",
|
||||||
"lint:eslint": "eslint . --max-warnings 0",
|
"typegen": "next typegen",
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "next typegen && tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@mdx-js/mdx": "catalog:",
|
||||||
"@repo/blog": "workspace:*",
|
"@repo/blog": "workspace:*",
|
||||||
"@repo/config-tailwind": "workspace:*",
|
"@repo/config-tailwind": "workspace:*",
|
||||||
"@repo/utils": "workspace:*",
|
|
||||||
"@repo/i18n": "workspace:*",
|
"@repo/i18n": "workspace:*",
|
||||||
"@repo/ui": "workspace:*",
|
"@repo/ui": "workspace:*",
|
||||||
"@mdx-js/mdx": "catalog:",
|
"@repo/utils": "workspace:*",
|
||||||
"next-mdx-remote": "catalog:",
|
|
||||||
"shiki": "catalog:",
|
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-intl": "catalog:",
|
"next-intl": "catalog:",
|
||||||
|
"next-mdx-remote": "catalog:",
|
||||||
"react": "catalog:",
|
"react": "catalog:",
|
||||||
"react-dom": "catalog:"
|
"react-dom": "catalog:",
|
||||||
|
"shiki": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
"@repo/config-typescript": "workspace:*",
|
||||||
|
"@tailwindcss/postcss": "catalog:",
|
||||||
|
"@total-typescript/ts-reset": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"postcss": "catalog:",
|
"postcss": "catalog:",
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:",
|
||||||
"@tailwindcss/postcss": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -21,6 +21,6 @@ export const config = {
|
|||||||
* - _next/image (image optimization files)
|
* - _next/image (image optimization files)
|
||||||
* - favicon.ico (favicon file)
|
* - favicon.ico (favicon file)
|
||||||
*/
|
*/
|
||||||
"/((?!api|_next/static|_next/image|images|favicon.ico).*)",
|
"/((?!api|_next/static|_next/image|images|favicon.ico|robots.txt).*)",
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
BIN
apps/website/public/images/portfolio/Fusey-ARK.webp
Normal file
|
After Width: | Height: | Size: 1.2 MiB |
|
Before Width: | Height: | Size: 945 KiB After Width: | Height: | Size: 313 KiB |
BIN
apps/website/public/images/skills/Caddy.webp
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
apps/website/public/images/skills/GitHub-dark.webp
Normal file
|
After Width: | Height: | Size: 3.0 KiB |
BIN
apps/website/public/images/skills/GitHub-light.webp
Normal file
|
After Width: | Height: | Size: 4.1 KiB |
BIN
apps/website/public/images/skills/GitLab.webp
Normal file
|
After Width: | Height: | Size: 5.0 KiB |
BIN
apps/website/public/images/skills/Go.webp
Normal file
|
After Width: | Height: | Size: 5.3 KiB |
BIN
apps/website/public/images/skills/Proxmox-dark.webp
Normal file
|
After Width: | Height: | Size: 67 KiB |
BIN
apps/website/public/images/skills/Proxmox-light.webp
Normal file
|
After Width: | Height: | Size: 18 KiB |
BIN
apps/website/public/images/skills/Rust.webp
Normal file
|
After Width: | Height: | Size: 60 KiB |
BIN
apps/website/public/images/skills/tRPC.webp
Normal file
|
After Width: | Height: | Size: 17 KiB |
@@ -4,9 +4,6 @@
|
|||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"types": ["@total-typescript/ts-reset", "@repo/i18n/messages.d.ts"],
|
"types": ["@total-typescript/ts-reset", "@repo/i18n/messages.d.ts"],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"paths": {
|
|
||||||
"#*": ["./*"]
|
|
||||||
},
|
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
"name": "next"
|
"name": "next"
|
||||||
@@ -14,5 +11,5 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||||
"exclude": ["node_modules", ".next"]
|
"exclude": ["node_modules"]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,35 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import configConventions from "eslint-config-conventions"
|
|
||||||
import importX from "eslint-plugin-import-x"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(
|
|
||||||
{
|
|
||||||
ignores: [
|
|
||||||
".next",
|
|
||||||
"**/next.config.js",
|
|
||||||
"**/eslint.config.js",
|
|
||||||
"**/tailwind.config.js",
|
|
||||||
"**/postcss.config.js",
|
|
||||||
"**/kysely.config.ts",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
...configConventions,
|
|
||||||
{
|
|
||||||
name: "config-eslint",
|
|
||||||
plugins: {
|
|
||||||
"import-x": importX,
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"import-x/extensions": [
|
|
||||||
"error",
|
|
||||||
"ignorePackages",
|
|
||||||
{
|
|
||||||
ts: "always",
|
|
||||||
tsx: "always",
|
|
||||||
js: "never",
|
|
||||||
jsx: "never",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
7
configs/config-eslint/index.d.ts
vendored
@@ -1,7 +0,0 @@
|
|||||||
import type typescriptESLint from "typescript-eslint"
|
|
||||||
|
|
||||||
declare const eslintConfigConventions: ReturnType<
|
|
||||||
typeof typescriptESLint.config
|
|
||||||
>
|
|
||||||
|
|
||||||
export default eslintConfigConventions
|
|
||||||
@@ -1,57 +0,0 @@
|
|||||||
import { FlatCompat } from "@eslint/eslintrc"
|
|
||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import config from "../eslint.config.js"
|
|
||||||
|
|
||||||
const flatCompat = new FlatCompat({
|
|
||||||
baseDirectory: import.meta.dirname,
|
|
||||||
})
|
|
||||||
|
|
||||||
export default typescriptESLint.config(
|
|
||||||
...config,
|
|
||||||
...flatCompat.extends("next/core-web-vitals"),
|
|
||||||
{
|
|
||||||
name: "config-eslint/nextjs",
|
|
||||||
settings: {
|
|
||||||
tailwindcss: {
|
|
||||||
callees: ["classNames", "cva"],
|
|
||||||
},
|
|
||||||
react: {
|
|
||||||
version: "detect",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
rules: {
|
|
||||||
"@next/next/no-html-link-for-pages": "off",
|
|
||||||
"@next/next/no-img-element": "off",
|
|
||||||
"react/self-closing-comp": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
component: true,
|
|
||||||
html: true,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
"react/void-dom-elements-no-children": "error",
|
|
||||||
"react/jsx-boolean-value": "error",
|
|
||||||
"no-restricted-imports": [
|
|
||||||
"error",
|
|
||||||
{
|
|
||||||
paths: [
|
|
||||||
{
|
|
||||||
name: "next/link",
|
|
||||||
message: "Please import from `@repo/i18n/routing` instead.",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
name: "next/navigation",
|
|
||||||
importNames: [
|
|
||||||
"redirect",
|
|
||||||
"permanentRedirect",
|
|
||||||
"useRouter",
|
|
||||||
"usePathname",
|
|
||||||
],
|
|
||||||
message: "Please import from `@repo/i18n/routing` instead.",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
)
|
|
||||||
@@ -1,32 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@repo/config-eslint",
|
|
||||||
"version": "0.0.0-develop",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
".": {
|
|
||||||
"types": "./index.d.ts",
|
|
||||||
"import": "./eslint.config.js",
|
|
||||||
"require": "./eslint.config.js",
|
|
||||||
"default": "./eslint.config.js"
|
|
||||||
},
|
|
||||||
"./nextjs": {
|
|
||||||
"types": "./index.d.ts",
|
|
||||||
"import": "./nextjs/eslint.config.js",
|
|
||||||
"require": "./nextjs/eslint.config.js",
|
|
||||||
"default": "./nextjs/eslint.config.js"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@eslint/eslintrc": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"eslint-config-conventions": "catalog:",
|
|
||||||
"eslint-plugin-promise": "catalog:",
|
|
||||||
"eslint-plugin-unicorn": "catalog:",
|
|
||||||
"eslint-config-next": "catalog:",
|
|
||||||
"eslint-plugin-import-x": "catalog:",
|
|
||||||
"typescript": "catalog:",
|
|
||||||
"globals": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import config from "@repo/config-eslint"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...config, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
4
configs/config-tailwind/index.d.ts
vendored
@@ -1,3 +1,5 @@
|
|||||||
import type { Config } from "tailwindcss"
|
import type { Config } from "tailwindcss"
|
||||||
|
|
||||||
export default Config
|
declare const config: Config
|
||||||
|
|
||||||
|
export default config
|
||||||
|
|||||||
@@ -21,13 +21,10 @@
|
|||||||
"tailwind-merge": "catalog:"
|
"tailwind-merge": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
"@repo/config-typescript": "workspace:*",
|
||||||
|
"@tailwindcss/postcss": "catalog:",
|
||||||
"@tailwindcss/typography": "catalog:",
|
"@tailwindcss/typography": "catalog:",
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"postcss": "catalog:",
|
"postcss": "catalog:",
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:"
|
||||||
"@tailwindcss/postcss": "catalog:"
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@config "./tailwind.config.js";
|
@config "./tailwind.config.js";
|
||||||
|
|
||||||
@source "../../packages/ui";
|
@source "../../apps/website/app";
|
||||||
@source "../../packages/blog";
|
@source "../../apps/website/components";
|
||||||
@source "../../apps/website";
|
|
||||||
@source "../../apps/storybook/.storybook";
|
@source "../../apps/storybook/.storybook";
|
||||||
|
@source "../../packages/ui/src";
|
||||||
|
@source "../../packages/blog/src";
|
||||||
|
|
||||||
@import "@fontsource/montserrat/400.css" layer(base);
|
@import "@fontsource/montserrat/400.css";
|
||||||
@import "@fontsource/montserrat/500.css" layer(base);
|
@import "@fontsource/montserrat/500.css";
|
||||||
@import "@fontsource/montserrat/600.css" layer(base);
|
@import "@fontsource/montserrat/600.css";
|
||||||
@import "@fontsource/montserrat/700.css" layer(base);
|
@import "@fontsource/montserrat/700.css";
|
||||||
@import "@fontsource/montserrat/800.css" layer(base);
|
@import "@fontsource/montserrat/800.css";
|
||||||
|
|
||||||
@layer base {
|
@layer base {
|
||||||
[type="search"]::-webkit-search-decoration,
|
[type="search"]::-webkit-search-decoration,
|
||||||
@@ -47,7 +48,7 @@
|
|||||||
|
|
||||||
q,
|
q,
|
||||||
blockquote {
|
blockquote {
|
||||||
@apply italic tracking-wider;
|
@apply tracking-wider italic;
|
||||||
}
|
}
|
||||||
blockquote {
|
blockquote {
|
||||||
@apply border-gray-lighter border-l-4 pl-3 italic;
|
@apply border-gray-lighter border-l-4 pl-3 italic;
|
||||||
@@ -73,6 +74,10 @@
|
|||||||
@apply font-semibold italic;
|
@apply font-semibold italic;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 87%;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
body {
|
body {
|
||||||
@@ -172,9 +177,8 @@ code .line::before {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.curriculum-vitae {
|
.curriculum-vitae {
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
color: #333;
|
||||||
font-family: Arial, sans-serif;
|
font-family: Montserrat, sans-serif;
|
||||||
|
|
||||||
hr {
|
hr {
|
||||||
margin-top: 15px;
|
margin-top: 15px;
|
||||||
|
|||||||
@@ -17,9 +17,10 @@
|
|||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"allowImportingTsExtensions": true,
|
"allowImportingTsExtensions": true,
|
||||||
|
"allowArbitraryExtensions": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"jsx": "preserve",
|
"jsx": "preserve",
|
||||||
"incremental": true,
|
"incremental": false,
|
||||||
"noEmit": true,
|
"noEmit": true,
|
||||||
|
|
||||||
"target": "ESNext",
|
"target": "ESNext",
|
||||||
|
|||||||
30
package.json
@@ -3,30 +3,34 @@
|
|||||||
"version": "0.0.0-develop",
|
"version": "0.0.0-develop",
|
||||||
"private": true,
|
"private": true,
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
|
||||||
"engines": {
|
|
||||||
"node": ">=24.0.0"
|
|
||||||
},
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "turbo run dev --parallel",
|
"dev": "turbo run dev --parallel",
|
||||||
"start": "turbo run start --parallel",
|
"start": "turbo run start --parallel",
|
||||||
"build": "turbo run build",
|
|
||||||
"test": "turbo run test",
|
|
||||||
"lint:editorconfig": "editorconfig-checker",
|
"lint:editorconfig": "editorconfig-checker",
|
||||||
"lint:markdown": "markdownlint-cli2",
|
"lint:markdown": "markdownlint-cli2",
|
||||||
|
"lint:turbo": "turbo boundaries",
|
||||||
"lint:typescript": "turbo run lint:typescript",
|
"lint:typescript": "turbo run lint:typescript",
|
||||||
"lint:eslint": "turbo run lint:eslint",
|
"lint:oxlint": "turbo run typegen && oxlint . --type-aware --type-check --deny-warnings --report-unused-disable-directives",
|
||||||
"lint:prettier": "prettier . --check"
|
"lint:oxfmt": "oxfmt . --check",
|
||||||
|
"oxfmt": "oxfmt .",
|
||||||
|
"test": "turbo run test",
|
||||||
|
"build": "turbo run build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"editorconfig-checker": "catalog:",
|
"editorconfig-checker": "catalog:",
|
||||||
"playwright": "catalog:",
|
"eslint-config-conventions": "catalog:",
|
||||||
"prettier": "catalog:",
|
|
||||||
"prettier-plugin-tailwindcss": "catalog:",
|
|
||||||
"markdownlint-cli2": "catalog:",
|
|
||||||
"markdownlint": "catalog:",
|
"markdownlint": "catalog:",
|
||||||
|
"markdownlint-cli2": "catalog:",
|
||||||
"markdownlint-rule-relative-links": "catalog:",
|
"markdownlint-rule-relative-links": "catalog:",
|
||||||
|
"oxfmt": "catalog:",
|
||||||
|
"oxlint": "catalog:",
|
||||||
|
"oxlint-tsgolint": "catalog:",
|
||||||
|
"playwright": "catalog:",
|
||||||
"turbo": "catalog:",
|
"turbo": "catalog:",
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=24.0.0"
|
||||||
|
},
|
||||||
|
"packageManager": "pnpm@10.29.3+sha512.498e1fb4cca5aa06c1dcf2611e6fafc50972ffe7189998c409e90de74566444298ffe43e6cd2acdc775ba1aa7cc5e092a8b7054c811ba8c5770f84693d33d2dc"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import configNextjs from "@repo/config-eslint/nextjs"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...configNextjs, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -9,48 +9,42 @@
|
|||||||
"./BlogPostUI": "./src/BlogPostUI.tsx"
|
"./BlogPostUI": "./src/BlogPostUI.tsx"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:eslint": "eslint src --max-warnings 0",
|
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
"@giscus/react": "catalog:",
|
||||||
|
"@mdx-js/mdx": "catalog:",
|
||||||
"@repo/config-tailwind": "workspace:*",
|
"@repo/config-tailwind": "workspace:*",
|
||||||
"@repo/utils": "workspace:*",
|
|
||||||
"@repo/i18n": "workspace:*",
|
"@repo/i18n": "workspace:*",
|
||||||
"@repo/ui": "workspace:*",
|
"@repo/ui": "workspace:*",
|
||||||
"@repo/react-hooks": "workspace:*",
|
"@repo/utils": "workspace:*",
|
||||||
"@giscus/react": "catalog:",
|
|
||||||
"@shikijs/rehype": "catalog:",
|
"@shikijs/rehype": "catalog:",
|
||||||
"@mdx-js/mdx": "catalog:",
|
|
||||||
"gray-matter": "catalog:",
|
"gray-matter": "catalog:",
|
||||||
"katex": "catalog:",
|
"katex": "catalog:",
|
||||||
|
"next": "catalog:",
|
||||||
|
"next-intl": "catalog:",
|
||||||
|
"next-mdx-remote": "catalog:",
|
||||||
|
"react": "catalog:",
|
||||||
|
"react-dom": "catalog:",
|
||||||
|
"react-icons": "catalog:",
|
||||||
"rehype-katex": "catalog:",
|
"rehype-katex": "catalog:",
|
||||||
"rehype-raw": "catalog:",
|
"rehype-raw": "catalog:",
|
||||||
"rehype-slug": "catalog:",
|
"rehype-slug": "catalog:",
|
||||||
"remark-gfm": "catalog:",
|
"remark-gfm": "catalog:",
|
||||||
"remark-math": "catalog:",
|
"remark-math": "catalog:",
|
||||||
"shiki": "catalog:",
|
"shiki": "catalog:"
|
||||||
"next": "catalog:",
|
|
||||||
"next-mdx-remote": "catalog:",
|
|
||||||
"next-intl": "catalog:",
|
|
||||||
"react": "catalog:",
|
|
||||||
"react-dom": "catalog:",
|
|
||||||
"react-icons": "catalog:"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
"@repo/config-typescript": "workspace:*",
|
||||||
|
"@storybook/nextjs": "catalog:",
|
||||||
|
"@tailwindcss/postcss": "catalog:",
|
||||||
|
"@total-typescript/ts-reset": "catalog:",
|
||||||
"@types/node": "catalog:",
|
"@types/node": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
|
||||||
"@storybook/blocks": "catalog:",
|
|
||||||
"@storybook/react": "catalog:",
|
|
||||||
"@storybook/test": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"postcss": "catalog:",
|
"postcss": "catalog:",
|
||||||
|
"storybook": "catalog:",
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:",
|
||||||
"@tailwindcss/postcss": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -173,10 +173,7 @@ We have to keep it as simple as possible, not to implement features that are not
|
|||||||
import fs from "node:fs"
|
import fs from "node:fs"
|
||||||
import path from "node:path"
|
import path from "node:path"
|
||||||
|
|
||||||
const createFile = async (
|
const createFile = async (name: string, isTemporary: boolean = false): Promise<void> => {
|
||||||
name: string,
|
|
||||||
isTemporary: boolean = false,
|
|
||||||
): Promise<void> => {
|
|
||||||
if (isTemporary) {
|
if (isTemporary) {
|
||||||
return await fs.promises.writeFile(path.join("temporary", name), "")
|
return await fs.promises.writeFile(path.join("temporary", name), "")
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Keep in mind that I will not translate the posts in French, all the posts will b
|
|||||||
|
|
||||||
I plan to publish new posts when I have something new to share. There's no schedule, so stay tuned!
|
I plan to publish new posts when I have something new to share. There's no schedule, so stay tuned!
|
||||||
|
|
||||||
To stay informed of new blog post and to ask questions, feel free to follow me on Twitter: [@theoludwig\_](https://twitter.com/theoludwig_).
|
To stay informed of new blog post and to ask questions, feel free to follow me on X/Twitter: [@theoludwig\_](https://twitter.com/theoludwig_).
|
||||||
|
|
||||||
## Project based learning
|
## Project based learning
|
||||||
|
|
||||||
|
|||||||
@@ -37,7 +37,7 @@ The idea is that a user can create an account to authenticate with an email addr
|
|||||||
|
|
||||||
## History
|
## History
|
||||||
|
|
||||||
The idea for the project has existed since May 13, 2020, symbolized by a [publication on Twitter](https://twitter.com/theoludwig_/status/1260638175246135296) by the creator: Théo LUDWIG.
|
The idea for the project has existed since May 13, 2020, symbolized by a [publication on X/Twitter](https://twitter.com/theoludwig_/status/1260638175246135296) by the creator: Théo LUDWIG.
|
||||||
|
|
||||||
The main goal is to put into **practice knowledge in web development** and computer science in general on a concrete project that can **easily evolve over time** where you can add many features.
|
The main goal is to put into **practice knowledge in web development** and computer science in general on a concrete project that can **easily evolve over time** where you can add many features.
|
||||||
|
|
||||||
|
|||||||
@@ -17,8 +17,7 @@ export const BLOG_POST_MOCK = {
|
|||||||
"\nHello, world! 👋\n\n## Introduction\n\nThis blog is here to document my journey of learning computer science.",
|
"\nHello, world! 👋\n\n## Introduction\n\nThis blog is here to document my journey of learning computer science.",
|
||||||
frontmatter: {
|
frontmatter: {
|
||||||
title: "👋 Hello, world!",
|
title: "👋 Hello, world!",
|
||||||
description:
|
description: "First post of the blog, introduction and explanation of how this blog is made.",
|
||||||
"First post of the blog, introduction and explanation of how this blog is made.",
|
|
||||||
isPublished: true,
|
isPublished: true,
|
||||||
publishedOn: "2022-02-20T08:00:18.758Z",
|
publishedOn: "2022-02-20T08:00:18.758Z",
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -14,10 +14,9 @@ import "katex/dist/katex.min.css"
|
|||||||
import { BlogPostComments } from "./BlogPostComments.tsx"
|
import { BlogPostComments } from "./BlogPostComments.tsx"
|
||||||
|
|
||||||
const Heading: React.FC<
|
const Heading: React.FC<
|
||||||
React.DetailedHTMLProps<
|
React.DetailedHTMLProps<React.HTMLAttributes<HTMLHeadingElement>, HTMLHeadingElement> & {
|
||||||
React.HTMLAttributes<HTMLHeadingElement>,
|
as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6"
|
||||||
HTMLHeadingElement
|
}
|
||||||
> & { as: "h1" | "h2" | "h3" | "h4" | "h5" | "h6" }
|
|
||||||
> = (props) => {
|
> = (props) => {
|
||||||
const { children, as, id = "", ...rest } = props
|
const { children, as, id = "", ...rest } = props
|
||||||
|
|
||||||
@@ -37,9 +36,7 @@ export interface BlogPostContentProps {
|
|||||||
content: string
|
content: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const BlogPostContent: React.FC<BlogPostContentProps> = async (
|
export const BlogPostContent: React.FC<BlogPostContentProps> = async (props) => {
|
||||||
props,
|
|
||||||
) => {
|
|
||||||
const { content } = props
|
const { content } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -90,14 +87,7 @@ export const BlogPostContent: React.FC<BlogPostContentProps> = async (
|
|||||||
const source = src.replace("../../../apps/website/public/", "/")
|
const source = src.replace("../../../apps/website/public/", "/")
|
||||||
return (
|
return (
|
||||||
<span className="flex flex-col items-center justify-center">
|
<span className="flex flex-col items-center justify-center">
|
||||||
<Image
|
<Image src={source} alt={alt} width={1000} height={1000} className="size-auto" />
|
||||||
src={source}
|
|
||||||
alt={alt}
|
|
||||||
width={1000}
|
|
||||||
height={1000}
|
|
||||||
quality={100}
|
|
||||||
className="size-auto"
|
|
||||||
/>
|
|
||||||
</span>
|
</span>
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
@@ -110,9 +100,7 @@ export const BlogPostContent: React.FC<BlogPostContentProps> = async (
|
|||||||
if (hrefString.startsWith("../posts/")) {
|
if (hrefString.startsWith("../posts/")) {
|
||||||
return (
|
return (
|
||||||
<Link
|
<Link
|
||||||
href={hrefString
|
href={hrefString.replace("../posts/", "/blog/").replace(".md", "")}
|
||||||
.replace("../posts/", "/blog/")
|
|
||||||
.replace(".md", "")}
|
|
||||||
{...rest}
|
{...rest}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -19,9 +19,7 @@ export const BlogPostUI: React.FC<BlogPostUIProps> = (props) => {
|
|||||||
<Typography variant="h2" as="h1">
|
<Typography variant="h2" as="h1">
|
||||||
{blogPost.frontmatter.title}
|
{blogPost.frontmatter.title}
|
||||||
</Typography>
|
</Typography>
|
||||||
<p className="mt-2">
|
<p className="mt-2">{getISODate(new Date(blogPost.frontmatter.publishedOn))}</p>
|
||||||
{getISODate(new Date(blogPost.frontmatter.publishedOn))}
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
<BlogPostContent content={blogPost.content} />
|
<BlogPostContent content={blogPost.content} />
|
||||||
</MainLayout>
|
</MainLayout>
|
||||||
|
|||||||
@@ -14,9 +14,7 @@ export const BlogPosts: React.FC<BlogPostsProps> = (props) => {
|
|||||||
return (
|
return (
|
||||||
<ul className="list-none">
|
<ul className="list-none">
|
||||||
{posts.map((post) => {
|
{posts.map((post) => {
|
||||||
const postPublishedOn = getISODate(
|
const postPublishedOn = getISODate(new Date(post.frontmatter.publishedOn))
|
||||||
new Date(post.frontmatter.publishedOn),
|
|
||||||
)
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<li key={post.slug}>
|
<li key={post.slug}>
|
||||||
|
|||||||
@@ -4,14 +4,7 @@ import path from "node:path"
|
|||||||
import matter from "gray-matter"
|
import matter from "gray-matter"
|
||||||
import type { BlogPost, FrontMatter } from "./BlogPost.tsx"
|
import type { BlogPost, FrontMatter } from "./BlogPost.tsx"
|
||||||
|
|
||||||
export const BLOG_POSTS_PATH = path.join(
|
export const BLOG_POSTS_PATH = path.join(process.cwd(), "..", "..", "packages", "blog", "posts")
|
||||||
process.cwd(),
|
|
||||||
"..",
|
|
||||||
"..",
|
|
||||||
"packages",
|
|
||||||
"blog",
|
|
||||||
"posts",
|
|
||||||
)
|
|
||||||
|
|
||||||
export const getBlogPosts = async (): Promise<BlogPost[]> => {
|
export const getBlogPosts = async (): Promise<BlogPost[]> => {
|
||||||
const blogPosts = await fs.promises.readdir(BLOG_POSTS_PATH)
|
const blogPosts = await fs.promises.readdir(BLOG_POSTS_PATH)
|
||||||
@@ -48,9 +41,7 @@ export const getBlogPosts = async (): Promise<BlogPost[]> => {
|
|||||||
return blogPostsSortedByPublicationDate
|
return blogPostsSortedByPublicationDate
|
||||||
}
|
}
|
||||||
|
|
||||||
export const getBlogPostBySlug = async (
|
export const getBlogPostBySlug = async (slug: string): Promise<BlogPost | undefined> => {
|
||||||
slug: string,
|
|
||||||
): Promise<BlogPost | undefined> => {
|
|
||||||
const blogPosts = await getBlogPosts()
|
const blogPosts = await getBlogPosts()
|
||||||
const blogPost = blogPosts.find((blogPost) => {
|
const blogPost = blogPosts.find((blogPost) => {
|
||||||
return blogPost.slug === slug
|
return blogPost.slug === slug
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react"
|
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||||
|
|
||||||
import { BLOG_POST_MOCK } from "../BlogPost.ts"
|
import { BLOG_POST_MOCK } from "../BlogPost.ts"
|
||||||
import { BlogPostUI as BlogPostUIComponent } from "../BlogPostUI.tsx"
|
import { BlogPostUI as BlogPostUIComponent } from "../BlogPostUI.tsx"
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react"
|
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||||
|
|
||||||
import { BLOG_POST_MOCK } from "../BlogPost.ts"
|
import { BLOG_POST_MOCK } from "../BlogPost.ts"
|
||||||
import { BlogPosts as BlogPostsComponent } from "../BlogPosts.tsx"
|
import { BlogPosts as BlogPostsComponent } from "../BlogPosts.tsx"
|
||||||
|
|||||||
@@ -2,10 +2,6 @@
|
|||||||
"extends": "@repo/config-typescript/tsconfig.json",
|
"extends": "@repo/config-typescript/tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
||||||
"types": [
|
"types": ["@total-typescript/ts-reset", "@types/node", "@repo/i18n/messages.d.ts"]
|
||||||
"@total-typescript/ts-reset",
|
|
||||||
"@types/node",
|
|
||||||
"@repo/i18n/messages.d.ts"
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import config from "@repo/config-eslint"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...config, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -10,7 +10,6 @@
|
|||||||
"./routing": "./src/routing.ts"
|
"./routing": "./src/routing.ts"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:eslint": "eslint src --max-warnings 0",
|
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
@@ -21,13 +20,10 @@
|
|||||||
"react-dom": "catalog:"
|
"react-dom": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
"@repo/config-typescript": "workspace:*",
|
||||||
|
"@total-typescript/ts-reset": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,11 @@ import { deepMerge } from "@repo/utils/objects"
|
|||||||
|
|
||||||
export default getRequestConfig(async ({ requestLocale }) => {
|
export default getRequestConfig(async ({ requestLocale }) => {
|
||||||
const requested = await requestLocale
|
const requested = await requestLocale
|
||||||
const locale = hasLocale(routing.locales, requested)
|
const locale = hasLocale(routing.locales, requested) ? requested : routing.defaultLocale
|
||||||
? requested
|
|
||||||
: routing.defaultLocale
|
|
||||||
|
|
||||||
const userMessages = (await import(`./translations/${locale}.json`)).default
|
const userMessages = (await import(`./translations/${locale}.json`)).default
|
||||||
const defaultMessages = (
|
const defaultMessages = (await import(`./translations/${LOCALE_DEFAULT}.json`)).default
|
||||||
await import(`./translations/${LOCALE_DEFAULT}.json`)
|
const messages = deepMerge<AbstractIntlMessages>(defaultMessages, userMessages)
|
||||||
).default
|
|
||||||
const messages = deepMerge<AbstractIntlMessages>(
|
|
||||||
defaultMessages,
|
|
||||||
userMessages,
|
|
||||||
)
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
locale,
|
locale,
|
||||||
|
|||||||
@@ -23,11 +23,5 @@ export const routing = defineRouting({
|
|||||||
localePrefix: LOCALE_PREFIX,
|
localePrefix: LOCALE_PREFIX,
|
||||||
})
|
})
|
||||||
|
|
||||||
export const {
|
export const { Link, redirect, usePathname, useRouter, getPathname, permanentRedirect } =
|
||||||
Link,
|
createNavigation(routing)
|
||||||
redirect,
|
|
||||||
usePathname,
|
|
||||||
useRouter,
|
|
||||||
getPathname,
|
|
||||||
permanentRedirect,
|
|
||||||
} = createNavigation(routing)
|
|
||||||
|
|||||||
@@ -88,7 +88,7 @@
|
|||||||
"position": "Stagiaire Développeur Web Full Stack",
|
"position": "Stagiaire Développeur Web Full Stack",
|
||||||
"startDate": "2023-04-11",
|
"startDate": "2023-04-11",
|
||||||
"endDate": "2023-07-26",
|
"endDate": "2023-07-26",
|
||||||
"duration": "4 mois",
|
"duration": "3 mois",
|
||||||
},
|
},
|
||||||
// {
|
// {
|
||||||
// "summary": "Agent administratif - Numérisation et archivage des plans électriques initialement sous format papier calque.",
|
// "summary": "Agent administratif - Numérisation et archivage des plans électriques initialement sous format papier calque.",
|
||||||
@@ -164,13 +164,7 @@
|
|||||||
"name": "Backend",
|
"name": "Backend",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"keywords": [
|
"keywords": ["GNU/Linux", "Arch Linux", "Visual Studio Code", "Git", "Docker"],
|
||||||
"GNU/Linux",
|
|
||||||
"Arch Linux",
|
|
||||||
"Visual Studio Code",
|
|
||||||
"Git",
|
|
||||||
"Docker",
|
|
||||||
],
|
|
||||||
"name": "Logiciels et outils",
|
"name": "Logiciels et outils",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"en-US": "English",
|
"en-US": "English",
|
||||||
"fr-FR": "French"
|
"fr-FR": "French"
|
||||||
},
|
},
|
||||||
|
"fr-FR-main": "French (primary language)",
|
||||||
"loading": "Loading...",
|
"loading": "Loading...",
|
||||||
"errors": {
|
"errors": {
|
||||||
"error": "Error",
|
"error": "Error",
|
||||||
@@ -24,7 +25,7 @@
|
|||||||
"description": "I constantly wonder how to improve our present, to make our future better, particularly thanks to the advancements in computer science. <br></br> My priority is to craft intuitive user experiences (UX), that meet the needs of the users in the most efficient way possible.",
|
"description": "I constantly wonder how to improve our present, to make our future better, particularly thanks to the advancements in computer science. <br></br> My priority is to craft intuitive user experiences (UX), that meet the needs of the users in the most efficient way possible.",
|
||||||
"title": "About"
|
"title": "About"
|
||||||
},
|
},
|
||||||
"description": "Developer Full Stack • Student",
|
"description": "Developer Full Stack • French Student",
|
||||||
"education": {
|
"education": {
|
||||||
"cnam": {
|
"cnam": {
|
||||||
"institution": "Conservatoire National des Arts et Métiers (CNAM), in Eckbolsheim - UIMM Alsace - ITII Alsace",
|
"institution": "Conservatoire National des Arts et Métiers (CNAM), in Eckbolsheim - UIMM Alsace - ITII Alsace",
|
||||||
@@ -41,10 +42,11 @@
|
|||||||
"institution": "IUT Robert Schuman in Illkirch-Graffenstaden",
|
"institution": "IUT Robert Schuman in Illkirch-Graffenstaden",
|
||||||
"study-type": "University Bachelor of Technology (BUT) Computer Science",
|
"study-type": "University Bachelor of Technology (BUT) Computer Science",
|
||||||
"years": {
|
"years": {
|
||||||
|
"title": "2021 - 2024",
|
||||||
"2021-2022": {
|
"2021-2022": {
|
||||||
"courses": {
|
"courses": {
|
||||||
"java": "Object Oriented Development in Java",
|
"java": "Object Oriented Development in Java",
|
||||||
"sql": "Relational database and SQL language",
|
"sql": "<strong>Relational database and SQL language</strong>",
|
||||||
"systems-c": "Systems programming in C (Memory allocation, Pointers, Structures)",
|
"systems-c": "Systems programming in C (Memory allocation, Pointers, Structures)",
|
||||||
"windows-forms": "Windows Forms (.NET Framework) Application Development in C#"
|
"windows-forms": "Windows Forms (.NET Framework) Application Development in C#"
|
||||||
},
|
},
|
||||||
@@ -53,10 +55,10 @@
|
|||||||
},
|
},
|
||||||
"2022-2023": {
|
"2022-2023": {
|
||||||
"courses": {
|
"courses": {
|
||||||
"clean-code": "Design Patterns and Principles (Maintainable and Reusable Code) in UML",
|
"clean-code": "Design Patterns and Principles (<strong>Maintainable and Reusable Code</strong>) in UML",
|
||||||
"sql-security": "Securing database access and PL/SQL",
|
"sql-security": "Securing database access and PL/SQL",
|
||||||
"systems-c": "Systems programming in C (Multi-Thread, Server/Client UDP/TCP)",
|
"systems-c": "Systems programming in <strong>C (Multi-Thread, Server/Client UDP/TCP)</strong>",
|
||||||
"tests": "Development Quality and Automated Testing",
|
"tests": "<strong>Development Quality and Automated Testing</strong>",
|
||||||
"web": "Web development with the Laravel framework in PHP"
|
"web": "Web development with the Laravel framework in PHP"
|
||||||
},
|
},
|
||||||
"description": "2nd year",
|
"description": "2nd year",
|
||||||
@@ -67,7 +69,7 @@
|
|||||||
"ci-cd": "Continuous Integration/Deployment (CI/CD) and Docker",
|
"ci-cd": "Continuous Integration/Deployment (CI/CD) and Docker",
|
||||||
"complexity-algorithms": "Theoretical and Practical Algorithmic Complexity in C++",
|
"complexity-algorithms": "Theoretical and Practical Algorithmic Complexity in C++",
|
||||||
"no-sql": "NoSQL database (Redis, MongoDB, Cassandra)",
|
"no-sql": "NoSQL database (Redis, MongoDB, Cassandra)",
|
||||||
"web": "Web development in Node.js and React.js"
|
"web": "<strong>Web development in Node.js and React.js</strong>"
|
||||||
},
|
},
|
||||||
"description": "3rd year",
|
"description": "3rd year",
|
||||||
"title": "2023 - 2024"
|
"title": "2023 - 2024"
|
||||||
@@ -87,19 +89,28 @@
|
|||||||
"title": "Studies"
|
"title": "Studies"
|
||||||
},
|
},
|
||||||
"interests": {
|
"interests": {
|
||||||
"high-tech": "Passionate about High-Tech",
|
"title": "Interests & hobbies",
|
||||||
"open-source": "Open-Source Enthusiast"
|
"fusey": "<link>Fusey (fusey.gg)</link>: website I'm developing for the game ARK that tracks the number of players connected to the servers in real time and has <strong>over ~5,000 visitors each month, ~100,000 members on Discord, and ~120,000 followers on X/Twitter</strong>.",
|
||||||
|
"open-source": "<strong>Open-Source Contributor</strong>: publishing and <strong>contributing to npm packages</strong> and <link-github>GitHub</link-github> repositories, notably with <link-markdownlint>markdownlint-rule-relative-links</link-markdownlint> (around 20,000 downloads per week), contributions to <link-leon>Leon</link-leon>, an open-source personal assistant, among other projects."
|
||||||
},
|
},
|
||||||
"work": {
|
"work": {
|
||||||
"ircad": {
|
"ircad": {
|
||||||
"duration": "4 ans",
|
"duration": "4 years",
|
||||||
"position": "Full Stack Web Developer Apprentice",
|
"position": "Full Stack Web Developer Apprentice",
|
||||||
"summary": "Development of WebSurg, a virtual university dedicated to medical-surgical training, in React.js/Next.js and API Platform with Symfony."
|
"tasks": {
|
||||||
|
"WebSurg": "Development of WebSurg, a virtual university dedicated to medical-surgical training, built with React.js/Next.js and API Platform with Symfony.",
|
||||||
|
"Figma": "<strong>Integration of Figma mockups</strong> for WebSurg, website dedicated to medical-surgical training.",
|
||||||
|
"IRCAD-Core": "IRCAD Core, an internal tool for managing medical training sessions and their requirements (anatomical models, medications, staff training, etc.).",
|
||||||
|
"feature-logs": "History and traceability of data modifications (what? who? when?) in IRCAD Core.",
|
||||||
|
"feature-permissions": "<strong>Advanced permissions system and OAuth2 authentication</strong>, with read, write, and delete access restricted for specific users of the IRCAD Core application.",
|
||||||
|
"feature-search": "<strong>Search engine with filters, sorting, and customizable display</strong> for each user of IRCAD Core.",
|
||||||
|
"feature-architecture": "IRCAD Core project architecture in <strong>TypeScript Monorepo</strong> with Turborepo, <strong>automatic deployment (CI/CD)</strong> and <strong>self-hosted internally with Docker Compose</strong>."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"numerize": {
|
"numerize": {
|
||||||
"duration": "4 months",
|
"duration": "3 months",
|
||||||
"position": "Full Stack Web Developer Intern",
|
"position": "Full Stack Web Developer Intern",
|
||||||
"summary": "Development of an DMS (Document Management System) tool in React.js, Laravel and GraphQL."
|
"summary": "Development of a DMS (Document Management System) tool in React.js, Laravel and GraphQL."
|
||||||
},
|
},
|
||||||
"title": "Work experiences"
|
"title": "Work experiences"
|
||||||
}
|
}
|
||||||
@@ -141,27 +152,34 @@
|
|||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"fusey": {
|
"fusey": {
|
||||||
"description": "ARK: Survival Ascended Wiki and Player stats tracker.",
|
"description": "A platform connecting creators and game server owners through automated, safe, and verifiable paid promotions.",
|
||||||
"title": "Fusey"
|
"title": "Fusey"
|
||||||
},
|
},
|
||||||
|
"fusey-ark": {
|
||||||
|
"description": "Website I'm developing for the game ARK that tracks the number of players connected to the servers in real time.",
|
||||||
|
"title": "Fusey ARK"
|
||||||
|
},
|
||||||
"carolo": {
|
"carolo": {
|
||||||
"description": "Strategy board game similar to chess which allows grandiose moves (only available in French).",
|
"description": "Strategy board game similar to chess which allows grandiose moves (only available in French).",
|
||||||
"title": "Carolo"
|
"title": "Carolo"
|
||||||
},
|
},
|
||||||
"leon": {
|
"title": "Projects"
|
||||||
"description": "Leon is your open-source personal assistant.",
|
|
||||||
"title": "Leon"
|
|
||||||
},
|
|
||||||
"title": "Portfolio"
|
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
|
"software-development": "Software Development",
|
||||||
|
"sys-admin": "SysAdmin",
|
||||||
|
"systems-programming": "Systems Programming",
|
||||||
|
|
||||||
"backend": "Backend",
|
"backend": "Backend",
|
||||||
"driving-license": "Driving license",
|
"driving-license": "Driving license",
|
||||||
"frontend": "Frontend",
|
"frontend": "Frontend",
|
||||||
"others": "Others",
|
"others": "Others",
|
||||||
"programming-languages": "Programming languages",
|
"programming-languages": "Programming languages",
|
||||||
"software-tools": "Software and tools",
|
"software-tools": "Software and tools",
|
||||||
"title": "Skills"
|
"title": "Skills",
|
||||||
|
|
||||||
|
"rust-advent-of-code": " (as hobby and <link-aoc>Advent Of Code</link-aoc>)",
|
||||||
|
"go-basics": " (basics and fundamentals)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
"en-US": "Anglais",
|
"en-US": "Anglais",
|
||||||
"fr-FR": "Français"
|
"fr-FR": "Français"
|
||||||
},
|
},
|
||||||
|
"fr-FR-main": "Français (langue principale)",
|
||||||
"loading": "Chargement...",
|
"loading": "Chargement...",
|
||||||
"errors": {
|
"errors": {
|
||||||
"error": "Erreur",
|
"error": "Erreur",
|
||||||
@@ -41,10 +42,11 @@
|
|||||||
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
||||||
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
||||||
"years": {
|
"years": {
|
||||||
|
"title": "2021 - 2024",
|
||||||
"2021-2022": {
|
"2021-2022": {
|
||||||
"courses": {
|
"courses": {
|
||||||
"java": "Développement Orientée Objet en Java",
|
"java": "Développement Orientée Objet en Java",
|
||||||
"sql": "Base de données relationnelles et langage SQL",
|
"sql": "<strong>Base de données relationnelles et langage SQL</strong>",
|
||||||
"systems-c": "Programmation systèmes en C (Allocation mémoire, Pointeurs, Structures)",
|
"systems-c": "Programmation systèmes en C (Allocation mémoire, Pointeurs, Structures)",
|
||||||
"windows-forms": "Développement d'application Windows Forms (.NET Framework) en C#"
|
"windows-forms": "Développement d'application Windows Forms (.NET Framework) en C#"
|
||||||
},
|
},
|
||||||
@@ -53,10 +55,10 @@
|
|||||||
},
|
},
|
||||||
"2022-2023": {
|
"2022-2023": {
|
||||||
"courses": {
|
"courses": {
|
||||||
"clean-code": "Patrons et Principes de conceptions (Code maintenable et réutilisable) en UML",
|
"clean-code": "Patrons et Principes de conceptions (<strong>Code maintenable et réutilisable</strong>) en UML",
|
||||||
"sql-security": "Sécurisation des accès à la base de données et PL/SQL",
|
"sql-security": "Sécurisation des accès à la base de données et PL/SQL",
|
||||||
"systems-c": "Programmation systèmes en C (Multi-Thread, Serveur/Client UDP/TCP)",
|
"systems-c": "Programmation systèmes en <strong>C (Multi-Thread, Serveur/Client UDP/TCP)</strong>",
|
||||||
"tests": "Qualité de développement et Tests automatisés",
|
"tests": "<strong>Qualité de développement et Tests automatisés</strong>",
|
||||||
"web": "Développement Web avec le framework Laravel en PHP"
|
"web": "Développement Web avec le framework Laravel en PHP"
|
||||||
},
|
},
|
||||||
"description": "2ème année",
|
"description": "2ème année",
|
||||||
@@ -67,7 +69,7 @@
|
|||||||
"ci-cd": "Intégration/Déploiement Continue et Docker",
|
"ci-cd": "Intégration/Déploiement Continue et Docker",
|
||||||
"complexity-algorithms": "Complexité Algorithmique Théorique et Pratique en C++",
|
"complexity-algorithms": "Complexité Algorithmique Théorique et Pratique en C++",
|
||||||
"no-sql": "Base de données NoSQL (Redis, MongoDB, Cassandra)",
|
"no-sql": "Base de données NoSQL (Redis, MongoDB, Cassandra)",
|
||||||
"web": "Développement Web en Node.js et React.js"
|
"web": "<strong>Développement Web en Node.js et React.js</strong>"
|
||||||
},
|
},
|
||||||
"description": "3ème année",
|
"description": "3ème année",
|
||||||
"title": "2023 - 2024"
|
"title": "2023 - 2024"
|
||||||
@@ -87,17 +89,26 @@
|
|||||||
"title": "Études"
|
"title": "Études"
|
||||||
},
|
},
|
||||||
"interests": {
|
"interests": {
|
||||||
"high-tech": "Passionné de High-Tech",
|
"title": "Intérêts & loisirs",
|
||||||
"open-source": "Enthousiaste de l'Open-Source"
|
"fusey": "<link>Fusey (fusey.gg)</link> : site web que je développe pour le jeu ARK qui permet de suivre en temps réel le nombre de joueurs connectés sur les serveurs et a plus de <strong>~5 000 visiteurs chaque mois, ~100 000 membres sur Discord et ~120 000 followers sur X/Twitter</strong>.",
|
||||||
|
"open-source": "<strong>Contributeur de l'Open-Source</strong> : publications et <strong>contributions sur npm</strong> et <link-github>GitHub</link-github> avec notamment <link-markdownlint>markdownlint-rule-relative-links</link-markdownlint> avec ~20 000 téléchargements par semaine, contributions à <link-leon>Leon</link-leon>, un assistant personnel open source, parmis d'autres projets."
|
||||||
},
|
},
|
||||||
"work": {
|
"work": {
|
||||||
"ircad": {
|
"ircad": {
|
||||||
"duration": "4 ans",
|
"duration": "4 ans",
|
||||||
"position": "Apprenti Développeur Web Full Stack",
|
"position": "Apprenti Développeur Web Full Stack",
|
||||||
"summary": "Développement de WebSurg, une université virtuelle consacrée à la formation médico-chirurgicale, en React.js/Next.js et API Platform avec Symfony."
|
"tasks": {
|
||||||
|
"WebSurg": "Développement de WebSurg, une université virtuelle consacrée à la formation médico-chirurgicale, en React.js/Next.js et API Platform avec Symfony.",
|
||||||
|
"Figma": "<strong>Intégration des maquettes Figma</strong> pour WebSurg, site web consacrée à la formation médico-chirurgicale.",
|
||||||
|
"IRCAD-Core": "IRCAD Core, outil interne de gestion des formations médicales et leurs besoins (modèles anatomiques, médicaments, formations du personnel, etc.).",
|
||||||
|
"feature-logs": "Historique et traçabilité des modifications des données (quoi? qui? quand?) dans IRCAD Core.",
|
||||||
|
"feature-permissions": "<strong>Système de permissions avancé</strong> et authentification OAuth2, avec accès en lecture, écriture et suppression restreint pour des utilisateurs spécifiques de l'application IRCAD Core.",
|
||||||
|
"feature-search": "<strong>Moteur de recherche avec filtres, tris et ordre d'affichage personnalisable</strong> pour IRCAD Core.",
|
||||||
|
"feature-architecture": "Architecture du projet IRCAD Core en <strong>Monorepo TypeScript</strong> avec Turborepo, <strong>déploiement automatique (CI/CD)</strong> et <strong>hébergé en interne avec Docker Compose</strong>."
|
||||||
|
}
|
||||||
},
|
},
|
||||||
"numerize": {
|
"numerize": {
|
||||||
"duration": "4 mois",
|
"duration": "3 mois",
|
||||||
"position": "Stagiaire Développeur Web Full Stack",
|
"position": "Stagiaire Développeur Web Full Stack",
|
||||||
"summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL."
|
"summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL."
|
||||||
},
|
},
|
||||||
@@ -141,27 +152,34 @@
|
|||||||
},
|
},
|
||||||
"portfolio": {
|
"portfolio": {
|
||||||
"fusey": {
|
"fusey": {
|
||||||
"description": "ARK: Survival Ascended Wiki et suivi des statistiques des joueurs.",
|
"description": "Une plateforme mettant en relation les créateurs de contenu (e.g: YouTube) et les propriétaires de serveurs de jeux pour des promotions sécurisées et fiables.",
|
||||||
"title": "Fusey"
|
"title": "Fusey"
|
||||||
},
|
},
|
||||||
|
"fusey-ark": {
|
||||||
|
"description": "Site web que je développe pour le jeu ARK qui permet de suivre en temps réel le nombre de joueurs connectés sur les serveurs.",
|
||||||
|
"title": "Fusey ARK"
|
||||||
|
},
|
||||||
"carolo": {
|
"carolo": {
|
||||||
"description": "Jeu de plateau stratégique similaire aux échecs qui permet des coups grandioses, reposant sur des enchaînements remarquables.",
|
"description": "Jeu de plateau stratégique similaire aux échecs qui permet des coups grandioses, reposant sur des enchaînements remarquables.",
|
||||||
"title": "Carolo"
|
"title": "Carolo"
|
||||||
},
|
},
|
||||||
"leon": {
|
"title": "Projets"
|
||||||
"description": "Leon est votre assistant personnel open source.",
|
|
||||||
"title": "Leon"
|
|
||||||
},
|
|
||||||
"title": "Portfolio"
|
|
||||||
},
|
},
|
||||||
"skills": {
|
"skills": {
|
||||||
|
"software-development": "Développement informatique",
|
||||||
|
"sys-admin": "SysAdmin",
|
||||||
|
"systems-programming": "Programmation Système",
|
||||||
|
|
||||||
"backend": "Backend",
|
"backend": "Backend",
|
||||||
"driving-license": "Permis B",
|
"driving-license": "Permis B",
|
||||||
"frontend": "Frontend",
|
"frontend": "Frontend",
|
||||||
"others": "Autres",
|
"others": "Autres",
|
||||||
"programming-languages": "Langages de programmation",
|
"programming-languages": "Langages de programmation",
|
||||||
"software-tools": "Logiciels et outils",
|
"software-tools": "Logiciels et outils",
|
||||||
"title": "Compétences"
|
"title": "Compétences",
|
||||||
|
|
||||||
|
"rust-advent-of-code": " (pendant temps libre et <link-aoc>Advent Of Code</link-aoc>)",
|
||||||
|
"go-basics": " (bases et fondamentaux)"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import configNextjs from "@repo/config-eslint/nextjs"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...configNextjs, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@repo/react-hooks",
|
|
||||||
"version": "0.0.0-develop",
|
|
||||||
"private": true,
|
|
||||||
"type": "module",
|
|
||||||
"exports": {
|
|
||||||
"./useBoolean": "./src/useBoolean.ts",
|
|
||||||
"./useIsMounted": "./src/useIsMounted.ts"
|
|
||||||
},
|
|
||||||
"scripts": {
|
|
||||||
"lint:eslint": "eslint src --max-warnings 0",
|
|
||||||
"lint:typescript": "tsc --noEmit"
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"react": "catalog:",
|
|
||||||
"react-dom": "catalog:"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
|
||||||
"@types/react": "catalog:",
|
|
||||||
"@types/react-dom": "catalog:",
|
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"playwright": "catalog:",
|
|
||||||
"typescript": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
import { useEffect, useState } from "react"
|
|
||||||
|
|
||||||
export interface UseIsMountedOutput {
|
|
||||||
isMounted: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useIsMounted = (): UseIsMountedOutput => {
|
|
||||||
const [isMounted, setIsMounted] = useState(false)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
setIsMounted(true)
|
|
||||||
}, [])
|
|
||||||
|
|
||||||
return { isMounted }
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@repo/config-typescript/tsconfig.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"lib": ["DOM", "DOM.Iterable", "ESNext"],
|
|
||||||
"types": ["@total-typescript/ts-reset"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
import typescriptESLint from "typescript-eslint"
|
|
||||||
import configNextjs from "@repo/config-eslint/nextjs"
|
|
||||||
|
|
||||||
export default typescriptESLint.config(...configNextjs, {
|
|
||||||
files: ["**/*.ts", "**/*.tsx"],
|
|
||||||
languageOptions: {
|
|
||||||
parser: typescriptESLint.parser,
|
|
||||||
parserOptions: {
|
|
||||||
projectService: true,
|
|
||||||
tsconfigRootDir: import.meta.dirname,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
})
|
|
||||||
@@ -23,15 +23,12 @@
|
|||||||
"./Layout/Section": "./src/Layout/Section/Section.tsx"
|
"./Layout/Section": "./src/Layout/Section/Section.tsx"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint:eslint": "eslint src --max-warnings 0",
|
|
||||||
"lint:typescript": "tsc --noEmit"
|
"lint:typescript": "tsc --noEmit"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@repo/config-tailwind": "workspace:*",
|
"@repo/config-tailwind": "workspace:*",
|
||||||
"@repo/utils": "workspace:*",
|
|
||||||
"@repo/i18n": "workspace:*",
|
"@repo/i18n": "workspace:*",
|
||||||
"@repo/react-hooks": "workspace:*",
|
"@repo/utils": "workspace:*",
|
||||||
"cva": "catalog:",
|
|
||||||
"next": "catalog:",
|
"next": "catalog:",
|
||||||
"next-intl": "catalog:",
|
"next-intl": "catalog:",
|
||||||
"next-themes": "catalog:",
|
"next-themes": "catalog:",
|
||||||
@@ -40,19 +37,15 @@
|
|||||||
"react-icons": "catalog:"
|
"react-icons": "catalog:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@repo/config-eslint": "workspace:*",
|
|
||||||
"@repo/config-typescript": "workspace:*",
|
"@repo/config-typescript": "workspace:*",
|
||||||
|
"@storybook/nextjs": "catalog:",
|
||||||
|
"@tailwindcss/postcss": "catalog:",
|
||||||
|
"@total-typescript/ts-reset": "catalog:",
|
||||||
"@types/react": "catalog:",
|
"@types/react": "catalog:",
|
||||||
"@types/react-dom": "catalog:",
|
"@types/react-dom": "catalog:",
|
||||||
"@total-typescript/ts-reset": "catalog:",
|
|
||||||
"@storybook/blocks": "catalog:",
|
|
||||||
"@storybook/react": "catalog:",
|
|
||||||
"@storybook/test": "catalog:",
|
|
||||||
"eslint": "catalog:",
|
|
||||||
"postcss": "catalog:",
|
"postcss": "catalog:",
|
||||||
|
"storybook": "catalog:",
|
||||||
"tailwindcss": "catalog:",
|
"tailwindcss": "catalog:",
|
||||||
"@tailwindcss/postcss": "catalog:",
|
|
||||||
"typescript-eslint": "catalog:",
|
|
||||||
"typescript": "catalog:"
|
"typescript": "catalog:"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react"
|
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||||
|
|
||||||
import { CurriculumVitae as CurriculumVitaeComponent } from "./CurriculumVitae.tsx"
|
import { CurriculumVitae as CurriculumVitaeComponent } from "./CurriculumVitae.tsx"
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,3 @@
|
|||||||
import { CurriculumVitaeAbout } from "./CurriculumVitaeAbout.tsx"
|
|
||||||
import { CurriculumVitaeEducation } from "./CurriculumVitaeEducation.tsx"
|
import { CurriculumVitaeEducation } from "./CurriculumVitaeEducation.tsx"
|
||||||
import { CurriculumVitaeInterests } from "./CurriculumVitaeInterests.tsx"
|
import { CurriculumVitaeInterests } from "./CurriculumVitaeInterests.tsx"
|
||||||
import { CurriculumVitaeProfile } from "./CurriculumVitaeProfile.tsx"
|
import { CurriculumVitaeProfile } from "./CurriculumVitaeProfile.tsx"
|
||||||
@@ -16,18 +15,14 @@ export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => {
|
|||||||
|
|
||||||
<div className="card background-card">
|
<div className="card background-card">
|
||||||
<div className="background-details">
|
<div className="background-details">
|
||||||
<CurriculumVitaeAbout />
|
<section className="flex justify-between">
|
||||||
|
|
||||||
<hr />
|
|
||||||
|
|
||||||
<section className="flex">
|
|
||||||
<CurriculumVitaeEducation />
|
<CurriculumVitaeEducation />
|
||||||
<CurriculumVitaeSkills />
|
<CurriculumVitaeSkills />
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
<section className="flex">
|
<section className="flex justify-between">
|
||||||
<CurriculumVitaeWork />
|
<CurriculumVitaeWork />
|
||||||
<CurriculumVitaeInterests />
|
<CurriculumVitaeInterests />
|
||||||
</section>
|
</section>
|
||||||
|
|||||||
@@ -1,25 +0,0 @@
|
|||||||
import { useTranslations } from "next-intl"
|
|
||||||
import { FaUser } from "react-icons/fa"
|
|
||||||
import { CurriculumVitaeSection } from "./CurriculumVitaeSection.tsx"
|
|
||||||
|
|
||||||
export interface CurriculumVitaeAboutProps {}
|
|
||||||
|
|
||||||
export const CurriculumVitaeAbout: React.FC<CurriculumVitaeAboutProps> = () => {
|
|
||||||
const t = useTranslations()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<CurriculumVitaeSection
|
|
||||||
id="about"
|
|
||||||
title={t("curriculum-vitae.about.title")}
|
|
||||||
icon={<FaUser size={24} />}
|
|
||||||
>
|
|
||||||
<p>
|
|
||||||
{t.rich("curriculum-vitae.about.description", {
|
|
||||||
br: () => {
|
|
||||||
return <br />
|
|
||||||
},
|
|
||||||
})}
|
|
||||||
</p>
|
|
||||||
</CurriculumVitaeSection>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,7 @@ import { CurriculumVitaeSection } from "./CurriculumVitaeSection.tsx"
|
|||||||
|
|
||||||
export interface CurriculumVitaeEducationProps {}
|
export interface CurriculumVitaeEducationProps {}
|
||||||
|
|
||||||
export const CurriculumVitaeEducation: React.FC<
|
export const CurriculumVitaeEducation: React.FC<CurriculumVitaeEducationProps> = () => {
|
||||||
CurriculumVitaeEducationProps
|
|
||||||
> = () => {
|
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
const educations = [
|
const educations = [
|
||||||
@@ -14,59 +12,76 @@ export const CurriculumVitaeEducation: React.FC<
|
|||||||
years: t("curriculum-vitae.education.cnam.years.2024-2027.title"),
|
years: t("curriculum-vitae.education.cnam.years.2024-2027.title"),
|
||||||
studyType: t("curriculum-vitae.education.cnam.study-type"),
|
studyType: t("curriculum-vitae.education.cnam.study-type"),
|
||||||
institution: t("curriculum-vitae.education.cnam.institution"),
|
institution: t("curriculum-vitae.education.cnam.institution"),
|
||||||
score: t("curriculum-vitae.education.cnam.years.2024-2027.description"),
|
// score: t("curriculum-vitae.education.cnam.years.2024-2027.description"),
|
||||||
courses: [],
|
courses: [],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
years: t("curriculum-vitae.education.iut.years.2023-2024.title"),
|
years: t("curriculum-vitae.education.iut.years.title"),
|
||||||
studyType: t("curriculum-vitae.education.iut.study-type"),
|
studyType: t("curriculum-vitae.education.iut.study-type"),
|
||||||
institution: t("curriculum-vitae.education.iut.institution"),
|
institution: t("curriculum-vitae.education.iut.institution"),
|
||||||
score: t("curriculum-vitae.education.iut.years.2023-2024.description"),
|
|
||||||
courses: [
|
courses: [
|
||||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.web"),
|
|
||||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.ci-cd"),
|
|
||||||
t(
|
|
||||||
"curriculum-vitae.education.iut.years.2023-2024.courses.complexity-algorithms",
|
|
||||||
),
|
|
||||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.no-sql"),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
years: t("curriculum-vitae.education.iut.years.2022-2023.title"),
|
year: t("curriculum-vitae.education.iut.years.2021-2022.description"),
|
||||||
studyType: t("curriculum-vitae.education.iut.study-type"),
|
title: t("curriculum-vitae.education.iut.years.2021-2022.title"),
|
||||||
institution: t("curriculum-vitae.education.iut.institution"),
|
|
||||||
score: t("curriculum-vitae.education.iut.years.2022-2023.description"),
|
|
||||||
courses: [
|
|
||||||
t("curriculum-vitae.education.iut.years.2022-2023.courses.web"),
|
|
||||||
t("curriculum-vitae.education.iut.years.2022-2023.courses.tests"),
|
|
||||||
t("curriculum-vitae.education.iut.years.2022-2023.courses.clean-code"),
|
|
||||||
t("curriculum-vitae.education.iut.years.2022-2023.courses.systems-c"),
|
|
||||||
t(
|
|
||||||
"curriculum-vitae.education.iut.years.2022-2023.courses.sql-security",
|
|
||||||
),
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
years: t("curriculum-vitae.education.iut.years.2021-2022.title"),
|
|
||||||
studyType: t("curriculum-vitae.education.iut.study-type"),
|
|
||||||
institution: t("curriculum-vitae.education.iut.institution"),
|
|
||||||
score: t("curriculum-vitae.education.iut.years.2021-2022.description"),
|
|
||||||
courses: [
|
courses: [
|
||||||
t("curriculum-vitae.education.iut.years.2021-2022.courses.java"),
|
t("curriculum-vitae.education.iut.years.2021-2022.courses.java"),
|
||||||
t("curriculum-vitae.education.iut.years.2021-2022.courses.systems-c"),
|
t("curriculum-vitae.education.iut.years.2021-2022.courses.systems-c"),
|
||||||
t(
|
// t(
|
||||||
"curriculum-vitae.education.iut.years.2021-2022.courses.windows-forms",
|
// "curriculum-vitae.education.iut.years.2021-2022.courses.windows-forms",
|
||||||
),
|
// ),
|
||||||
t("curriculum-vitae.education.iut.years.2021-2022.courses.sql"),
|
t.rich("curriculum-vitae.education.iut.years.2021-2022.courses.sql", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
years: t("curriculum-vitae.education.lycee.years.2019-2021.title"),
|
year: t("curriculum-vitae.education.iut.years.2022-2023.description"),
|
||||||
studyType: t("curriculum-vitae.education.lycee.study-type"),
|
title: t("curriculum-vitae.education.iut.years.2022-2023.title"),
|
||||||
institution: t("curriculum-vitae.education.lycee.institution"),
|
courses: [
|
||||||
score: t("curriculum-vitae.education.lycee.score"),
|
t("curriculum-vitae.education.iut.years.2022-2023.courses.web"),
|
||||||
courses: [],
|
t.rich("curriculum-vitae.education.iut.years.2022-2023.courses.tests", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
},
|
},
|
||||||
|
}),
|
||||||
|
t.rich("curriculum-vitae.education.iut.years.2022-2023.courses.clean-code", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
// t("curriculum-vitae.education.iut.years.2022-2023.courses.sql-security"),
|
||||||
|
t.rich("curriculum-vitae.education.iut.years.2022-2023.courses.systems-c", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
year: t("curriculum-vitae.education.iut.years.2023-2024.description"),
|
||||||
|
title: t("curriculum-vitae.education.iut.years.2023-2024.title"),
|
||||||
|
courses: [
|
||||||
|
t.rich("curriculum-vitae.education.iut.years.2023-2024.courses.web", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
t("curriculum-vitae.education.iut.years.2023-2024.courses.ci-cd"),
|
||||||
|
t("curriculum-vitae.education.iut.years.2023-2024.courses.complexity-algorithms"),
|
||||||
|
t("curriculum-vitae.education.iut.years.2023-2024.courses.no-sql"),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
// {
|
||||||
|
// years: t("curriculum-vitae.education.lycee.years.2019-2021.title"),
|
||||||
|
// studyType: t("curriculum-vitae.education.lycee.study-type"),
|
||||||
|
// institution: t("curriculum-vitae.education.lycee.institution"),
|
||||||
|
// score: t("curriculum-vitae.education.lycee.score"),
|
||||||
|
// courses: [],
|
||||||
|
// },
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -79,14 +94,17 @@ export const CurriculumVitaeEducation: React.FC<
|
|||||||
{educations.map((education) => {
|
{educations.map((education) => {
|
||||||
return (
|
return (
|
||||||
<li key={education.years} className="card card-nested">
|
<li key={education.years} className="card card-nested">
|
||||||
<div className="content">
|
|
||||||
<p className="relative m-0">
|
<p className="relative m-0">
|
||||||
<strong>{education.studyType}</strong>
|
<strong>{education.studyType}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{/* {education.score != null ? (
|
||||||
<p className="relative m-0">
|
<p className="relative m-0">
|
||||||
<strong>{education.score}</strong>
|
<strong>{education.score}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)} */}
|
||||||
|
|
||||||
<p className="text-muted m-0">{education.institution}</p>
|
<p className="text-muted m-0">{education.institution}</p>
|
||||||
|
|
||||||
@@ -95,19 +113,28 @@ export const CurriculumVitaeEducation: React.FC<
|
|||||||
</p>
|
</p>
|
||||||
|
|
||||||
{education.courses.length > 0 ? (
|
{education.courses.length > 0 ? (
|
||||||
|
<ul className="list-none">
|
||||||
|
{education.courses.map(({ year, title, courses }) => {
|
||||||
|
return (
|
||||||
|
<li key={year}>
|
||||||
|
<span className="font-medium">{year}</span>
|
||||||
|
<span> ({title})</span>
|
||||||
<ul
|
<ul
|
||||||
style={{
|
style={{
|
||||||
paddingInlineStart: 20,
|
paddingInlineStart: 20,
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
{education.courses.map((course) => {
|
{courses.map((course, index) => {
|
||||||
return <li key={course}>{course}</li>
|
return <li key={index}>{course}</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
</li>
|
||||||
|
)
|
||||||
})}
|
})}
|
||||||
</ul>
|
</ul>
|
||||||
) : (
|
) : (
|
||||||
<></>
|
<></>
|
||||||
)}
|
)}
|
||||||
</div>
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -4,29 +4,65 @@ import { CurriculumVitaeSection } from "./CurriculumVitaeSection.tsx"
|
|||||||
|
|
||||||
export interface CurriculumVitaeInterestsProps {}
|
export interface CurriculumVitaeInterestsProps {}
|
||||||
|
|
||||||
export const CurriculumVitaeInterests: React.FC<
|
export const CurriculumVitaeInterests: React.FC<CurriculumVitaeInterestsProps> = () => {
|
||||||
CurriculumVitaeInterestsProps
|
|
||||||
> = () => {
|
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
const interests = [
|
const interests = [
|
||||||
t("curriculum-vitae.interests.open-source"),
|
t.rich("curriculum-vitae.interests.open-source", {
|
||||||
t("curriculum-vitae.interests.high-tech"),
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
"link-github": (children) => {
|
||||||
|
return (
|
||||||
|
<a href="https://github.com/theoludwig" target="_blank" className="font-semibold">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"link-leon": (children) => {
|
||||||
|
return (
|
||||||
|
<a href="https://github.com/leon-ai/leon" target="_blank" className="font-semibold">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
"link-markdownlint": (children) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://www.npmjs.com/package/markdownlint-rule-relative-links"
|
||||||
|
target="_blank"
|
||||||
|
className="font-semibold"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
t.rich("curriculum-vitae.interests.fusey", {
|
||||||
|
link: (children) => {
|
||||||
|
return (
|
||||||
|
<a href="https://fusey.gg" target="_blank" className="font-semibold">
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
]
|
]
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<CurriculumVitaeSection
|
<CurriculumVitaeSection
|
||||||
id="interests"
|
id="interests"
|
||||||
title={t("home.interests.title")}
|
title={t("curriculum-vitae.interests.title")}
|
||||||
icon={<FaHeart size={24} />}
|
icon={<FaHeart size={24} />}
|
||||||
>
|
>
|
||||||
<ul className="list-unstyled m-0">
|
<ul className="list-unstyled m-0">
|
||||||
{interests.map((interest) => {
|
{interests.map((interest, index) => {
|
||||||
return (
|
return (
|
||||||
<li key={interest} className="card card-nested">
|
<li key={index} className="card card-nested max-w-2xl">
|
||||||
<p>
|
<p>{interest}</p>
|
||||||
<strong>{interest}</strong>
|
|
||||||
</p>
|
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -6,9 +6,7 @@ import { Locales } from "../Layout/Header/Locales/Locales.tsx"
|
|||||||
|
|
||||||
export interface CurriculumVitaeProfileProps {}
|
export interface CurriculumVitaeProfileProps {}
|
||||||
|
|
||||||
export const CurriculumVitaeProfile: React.FC<
|
export const CurriculumVitaeProfile: React.FC<CurriculumVitaeProfileProps> = () => {
|
||||||
CurriculumVitaeProfileProps
|
|
||||||
> = () => {
|
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@@ -21,16 +19,16 @@ export const CurriculumVitaeProfile: React.FC<
|
|||||||
<Image
|
<Image
|
||||||
className="mx-auto block"
|
className="mx-auto block"
|
||||||
alt={t("meta.title")}
|
alt={t("meta.title")}
|
||||||
src="/images/logo_background.webp"
|
src="/images/logo.webp"
|
||||||
width={800}
|
width={450}
|
||||||
height={800}
|
height={450}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div className="name-and-profession text-center">
|
<div className="name-and-profession text-center">
|
||||||
<h1 className="h3">
|
<h1 className="h3 my-1!">
|
||||||
<strong>{t("meta.title")}</strong>
|
<strong>{t("meta.title")}</strong>
|
||||||
</h1>
|
</h1>
|
||||||
<h2 className="text-muted h5">{t("curriculum-vitae.description")}</h2>
|
<h2 className="text-muted h5 font-semibold">{t("curriculum-vitae.description")}</h2>
|
||||||
<h2 className="text-muted h5">
|
<h2 className="text-muted h5">
|
||||||
<BirthDate />
|
<BirthDate />
|
||||||
</h2>
|
</h2>
|
||||||
@@ -39,18 +37,14 @@ export const CurriculumVitaeProfile: React.FC<
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex justify-center">
|
<div className="flex justify-center">
|
||||||
<div className="relative px-3">
|
<div className="relative px-3">
|
||||||
<span className="info">
|
<strong className="info font-bold">
|
||||||
<a className="link-disguise" href="mailto:contact@theoludwig.fr">
|
<a href="mailto:contact@theoludwig.fr">contact@theoludwig.fr</a>
|
||||||
contact@theoludwig.fr
|
</strong>
|
||||||
</a>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
<div className="detail">
|
<div className="detail">
|
||||||
<span className="info">
|
<strong className="info font-bold">
|
||||||
<Link className="link-disguise" href="/">
|
<Link href="/">https://theoludwig.fr</Link>
|
||||||
https://theoludwig.fr/
|
</strong>
|
||||||
</Link>
|
|
||||||
</span>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ export interface CurriculumVitaeSectionProps extends React.PropsWithChildren {
|
|||||||
title: string
|
title: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CurriculumVitaeSection: React.FC<CurriculumVitaeSectionProps> = (
|
export const CurriculumVitaeSection: React.FC<CurriculumVitaeSectionProps> = (props) => {
|
||||||
props,
|
|
||||||
) => {
|
|
||||||
const { id, icon, title, children } = props
|
const { id, icon, title, children } = props
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,16 +1,11 @@
|
|||||||
import { useTranslations } from "next-intl"
|
import { useTranslations } from "next-intl"
|
||||||
import { FaToolbox } from "react-icons/fa"
|
import { FaToolbox } from "react-icons/fa"
|
||||||
import {
|
import { SKILL_CATEGORIES, SKILL_NAMES_BY_CATEGORY } from "../Home/Skills/skills.ts"
|
||||||
SKILL_CATEGORIES,
|
|
||||||
SKILL_NAMES_BY_CATEGORY,
|
|
||||||
} from "../Home/Skills/skills.ts"
|
|
||||||
import { CurriculumVitaeSection } from "./CurriculumVitaeSection.tsx"
|
import { CurriculumVitaeSection } from "./CurriculumVitaeSection.tsx"
|
||||||
|
|
||||||
export interface CurriculumVitaeSkillsProps {}
|
export interface CurriculumVitaeSkillsProps {}
|
||||||
|
|
||||||
export const CurriculumVitaeSkills: React.FC<
|
export const CurriculumVitaeSkills: React.FC<CurriculumVitaeSkillsProps> = () => {
|
||||||
CurriculumVitaeSkillsProps
|
|
||||||
> = () => {
|
|
||||||
const t = useTranslations()
|
const t = useTranslations()
|
||||||
|
|
||||||
const skills = [
|
const skills = [
|
||||||
@@ -23,7 +18,7 @@ export const CurriculumVitaeSkills: React.FC<
|
|||||||
}),
|
}),
|
||||||
{
|
{
|
||||||
category: "others",
|
category: "others",
|
||||||
skillNames: [t("locales.en-US"), t("home.skills.driving-license")],
|
skillNames: [t("fr-FR-main"), t("locales.en-US"), t("home.skills.driving-license")],
|
||||||
},
|
},
|
||||||
] as const
|
] as const
|
||||||
|
|
||||||
@@ -45,6 +40,23 @@ export const CurriculumVitaeSkills: React.FC<
|
|||||||
return (
|
return (
|
||||||
<p key={skillName} className="label label-keyword">
|
<p key={skillName} className="label label-keyword">
|
||||||
{skillName}
|
{skillName}
|
||||||
|
{skillName === "Rust"
|
||||||
|
? t.rich("home.skills.rust-advent-of-code", {
|
||||||
|
"link-aoc": (children) => {
|
||||||
|
return (
|
||||||
|
<a
|
||||||
|
href="https://github.com/theoludwig/advent_of_code"
|
||||||
|
target="_blank"
|
||||||
|
className="no-underline"
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</a>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
})
|
||||||
|
: skillName === "Go"
|
||||||
|
? t("home.skills.go-basics")
|
||||||
|
: ""}
|
||||||
</p>
|
</p>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -9,13 +9,37 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
|||||||
|
|
||||||
const workExperiences = [
|
const workExperiences = [
|
||||||
{
|
{
|
||||||
summary: t("curriculum-vitae.work.ircad.summary"),
|
|
||||||
website: "https://ircad.fr/",
|
website: "https://ircad.fr/",
|
||||||
name: "IRCAD",
|
name: "IRCAD",
|
||||||
location: "1 Place de l'Hôpital, FR-67000 Strasbourg",
|
location: "1 Place de l'Hôpital, FR-67000 Strasbourg",
|
||||||
position: t("curriculum-vitae.work.ircad.position"),
|
position: t("curriculum-vitae.work.ircad.position"),
|
||||||
dates: "28/08/2023 - 31/08/2027",
|
dates: "28/08/2023 - 31/08/2027",
|
||||||
duration: t("curriculum-vitae.work.ircad.duration"),
|
duration: t("curriculum-vitae.work.ircad.duration"),
|
||||||
|
tasks: [
|
||||||
|
// t("curriculum-vitae.work.ircad.tasks.WebSurg"),
|
||||||
|
t.rich("curriculum-vitae.work.ircad.tasks.Figma", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
t("curriculum-vitae.work.ircad.tasks.IRCAD-Core"),
|
||||||
|
t("curriculum-vitae.work.ircad.tasks.feature-logs"),
|
||||||
|
t.rich("curriculum-vitae.work.ircad.tasks.feature-permissions", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
t.rich("curriculum-vitae.work.ircad.tasks.feature-search", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
t.rich("curriculum-vitae.work.ircad.tasks.feature-architecture", {
|
||||||
|
strong: (children) => {
|
||||||
|
return <strong>{children}</strong>
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
summary: t("curriculum-vitae.work.numerize.summary"),
|
summary: t("curriculum-vitae.work.numerize.summary"),
|
||||||
@@ -25,6 +49,7 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
|||||||
position: t("curriculum-vitae.work.numerize.position"),
|
position: t("curriculum-vitae.work.numerize.position"),
|
||||||
dates: "11/04/2023 - 26/07/2023",
|
dates: "11/04/2023 - 26/07/2023",
|
||||||
duration: t("curriculum-vitae.work.numerize.duration"),
|
duration: t("curriculum-vitae.work.numerize.duration"),
|
||||||
|
tasks: [],
|
||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -50,16 +75,36 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
|||||||
<strong>{workExperience.position}</strong>
|
<strong>{workExperience.position}</strong>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="text-muted">
|
<p className="text-muted m-0">
|
||||||
<small>
|
<small>
|
||||||
<span className="space-right">
|
<span className="space-right">
|
||||||
{workExperience.dates} ({workExperience.duration})
|
{workExperience.dates} ({workExperience.duration})
|
||||||
</span>
|
</span>
|
||||||
</small>
|
</small>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
|
{workExperience.tasks.length > 0 ? (
|
||||||
|
<ul
|
||||||
|
style={{
|
||||||
|
paddingInlineStart: 20,
|
||||||
|
}}
|
||||||
|
className="space-y-1"
|
||||||
|
>
|
||||||
|
{workExperience.tasks.map((task, index) => {
|
||||||
|
return <li key={index}>{task}</li>
|
||||||
|
})}
|
||||||
|
</ul>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{workExperience.summary != null ? (
|
||||||
<div className="mt-2">
|
<div className="mt-2">
|
||||||
<p>{workExperience.summary}</p>
|
<p>{workExperience.summary}</p>
|
||||||
</div>
|
</div>
|
||||||
|
) : (
|
||||||
|
<></>
|
||||||
|
)}
|
||||||
</li>
|
</li>
|
||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react"
|
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||||
import { expect, fn, userEvent, within } from "@storybook/test"
|
import { expect, fn, userEvent, within } from "storybook/test"
|
||||||
import { FaCheck } from "react-icons/fa"
|
import { FaCheck } from "react-icons/fa"
|
||||||
|
|
||||||
import type { ButtonLinkProps } from "./Button.tsx"
|
import type { ButtonLinkProps } from "./Button.tsx"
|
||||||
@@ -136,10 +136,7 @@ export const LinkWithIcons: Story = {
|
|||||||
<Button leftIcon={<FaCheck size={18} />} {...(args as ButtonLinkProps)}>
|
<Button leftIcon={<FaCheck size={18} />} {...(args as ButtonLinkProps)}>
|
||||||
Link Left Icon
|
Link Left Icon
|
||||||
</Button>
|
</Button>
|
||||||
<Button
|
<Button rightIcon={<FaCheck size={18} />} {...(args as ButtonLinkProps)}>
|
||||||
rightIcon={<FaCheck size={18} />}
|
|
||||||
{...(args as ButtonLinkProps)}
|
|
||||||
>
|
|
||||||
Link Right Icon
|
Link Right Icon
|
||||||
</Button>
|
</Button>
|
||||||
</ButtonContainer>
|
</ButtonContainer>
|
||||||
|
|||||||
@@ -1,32 +1,37 @@
|
|||||||
import { classNames } from "@repo/config-tailwind/classNames"
|
import { classNames } from "@repo/config-tailwind/classNames"
|
||||||
import { Link as NextLink } from "@repo/i18n/routing"
|
import { Link as NextLink } from "@repo/i18n/routing"
|
||||||
import type { VariantProps } from "cva"
|
|
||||||
import { cva } from "cva"
|
|
||||||
|
|
||||||
import { Spinner } from "../Spinner/Spinner.tsx"
|
import { Spinner } from "../Spinner/Spinner.tsx"
|
||||||
import { Ripple } from "./Ripple.tsx"
|
import { Ripple } from "./Ripple.tsx"
|
||||||
|
|
||||||
const buttonVariants = cva({
|
const BUTTON_VARIANTS = ["solid", "outline"] as const
|
||||||
base: "relative inline-flex items-center justify-center overflow-hidden rounded-md text-base font-semibold transition duration-150 ease-in-out focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
|
type ButtonVariant = (typeof BUTTON_VARIANTS)[number]
|
||||||
variants: {
|
|
||||||
variant: {
|
const BUTTON_SIZES = ["small", "medium", "large"] as const
|
||||||
solid: "bg-primary hover:bg-primary/80 text-white",
|
type ButtonSize = (typeof BUTTON_SIZES)[number]
|
||||||
outline:
|
|
||||||
"dark:border-primary-dark/60 dark:text-primary-dark dark:hover:border-primary-dark border-primary/60 text-primary hover:border-primary hover:bg-primary border bg-transparent hover:text-white",
|
const buttonVariants = (options?: { variant?: ButtonVariant; size?: ButtonSize }): string => {
|
||||||
},
|
const { variant = "solid", size = "medium" } = options ?? {}
|
||||||
size: {
|
return classNames(
|
||||||
small: "h-9 rounded-md px-3",
|
"relative inline-flex items-center justify-center overflow-hidden rounded-md text-base font-semibold transition duration-150 ease-in-out focus-visible:ring-2 focus-visible:ring-offset-2 focus-visible:outline-none disabled:cursor-not-allowed disabled:opacity-50",
|
||||||
medium: "h-10 px-4 py-2",
|
{
|
||||||
large: "h-11 rounded-md px-8",
|
"bg-primary text-white hover:bg-primary/80": variant === "solid",
|
||||||
},
|
|
||||||
},
|
"border border-primary/60 bg-transparent text-primary hover:border-primary hover:bg-primary hover:text-white dark:border-primary-dark/60 dark:text-primary-dark dark:hover:border-primary-dark":
|
||||||
defaultVariants: {
|
variant === "outline",
|
||||||
variant: "solid",
|
},
|
||||||
size: "medium",
|
{
|
||||||
},
|
"h-9 rounded-md px-3": size === "small",
|
||||||
})
|
"h-10 px-4 py-2": size === "medium",
|
||||||
|
"h-11 rounded-md px-8": size === "large",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface ButtonBaseProps {
|
||||||
|
variant?: ButtonVariant
|
||||||
|
size?: ButtonSize
|
||||||
|
|
||||||
interface ButtonBaseProps extends VariantProps<typeof buttonVariants> {
|
|
||||||
leftIcon?: React.ReactNode
|
leftIcon?: React.ReactNode
|
||||||
rightIcon?: React.ReactNode
|
rightIcon?: React.ReactNode
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
@@ -34,13 +39,10 @@ interface ButtonBaseProps extends VariantProps<typeof buttonVariants> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
interface ButtonElementProps extends React.ComponentPropsWithoutRef<"button"> {}
|
interface ButtonElementProps extends React.ComponentPropsWithoutRef<"button"> {}
|
||||||
interface LinkElementProps
|
interface LinkElementProps extends React.ComponentPropsWithoutRef<typeof NextLink> {}
|
||||||
extends React.ComponentPropsWithoutRef<typeof NextLink> {}
|
|
||||||
|
|
||||||
export type ButtonLinkProps = ButtonBaseProps &
|
export type ButtonLinkProps = ButtonBaseProps & LinkElementProps & { href: string }
|
||||||
LinkElementProps & { href: string }
|
export type ButtonButtonProps = ButtonBaseProps & ButtonElementProps & { href?: never }
|
||||||
export type ButtonButtonProps = ButtonBaseProps &
|
|
||||||
ButtonElementProps & { href?: never }
|
|
||||||
|
|
||||||
export type ButtonProps = ButtonButtonProps | ButtonLinkProps
|
export type ButtonProps = ButtonButtonProps | ButtonLinkProps
|
||||||
|
|
||||||
@@ -50,18 +52,13 @@ export type ButtonProps = ButtonButtonProps | ButtonLinkProps
|
|||||||
* @returns
|
* @returns
|
||||||
*/
|
*/
|
||||||
export const Button: React.FC<ButtonProps> = (props) => {
|
export const Button: React.FC<ButtonProps> = (props) => {
|
||||||
const rippleColor =
|
const rippleColor = props.variant === "outline" ? "rgb(30, 64, 175)" : "rgb(229, 231, 235)"
|
||||||
props.variant === "outline" ? "rgb(30, 64, 175)" : "rgb(229, 231, 235)"
|
|
||||||
|
|
||||||
if (typeof props.href === "string") {
|
if (typeof props.href === "string") {
|
||||||
const { variant, size, leftIcon, rightIcon, className, children, ...rest } =
|
const { variant, size, leftIcon, rightIcon, className, children, ...rest } = props
|
||||||
props
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<NextLink
|
<NextLink className={classNames(buttonVariants({ variant, size }), className)} {...rest}>
|
||||||
className={classNames(buttonVariants({ variant, size }), className)}
|
|
||||||
{...rest}
|
|
||||||
>
|
|
||||||
{leftIcon != null ? <span className="mr-2">{leftIcon}</span> : null}
|
{leftIcon != null ? <span className="mr-2">{leftIcon}</span> : null}
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
{rightIcon != null ? <span className="ml-2">{rightIcon}</span> : null}
|
{rightIcon != null ? <span className="ml-2">{rightIcon}</span> : null}
|
||||||
@@ -97,13 +94,9 @@ export const Button: React.FC<ButtonProps> = (props) => {
|
|||||||
disabled={isDisabled}
|
disabled={isDisabled}
|
||||||
{...rest}
|
{...rest}
|
||||||
>
|
>
|
||||||
{leftIconElement != null ? (
|
{leftIconElement != null ? <span className="mr-2">{leftIconElement}</span> : null}
|
||||||
<span className="mr-2">{leftIconElement}</span>
|
|
||||||
) : null}
|
|
||||||
<span>{children}</span>
|
<span>{children}</span>
|
||||||
{rightIcon != null && !isLoading ? (
|
{rightIcon != null && !isLoading ? <span className="ml-2">{rightIcon}</span> : null}
|
||||||
<span className="ml-2">{rightIcon}</span>
|
|
||||||
) : null}
|
|
||||||
|
|
||||||
<Ripple color={rippleColor} />
|
<Ripple color={rippleColor} />
|
||||||
</button>
|
</button>
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import type { Meta, StoryObj } from "@storybook/react"
|
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||||
|
|
||||||
import { Link } from "./Link.tsx"
|
import { Link } from "./Link.tsx"
|
||||||
|
|
||||||
|
|||||||