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

Compare commits

..

6 Commits

Author SHA1 Message Date
b25451e631 chore(release): 2.5.5 [skip ci] 2023-01-10 23:05:24 +00:00
042a861f58 fix: update dependencies to latest 2023-01-10 23:56:46 +01:00
d76db36dbc chore(release): 2.5.4 [skip ci] 2022-12-08 08:54:03 +00:00
99d9dcf334 fix: improve Resume 2022-12-08 09:52:39 +01:00
ece5ded1b4 chore(release): 2.5.3 [skip ci] 2022-11-29 09:33:10 +00:00
1514600998 fix: improve Resume 2022-11-29 10:29:02 +01:00
25 changed files with 2567 additions and 19153 deletions

View File

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

View File

@ -10,7 +10,6 @@
}, },
"rules": { "rules": {
"prettier/prettier": "error", "prettier/prettier": "error",
"unicorn/prefer-node-protocol": "error",
"@next/next/no-img-element": "off" "@next/next/no-img-element": "off"
} }
} }

View File

@ -16,7 +16,7 @@ jobs:
language: ['javascript'] language: ['javascript']
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Initialize CodeQL' - name: 'Initialize CodeQL'
uses: 'github/codeql-action/init@v2' uses: 'github/codeql-action/init@v2'

View File

@ -10,10 +10,10 @@ jobs:
build: build:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'

View File

@ -10,10 +10,10 @@ jobs:
lint: lint:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'
@ -30,8 +30,8 @@ jobs:
- name: 'lint:markdown' - name: 'lint:markdown'
run: 'npm run lint:markdown' run: 'npm run lint:markdown'
- name: 'lint:typescript' - name: 'lint:eslint'
run: 'npm run lint:typescript' run: 'npm run lint:eslint'
- name: 'lint:prettier' - name: 'lint:prettier'
run: 'npm run lint:prettier' run: 'npm run lint:prettier'
@ -40,8 +40,3 @@ jobs:
uses: 'dotenv-linter/action-dotenv-linter@v2' uses: 'dotenv-linter/action-dotenv-linter@v2'
with: with:
github_token: ${{ secrets.github_token }} github_token: ${{ secrets.github_token }}
- name: 'lint:docker'
uses: 'hadolint/hadolint-action@v1.6.0'
with:
dockerfile: './Dockerfile'

View File

@ -8,7 +8,7 @@ jobs:
release: release:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
with: with:
fetch-depth: 0 fetch-depth: 0
persist-credentials: false persist-credentials: false
@ -21,7 +21,7 @@ jobs:
git_commit_gpgsign: true git_commit_gpgsign: true
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'

View File

@ -10,10 +10,10 @@ jobs:
test-unit: test-unit:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'
@ -27,10 +27,10 @@ jobs:
test-lighthouse: test-lighthouse:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'
@ -52,10 +52,10 @@ jobs:
test-e2e: test-e2e:
runs-on: 'ubuntu-latest' runs-on: 'ubuntu-latest'
steps: steps:
- uses: 'actions/checkout@v3.1.0' - uses: 'actions/checkout@v3.3.0'
- name: 'Use Node.js' - name: 'Use Node.js'
uses: 'actions/setup-node@v3.5.1' uses: 'actions/setup-node@v3.6.0'
with: with:
node-version: '18.x' node-version: '18.x'
cache: 'npm' cache: 'npm'

View File

@ -1,11 +1,5 @@
{ {
"config": {
"default": true,
"MD013": false,
"MD024": false,
"MD033": false,
"MD041": false
},
"globs": ["**/*.{md,mdx}"], "globs": ["**/*.{md,mdx}"],
"ignores": ["**/node_modules"] "ignores": ["**/node_modules"],
"customRules": ["markdownlint-rule-relative-links"]
} }

8
.markdownlint.json Normal file
View File

@ -0,0 +1,8 @@
{
"default": true,
"relative-links": true,
"extends": "markdownlint/style/prettier",
"MD024": false,
"MD033": false,
"MD041": false
}

View File

