Compare commits
12 Commits
Author | SHA1 | Date | |
---|---|---|---|
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?
|
7
.github/workflows/chromatic.yml
vendored
@@ -11,18 +11,21 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: "ubuntu-latest"
|
||||
env:
|
||||
CI: "1"
|
||||
TZ: "Europe/Paris"
|
||||
DO_NOT_TRACK: "1"
|
||||
TURBO_TELEMETRY_DISABLED: "1"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
STORYBOOK_DISABLE_TELEMETRY: "1"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.2.2"
|
||||
- uses: "actions/checkout@v5.0.0"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: "pnpm/action-setup@v4.1.0"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.2.0"
|
||||
uses: "actions/setup-node@v4.4.0"
|
||||
with:
|
||||
node-version: "24.x"
|
||||
cache: "pnpm"
|
||||
|
14
.github/workflows/ci.yml
vendored
@@ -11,16 +11,19 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
runs-on: "ubuntu-latest"
|
||||
env:
|
||||
CI: "1"
|
||||
TZ: "Europe/Paris"
|
||||
DO_NOT_TRACK: "1"
|
||||
TURBO_TELEMETRY_DISABLED: "1"
|
||||
NEXT_TELEMETRY_DISABLED: "1"
|
||||
STORYBOOK_DISABLE_TELEMETRY: "1"
|
||||
steps:
|
||||
- uses: "actions/checkout@v4.2.2"
|
||||
- uses: "actions/checkout@v5.0.0"
|
||||
|
||||
- uses: "pnpm/action-setup@v4.1.0"
|
||||
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.2.0"
|
||||
uses: "actions/setup-node@v4.4.0"
|
||||
with:
|
||||
node-version: "24.x"
|
||||
cache: "pnpm"
|
||||
@@ -38,10 +41,3 @@ jobs:
|
||||
- run: "node --run lint:prettier"
|
||||
- run: "node --run test"
|
||||
- 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
|
||||
node_modules
|
||||
.npm
|
||||
.cache
|
||||
package-lock.json
|
||||
.pnpm-store
|
||||
.apt
|
||||
.pnp
|
||||
.pnp.js
|
||||
.yarn/install-state.gz
|
||||
@@ -15,12 +17,16 @@ coverage
|
||||
out/
|
||||
dist/
|
||||
build/
|
||||
.swc/
|
||||
.react-email/
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
*.bak
|
||||
.turbo
|
||||
bin/
|
||||
dump_*
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
@@ -30,6 +36,7 @@ yarn-error.log*
|
||||
# Storybook
|
||||
*storybook.log
|
||||
storybook-static
|
||||
/public
|
||||
|
||||
# IDEs and editors
|
||||
.idea
|
||||
@@ -42,6 +49,7 @@ storybook-static
|
||||
|
||||
# local env files
|
||||
.env
|
||||
.env.dev
|
||||
.env.production
|
||||
.env*.local
|
||||
|
||||
@@ -50,4 +58,5 @@ storybook-static
|
||||
|
||||
# typescript
|
||||
*.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 = {
|
||||
config: {
|
||||
@@ -11,9 +11,6 @@ const config = {
|
||||
globs: ["**/*.md"],
|
||||
ignores: ["**/node_modules"],
|
||||
customRules: [relativeLinksRule],
|
||||
markdownItFactory: () => {
|
||||
return markdownIt
|
||||
},
|
||||
}
|
||||
|
||||
export default config
|
||||
|
1
.vscode/extensions.json
vendored
@@ -5,7 +5,6 @@
|
||||
"dbaeumer.vscode-eslint",
|
||||
"davidanson.vscode-markdownlint",
|
||||
"bradlc.vscode-tailwindcss",
|
||||
"mikestead.dotenv",
|
||||
"antfu.pnpm-catalog-lens",
|
||||
"Lokalise.i18n-ally"
|
||||
]
|
||||
|
2
.vscode/react.code-snippets
vendored
@@ -20,7 +20,7 @@
|
||||
"scope": "typescriptreact",
|
||||
"prefix": "rfcs",
|
||||
"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\"",
|
||||
"",
|
||||
|
15
.vscode/settings.json
vendored
@@ -9,10 +9,19 @@
|
||||
"source.fixAll": "explicit",
|
||||
"source.organizeImports": "never"
|
||||
},
|
||||
"tailwindCSS.experimental.classRegex": [
|
||||
["cva\\(([^)]*)\\)", "[\"'`]([^\"'`]*).*?[\"'`]"],
|
||||
["cx\\(([^)]*)\\)", "(?:'|\"|`)([^']*)(?:'|\"|`)"]
|
||||
"eslint.workingDirectories": [
|
||||
{
|
||||
"mode": "auto"
|
||||
}
|
||||
],
|
||||
"files.associations": {
|
||||
"*.css": "tailwindcss"
|
||||
},
|
||||
"editor.quickSuggestions": {
|
||||
"strings": "on"
|
||||
},
|
||||
"tailwindCSS.classFunctions": ["classNames", "cva"],
|
||||
"tailwindCSS.experimental.configFile": "./configs/config-tailwind/styles.css",
|
||||
"i18n-ally.localesPaths": ["./packages/i18n/src/translations/"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": false,
|
||||
|
@@ -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**! 🎉
|
||||
|
||||
## 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.
|
||||
Thanks a lot for your interest in contributing to **theoludwig.fr**!
|
||||
|
||||
## Types of contributions
|
||||
|
||||
@@ -32,7 +28,7 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv
|
||||
### Prerequisites
|
||||
|
||||
- [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/) v10.15.1 [(`npm install --global corepack@0.34.0 && corepack enable`)](https://github.com/nodejs/corepack)
|
||||
- [Docker](https://www.docker.com/)
|
||||
|
||||
### Installation
|
||||
|
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.
|
35
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://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://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.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>
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
<hr />
|
||||
|
||||
## 📜 About
|
||||
## About
|
||||
|
||||
```json
|
||||
{
|
||||
@@ -27,28 +27,35 @@
|
||||
"nationality": "Alsace, France",
|
||||
"interests": ["Developer Full Stack", "Open-Source Enthusiast"],
|
||||
"skills": {
|
||||
"programmingLanguages": [
|
||||
"JavaScript/TypeScript",
|
||||
"Python",
|
||||
"C/C++",
|
||||
"PHP"
|
||||
"software-development": [
|
||||
"TypeScript",
|
||||
"React.js (+ Next.js)",
|
||||
"Tailwind CSS",
|
||||
"Node.js",
|
||||
"tRPC/oRPC",
|
||||
"PostgreSQL"
|
||||
],
|
||||
"frontend": ["HTML/CSS", "Tailwind CSS", "React.js/Next.js"],
|
||||
"backend": ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
|
||||
"tools": [
|
||||
"sys-admin": [
|
||||
"Docker",
|
||||
"Proxmox",
|
||||
"Caddy",
|
||||
"GitHub Actions",
|
||||
"GitLab CI/CD"
|
||||
],
|
||||
"software-tools": [
|
||||
"GNU/Linux",
|
||||
"Arch Linux",
|
||||
"Visual Studio Code",
|
||||
"Git",
|
||||
"Docker"
|
||||
]
|
||||
"Git"
|
||||
],
|
||||
"systems-programming": ["C/C++", "Rust", "Go"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
<hr />
|
||||
|
||||
## 📈 Statistics
|
||||
## 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" />
|
||||
|
@@ -14,11 +14,8 @@ const config: StorybookConfig = {
|
||||
],
|
||||
addons: [
|
||||
"@chromatic-com/storybook",
|
||||
"@storybook/addon-essentials",
|
||||
"@storybook/addon-storysource",
|
||||
"@storybook/addon-docs",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-interactions",
|
||||
"storybook-dark-mode",
|
||||
],
|
||||
framework: {
|
||||
name: "@storybook/nextjs",
|
||||
|
@@ -2,18 +2,23 @@ import "@repo/config-tailwind/styles.css"
|
||||
import "./storybook-css-overrides.css"
|
||||
import i18nMessages from "@repo/i18n/translations/en-US.json"
|
||||
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 { ThemeProvider as NextThemeProvider } from "next-themes"
|
||||
import React from "react"
|
||||
|
||||
const preview: Preview = {
|
||||
globals: {
|
||||
initialGlobals: {
|
||||
a11y: {
|
||||
manual: true,
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
a11y: {
|
||||
test: "error",
|
||||
},
|
||||
docs: {
|
||||
codePanel: true,
|
||||
},
|
||||
nextjs: {
|
||||
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
|
@@ -6,7 +6,7 @@
|
||||
"scripts": {
|
||||
"build": "storybook build",
|
||||
"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:dev": "start-server-and-test \"dev\" http://localhost:6006 \"test:storybook\"",
|
||||
"test:storybook": "test-storybook --testTimeout=60000 --maxWorkers=2",
|
||||
@@ -30,25 +30,18 @@
|
||||
"@repo/config-typescript": "workspace:*",
|
||||
"@chromatic-com/storybook": "catalog:",
|
||||
"@playwright/test": "catalog:",
|
||||
"@storybook/addon-essentials": "catalog:",
|
||||
"@storybook/addon-storysource": "catalog:",
|
||||
"@storybook/addon-docs": "catalog:",
|
||||
"@storybook/addon-a11y": "catalog:",
|
||||
"@storybook/addon-interactions": "catalog:",
|
||||
"@storybook/addon-themes": "catalog:",
|
||||
"@storybook/blocks": "catalog:",
|
||||
"@storybook/nextjs": "catalog:",
|
||||
"@storybook/react": "catalog:",
|
||||
"@storybook/test": "catalog:",
|
||||
"@storybook/test-runner": "catalog:",
|
||||
"@types/node": "catalog:",
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"axe-playwright": "catalog:",
|
||||
"chromatic": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"start-server-and-test": "catalog:",
|
||||
"storybook": "catalog:",
|
||||
"storybook-dark-mode": "catalog:",
|
||||
"postcss": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
"@tailwindcss/postcss": "catalog:",
|
||||
|
@@ -1,4 +1,9 @@
|
||||
import { Meta, Title, ColorPalette, ColorItem } from "@storybook/blocks"
|
||||
import {
|
||||
Meta,
|
||||
Title,
|
||||
ColorPalette,
|
||||
ColorItem,
|
||||
} from "@storybook/addon-docs/blocks"
|
||||
import tailwindConfig from "@repo/config-tailwind"
|
||||
|
||||
<Meta title="Design System/Colors" />
|
||||
|
@@ -1,7 +1,7 @@
|
||||
FROM node:24.0.0-slim AS node-pnpm
|
||||
FROM node:24.7.0-slim AS node-pnpm
|
||||
ENV PNPM_HOME="/pnpm"
|
||||
ENV PATH="$PNPM_HOME:$PATH"
|
||||
RUN npm install --global corepack@0.32.0 && corepack enable
|
||||
RUN npm install --global corepack@0.34.0 && corepack enable
|
||||
ENV TURBO_TELEMETRY_DISABLED=1
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV DO_NOT_TRACK=1
|
||||
@@ -9,7 +9,7 @@ WORKDIR /usr/src/app
|
||||
|
||||
FROM node-pnpm AS builder
|
||||
COPY ./ ./
|
||||
RUN pnpm install --global turbo@2.5.3
|
||||
RUN pnpm install --global turbo@2.5.6
|
||||
RUN turbo prune @repo/website --docker
|
||||
|
||||
FROM node-pnpm AS installer
|
||||
@@ -34,7 +34,7 @@ ENV IS_STANDALONE=true
|
||||
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 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 --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
|
||||
|
@@ -1,19 +1,23 @@
|
||||
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 { Header } from "@repo/ui/Layout/Header"
|
||||
import { ThemeProvider } from "@repo/ui/Layout/Header/SwitchTheme"
|
||||
import { VERSION } from "@repo/utils/constants"
|
||||
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 { children, params } = props
|
||||
|
||||
const { locale } = await params
|
||||
// Enable static rendering
|
||||
setRequestLocale(locale)
|
||||
setRequestLocale(locale as Locale)
|
||||
|
||||
return (
|
||||
<ThemeProvider>
|
||||
|
@@ -21,21 +21,21 @@ const HomePage: React.FC<HomePageProps> = async (props) => {
|
||||
<MainLayout>
|
||||
<About />
|
||||
|
||||
<RevealFade>
|
||||
<Interests />
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<Skills />
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<Portfolio />
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<Interests />
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<OpenSource />
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<Skills />
|
||||
</RevealFade>
|
||||
</MainLayout>
|
||||
)
|
||||
}
|
||||
|
@@ -1,11 +1,13 @@
|
||||
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 { setRequestLocale } from "next-intl/server"
|
||||
|
||||
interface CurriculumVitaeLayoutProps
|
||||
extends React.PropsWithChildren,
|
||||
LocaleProps {}
|
||||
interface CurriculumVitaeLayoutProps extends React.PropsWithChildren {
|
||||
params: Promise<{
|
||||
locale: string
|
||||
}>
|
||||
}
|
||||
|
||||
const CurriculumVitaeLayout: React.FC<CurriculumVitaeLayoutProps> = async (
|
||||
props,
|
||||
@@ -14,7 +16,7 @@ const CurriculumVitaeLayout: React.FC<CurriculumVitaeLayoutProps> = async (
|
||||
|
||||
const { locale } = await params
|
||||
// Enable static rendering
|
||||
setRequestLocale(locale)
|
||||
setRequestLocale(locale as Locale)
|
||||
|
||||
return <ThemeProvider forcedTheme="light">{children}</ThemeProvider>
|
||||
}
|
||||
|
@@ -1,5 +1,4 @@
|
||||
import "@repo/config-tailwind/styles.css"
|
||||
import type { LocaleProps } from "@repo/i18n/routing"
|
||||
import type { Locale } from "@repo/utils/constants"
|
||||
import { LOCALES } from "@repo/utils/constants"
|
||||
import type { Metadata, Viewport } from "next"
|
||||
@@ -19,9 +18,13 @@ export const viewport: Viewport = {
|
||||
|
||||
export const generateMetadata = async ({
|
||||
params,
|
||||
}: LocaleProps): Promise<Metadata> => {
|
||||
}: {
|
||||
params: Promise<{
|
||||
locale: string
|
||||
}>
|
||||
}): Promise<Metadata> => {
|
||||
const { locale } = await params
|
||||
const t = await getTranslations({ locale })
|
||||
const t = await getTranslations({ locale: locale as Locale })
|
||||
const title = t("meta.title")
|
||||
const description = `${title} - ${t("meta.description")}`
|
||||
const image = "/images/logo.webp"
|
||||
@@ -64,14 +67,18 @@ 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 { children, params } = props
|
||||
|
||||
const { locale } = await params
|
||||
// Enable static rendering
|
||||
setRequestLocale(locale)
|
||||
setRequestLocale(locale as Locale)
|
||||
|
||||
const messages = await getMessages()
|
||||
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptESLint.parser,
|
||||
|
@@ -21,6 +21,6 @@ export const config = {
|
||||
* - _next/image (image optimization files)
|
||||
* - favicon.ico (favicon file)
|
||||
*/
|
||||
"/((?!api|_next/static|_next/image|images|favicon.ico).*)",
|
||||
"/((?!api|_next/static|_next/image|images|favicon.ico|robots.txt).*)",
|
||||
],
|
||||
}
|
||||
|
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)
|
37
apps/website/next.config.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
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,
|
||||
},
|
||||
eslint: {
|
||||
ignoreDuringBuilds: true,
|
||||
},
|
||||
typescript: {
|
||||
ignoreBuildErrors: true,
|
||||
},
|
||||
}
|
||||
|
||||
const withNextIntl = createNextIntlPlugin({
|
||||
experimental: {
|
||||
createMessagesDeclaration: path.join(
|
||||
process.cwd(),
|
||||
"..",
|
||||
"..",
|
||||
"packages",
|
||||
"i18n",
|
||||
"src",
|
||||
"translations",
|
||||
`${LOCALE_DEFAULT}.json`,
|
||||
),
|
||||
},
|
||||
})
|
||||
|
||||
export default withNextIntl(nextConfig)
|
@@ -8,10 +8,11 @@
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "next dev --port 3000 --turbopack",
|
||||
"build": "next build",
|
||||
"build": "next build --turbopack",
|
||||
"start": "next start --port 3000",
|
||||
"typegen": "next typegen",
|
||||
"lint:eslint": "eslint . --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit"
|
||||
"lint:typescript": "next typegen && tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"@repo/blog": "workspace:*",
|
||||
|
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 |
@@ -14,5 +14,5 @@
|
||||
]
|
||||
},
|
||||
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
|
||||
"exclude": ["node_modules", ".next"]
|
||||
"exclude": ["node_modules"]
|
||||
}
|
||||
|
@@ -1,25 +1,75 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import nextPlugin from "@next/eslint-plugin-next"
|
||||
import configConventions from "eslint-config-conventions"
|
||||
import importX from "eslint-plugin-import-x"
|
||||
import importXPlugin from "eslint-plugin-import-x"
|
||||
import reactPlugin from "eslint-plugin-react"
|
||||
import reactHooksPlugin from "eslint-plugin-react-hooks"
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
|
||||
export default typescriptESLint.config(
|
||||
...configConventions,
|
||||
reactHooksPlugin.configs.recommended,
|
||||
reactPlugin.configs.flat.recommended,
|
||||
{
|
||||
ignores: [
|
||||
".next",
|
||||
"**/next.config.js",
|
||||
"**/next.config.ts",
|
||||
"**/eslint.config.js",
|
||||
"**/tailwind.config.js",
|
||||
"**/postcss.config.js",
|
||||
"**/kysely.config.ts",
|
||||
],
|
||||
},
|
||||
...configConventions,
|
||||
{
|
||||
name: "config-eslint",
|
||||
settings: {
|
||||
react: {
|
||||
version: "19.1.1",
|
||||
},
|
||||
},
|
||||
plugins: {
|
||||
"import-x": importX,
|
||||
"@next/next": nextPlugin,
|
||||
"import-x": importXPlugin,
|
||||
},
|
||||
rules: {
|
||||
...nextPlugin.configs.recommended.rules,
|
||||
...nextPlugin.configs["core-web-vitals"].rules,
|
||||
"@next/next/no-html-link-for-pages": "off",
|
||||
"@next/next/no-img-element": "off",
|
||||
|
||||
"react-hooks/react-compiler": "error",
|
||||
|
||||
"react/jsx-no-target-blank": "off",
|
||||
"react/no-unknown-property": "off",
|
||||
"react/react-in-jsx-scope": "off",
|
||||
"react/prop-types": "off",
|
||||
"react/self-closing-comp": [
|
||||
"error",
|
||||
{
|
||||
component: true,
|
||||
html: true,
|
||||
},
|
||||
],
|
||||
"react/void-dom-elements-no-children": "error",
|
||||
"react/jsx-boolean-value": "error",
|
||||
"react/iframe-missing-sandbox": "off",
|
||||
|
||||
"no-restricted-imports": [
|
||||
"error",
|
||||
{
|
||||
paths: [
|
||||
{
|
||||
name: "next/navigation",
|
||||
importNames: [
|
||||
"redirect",
|
||||
"permanentRedirect",
|
||||
"useRouter",
|
||||
"usePathname",
|
||||
],
|
||||
message: "Please import from `@repo/i18n/routing` instead.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
"import-x/extensions": [
|
||||
"error",
|
||||
"ignorePackages",
|
||||
|
@@ -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.",
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
)
|
@@ -9,23 +9,18 @@
|
||||
"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:",
|
||||
"@next/eslint-plugin-next": "catalog:",
|
||||
"eslint-plugin-react": "catalog:",
|
||||
"eslint-plugin-react-hooks": "catalog:",
|
||||
"typescript": "catalog:",
|
||||
"globals": "catalog:"
|
||||
}
|
||||
|
4
configs/config-tailwind/index.d.ts
vendored
@@ -1,3 +1,5 @@
|
||||
import type { Config } from "tailwindcss"
|
||||
|
||||
export default Config
|
||||
declare const config: Config
|
||||
|
||||
export default config
|
||||
|
@@ -1,10 +1,11 @@
|
||||
@import "tailwindcss";
|
||||
@config "./tailwind.config.js";
|
||||
|
||||
@source "../../packages/ui";
|
||||
@source "../../packages/blog";
|
||||
@source "../../apps/website";
|
||||
@source "../../apps/website/app";
|
||||
@source "../../apps/website/components";
|
||||
@source "../../apps/storybook/.storybook";
|
||||
@source "../../packages/ui/src";
|
||||
@source "../../packages/blog/src";
|
||||
|
||||
@import "@fontsource/montserrat/400.css" layer(base);
|
||||
@import "@fontsource/montserrat/500.css" layer(base);
|
||||
@@ -172,9 +173,8 @@ code .line::before {
|
||||
}
|
||||
|
||||
.curriculum-vitae {
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
font-family: Arial, sans-serif;
|
||||
font-family: Montserrat, sans-serif;
|
||||
|
||||
hr {
|
||||
margin-top: 15px;
|
||||
@@ -250,8 +250,8 @@ code .line::before {
|
||||
padding: 10px 0;
|
||||
}
|
||||
.profile-pic img {
|
||||
width: 100px;
|
||||
height: 100px;
|
||||
width: 80px;
|
||||
height: 80px;
|
||||
border-radius: 50%;
|
||||
vertical-align: middle;
|
||||
border: 0;
|
||||
|
@@ -19,7 +19,7 @@
|
||||
"allowImportingTsExtensions": true,
|
||||
"skipLibCheck": true,
|
||||
"jsx": "preserve",
|
||||
"incremental": true,
|
||||
"incremental": false,
|
||||
"noEmit": true,
|
||||
|
||||
"target": "ESNext",
|
||||
|
@@ -3,7 +3,7 @@
|
||||
"version": "0.0.0-develop",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@10.10.0+sha512.d615db246fe70f25dcfea6d8d73dee782ce23e2245e3c4f6f888249fb568149318637dca73c2c5c8ef2a4ca0d5657fb9567188bfab47f566d1ee6ce987815c39",
|
||||
"packageManager": "pnpm@10.15.1+sha512.34e538c329b5553014ca8e8f4535997f96180a1d0f614339357449935350d924e22f8614682191264ec33d1462ac21561aff97f6bb18065351c162c7e8f6de67",
|
||||
"engines": {
|
||||
"node": ">=24.0.0"
|
||||
},
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptESLint.parser,
|
||||
|
@@ -43,9 +43,8 @@
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"@storybook/blocks": "catalog:",
|
||||
"@storybook/react": "catalog:",
|
||||
"@storybook/test": "catalog:",
|
||||
"storybook": "catalog:",
|
||||
"@storybook/nextjs": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"postcss": "catalog:",
|
||||
"tailwindcss": "catalog:",
|
||||
|
@@ -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!
|
||||
|
||||
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
|
||||
|
||||
|
@@ -37,7 +37,7 @@ The idea is that a user can create an account to authenticate with an email addr
|
||||
|
||||
## 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.
|
||||
|
||||
|
@@ -95,7 +95,6 @@ export const BlogPostContent: React.FC<BlogPostContentProps> = async (
|
||||
alt={alt}
|
||||
width={1000}
|
||||
height={1000}
|
||||
quality={100}
|
||||
className="size-auto"
|
||||
/>
|
||||
</span>
|
||||
|
@@ -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 { 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 { BlogPosts as BlogPostsComponent } from "../BlogPosts.tsx"
|
||||
|
@@ -88,7 +88,7 @@
|
||||
"position": "Stagiaire Développeur Web Full Stack",
|
||||
"startDate": "2023-04-11",
|
||||
"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.",
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"institution": "IUT Robert Schuman in Illkirch-Graffenstaden",
|
||||
"study-type": "University Bachelor of Technology (BUT) Computer Science",
|
||||
"years": {
|
||||
"title": "2021 - 2024",
|
||||
"2021-2022": {
|
||||
"courses": {
|
||||
"java": "Object Oriented Development in Java",
|
||||
@@ -87,19 +88,28 @@
|
||||
"title": "Studies"
|
||||
},
|
||||
"interests": {
|
||||
"high-tech": "Passionate about High-Tech",
|
||||
"title": "Interests & hobbies",
|
||||
"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": "Open-Source Enthusiast"
|
||||
},
|
||||
"work": {
|
||||
"ircad": {
|
||||
"duration": "4 ans",
|
||||
"duration": "4 years",
|
||||
"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": "Integration of Figma mockups 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": "Advanced permissions system and OAuth2 authentication, with read, write, and delete access restricted for specific users of the IRCAD Core application.",
|
||||
"feature-search": "Search engine with filters, sorting, and customizable display for each user of IRCAD Core.",
|
||||
"feature-architecture": "IRCAD Core project architecture in TypeScript Monorepo with Turborepo, automatic deployment (CI/CD) and self-hosted internally with Docker Compose."
|
||||
}
|
||||
},
|
||||
"numerize": {
|
||||
"duration": "4 months",
|
||||
"duration": "3 months",
|
||||
"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"
|
||||
}
|
||||
@@ -141,7 +151,7 @@
|
||||
},
|
||||
"portfolio": {
|
||||
"fusey": {
|
||||
"description": "ARK: Survival Ascended Wiki and Player stats tracker.",
|
||||
"description": "Website I'm developing for the game ARK that tracks the number of players connected to the servers in real time and has over ~5,000 visitors each month, ~100,000 members on Discord, and ~120,000 followers on X/Twitter.",
|
||||
"title": "Fusey"
|
||||
},
|
||||
"carolo": {
|
||||
@@ -152,9 +162,13 @@
|
||||
"description": "Leon is your open-source personal assistant.",
|
||||
"title": "Leon"
|
||||
},
|
||||
"title": "Portfolio"
|
||||
"title": "Projects"
|
||||
},
|
||||
"skills": {
|
||||
"software-development": "Software Development",
|
||||
"sys-admin": "SysAdmin",
|
||||
"systems-programming": "Systems Programming",
|
||||
|
||||
"backend": "Backend",
|
||||
"driving-license": "Driving license",
|
||||
"frontend": "Frontend",
|
||||
|
@@ -41,6 +41,7 @@
|
||||
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
|
||||
"study-type": "Bachelor Universitaire de Technologie (BUT) Informatique",
|
||||
"years": {
|
||||
"title": "2021 - 2024",
|
||||
"2021-2022": {
|
||||
"courses": {
|
||||
"java": "Développement Orientée Objet en Java",
|
||||
@@ -87,17 +88,26 @@
|
||||
"title": "Études"
|
||||
},
|
||||
"interests": {
|
||||
"high-tech": "Passionné de High-Tech",
|
||||
"title": "Intérêts & loisirs",
|
||||
"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": "Enthousiaste de l'Open-Source"
|
||||
},
|
||||
"work": {
|
||||
"ircad": {
|
||||
"duration": "4 ans",
|
||||
"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": "Intégration des maquettes Figma 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": "Système de permissions avancé et authentification OAuth2, avec accès en lecture, écriture et suppression restreint pour des utilisateurs spécifiques de l'application IRCAD Core.",
|
||||
"feature-search": "Moteur de recherche avec filtres, tris et ordre d'affichage personnalisable pour IRCAD Core.",
|
||||
"feature-architecture": "Architecture du projet IRCAD Core en Monorepo TypeScript avec Turborepo, déploiement automatique (CI/CD) et hébergé en interne avec Docker Compose."
|
||||
}
|
||||
},
|
||||
"numerize": {
|
||||
"duration": "4 mois",
|
||||
"duration": "3 mois",
|
||||
"position": "Stagiaire Développeur Web Full Stack",
|
||||
"summary": "Développement d'un outil GED (Gestion Électronique de Documents) en React.js, Laravel et GraphQL."
|
||||
},
|
||||
@@ -141,7 +151,7 @@
|
||||
},
|
||||
"portfolio": {
|
||||
"fusey": {
|
||||
"description": "ARK: Survival Ascended Wiki et suivi des statistiques des joueurs.",
|
||||
"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 et a plus de ~5 000 visiteurs chaque mois, ~100 000 membres sur Discord et ~120 000 followers sur X/Twitter.",
|
||||
"title": "Fusey"
|
||||
},
|
||||
"carolo": {
|
||||
@@ -152,9 +162,13 @@
|
||||
"description": "Leon est votre assistant personnel open source.",
|
||||
"title": "Leon"
|
||||
},
|
||||
"title": "Portfolio"
|
||||
"title": "Projets"
|
||||
},
|
||||
"skills": {
|
||||
"software-development": "Développement informatique",
|
||||
"sys-admin": "SysAdmin",
|
||||
"systems-programming": "Programmation Système",
|
||||
|
||||
"backend": "Backend",
|
||||
"driving-license": "Permis B",
|
||||
"frontend": "Frontend",
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptESLint.parser,
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import typescriptESLint from "typescript-eslint"
|
||||
import configNextjs from "@repo/config-eslint/nextjs"
|
||||
import config from "@repo/config-eslint"
|
||||
|
||||
export default typescriptESLint.config(...configNextjs, {
|
||||
export default typescriptESLint.config(...config, {
|
||||
files: ["**/*.ts", "**/*.tsx"],
|
||||
languageOptions: {
|
||||
parser: typescriptESLint.parser,
|
||||
|
@@ -45,9 +45,8 @@
|
||||
"@types/react": "catalog:",
|
||||
"@types/react-dom": "catalog:",
|
||||
"@total-typescript/ts-reset": "catalog:",
|
||||
"@storybook/blocks": "catalog:",
|
||||
"@storybook/react": "catalog:",
|
||||
"@storybook/test": "catalog:",
|
||||
"storybook": "catalog:",
|
||||
"@storybook/nextjs": "catalog:",
|
||||
"eslint": "catalog:",
|
||||
"postcss": "catalog:",
|
||||
"tailwindcss": "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"
|
||||
|
||||
|
@@ -1,4 +1,3 @@
|
||||
import { CurriculumVitaeAbout } from "./CurriculumVitaeAbout.tsx"
|
||||
import { CurriculumVitaeEducation } from "./CurriculumVitaeEducation.tsx"
|
||||
import { CurriculumVitaeInterests } from "./CurriculumVitaeInterests.tsx"
|
||||
import { CurriculumVitaeProfile } from "./CurriculumVitaeProfile.tsx"
|
||||
@@ -16,18 +15,14 @@ export const CurriculumVitae: React.FC<CurriculumVitaeProps> = () => {
|
||||
|
||||
<div className="card background-card">
|
||||
<div className="background-details">
|
||||
<CurriculumVitaeAbout />
|
||||
|
||||
<hr />
|
||||
|
||||
<section className="flex">
|
||||
<section className="flex justify-between">
|
||||
<CurriculumVitaeEducation />
|
||||
<CurriculumVitaeSkills />
|
||||
</section>
|
||||
|
||||
<hr />
|
||||
|
||||
<section className="flex">
|
||||
<section className="flex justify-between">
|
||||
<CurriculumVitaeWork />
|
||||
<CurriculumVitaeInterests />
|
||||
</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>
|
||||
)
|
||||
}
|
@@ -14,14 +14,46 @@ export const CurriculumVitaeEducation: React.FC<
|
||||
years: t("curriculum-vitae.education.cnam.years.2024-2027.title"),
|
||||
studyType: t("curriculum-vitae.education.cnam.study-type"),
|
||||
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: [],
|
||||
},
|
||||
{
|
||||
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"),
|
||||
institution: t("curriculum-vitae.education.iut.institution"),
|
||||
score: t("curriculum-vitae.education.iut.years.2023-2024.description"),
|
||||
courses: [
|
||||
{
|
||||
year: t("curriculum-vitae.education.iut.years.2021-2022.description"),
|
||||
title: t("curriculum-vitae.education.iut.years.2021-2022.title"),
|
||||
courses: [
|
||||
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.windows-forms",
|
||||
// ),
|
||||
t("curriculum-vitae.education.iut.years.2021-2022.courses.sql"),
|
||||
],
|
||||
},
|
||||
{
|
||||
year: t("curriculum-vitae.education.iut.years.2022-2023.description"),
|
||||
title: t("curriculum-vitae.education.iut.years.2022-2023.title"),
|
||||
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.sql-security"),
|
||||
t(
|
||||
"curriculum-vitae.education.iut.years.2022-2023.courses.systems-c",
|
||||
),
|
||||
],
|
||||
},
|
||||
{
|
||||
year: t("curriculum-vitae.education.iut.years.2023-2024.description"),
|
||||
title: t("curriculum-vitae.education.iut.years.2023-2024.title"),
|
||||
courses: [
|
||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.web"),
|
||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.ci-cd"),
|
||||
@@ -31,42 +63,15 @@ export const CurriculumVitaeEducation: React.FC<
|
||||
t("curriculum-vitae.education.iut.years.2023-2024.courses.no-sql"),
|
||||
],
|
||||
},
|
||||
{
|
||||
years: t("curriculum-vitae.education.iut.years.2022-2023.title"),
|
||||
studyType: t("curriculum-vitae.education.iut.study-type"),
|
||||
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: [
|
||||
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.windows-forms",
|
||||
),
|
||||
t("curriculum-vitae.education.iut.years.2021-2022.courses.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: [],
|
||||
},
|
||||
// {
|
||||
// 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 (
|
||||
@@ -79,14 +84,17 @@ export const CurriculumVitaeEducation: React.FC<
|
||||
{educations.map((education) => {
|
||||
return (
|
||||
<li key={education.years} className="card card-nested">
|
||||
<div className="content">
|
||||
<p className="relative m-0">
|
||||
<strong>{education.studyType}</strong>
|
||||
</p>
|
||||
|
||||
{/* {education.score != null ? (
|
||||
<p className="relative m-0">
|
||||
<strong>{education.score}</strong>
|
||||
</p>
|
||||
) : (
|
||||
<></>
|
||||
)} */}
|
||||
|
||||
<p className="text-muted m-0">{education.institution}</p>
|
||||
|
||||
@@ -95,19 +103,28 @@ export const CurriculumVitaeEducation: React.FC<
|
||||
</p>
|
||||
|
||||
{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
|
||||
style={{
|
||||
paddingInlineStart: 20,
|
||||
}}
|
||||
>
|
||||
{education.courses.map((course) => {
|
||||
{courses.map((course) => {
|
||||
return <li key={course}>{course}</li>
|
||||
})}
|
||||
</ul>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</div>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
@@ -10,23 +10,34 @@ export const CurriculumVitaeInterests: React.FC<
|
||||
const t = useTranslations()
|
||||
|
||||
const interests = [
|
||||
t("curriculum-vitae.interests.open-source"),
|
||||
t("curriculum-vitae.interests.high-tech"),
|
||||
<strong key="open-source">
|
||||
{t("curriculum-vitae.interests.open-source")}
|
||||
</strong>,
|
||||
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 (
|
||||
<CurriculumVitaeSection
|
||||
id="interests"
|
||||
title={t("home.interests.title")}
|
||||
title={t("curriculum-vitae.interests.title")}
|
||||
icon={<FaHeart size={24} />}
|
||||
>
|
||||
<ul className="list-unstyled m-0">
|
||||
{interests.map((interest) => {
|
||||
{interests.map((interest, index) => {
|
||||
return (
|
||||
<li key={interest} className="card card-nested">
|
||||
<p>
|
||||
<strong>{interest}</strong>
|
||||
</p>
|
||||
<li key={index} className="card card-nested max-w-2xl">
|
||||
<p>{interest}</p>
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
@@ -22,12 +22,12 @@ export const CurriculumVitaeProfile: React.FC<
|
||||
className="mx-auto block"
|
||||
alt={t("meta.title")}
|
||||
src="/images/logo_background.webp"
|
||||
width={800}
|
||||
height={800}
|
||||
width={400}
|
||||
height={400}
|
||||
/>
|
||||
</div>
|
||||
<div className="name-and-profession text-center">
|
||||
<h1 className="h3">
|
||||
<h1 className="h3 my-1!">
|
||||
<strong>{t("meta.title")}</strong>
|
||||
</h1>
|
||||
<h2 className="text-muted h5">{t("curriculum-vitae.description")}</h2>
|
||||
@@ -39,18 +39,18 @@ export const CurriculumVitaeProfile: React.FC<
|
||||
</div>
|
||||
<div className="flex justify-center">
|
||||
<div className="relative px-3">
|
||||
<span className="info">
|
||||
<a className="link-disguise" href="mailto:contact@theoludwig.fr">
|
||||
<strong className="info">
|
||||
<a className="" href="mailto:contact@theoludwig.fr">
|
||||
contact@theoludwig.fr
|
||||
</a>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
<div className="detail">
|
||||
<span className="info">
|
||||
<Link className="link-disguise" href="/">
|
||||
https://theoludwig.fr/
|
||||
<strong className="info">
|
||||
<Link className="" href="/">
|
||||
https://theoludwig.fr
|
||||
</Link>
|
||||
</span>
|
||||
</strong>
|
||||
</div>
|
||||
</div>
|
||||
<hr />
|
||||
|
@@ -9,13 +9,21 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
||||
|
||||
const workExperiences = [
|
||||
{
|
||||
summary: t("curriculum-vitae.work.ircad.summary"),
|
||||
website: "https://ircad.fr/",
|
||||
name: "IRCAD",
|
||||
location: "1 Place de l'Hôpital, FR-67000 Strasbourg",
|
||||
position: t("curriculum-vitae.work.ircad.position"),
|
||||
dates: "28/08/2023 - 31/08/2027",
|
||||
duration: t("curriculum-vitae.work.ircad.duration"),
|
||||
tasks: [
|
||||
// t("curriculum-vitae.work.ircad.tasks.WebSurg"),
|
||||
t("curriculum-vitae.work.ircad.tasks.Figma"),
|
||||
t("curriculum-vitae.work.ircad.tasks.IRCAD-Core"),
|
||||
t("curriculum-vitae.work.ircad.tasks.feature-logs"),
|
||||
t("curriculum-vitae.work.ircad.tasks.feature-permissions"),
|
||||
t("curriculum-vitae.work.ircad.tasks.feature-search"),
|
||||
t("curriculum-vitae.work.ircad.tasks.feature-architecture"),
|
||||
],
|
||||
},
|
||||
{
|
||||
summary: t("curriculum-vitae.work.numerize.summary"),
|
||||
@@ -25,6 +33,7 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
||||
position: t("curriculum-vitae.work.numerize.position"),
|
||||
dates: "11/04/2023 - 26/07/2023",
|
||||
duration: t("curriculum-vitae.work.numerize.duration"),
|
||||
tasks: [],
|
||||
},
|
||||
]
|
||||
|
||||
@@ -50,16 +59,36 @@ export const CurriculumVitaeWork: React.FC<CurriculumVitaeWorkProps> = () => {
|
||||
<strong>{workExperience.position}</strong>
|
||||
</p>
|
||||
|
||||
<p className="text-muted">
|
||||
<p className="text-muted m-0">
|
||||
<small>
|
||||
<span className="space-right">
|
||||
{workExperience.dates} ({workExperience.duration})
|
||||
</span>
|
||||
</small>
|
||||
</p>
|
||||
|
||||
{workExperience.tasks.length > 0 ? (
|
||||
<ul
|
||||
style={{
|
||||
paddingInlineStart: 20,
|
||||
}}
|
||||
className="space-y-1"
|
||||
>
|
||||
{workExperience.tasks.map((task) => {
|
||||
return <li key={task}>{task}</li>
|
||||
})}
|
||||
</ul>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
|
||||
{workExperience.summary != null ? (
|
||||
<div className="mt-2">
|
||||
<p>{workExperience.summary}</p>
|
||||
</div>
|
||||
) : (
|
||||
<></>
|
||||
)}
|
||||
</li>
|
||||
)
|
||||
})}
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { expect, fn, userEvent, within } from "@storybook/test"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
import { expect, fn, userEvent, within } from "storybook/test"
|
||||
import { FaCheck } from "react-icons/fa"
|
||||
|
||||
import type { ButtonLinkProps } from "./Button.tsx"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Link } from "./Link.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Spinner } from "./Spinner.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import type { TypographyProps } from "./Typography.tsx"
|
||||
import { Typography } from "./Typography.tsx"
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { ErrorNotFound as ErrorNotFoundComponent } from "./ErrorNotFound.tsx"
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import { expect, fn, userEvent, within } from "@storybook/test"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
import { expect, fn, userEvent, within } from "storybook/test"
|
||||
|
||||
import { ErrorServer as ErrorServerComponent } from "./ErrorServer.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { About as AboutComponent } from "./About.tsx"
|
||||
|
||||
|
@@ -15,7 +15,7 @@ export const BirthDate: React.FC<BirthDateProps> = () => {
|
||||
}, [])
|
||||
|
||||
return t("home.about.birth-date.value", {
|
||||
age,
|
||||
age: age.toString(),
|
||||
birthDate: BIRTH_DATE_STRING,
|
||||
})
|
||||
}
|
||||
|
@@ -9,7 +9,6 @@ export const AboutLogo: React.FC<AboutLogoProps> = () => {
|
||||
return (
|
||||
<div className="max-h-[370px] max-w-[370px] px-2 py-6">
|
||||
<Image
|
||||
quality={100}
|
||||
src="/images/logo.webp"
|
||||
alt={t("meta.title")}
|
||||
width={800}
|
||||
|
@@ -2,9 +2,9 @@ import { Icon } from "./Icon.tsx"
|
||||
|
||||
export const TwitterIcon: React.FC<React.SVGProps<SVGSVGElement>> = (props) => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>Twitter</title>
|
||||
<path d="M23.953 4.57a10 10 0 01-2.825.775 4.958 4.958 0 002.163-2.723c-.951.555-2.005.959-3.127 1.184a4.92 4.92 0 00-8.384 4.482C7.69 8.095 4.067 6.13 1.64 3.162a4.822 4.822 0 00-.666 2.475c0 1.71.87 3.213 2.188 4.096a4.904 4.904 0 01-2.228-.616v.06a4.923 4.923 0 003.946 4.827 4.996 4.996 0 01-2.212.085 4.936 4.936 0 004.604 3.417 9.867 9.867 0 01-6.102 2.105c-.39 0-.779-.023-1.17-.067a13.995 13.995 0 007.557 2.209c9.053 0 13.998-7.496 13.998-13.985 0-.21 0-.42-.015-.63A9.935 9.935 0 0024 4.59z" />
|
||||
<Icon {...props} viewBox="0 0 1200 1227">
|
||||
<title>X/Twitter</title>
|
||||
<path d="M714.163 519.284L1160.89 0H1055.03L667.137 450.887L357.328 0H0L468.492 681.821L0 1226.37H105.866L515.491 750.218L842.672 1226.37H1200L714.137 519.284H714.163ZM569.165 687.828L521.697 619.934L144.011 79.6944H306.615L611.412 515.685L658.88 583.579L1055.08 1150.3H892.476L569.165 687.854V687.828Z" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@@ -26,7 +26,7 @@ export const SocialMediaList: React.FC<SocialMediaListProps> = () => {
|
||||
|
||||
<SocialMediaItem
|
||||
link="https://twitter.com/theoludwig_"
|
||||
ariaLabel="Twitter"
|
||||
ariaLabel="X/Twitter"
|
||||
>
|
||||
<TwitterIcon />
|
||||
</SocialMediaItem>
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Interests as InterestsComponent } from "./Interests.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { OpenSource as OpenSourceComponent } from "./OpenSource.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Portfolio as PortfolioComponent } from "./Portfolio.tsx"
|
||||
|
||||
|
@@ -32,8 +32,7 @@ export const PortfolioItem: React.FC<PortfolioItemProps> = (props) => {
|
||||
>
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
className="size-[300px] rounded-xl transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5"
|
||||
className="size-[300px] rounded-xl transition-opacity duration-500 group-hover:opacity-15 dark:group-hover:opacity-5"
|
||||
width={300}
|
||||
height={300}
|
||||
src={image}
|
||||
|
@@ -38,7 +38,6 @@ export const SkillItem: React.FC<SkillItemProps> = (props) => {
|
||||
>
|
||||
<Image
|
||||
className="inline size-16"
|
||||
quality={100}
|
||||
width={64}
|
||||
height={64}
|
||||
alt={`Logo of ${skillName}`}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Skills as SkillsComponent } from "./Skills.tsx"
|
||||
|
||||
|
@@ -110,29 +110,64 @@ export const SKILLS = {
|
||||
link: "https://www.docker.com/",
|
||||
image: "/images/skills/Docker.webp",
|
||||
},
|
||||
"tRPC/oRPC": {
|
||||
link: "https://trpc.io/",
|
||||
image: "/images/skills/tRPC.webp",
|
||||
},
|
||||
Rust: {
|
||||
link: "https://www.rust-lang.org/",
|
||||
image: "/images/skills/Rust.webp",
|
||||
},
|
||||
Caddy: {
|
||||
link: "https://caddyserver.com/",
|
||||
image: "/images/skills/Caddy.webp",
|
||||
},
|
||||
Proxmox: {
|
||||
link: "https://www.proxmox.com/",
|
||||
image: {
|
||||
light: "/images/skills/Proxmox-light.webp",
|
||||
dark: "/images/skills/Proxmox-dark.webp",
|
||||
},
|
||||
},
|
||||
"GitHub Actions": {
|
||||
link: "https://github.com/features/actions",
|
||||
image: {
|
||||
light: "/images/skills/GitHub-light.webp",
|
||||
dark: "/images/skills/GitHub-dark.webp",
|
||||
},
|
||||
},
|
||||
"GitLab CI/CD": {
|
||||
link: "https://docs.gitlab.com/ci",
|
||||
image: "/images/skills/GitLab.webp",
|
||||
},
|
||||
Go: {
|
||||
link: "https://go.dev/",
|
||||
image: "/images/skills/Go.webp",
|
||||
},
|
||||
} as const
|
||||
|
||||
export type SkillName = keyof typeof SKILLS
|
||||
|
||||
export const SKILL_CATEGORIES = [
|
||||
"programming-languages",
|
||||
"frontend",
|
||||
"backend",
|
||||
"software-development",
|
||||
"sys-admin",
|
||||
"systems-programming",
|
||||
"software-tools",
|
||||
] as const
|
||||
export type SkillCategory = (typeof SKILL_CATEGORIES)[number]
|
||||
|
||||
export const SKILL_NAMES_BY_CATEGORY = {
|
||||
"programming-languages": ["TypeScript", "Python", "C/C++", "PHP"],
|
||||
frontend: ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
|
||||
backend: ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
|
||||
"software-tools": [
|
||||
"GNU/Linux",
|
||||
"Arch Linux",
|
||||
"Visual Studio Code",
|
||||
"Git",
|
||||
"Docker",
|
||||
"software-development": [
|
||||
"TypeScript",
|
||||
"React.js (+ Next.js)",
|
||||
"Tailwind CSS",
|
||||
"Node.js",
|
||||
"tRPC/oRPC",
|
||||
"PostgreSQL",
|
||||
],
|
||||
"sys-admin": ["Docker", "Proxmox", "Caddy", "GitHub Actions", "GitLab CI/CD"],
|
||||
"software-tools": ["GNU/Linux", "Arch Linux", "Visual Studio Code", "Git"],
|
||||
"systems-programming": ["C/C++", "Rust", "Go"],
|
||||
} as const satisfies {
|
||||
[key in SkillCategory]: SkillName[]
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Footer as FooterComponent } from "./Footer.tsx"
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import type { Meta, StoryObj } from "@storybook/react"
|
||||
import type { Meta, StoryObj } from "@storybook/nextjs"
|
||||
|
||||
import { Header as HeaderComponent } from "./Header.tsx"
|
||||
|
||||
|
@@ -14,7 +14,6 @@ export const Header: React.FC<HeaderProps> = () => {
|
||||
<h1>
|
||||
<Link href="/" className="flex items-center justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
className="w-16"
|
||||
src="/images/logo.webp"
|
||||
width={800}
|
||||
|
@@ -14,7 +14,6 @@ export const LocaleFlag: React.FC<LocaleFlagProps> = (props) => {
|
||||
return (
|
||||
<>
|
||||
<Image
|
||||
quality={100}
|
||||
width={35}
|
||||
height={35}
|
||||
src={`/images/locales/${locale}.svg`}
|
||||
|
@@ -14,7 +14,7 @@
|
||||
"scripts": {
|
||||
"lint:eslint": "eslint src --max-warnings 0",
|
||||
"lint:typescript": "tsc --noEmit",
|
||||
"test": "node --experimental-strip-types --test"
|
||||
"test": "node --test"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@repo/config-eslint": "workspace:*",
|
||||
|
@@ -13,7 +13,6 @@ export type Primitive =
|
||||
export type Satisfies<U, T extends U> = T
|
||||
|
||||
export type OmitStrict<T, K extends keyof T> = Omit<T, K>
|
||||
export type PickStrict<T, K extends keyof T> = Pick<T, K>
|
||||
|
||||
export type OverrideStrict<
|
||||
Type,
|
||||
|
6621
pnpm-lock.yaml
generated
@@ -3,29 +3,36 @@ packages:
|
||||
- "configs/*"
|
||||
- "packages/*"
|
||||
|
||||
catalogMode: "strict"
|
||||
|
||||
catalog:
|
||||
# Turborepo
|
||||
"turbo": "2.5.3"
|
||||
"turbo": "2.5.6"
|
||||
|
||||
# TypeScript
|
||||
"typescript": "5.9.2"
|
||||
"@total-typescript/ts-reset": "0.6.1"
|
||||
"@types/node": "24.3.1"
|
||||
|
||||
# Utils
|
||||
"mime": "4.0.7"
|
||||
|
||||
# React.js/Next.js
|
||||
"next": &next "15.3.2"
|
||||
"next-intl": "4.1.0"
|
||||
"next": &next "15.5.2"
|
||||
"next-intl": "4.3.7"
|
||||
"next-themes": "0.4.6"
|
||||
"react": "19.1.0"
|
||||
"react-dom": "19.1.0"
|
||||
"react": "19.1.1"
|
||||
"react-dom": "19.1.1"
|
||||
"@types/react": "19.1.12"
|
||||
"@types/react-dom": "19.1.9"
|
||||
"react-icons": "5.5.0"
|
||||
"@types/react": "19.1.3"
|
||||
"@types/react-dom": "19.1.3"
|
||||
|
||||
# Blog
|
||||
"@giscus/react": "3.1.0"
|
||||
"gray-matter": "4.0.3"
|
||||
"katex": "0.16.22"
|
||||
"next-mdx-remote": "5.0.0"
|
||||
"@mdx-js/mdx": "3.1.0"
|
||||
"@mdx-js/mdx": "3.1.1"
|
||||
"rehype-katex": "7.0.1"
|
||||
"rehype-raw": "7.0.0"
|
||||
"rehype-slug": "6.0.0"
|
||||
@@ -35,62 +42,51 @@ catalog:
|
||||
"@shikijs/rehype": "1.24.0"
|
||||
|
||||
# Markdown Lint
|
||||
"markdownlint-cli2": "0.18.0"
|
||||
"markdownlint-cli2": "0.18.1"
|
||||
"markdownlint": "0.38.0"
|
||||
"markdownlint-rule-relative-links": "4.1.0"
|
||||
|
||||
# TypeScript
|
||||
"typescript": "5.8.3"
|
||||
"@total-typescript/ts-reset": "0.6.1"
|
||||
"@types/node": "22.15.17"
|
||||
"markdownlint-rule-relative-links": "4.2.0"
|
||||
|
||||
# ESLint
|
||||
"globals": "16.1.0"
|
||||
"typescript-eslint": "8.32.0"
|
||||
"@eslint/eslintrc": "3.3.1"
|
||||
"eslint": "9.26.0"
|
||||
"eslint-config-conventions": "19.2.0"
|
||||
"globals": "16.3.0"
|
||||
"typescript-eslint": "8.43.0"
|
||||
"eslint": "9.35.0"
|
||||
"eslint-config-conventions": "20.2.0"
|
||||
"eslint-plugin-promise": "7.2.1"
|
||||
"eslint-plugin-unicorn": "59.0.1"
|
||||
"eslint-config-next": *next
|
||||
"eslint-plugin-import-x": "4.11.1"
|
||||
"eslint-plugin-unicorn": "61.0.2"
|
||||
"eslint-plugin-import-x": "4.16.1"
|
||||
"@next/eslint-plugin-next": *next
|
||||
"eslint-plugin-react": "7.37.5"
|
||||
"eslint-plugin-react-hooks": "6.0.0-rc.1"
|
||||
|
||||
# Prettier
|
||||
"prettier": "3.5.3"
|
||||
"prettier-plugin-tailwindcss": "0.6.11"
|
||||
"editorconfig-checker": "6.0.1"
|
||||
"prettier": "3.6.2"
|
||||
"prettier-plugin-tailwindcss": "0.6.14"
|
||||
"editorconfig-checker": "6.1.0"
|
||||
|
||||
# Storybook
|
||||
"storybook": &storybook "8.6.12"
|
||||
"@storybook/addon-essentials": *storybook
|
||||
"@storybook/addon-storysource": *storybook
|
||||
"storybook": &storybook "9.1.5"
|
||||
"@storybook/addon-docs": *storybook
|
||||
"@storybook/addon-a11y": *storybook
|
||||
"@storybook/addon-interactions": *storybook
|
||||
"@storybook/blocks": *storybook
|
||||
"@storybook/nextjs": *storybook
|
||||
"@storybook/react": *storybook
|
||||
"@storybook/test": *storybook
|
||||
"@storybook/addon-themes": *storybook
|
||||
"@storybook/test-runner": "0.22.0"
|
||||
"@chromatic-com/storybook": "3.2.6"
|
||||
"chromatic": "11.28.2"
|
||||
"storybook-dark-mode": "4.0.2"
|
||||
"@storybook/test-runner": "0.23.0"
|
||||
"@chromatic-com/storybook": "4.1.1"
|
||||
"chromatic": "13.1.4"
|
||||
|
||||
# Testing
|
||||
"playwright": &playwright "1.52.0"
|
||||
"playwright": &playwright "1.55.0"
|
||||
"@playwright/test": *playwright
|
||||
"axe-playwright": "2.1.0"
|
||||
"start-server-and-test": "2.0.11"
|
||||
"start-server-and-test": "2.1.0"
|
||||
|
||||
# CSS
|
||||
"postcss": "8.5.3"
|
||||
"@tailwindcss/postcss": "4.1.6"
|
||||
"postcss": "8.5.6"
|
||||
"@tailwindcss/postcss": "4.1.13"
|
||||
"@tailwindcss/typography": "0.5.16"
|
||||
"tailwindcss": "4.1.6"
|
||||
"tailwind-merge": "3.2.0"
|
||||
"tailwindcss": "4.1.13"
|
||||
"tailwind-merge": "3.3.1"
|
||||
"clsx": "2.1.1"
|
||||
"cva": "1.0.0-beta.3"
|
||||
"@fontsource/montserrat": "5.2.5"
|
||||
"cva": "1.0.0-beta.4"
|
||||
"@fontsource/montserrat": "5.2.7"
|
||||
|
||||
onlyBuiltDependencies:
|
||||
- "@swc/core"
|
||||
|
@@ -27,7 +27,11 @@
|
||||
"dependsOn": ["^lint:typescript-transit-node"]
|
||||
},
|
||||
"lint:typescript": {
|
||||
"dependsOn": ["lint:typescript-transit-node"]
|
||||
"dependsOn": ["typegen", "lint:typescript-transit-node"]
|
||||
},
|
||||
"typegen": {
|
||||
"dependsOn": ["^typegen"],
|
||||
"outputs": []
|
||||
},
|
||||
"dev": {
|
||||
"cache": false,
|
||||
|