@ -1,21 +1,27 @@
FROM node:18.12.1 AS dependencies FROM node:18.13.0 AS builder-dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/application
COPY ./package*.json ./ COPY ./package*.json ./
RUN npm install RUN npm install
FROM node:18.12.1 AS builder FROM node:18.13.0 AS runner-dependencies
WORKDIR /usr/src/app WORKDIR /usr/src/application
ENV NODE_ENV=production
COPY ./package*.json ./
RUN npm install --omit=dev --ignore-scripts
FROM node:18.13.0 AS builder
WORKDIR /usr/src/application
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
COPY ./ ./ COPY ./ ./
COPY --from=dependencies /usr/src/app/node_modules ./node_modules
RUN npm run build RUN npm run build
FROM node:18.12.1 AS runner FROM gcr.io/distroless/nodejs18-debian11:latest AS runner
WORKDIR /usr/src/app WORKDIR /usr/src/application
ENV NODE_ENV=production ENV NODE_ENV=production
ENV NEXT_TELEMETRY_DISABLED=1 ENV NEXT_TELEMETRY_DISABLED=1
COPY --from=builder /usr/src/app/.next/standalone ./ COPY --from=builder /usr/src/application/.next/standalone ./
COPY --from=builder /usr/src/app/.next/static ./.next/static COPY --from=builder /usr/src/application/.next/static ./.next/static
COPY --from=builder /usr/src/app/public ./public COPY --from=builder /usr/src/application/public ./public
COPY --from=builder /usr/src/app/locales ./locales COPY --from=builder /usr/src/application/locales ./locales
COPY --from=builder /usr/src/app/next.config.js ./next.config.js COPY --from=builder /usr/src/application/next.config.js ./next.config.js
CMD ["node", "server.js"] CMD ["./server.js"]

View File

@ -25,21 +25,11 @@
"pronouns": "He/Him", "pronouns": "He/Him",
"birthDate": "31/03/2003", "birthDate": "31/03/2003",
"nationality": "Alsace, France", "nationality": "Alsace, France",
"interests": [ "interests": ["Open-Source enthusiast", "Passionate about High-Tech"],
"Developer Full Stack",
"Open-Source enthusiast",
"Passionate about High-Tech"
],
"skills": { "skills": {
"programmingLanguages": [ "programmingLanguages": ["JavaScript/TypeScript", "Python", "C/C++", "PHP"],
"JavaScript", "frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js/Next.js"],
"TypeScript", "backEnd": ["Laravel", "Node.js", "Fastify", "PostgreSQL"],
"Python",
"C/C++",
"PHP"
],
"frontEnd": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"],
"backEnd": ["Laravel", "Node.js", "Fastify", "Prisma", "PostgreSQL"],
"tools": ["GNU/Linux", "Ubuntu", "Visual Studio Code", "Git", "Docker"] "tools": ["GNU/Linux", "Ubuntu", "Visual Studio Code", "Git", "Docker"]
} }
} }

View File

@ -27,7 +27,6 @@ export const Skills: React.FC = () => {
<SkillComponent skill='Laravel' /> <SkillComponent skill='Laravel' />
<SkillComponent skill='Node.js' /> <SkillComponent skill='Node.js' />
<SkillComponent skill='Fastify' /> <SkillComponent skill='Fastify' />
<SkillComponent skill='Prisma' />
<SkillComponent skill='PostgreSQL' /> <SkillComponent skill='PostgreSQL' />
</SkillsSection> </SkillsSection>

View File

@ -3,10 +3,10 @@ import fs from 'node:fs'
import { build } from 'vite' import { build } from 'vite'
const jsonResumeThemeCustom = new URL('../', import.meta.url) const jsonResumeThemeCustom = new URL('./', import.meta.url)
const jsonResumeThemeCustomDist = new URL('./dist', jsonResumeThemeCustom) const jsonResumeThemeCustomDist = new URL('./dist', jsonResumeThemeCustom)
const publicResumeOutputURL = new URL( const publicResumeOutputURL = new URL(
'../../public/curriculum-vitae', '../public/curriculum-vitae',
import.meta.url import.meta.url
) )

View File

@ -6,6 +6,7 @@
<title><%= locals.basics.name %></title> <title><%= locals.basics.name %></title>
<link rel="icon" type="image/png" href="<%= locals.basics.image %>" /> <link rel="icon" type="image/png" href="<%= locals.basics.image %>" />
<link rel="stylesheet" href="./styles/global.css" /> <link rel="stylesheet" href="./styles/global.css" />
<script defer type="module" src="./scripts/main.js"></script>
</head> </head>
<body> <body>
<div class="container-fluid"> <div class="container-fluid">
@ -26,12 +27,15 @@
<strong><%= locals.basics.name %></strong> <strong><%= locals.basics.name %></strong>
</h3> </h3>
<h5 class="text-muted"><%= locals.basics.label %></h5> <h5 class="text-muted"><%= locals.basics.label %></h5>
<h5 class="text-muted">
<%= locals.basics.age %> (<span id="year-old"></span> ans)
</h5>
<h5 class="text-muted">
<%= locals.basics.location.address %>
</h5>
</div> </div>
</div> </div>
<div class="contact-details clearfix"> <div class="contact-details clearfix">
<div class="detail">
<span class="info"><%= locals.basics.phone %></span>
</div>
<div class="detail"> <div class="detail">
<span class="info"> <span class="info">
<a <a
@ -71,40 +75,44 @@
<hr /> <hr />
<section class="section-separated"> <section class="section-separated">
<div class="detail" id="work-experience"> <div class="detail" id="education">
<div class="icon"> <div class="icon">
<img src="./images/building-columns.svg" alt="work" /> <img src="./images/graduation-cap.svg" alt="graduation" />
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Expériences</h4> <h4 class="title text-uppercase">Formations</h4>
<ul class="list-unstyled clear-margin"> <div class="content">
<% locals.work.forEach((experience) => { %> <ul class="list-unstyled clear-margin">
<li class="card card-nested clearfix"> <% locals.education.forEach((degree) => { %>
<div class="content"> <li class="card card-nested">
<p class="clear-margin relative"> <div class="content">
<a href="<%= experience.website %>"> <p class="clear-margin relative">
<strong><%= experience.name %></strong> <strong><%= degree.studyType %></strong>
</a> </p>
</p> <p class="clear-margin relative">
<p class="clear-margin relative"> <strong><%= degree.score %></strong>
<strong><%- experience.position %></strong> </p>
</p> <p class="text-muted clear-margin">
<p class="text-muted"> <%= degree.institution %>
<small> </p>
<span class="space-right"> <p class="text-muted clear-margin">
<%= date.format(new Date(experience.startDate), <small>
'DD/MM/YYYY') %> - <%= date.format(new <%= degree.startDate %> <%= degree.endDate !=
Date(experience.endDate), 'DD/MM/YYYY') %> null ? " - " + degree.endDate : "" %>
</span> </small>
</small> </p>
</p> <% if (degree.courses != null) { %>
<div class="experience-description"> <ul class="education-courses">
<p><%- experience.summary %></p> <% degree.courses.forEach((course) => { %>
<li><%= course %></li>
<% }) %>
</ul>
<% } %>
</div> </div>
</div> </li>
</li> <% }) %>
<% }) %> </ul>
</ul> </div>
</div> </div>
</div> </div>
@ -137,37 +145,43 @@
<hr /> <hr />
<section class="section-separated"> <section class="section-separated">
<div class="detail" id="education"> <div class="detail" id="work-experience">
<div class="icon"> <div class="icon">
<img src="./images/graduation-cap.svg" alt="graduation" /> <img src="./images/building-columns.svg" alt="work" />
</div> </div>
<div class="info"> <div class="info">
<h4 class="title text-uppercase">Éducation</h4> <h4 class="title text-uppercase">Expériences</h4>
<div class="content"> <ul class="list-unstyled clear-margin">
<ul class="list-unstyled clear-margin"> <% locals.work.filter((experience) =>
<% locals.education.forEach((degree) => { %> experience.description == null).forEach((experience) => {
<li class="card card-nested"> %>
<div class="content"> <li class="card card-nested clearfix">
<p class="clear-margin relative"> <div class="content">
<strong><%= degree.studyType %></strong> <p class="clear-margin relative">
</p> <a href="<%= experience.website %>">
<p class="clear-margin relative"> <strong><%= experience.name %></strong>
<strong><%= degree.score %></strong> </a>
</p> </p>
<p class="text-muted clear-margin"> <p class="clear-margin relative">
<%= degree.institution %> <strong><%- experience.position %></strong>
</p> </p>
<p class="text-muted clear-margin"> <p class="text-muted">
<small> <small>
<%= degree.startDate %> <%= degree.endDate != <span class="space-right">
null ? " - " + degree.endDate : "" %> <%= date.format(new Date(experience.startDate),
</small> 'DD/MM/YYYY') %> - <%= date.format(new
</p> Date(experience.endDate), 'DD/MM/YYYY') %> (<%=
experience.duration %>)
</span>
</small>
</p>
<div class="experience-description">
<p><%- experience.summary %></p>
</div> </div>
</li> </div>
<% }) %> </li>
</ul> <% }) %>
</div> </ul>
</div> </div>
</div> </div>
@ -185,6 +199,38 @@
</li> </li>
<% }) %> <% }) %>
</ul> </ul>
<ul class="list-unstyled clear-margin">
<% locals.work.filter((experience) =>
experience.description != null).forEach((experience) =>
{ %>
<li class="card card-nested clearfix">
<div class="content">
<p class="clear-margin relative">
<a href="<%= experience.website %>">
<strong><%= experience.name %></strong>
</a>
</p>
<p class="clear-margin relative">
<strong><%- experience.position %></strong>
</p>
<p class="text-muted">
<small>
<span class="space-right">
<%= date.format(new
Date(experience.startDate), 'DD/MM/YYYY') %> -
<%= date.format(new Date(experience.endDate),
'DD/MM/YYYY') %> (<%= experience.duration %>)
</span>
</small>
</p>
<div class="experience-description">
<p><%- experience.summary %></p>
</div>
</div>
</li>
<% }) %>
</ul>
</div> </div>
</div> </div>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@ -9,12 +9,13 @@
"preview": "vite preview" "preview": "vite preview"
}, },
"dependencies": { "dependencies": {
"jsonc-parser": "3.2.0",
"modern-normalize": "1.1.0" "modern-normalize": "1.1.0"
}, },
"devDependencies": { "devDependencies": {
"@types/node": "18.11.9", "@types/node": "18.11.18",
"date-and-time": "2.4.1", "date-and-time": "2.4.1",
"vite": "3.2.4", "vite": "4.0.4",
"vite-plugin-html": "3.2.0" "vite-plugin-html": "3.2.0"
} }
} }

View File

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

View File

@ -226,5 +226,4 @@ h5 {
} }
.section-separated { .section-separated {
display: flex; display: flex;
align-items: space-evenly;
} }

View File

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

19497
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,6 @@
{ {
"name": "divlo", "name": "divlo",
"version": "2.5.2", "version": "2.5.5",
"private": true, "private": true,
"repository": { "repository": {
"type": "git", "type": "git",
@ -18,7 +18,7 @@
"lint:commit": "commitlint", "lint:commit": "commitlint",
"lint:editorconfig": "editorconfig-checker", "lint:editorconfig": "editorconfig-checker",
"lint:markdown": "markdownlint-cli2", "lint:markdown": "markdownlint-cli2",
"lint:typescript": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"", "lint:eslint": "eslint \"**/*.{js,jsx,ts,tsx}\" --ignore-path \".gitignore\"",
"lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"", "lint:prettier": "prettier \".\" --check --ignore-path \".gitignore\"",
"lint:staged": "lint-staged", "lint:staged": "lint-staged",
"test:unit": "cypress run --component", "test:unit": "cypress run --component",
@ -26,7 +26,7 @@
"test:lighthouse": "lhci autorun", "test:lighthouse": "lhci autorun",
"test:e2e": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"cypress run\"", "test:e2e": "start-server-and-test \"start\" \"http://127.0.0.1:3000\" \"cypress run\"",
"test:dev": "start-server-and-test \"dev\" \"http://127.0.0.1:3000\" \"cypress open\"", "test:dev": "start-server-and-test \"dev\" \"http://127.0.0.1:3000\" \"cypress open\"",
"resume:build": "node ./jsonresume-theme-custom/scripts/build.js", "resume:build": "node ./jsonresume-theme-custom/build.js",
"release": "semantic-release", "release": "semantic-release",
"deploy": "vercel", "deploy": "vercel",
"postinstall": "husky install" "postinstall": "husky install"
@ -37,13 +37,13 @@
"@fortawesome/free-brands-svg-icons": "6.2.1", "@fortawesome/free-brands-svg-icons": "6.2.1",
"@fortawesome/free-solid-svg-icons": "6.2.1", "@fortawesome/free-solid-svg-icons": "6.2.1",
"@fortawesome/react-fontawesome": "0.2.0", "@fortawesome/react-fontawesome": "0.2.0",
"@giscus/react": "2.2.3", "@giscus/react": "2.2.6",
"clsx": "1.2.1", "clsx": "1.2.1",
"date-and-time": "2.4.1", "date-and-time": "2.4.1",
"gray-matter": "4.0.3", "gray-matter": "4.0.3",
"html-react-parser": "3.0.4", "html-react-parser": "3.0.7",
"next": "13.0.4", "next": "13.1.1",
"next-mdx-remote": "4.2.0", "next-mdx-remote": "4.2.1",
"next-pwa": "5.6.0", "next-pwa": "5.6.0",
"next-themes": "0.2.1", "next-themes": "0.2.1",
"next-translate": "1.6.0", "next-translate": "1.6.0",
@ -53,46 +53,48 @@
"rehype-raw": "6.1.1", "rehype-raw": "6.1.1",
"rehype-slug": "5.1.0", "rehype-slug": "5.1.0",
"remark-gfm": "3.0.1", "remark-gfm": "3.0.1",
"sharp": "0.31.2", "sharp": "0.31.3",
"shiki": "0.11.1", "shiki": "0.12.1",
"unified": "10.1.2", "unified": "10.1.2",
"unist-util-visit": "4.1.1", "unist-util-visit": "4.1.1",
"universal-cookie": "4.0.4" "universal-cookie": "4.0.4"
}, },
"devDependencies": { "devDependencies": {
"@commitlint/cli": "17.2.0", "@commitlint/cli": "17.4.1",
"@commitlint/config-conventional": "17.2.0", "@commitlint/config-conventional": "17.4.0",
"@lhci/cli": "0.10.0", "@lhci/cli": "0.10.0",
"@saithodev/semantic-release-backmerge": "2.1.2", "@saithodev/semantic-release-backmerge": "2.1.2",
"@semantic-release/git": "10.0.1", "@semantic-release/git": "10.0.1",
"@tailwindcss/typography": "0.5.8", "@tailwindcss/typography": "0.5.9",
"@types/node": "18.11.9", "@tsconfig/strictest": "1.0.2",
"@types/react": "18.0.25", "@types/node": "18.11.18",
"@types/react": "18.0.26",
"@types/unist": "2.0.6", "@types/unist": "2.0.6",
"@typescript-eslint/eslint-plugin": "5.43.0", "@typescript-eslint/eslint-plugin": "5.48.1",
"autoprefixer": "10.4.13", "autoprefixer": "10.4.13",
"cypress": "11.1.0", "cypress": "12.3.0",
"editorconfig-checker": "4.0.2", "editorconfig-checker": "4.0.2",
"eslint": "8.28.0", "eslint": "8.31.0",
"eslint-config-conventions": "5.0.0", "eslint-config-conventions": "6.0.0",
"eslint-config-next": "13.0.4", "eslint-config-next": "13.1.1",
"eslint-config-prettier": "8.5.0", "eslint-config-prettier": "8.6.0",
"eslint-plugin-import": "2.26.0", "eslint-plugin-import": "2.26.0",
"eslint-plugin-prettier": "4.2.1", "eslint-plugin-prettier": "4.2.1",
"eslint-plugin-promise": "6.1.1", "eslint-plugin-promise": "6.1.1",
"eslint-plugin-unicorn": "44.0.2", "eslint-plugin-unicorn": "45.0.2",
"html-w3c-validator": "1.2.1", "html-w3c-validator": "1.2.2",
"husky": "8.0.2", "husky": "8.0.3",
"jsonresume-theme-custom": "file:./jsonresume-theme-custom", "jsonresume-theme-custom": "file:./jsonresume-theme-custom",
"lint-staged": "13.0.3", "lint-staged": "13.1.0",
"markdownlint-cli2": "0.5.1", "markdownlint-cli2": "0.6.0",
"postcss": "8.4.19", "markdownlint-rule-relative-links": "1.1.1",
"prettier": "2.7.1", "postcss": "8.4.21",
"prettier-plugin-tailwindcss": "0.1.13", "prettier": "2.8.2",
"prettier-plugin-tailwindcss": "0.2.1",
"semantic-release": "19.0.5", "semantic-release": "19.0.5",
"start-server-and-test": "1.14.0", "start-server-and-test": "1.15.2",
"tailwindcss": "3.2.4", "tailwindcss": "3.2.4",
"typescript": "4.9.3", "typescript": "4.9.4",
"vercel": "28.5.5" "vercel": "28.11.0"
} }
} }

View File

@ -82,7 +82,7 @@ const BlogPostPage: NextPage<BlogPostPageProps> = (props) => {
export const getStaticProps: GetStaticProps<BlogPostPageProps> = async ( export const getStaticProps: GetStaticProps<BlogPostPageProps> = async (
context context
) => { ) => {
const slug = context?.params?.slug const slug = context?.params?.['slug']
const { getPostBySlug } = await import('utils/blog') const { getPostBySlug } = await import('utils/blog')
const post = await getPostBySlug(slug) const post = await getPostBySlug(slug)
if (post == null || (post != null && !post.frontmatter.isPublished)) { if (post == null || (post != null && !post.frontmatter.isPublished)) {

View File

@ -5,43 +5,69 @@
}, },
"basics": { "basics": {
"name": "Théo LUDWIG", "name": "Théo LUDWIG",
"label": "Développeur Full Stack • Enthousiaste de l'Open-Source", "label": "Développeur Full Stack • Étudiant",
"image": "https://divlo.fr/images/logo_orange.png", "image": "https://divlo.fr/images/logo_orange.png",
"email": "contact@divlo.fr", "email": "contact@divlo.fr",
"location": {}, "age": "31/03/2003",
"location": {
"address": "Alsace, France"
},
"url": "https://divlo.fr", "url": "https://divlo.fr",
"summary": "Je suis étudiant à l'université suivant la formation \"BUT Informatique\" et me forme en autodidacte dans l'informatique en suivant des formations en ligne. <br/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets (disponible sur divlo.fr)." "summary": "Je suis étudiant à l'université suivant la formation \"BUT Informatique\" et me forme en autodidacte dans l'informatique en suivant des formations en ligne. <br/> Je mets en pratique tout ce que j'apprends et réalise de nombreux projets (disponible sur <a href=\"https://divlo.fr\">divlo.fr</a>)."
}, },
"education": [ "education": [
{ {
"startDate": "2022", "startDate": "2022",
"studyType": "Diplôme du Bachelor Universitaire de Technologie (BUT) Informatique", "endDate": "2023",
"studyType": "Bachelor Universitaire de Technologie (BUT) Informatique",
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden", "institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
"score": "2ème année" "score": "2ème année",
"courses": [
"Développement Web avec le framework Laravel en PHP",
"Patrons et Principes de conceptions (Code maintenable et réutilisable) en UML",
"Programmation systèmes en C (Multi-Thread, Serveur/Client UDP/TCP)",
"Sécurisation des accès à la base de données et PL/SQL",
"Projet développement d'une application web en React.js en équipe de 3 personnes pendant 3 mois"
]
},
{
"startDate": "2021",
"endDate": "2022",
"studyType": "Bachelor Universitaire de Technologie (BUT) Informatique",
"institution": "IUT Robert Schuman à Illkirch-Graffenstaden",
"score": "1ère année",
"courses": [
"Développement Orientée Objet en Java",
"Programmation systèmes en C (Allocation mémoire, Pointeurs, Structures)",
"Développement d'application Windows Forms (.NET Framework) en C#",
"Base de données relationnelles et langage SQL"
]
}, },
{ {
"startDate": "2019", "startDate": "2019",
"endDate": "2021", "endDate": "2021",
"studyType": "Diplôme du Baccalauréat Général (Mathématiques et Numériques Sciences Informatiques)", "studyType": "Baccalauréat Général (Mathématiques et Numériques Sciences Informatiques)",
"institution": "Lycée Heinrich Nessel à Haguenau", "institution": "Lycée Heinrich Nessel à Haguenau",
"score": "Mention Assez Bien" "score": "Mention Assez Bien"
},
{
"startDate": "2014",
"endDate": "2018",
"studyType": "Diplôme national du brevet",
"institution": "Collège Gustave Doré à Hochfelden",
"score": "Mention Bien"
} }
// {
// "startDate": "2014",
// "endDate": "2018",
// "studyType": "Diplôme national du brevet",
// "institution": "Collège Gustave Doré à Hochfelden",
// "score": "Mention Bien"
// }
], ],
"work": [ "work": [
{ {
"summary": "Développement site web en React.js et Strapi afin de répondre <a href=\"https://www.nuitdelinfo.com/nuitinfo/_media/infos:la_nuit_de_l_info_2021_-_sujet.pdf\">au sujet de la Nuit de l'Info 2021</a>.<br /> Classé n°1 en France sur le Défi de l'entreprise <a href=\"https://www.nuitdelinfo.com/inscription/defis/300\">ToolPad</a>.", "description": "interests",
"summary": "Développement site web en React.js et Strapi.<br /> Classé n°1 en France sur le Défi de l'entreprise <a href=\"https://www.toolpad.fr/\">ToolPad</a>.",
"website": "https://www.nuitdelinfo.com/", "website": "https://www.nuitdelinfo.com/",
"name": "La Nuit de l'info 2021", "name": "La Nuit de l'info 2021",
"position": "Participation en équipe de 5 personnes", "position": "Participation en équipe de 5 personnes",
"startDate": "2021-12-02", "startDate": "2021-12-02",
"endDate": "2021-12-03" "endDate": "2021-12-03",
"duration": "1 semaine"
}, },
{ {
"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.",
@ -50,16 +76,19 @@
"location": "5 Rue André Marie Ampère, 67450 Mundolsheim", "location": "5 Rue André Marie Ampère, 67450 Mundolsheim",
"position": "Emploi d'été en qualité d'agent administratif", "position": "Emploi d'été en qualité d'agent administratif",
"startDate": "2021-07-07", "startDate": "2021-07-07",
"endDate": "2021-07-30" "endDate": "2021-07-30",
"duration": "1 mois"
}, },
{ {
"description": "interests",
"summary": "Hackathon développement d'une landing page et web scraping.", "summary": "Hackathon développement d'une landing page et web scraping.",
"website": "https://www.wildcodeschool.fr/", "website": "https://www.wildcodeschool.fr/",
"name": "Wild Code School", "name": "Wild Code School",
"location": "32 Rue du Bass. d'Austerlitz, 67100 Strasbourg", "location": "32 Rue du Bass. d'Austerlitz, 67100 Strasbourg",
"position": "Stage initiation métier développeur web", "position": "Initiation métier développeur web",
"startDate": "2019-06-24", "startDate": "2019-06-24",
"endDate": "2019-06-28" "endDate": "2019-06-28",
"duration": "1 semaine"
}, },
{ {
"summary": "Développement d'un site web pour trouver un restaurant à la pause repas.", "summary": "Développement d'un site web pour trouver un restaurant à la pause repas.",
@ -68,7 +97,8 @@
"location": "16 Rue du Parc, 67205 Oberhausbergen", "location": "16 Rue du Parc, 67205 Oberhausbergen",
"position": "Stage initiation métier développeur web", "position": "Stage initiation métier développeur web",
"startDate": "2019-06-17", "startDate": "2019-06-17",
"endDate": "2019-06-21" "endDate": "2019-06-21",
"duration": "1 semaine"
}, },
{ {
"summary": "Apprentissage du métier \"Chargé de communication\" et des logiciels de graphisme tels que \"Adobe Photoshop\".", "summary": "Apprentissage du métier \"Chargé de communication\" et des logiciels de graphisme tels que \"Adobe Photoshop\".",
@ -77,13 +107,11 @@
"location": "26 Bd du Président-Wilson, 67000 Strasbourg", "location": "26 Bd du Président-Wilson, 67000 Strasbourg",
"position": "Stage de découverte (3ème)", "position": "Stage de découverte (3ème)",
"startDate": "2018-02-19", "startDate": "2018-02-19",
"endDate": "2018-02-23" "endDate": "2018-02-23",
"duration": "1 semaine"
} }
], ],
"interests": [ "interests": [
{
"name": "Développeur Full Stack"
},
{ {
"name": "Enthousiaste de l'Open-Source" "name": "Enthousiaste de l'Open-Source"
}, },
@ -93,11 +121,11 @@
], ],
"skills": [ "skills": [
{ {
"keywords": ["JavaScript", "TypeScript", "Python", "C/C++", "PHP"], "keywords": ["JavaScript/TypeScript", "Python", "C/C++", "PHP"],
"name": "Langages de programmation" "name": "Langages de programmation"
}, },
{ {
"keywords": ["HTML", "CSS", "Tailwind CSS", "React.js (+ Next.js)"], "keywords": ["HTML", "CSS", "Tailwind CSS", "React.js/Next.js"],
"name": "Front-end" "name": "Front-end"
}, },
{ {
@ -109,10 +137,14 @@
"GNU/Linux", "GNU/Linux",
"Ubuntu", "Ubuntu",
"Visual Studio Code", "Visual Studio Code",
"git", "Git",
"Docker" "Docker"
], ],
"name": "Logiciels et outils" "name": "Logiciels et outils"
},
{
"keywords": ["Permis B", "Anglais"],
"name": "Autres"
} }
] ]
} }

View File

@ -1,24 +1,21 @@
{ {
"extends": "@tsconfig/strictest/tsconfig.json",
"compilerOptions": { "compilerOptions": {
"target": "ESNext", "target": "ESNext",
"module": "ESNext", "module": "ESNext",
"moduleResolution": "node", "moduleResolution": "node",
"allowJs": true, "allowJs": true,
"checkJs": true,
"jsx": "preserve", "jsx": "preserve",
"sourceMap": true, "sourceMap": true,
"removeComments": true, "removeComments": true,
"noEmit": true, "noEmit": true,
"strict": true,
"types": ["cypress"], "types": ["cypress"],
"baseUrl": ".", "baseUrl": ".",
"esModuleInterop": true, "lib": ["dom", "dom.iterable", "ESNext"],
"forceConsistentCasingInFileNames": true,
"lib": ["dom", "dom.iterable", "esnext"],
"skipLibCheck": true,
"resolveJsonModule": true, "resolveJsonModule": true,
"isolatedModules": true, "isolatedModules": true,
"incremental": true "incremental": true,
"exactOptionalPropertyTypes": false
}, },
"exclude": ["dist", ".next", "out", "next.config.js"], "exclude": ["dist", ".next", "out", "next.config.js"],
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"] "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx"]

View File

@ -38,6 +38,9 @@ export const getPosts = async (): Promise<PostMetadata[]> => {
const postsWithTime = await Promise.all( const postsWithTime = await Promise.all(
posts.map(async (postFilename) => { posts.map(async (postFilename) => {
const [slug, extension] = postFilename.split('.') const [slug, extension] = postFilename.split('.')
if (slug == null || extension == null) {
throw new Error('Invalid postFilename.')
}
const blogPostPath = path.join(POSTS_PATH, `${slug}.${extension}`) const blogPostPath = path.join(POSTS_PATH, `${slug}.${extension}`)
const blogPostContent = await fs.promises.readFile(blogPostPath, { const blogPostContent = await fs.promises.readFile(blogPostPath, {
encoding: 'utf8' encoding: 'utf8'