mirror of
https://github.com/theoludwig/theoludwig.git
synced 2025-05-29 22:37:44 +02:00
Compare commits
8 Commits
Author | SHA1 | Date | |
---|---|---|---|
339e42acfa
|
|||
c123815a86
|
|||
dd26a277a2
|
|||
62222dbb0c
|
|||
ee0a02bc8b
|
|||
2e04053ec3
|
|||
45a9a69122
|
|||
e566ef6c38
|
@ -1,9 +1,9 @@
|
||||
services:
|
||||
workspace:
|
||||
build:
|
||||
context: './'
|
||||
dockerfile: './Dockerfile'
|
||||
context: "./"
|
||||
dockerfile: "./Dockerfile"
|
||||
volumes:
|
||||
- '..:/workspace:cached'
|
||||
command: 'sleep infinity'
|
||||
network_mode: 'host'
|
||||
- "..:/workspace:cached"
|
||||
command: "sleep infinity"
|
||||
network_mode: "host"
|
||||
|
@ -1,4 +1,21 @@
|
||||
build
|
||||
.next
|
||||
coverage
|
||||
node_modules
|
||||
**/.turbo
|
||||
**/.next
|
||||
**/out
|
||||
**/build
|
||||
**/coverage
|
||||
**/node_modules
|
||||
|
||||
# envs
|
||||
.env
|
||||
.env.production
|
||||
.env.development
|
||||
secrets
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
Dockerfile
|
||||
|
||||
# typescript
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
|
@ -1,4 +1,4 @@
|
||||
# For more information see: https://editorconfig.org/
|
||||
# https://editorconfig.org/
|
||||
|
||||
root = true
|
||||
|
||||
|
@ -1,2 +1,4 @@
|
||||
COMPOSE_PROJECT_NAME=theoludwig
|
||||
HOSTNAME=0.0.0.0
|
||||
PORT=3000
|
||||
NEXT_TELEMETRY_DISABLED=1
|
||||
|
8
.github/ISSUE_TEMPLATE/BUG.md
vendored
8
.github/ISSUE_TEMPLATE/BUG.md
vendored
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: '🐛 Bug Report'
|
||||
about: 'Report an unexpected problem or unintended behavior.'
|
||||
title: '[Bug]'
|
||||
labels: 'bug'
|
||||
name: "🐛 Bug Report"
|
||||
about: "Report an unexpected problem or unintended behavior."
|
||||
title: "[Bug]"
|
||||
labels: "bug"
|
||||
---
|
||||
|
||||
<!--
|
||||
|
8
.github/ISSUE_TEMPLATE/DOCUMENTATION.md
vendored
8
.github/ISSUE_TEMPLATE/DOCUMENTATION.md
vendored
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: '📜 Documentation'
|
||||
about: 'Correct spelling errors, improvements or additions to documentation files (README, CONTRIBUTING...).'
|
||||
title: '[Documentation]'
|
||||
labels: 'documentation'
|
||||
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. -->
|
||||
|
8
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
8
.github/ISSUE_TEMPLATE/FEATURE_REQUEST.md
vendored
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: '✨ Feature Request'
|
||||
about: 'Suggest a new feature idea.'
|
||||
title: '[Feature]'
|
||||
labels: 'feature request'
|
||||
name: "✨ Feature Request"
|
||||
about: "Suggest a new feature idea."
|
||||
title: "[Feature]"
|
||||
labels: "feature request"
|
||||
---
|
||||
|
||||
<!-- Please make sure your issue has not already been fixed. -->
|
||||
|
8
.github/ISSUE_TEMPLATE/IMPROVEMENT.md
vendored
8
.github/ISSUE_TEMPLATE/IMPROVEMENT.md
vendored
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: '🔧 Improvement'
|
||||
about: 'Improve structure/format/performance/refactor/tests of the code.'
|
||||
title: '[Improvement]'
|
||||
labels: 'improvement'
|
||||
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. -->
|
||||
|
8
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
8
.github/ISSUE_TEMPLATE/QUESTION.md
vendored
@ -1,8 +1,8 @@
|
||||
---
|
||||
name: '🙋 Question'
|
||||
about: 'Further information is requested.'
|
||||
title: '[Question]'
|
||||
labels: 'question'
|
||||
name: "🙋 Question"
|
||||
about: "Further information is requested."
|
||||
title: "[Question]"
|
||||
labels: "question"
|
||||
---
|
||||
|
||||
### Question
|
||||
|
22
.github/workflows/build.yml
vendored
22
.github/workflows/build.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: 'Build'
|
||||
name: "Build"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -8,18 +8,18 @@ on:
|
||||
|
||||
jobs:
|
||||
build:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4.0.0'
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@v3.8.1'
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.1"
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm clean-install'
|
||||
- name: "Install dependencies"
|
||||
run: "npm clean-install"
|
||||
|
||||
- name: 'Build'
|
||||
run: 'npm run build'
|
||||
- name: "Build"
|
||||
run: "npm run build"
|
||||
|
40
.github/workflows/lint.yml
vendored
40
.github/workflows/lint.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: 'Lint'
|
||||
name: "Lint"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -8,35 +8,35 @@ on:
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4.0.0'
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@v3.8.1'
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.1"
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm clean-install'
|
||||
- name: "Install dependencies"
|
||||
run: "npm clean-install"
|
||||
|
||||
- name: 'lint:commit'
|
||||
- name: "lint:commit"
|
||||
run: 'npm run lint:commit -- --to "${{ github.sha }}"'
|
||||
|
||||
- name: 'lint:editorconfig'
|
||||
run: 'npm run lint:editorconfig'
|
||||
- name: "lint:editorconfig"
|
||||
run: "npm run lint:editorconfig"
|
||||
|
||||
- name: 'lint:markdown'
|
||||
run: 'npm run lint:markdown'
|
||||
- name: "lint:markdown"
|
||||
run: "npm run lint:markdown"
|
||||
|
||||
- name: 'lint:eslint'
|
||||
run: 'npm run lint:eslint'
|
||||
- name: "lint:eslint"
|
||||
run: "npm run lint:eslint"
|
||||
|
||||
- name: 'lint:prettier'
|
||||
run: 'npm run lint:prettier'
|
||||
- name: "lint:prettier"
|
||||
run: "npm run lint:prettier"
|
||||
|
||||
- name: 'lint:dotenv'
|
||||
uses: 'dotenv-linter/action-dotenv-linter@v2.18.0'
|
||||
- name: "lint:dotenv"
|
||||
uses: "dotenv-linter/action-dotenv-linter@v2.18.0"
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
|
26
.github/workflows/release.yml
vendored
26
.github/workflows/release.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: 'Release'
|
||||
name: "Release"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -6,31 +6,31 @@ on:
|
||||
|
||||
jobs:
|
||||
release:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4.0.0'
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
with:
|
||||
fetch-depth: 0
|
||||
persist-credentials: false
|
||||
|
||||
- name: 'Import GPG key'
|
||||
uses: 'crazy-max/ghaction-import-gpg@v6.0.0'
|
||||
- name: "Import GPG key"
|
||||
uses: "crazy-max/ghaction-import-gpg@v6.0.0"
|
||||
with:
|
||||
gpg_private_key: ${{ secrets.GPG_PRIVATE_KEY }}
|
||||
git_user_signingkey: true
|
||||
git_commit_gpgsign: true
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@v3.8.1'
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.1"
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm clean-install'
|
||||
- name: "Install dependencies"
|
||||
run: "npm clean-install"
|
||||
|
||||
- name: 'Release'
|
||||
run: 'npm run release'
|
||||
- name: "Release"
|
||||
run: "npm run release"
|
||||
env:
|
||||
GH_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||
GIT_COMMITTER_NAME: ${{ secrets.GIT_NAME }}
|
||||
|
50
.github/workflows/test.yml
vendored
50
.github/workflows/test.yml
vendored
@ -1,4 +1,4 @@
|
||||
name: 'Test'
|
||||
name: "Test"
|
||||
|
||||
on:
|
||||
push:
|
||||
@ -8,41 +8,41 @@ on:
|
||||
|
||||
jobs:
|
||||
test-unit:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4.0.0'
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@v3.8.1'
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.1"
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm clean-install'
|
||||
- name: "Install dependencies"
|
||||
run: "npm clean-install"
|
||||
|
||||
- name: 'Unit Test'
|
||||
run: 'npm run test:unit'
|
||||
- name: "Unit Test"
|
||||
run: "npm run test:unit"
|
||||
|
||||
test-e2e:
|
||||
runs-on: 'ubuntu-latest'
|
||||
runs-on: "ubuntu-latest"
|
||||
steps:
|
||||
- uses: 'actions/checkout@v4.0.0'
|
||||
- uses: "actions/checkout@v4.1.1"
|
||||
|
||||
- name: 'Setup Node.js'
|
||||
uses: 'actions/setup-node@v3.8.1'
|
||||
- name: "Setup Node.js"
|
||||
uses: "actions/setup-node@v4.0.1"
|
||||
with:
|
||||
node-version: '20.x'
|
||||
cache: 'npm'
|
||||
node-version: "20.x"
|
||||
cache: "npm"
|
||||
|
||||
- name: 'Install dependencies'
|
||||
run: 'npm clean-install'
|
||||
- name: "Install dependencies"
|
||||
run: "npm clean-install"
|
||||
|
||||
- name: 'Build'
|
||||
run: 'npm run build'
|
||||
- name: "Build"
|
||||
run: "npm run build"
|
||||
|
||||
- name: 'html-w3c-validator'
|
||||
run: 'npm run test:html-w3c-validator'
|
||||
- name: "html-w3c-validator"
|
||||
run: "npm run test:html-w3c-validator"
|
||||
|
||||
- name: 'End To End (e2e) Test'
|
||||
run: 'npm run test:e2e'
|
||||
- name: "End To End (e2e) Test"
|
||||
run: "npm run test:e2e"
|
||||
|
10
.gitpod.yml
10
.gitpod.yml
@ -1,13 +1,13 @@
|
||||
image: 'gitpod/workspace-full'
|
||||
image: "gitpod/workspace-full"
|
||||
|
||||
tasks:
|
||||
- before: 'cp .env.example .env'
|
||||
init: 'npm clean-install'
|
||||
command: 'npm run dev'
|
||||
- before: "cp .env.example .env"
|
||||
init: "npm clean-install"
|
||||
command: "npm run dev"
|
||||
|
||||
ports:
|
||||
- port: 3000
|
||||
onOpen: 'open-preview'
|
||||
onOpen: "open-preview"
|
||||
|
||||
github:
|
||||
prebuilds:
|
||||
|
@ -1,6 +1,4 @@
|
||||
{
|
||||
"*": ["editorconfig-checker"],
|
||||
"*.{js,jsx,ts,tsx}": ["prettier --write", "eslint --fix"],
|
||||
"*.{css,scss,sass,json,jsonc,yml,yaml}": ["prettier --write"],
|
||||
"*.{md,mdx}": ["prettier --write", "markdownlint-cli2 --fix"]
|
||||
"**/*": ["prettier --write --ignore-unknown", "editorconfig-checker"],
|
||||
"*.{md,mdx}": ["markdownlint-cli2 --fix"]
|
||||
}
|
||||
|
@ -1,6 +1,3 @@
|
||||
{
|
||||
"singleQuote": true,
|
||||
"jsxSingleQuote": true,
|
||||
"semi": false,
|
||||
"trailingComma": "none"
|
||||
"semi": false
|
||||
}
|
||||
|
2
.vscode/settings.json
vendored
2
.vscode/settings.json
vendored
@ -5,7 +5,7 @@
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode",
|
||||
"editor.formatOnSave": true,
|
||||
"editor.codeActionsOnSave": {
|
||||
"source.fixAll": true
|
||||
"source.fixAll": "explicit"
|
||||
},
|
||||
"eslint.options": {
|
||||
"ignorePath": ".gitignore"
|
||||
|
@ -34,7 +34,7 @@ The commit message guidelines adheres to [Conventional Commits](https://www.conv
|
||||
### Prerequisites
|
||||
|
||||
- [Node.js](https://nodejs.org/) >= 20.0.0
|
||||
- [npm](https://www.npmjs.com/) >= 9.0.0
|
||||
- [npm](https://www.npmjs.com/) >= 10.0.0
|
||||
|
||||
### Installation
|
||||
|
||||
|
25
Dockerfile
25
Dockerfile
@ -1,23 +1,28 @@
|
||||
FROM node:20.6.1 AS builder-dependencies
|
||||
FROM node:20.10.0 AS builder-dependencies
|
||||
WORKDIR /usr/src/application
|
||||
COPY ./package*.json ./
|
||||
RUN npm clean-install
|
||||
|
||||
FROM node:20.6.1 AS builder
|
||||
FROM node:20.10.0 AS builder
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
ENV IS_STANDALONE=true
|
||||
WORKDIR /usr/src/application
|
||||
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
|
||||
COPY ./ ./
|
||||
RUN npm run build
|
||||
|
||||
FROM gcr.io/distroless/nodejs20-debian11:latest AS runner
|
||||
WORKDIR /usr/src/application
|
||||
FROM node:20.10.0-slim AS runner
|
||||
ENV NODE_ENV=production
|
||||
ENV HOSTNAME=0.0.0.0
|
||||
ENV NEXT_TELEMETRY_DISABLED=1
|
||||
COPY --from=builder-dependencies /usr/src/application/node_modules ./node_modules
|
||||
COPY --from=builder /usr/src/application/.next/standalone ./
|
||||
COPY --from=builder /usr/src/application/.next/static ./.next/static
|
||||
COPY --from=builder /usr/src/application/public ./public
|
||||
COPY --from=builder /usr/src/application/i18n/translations ./i18n/translations
|
||||
COPY --from=builder /usr/src/application/next.config.js ./next.config.js
|
||||
ENV IS_STANDALONE=true
|
||||
WORKDIR /usr/src/application
|
||||
RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 applicationrunner
|
||||
USER applicationrunner
|
||||
COPY --from=builder-dependencies --chown=applicationrunner:nodejs /usr/src/application/node_modules ./node_modules
|
||||
COPY --from=builder --chown=applicationrunner:nodejs /usr/src/application/.next/standalone ./
|
||||
COPY --from=builder --chown=applicationrunner:nodejs /usr/src/application/.next/static ./.next/static
|
||||
COPY --from=builder --chown=applicationrunner:nodejs /usr/src/application/public ./public
|
||||
COPY --from=builder --chown=applicationrunner:nodejs /usr/src/application/i18n/translations ./i18n/translations
|
||||
COPY --from=builder --chown=applicationrunner:nodejs /usr/src/application/next.config.js ./next.config.js
|
||||
CMD ["./server.js"]
|
||||
|
@ -40,6 +40,6 @@
|
||||
## 📈 Statistics
|
||||
|
||||
<p align=center>
|
||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=theoludwig&show_icons=true&theme=dark" />
|
||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=theoludwig&hide=html,css,javascript&langs_count=8&layout=compact&theme=dark" />
|
||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api?username=theoludwig&show_icons=true&theme=dark" alt="Théo LUDWIG's GitHub Stats" />
|
||||
<img height=175 align="center" src="https://github-readme-stats.vercel.app/api/top-langs/?username=theoludwig&hide=html,css,javascript&langs_count=8&layout=compact&theme=dark" alt="Théo LUDWIG's Programming Languages" />
|
||||
</p>
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Loader } from '@/components/design/Loader'
|
||||
import { Loader } from "@/components/design/Loader"
|
||||
|
||||
const Loading = (): JSX.Element => {
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center justify-center'>
|
||||
<main className="flex flex-col flex-1 items-center justify-center">
|
||||
<Loader />
|
||||
</main>
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
import type { Metadata } from 'next'
|
||||
import { notFound } from 'next/navigation'
|
||||
import type { Metadata } from "next"
|
||||
import { notFound } from "next/navigation"
|
||||
|
||||
import 'katex/dist/katex.min.css'
|
||||
import "katex/dist/katex.min.css"
|
||||
|
||||
import { getBlogPostBySlug } from '@/blog/blog'
|
||||
import { BlogPost } from '@/blog/BlogPost'
|
||||
import { getBlogPostBySlug } from "@/blog/blog"
|
||||
import { BlogPost } from "@/blog/BlogPost"
|
||||
|
||||
interface BlogPostPageProps {
|
||||
params: {
|
||||
@ -13,7 +13,7 @@ interface BlogPostPageProps {
|
||||
}
|
||||
|
||||
export const generateMetadata = async (
|
||||
props: BlogPostPageProps
|
||||
props: BlogPostPageProps,
|
||||
): Promise<Metadata> => {
|
||||
const blogPost = await getBlogPostBySlug(props.params.slug)
|
||||
if (blogPost == null) {
|
||||
@ -26,12 +26,12 @@ export const generateMetadata = async (
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description
|
||||
description,
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description
|
||||
}
|
||||
description,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Loader } from '@/components/design/Loader'
|
||||
import { Loader } from "@/components/design/Loader"
|
||||
|
||||
const Loading = (): JSX.Element => {
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center justify-center'>
|
||||
<main className="flex flex-col flex-1 items-center justify-center">
|
||||
<Loader />
|
||||
</main>
|
||||
)
|
||||
|
@ -1,36 +1,36 @@
|
||||
import { Suspense } from 'react'
|
||||
import type { Metadata } from 'next'
|
||||
import { Suspense } from "react"
|
||||
import type { Metadata } from "next"
|
||||
|
||||
import { BlogPosts } from '@/blog/BlogPosts'
|
||||
import { Loader } from '@/components/design/Loader'
|
||||
import { BlogPosts } from "@/blog/BlogPosts"
|
||||
import { Loader } from "@/components/design/Loader"
|
||||
|
||||
const title = 'Blog | Théo LUDWIG'
|
||||
const title = "Blog | Théo LUDWIG"
|
||||
const description =
|
||||
'The latest news about my journey of learning computer science.'
|
||||
"The latest news about my journey of learning computer science."
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
description,
|
||||
openGraph: {
|
||||
title,
|
||||
description
|
||||
description,
|
||||
},
|
||||
twitter: {
|
||||
title,
|
||||
description
|
||||
}
|
||||
description,
|
||||
},
|
||||
}
|
||||
|
||||
const BlogPage = async (): Promise<JSX.Element> => {
|
||||
return (
|
||||
<main className='flex flex-1 flex-col flex-wrap items-center'>
|
||||
<div className='mt-10 flex flex-col items-center'>
|
||||
<h1 className='text-4xl font-semibold'>Blog</h1>
|
||||
<p className='mt-6 text-center' data-cy='blog-post-date'>
|
||||
<main className="flex flex-1 flex-col flex-wrap items-center">
|
||||
<div className="mt-10 flex flex-col items-center">
|
||||
<h1 className="text-4xl font-semibold">Blog</h1>
|
||||
<p className="mt-6 text-center" data-cy="blog-post-date">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
<Suspense fallback={<Loader className='mt-8' />}>
|
||||
<Suspense fallback={<Loader className="mt-8" />}>
|
||||
<BlogPosts />
|
||||
</Suspense>
|
||||
</main>
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import { useEffect } from 'react'
|
||||
import { useEffect } from "react"
|
||||
|
||||
export interface ErrorHandlingProps {
|
||||
error: Error
|
||||
@ -14,17 +14,17 @@ const ErrorHandling = (props: ErrorHandlingProps): JSX.Element => {
|
||||
}, [error])
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center justify-center'>
|
||||
<h1 className='my-6 text-4xl font-semibold'>
|
||||
Error{' '}
|
||||
<main className="flex flex-col flex-1 items-center justify-center">
|
||||
<h1 className="my-6 text-4xl font-semibold">
|
||||
Error{" "}
|
||||
<span
|
||||
className='text-yellow dark:text-yellow-dark'
|
||||
data-cy='status-code'
|
||||
className="text-yellow dark:text-yellow-dark"
|
||||
data-cy="status-code"
|
||||
>
|
||||
500
|
||||
</span>
|
||||
</h1>
|
||||
<p className='text-center text-lg'>Server error</p>
|
||||
<p className="text-center text-lg">Server error</p>
|
||||
</main>
|
||||
)
|
||||
}
|
||||
|
@ -16,7 +16,7 @@
|
||||
}
|
||||
|
||||
.prose [id]::before {
|
||||
content: '';
|
||||
content: "";
|
||||
display: block;
|
||||
height: 90px;
|
||||
margin-top: -90px;
|
||||
@ -39,9 +39,9 @@
|
||||
.prose code {
|
||||
color: #ce9178;
|
||||
}
|
||||
.prose :where(code):not(:where([class~='not-prose'] *))::before,
|
||||
.prose :where(code):not(:where([class~='not-prose'] *))::after {
|
||||
content: '';
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::before,
|
||||
.prose :where(code):not(:where([class~="not-prose"] *))::after {
|
||||
content: "";
|
||||
}
|
||||
.shiki {
|
||||
white-space: pre-wrap !important;
|
||||
|
@ -1,21 +1,21 @@
|
||||
import type { Metadata } from 'next'
|
||||
import classNames from 'clsx'
|
||||
import type { Metadata } from "next"
|
||||
import classNames from "clsx"
|
||||
|
||||
import '@fontsource/montserrat/400.css'
|
||||
import '@fontsource/montserrat/600.css'
|
||||
import './globals.css'
|
||||
import "@fontsource/montserrat/400.css"
|
||||
import "@fontsource/montserrat/600.css"
|
||||
import "./globals.css"
|
||||
|
||||
import { Header } from '@/components/Header'
|
||||
import { Footer } from '@/components/Footer'
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getTheme } from '@/theme/theme.server'
|
||||
import { Header } from "@/components/Header"
|
||||
import { Footer } from "@/components/Footer"
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
import { getTheme } from "@/theme/theme.server"
|
||||
|
||||
const title = 'Théo LUDWIG'
|
||||
const title = "Théo LUDWIG"
|
||||
const description =
|
||||
'Théo LUDWIG - Developer Full Stack • Open-Source enthusiast'
|
||||
const image = '/images/icon-96x96.png'
|
||||
const url = new URL('https://theoludwig.fr')
|
||||
const locale = 'fr-FR, en-US'
|
||||
"Théo LUDWIG - Developer Full Stack • Open-Source enthusiast"
|
||||
const image = "/images/icon-96x96.png"
|
||||
const url = new URL("https://theoludwig.fr")
|
||||
const locale = "fr-FR, en-US"
|
||||
|
||||
export const metadata: Metadata = {
|
||||
title,
|
||||
@ -30,21 +30,21 @@ export const metadata: Metadata = {
|
||||
{
|
||||
url: image,
|
||||
width: 96,
|
||||
height: 96
|
||||
}
|
||||
height: 96,
|
||||
},
|
||||
],
|
||||
locale,
|
||||
type: 'website'
|
||||
type: "website",
|
||||
},
|
||||
icons: {
|
||||
icon: '/images/icon-96x96.png'
|
||||
icon: "/images/icon-96x96.png",
|
||||
},
|
||||
twitter: {
|
||||
card: 'summary',
|
||||
card: "summary",
|
||||
title,
|
||||
description,
|
||||
images: [image]
|
||||
}
|
||||
images: [image],
|
||||
},
|
||||
}
|
||||
|
||||
interface RootLayoutProps {
|
||||
@ -61,14 +61,14 @@ const RootLayout = (props: RootLayoutProps): JSX.Element => {
|
||||
<html
|
||||
lang={i18n.locale}
|
||||
className={classNames({
|
||||
dark: theme === 'dark',
|
||||
light: theme === 'light'
|
||||
dark: theme === "dark",
|
||||
light: theme === "light",
|
||||
})}
|
||||
style={{
|
||||
colorScheme: theme
|
||||
colorScheme: theme,
|
||||
}}
|
||||
>
|
||||
<body className='bg-white font-headline text-black dark:bg-black dark:text-white flex flex-col min-h-screen'>
|
||||
<body className="bg-white font-headline text-black dark:bg-black dark:text-white flex flex-col min-h-screen">
|
||||
<Header />
|
||||
{children}
|
||||
<Footer />
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { Loader } from '@/components/design/Loader'
|
||||
import { Loader } from "@/components/design/Loader"
|
||||
|
||||
const Loading = (): JSX.Element => {
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center justify-center'>
|
||||
<main className="flex flex-col flex-1 items-center justify-center">
|
||||
<Loader />
|
||||
</main>
|
||||
)
|
||||
|
@ -1,28 +1,28 @@
|
||||
import Link from 'next/link'
|
||||
import Link from "next/link"
|
||||
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
const NotFound = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<main className='flex flex-col flex-1 items-center justify-center'>
|
||||
<h1 className='my-6 text-4xl font-semibold'>
|
||||
{i18n.translate('errors.error')}{' '}
|
||||
<main className="flex flex-col flex-1 items-center justify-center">
|
||||
<h1 className="my-6 text-4xl font-semibold">
|
||||
{i18n.translate("errors.error")}{" "}
|
||||
<span
|
||||
className='text-yellow dark:text-yellow-dark'
|
||||
data-cy='status-code'
|
||||
className="text-yellow dark:text-yellow-dark"
|
||||
data-cy="status-code"
|
||||
>
|
||||
404
|
||||
</span>
|
||||
</h1>
|
||||
<p className='text-center text-lg'>
|
||||
{i18n.translate('errors.not-found')}{' '}
|
||||
<p className="text-center text-lg">
|
||||
{i18n.translate("errors.not-found")}{" "}
|
||||
<Link
|
||||
href='/'
|
||||
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||
href="/"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
>
|
||||
{i18n.translate('errors.return-to-home-page')}
|
||||
{i18n.translate("errors.return-to-home-page")}
|
||||
</Link>
|
||||
</p>
|
||||
</main>
|
||||
|
36
app/page.tsx
36
app/page.tsx
@ -1,27 +1,27 @@
|
||||
import { RevealFade } from '@/components/design/RevealFade'
|
||||
import { Section } from '@/components/design/Section'
|
||||
import { Interests } from '@/components/Interests'
|
||||
import { Portfolio } from '@/components/Portfolio'
|
||||
import { Profile } from '@/components/Profile'
|
||||
import { SocialMediaList } from '@/components/Profile/SocialMediaList'
|
||||
import { Skills } from '@/components/Skills'
|
||||
import { OpenSource } from '@/components/OpenSource'
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { RevealFade } from "@/components/design/RevealFade"
|
||||
import { Section } from "@/components/design/Section"
|
||||
import { Interests } from "@/components/Interests"
|
||||
import { Portfolio } from "@/components/Portfolio"
|
||||
import { Profile } from "@/components/Profile"
|
||||
import { SocialMediaList } from "@/components/Profile/SocialMediaList"
|
||||
import { Skills } from "@/components/Skills"
|
||||
import { OpenSource } from "@/components/OpenSource"
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
const HomePage = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<main className='flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl'>
|
||||
<Section isMain id='about'>
|
||||
<main className="flex flex-col md:mx-auto md:max-w-4xl lg:max-w-7xl">
|
||||
<Section isMain id="about">
|
||||
<Profile />
|
||||
<SocialMediaList />
|
||||
</Section>
|
||||
|
||||
<RevealFade>
|
||||
<Section
|
||||
id='interests'
|
||||
heading={i18n.translate('home.interests.title')}
|
||||
id="interests"
|
||||
heading={i18n.translate("home.interests.title")}
|
||||
>
|
||||
<Interests />
|
||||
</Section>
|
||||
@ -29,8 +29,8 @@ const HomePage = (): JSX.Element => {
|
||||
|
||||
<RevealFade>
|
||||
<Section
|
||||
id='skills'
|
||||
heading={i18n.translate('home.skills.title')}
|
||||
id="skills"
|
||||
heading={i18n.translate("home.skills.title")}
|
||||
withoutShadowContainer
|
||||
>
|
||||
<Skills />
|
||||
@ -39,8 +39,8 @@ const HomePage = (): JSX.Element => {
|
||||
|
||||
<RevealFade>
|
||||
<Section
|
||||
id='portfolio'
|
||||
heading={i18n.translate('home.portfolio.title')}
|
||||
id="portfolio"
|
||||
heading={i18n.translate("home.portfolio.title")}
|
||||
withoutShadowContainer
|
||||
>
|
||||
<Portfolio />
|
||||
@ -48,7 +48,7 @@ const HomePage = (): JSX.Element => {
|
||||
</RevealFade>
|
||||
|
||||
<RevealFade>
|
||||
<Section id='open-source' heading='Open source' withoutShadowContainer>
|
||||
<Section id="open-source" heading="Open source" withoutShadowContainer>
|
||||
<OpenSource />
|
||||
</Section>
|
||||
</RevealFade>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { notFound } from 'next/navigation'
|
||||
import date from 'date-and-time'
|
||||
import { notFound } from "next/navigation"
|
||||
import date from "date-and-time"
|
||||
|
||||
import 'katex/dist/katex.min.css'
|
||||
import "katex/dist/katex.min.css"
|
||||
|
||||
import { getBlogPostBySlug } from '@/blog/blog'
|
||||
import { BlogPostContent } from '@/blog/BlogPostContent'
|
||||
import { getBlogPostBySlug } from "@/blog/blog"
|
||||
import { BlogPostContent } from "@/blog/BlogPostContent"
|
||||
|
||||
export interface BlogPostProps {
|
||||
slug: string
|
||||
@ -19,13 +19,13 @@ export const BlogPost = async (props: BlogPostProps): Promise<JSX.Element> => {
|
||||
}
|
||||
|
||||
return (
|
||||
<main className='break-wrap-words flex flex-1 flex-col flex-wrap items-center justify-center'>
|
||||
<div className='my-10 flex flex-col items-center text-center'>
|
||||
<h1 className='text-3xl font-semibold'>{blogPost.frontmatter.title}</h1>
|
||||
<p className='mt-2' data-cy='blog-post-date'>
|
||||
<main className="break-wrap-words flex flex-1 flex-col flex-wrap items-center justify-center">
|
||||
<div className="my-10 flex flex-col items-center text-center">
|
||||
<h1 className="text-3xl font-semibold">{blogPost.frontmatter.title}</h1>
|
||||
<p className="mt-2" data-cy="blog-post-date">
|
||||
{date.format(
|
||||
new Date(blogPost.frontmatter.publishedOn),
|
||||
'DD/MM/YYYY'
|
||||
"DD/MM/YYYY",
|
||||
)}
|
||||
</p>
|
||||
</div>
|
||||
|
@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import Giscus from '@giscus/react'
|
||||
import Giscus from "@giscus/react"
|
||||
|
||||
import { useTheme } from '@/theme/theme.client'
|
||||
import type { CookiesStore } from '@/utils/constants'
|
||||
import { useTheme } from "@/theme/theme.client"
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
|
||||
interface BlogPostCommentsProps {
|
||||
cookiesStore: CookiesStore
|
||||
@ -16,18 +16,18 @@ export const BlogPostComments = (props: BlogPostCommentsProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<Giscus
|
||||
id='comments'
|
||||
repo='theoludwig/theoludwig'
|
||||
repoId='MDEwOlJlcG9zaXRvcnkzNTg5NDg1NDQ='
|
||||
category='General'
|
||||
categoryId='DIC_kwDOFWUewM4CQ_WK'
|
||||
mapping='pathname'
|
||||
reactionsEnabled='1'
|
||||
emitMetadata='0'
|
||||
inputPosition='top'
|
||||
id="comments"
|
||||
repo="theoludwig/theoludwig"
|
||||
repoId="MDEwOlJlcG9zaXRvcnkzNTg5NDg1NDQ="
|
||||
category="General"
|
||||
categoryId="DIC_kwDOFWUewM4CQ_WK"
|
||||
mapping="pathname"
|
||||
reactionsEnabled="1"
|
||||
emitMetadata="0"
|
||||
inputPosition="top"
|
||||
theme={theme}
|
||||
lang='en'
|
||||
loading='lazy'
|
||||
lang="en"
|
||||
loading="lazy"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -1,37 +1,37 @@
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { cookies } from 'next/headers'
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import { faLink } from '@fortawesome/free-solid-svg-icons'
|
||||
import { MDXRemote } from 'next-mdx-remote/rsc'
|
||||
import { nodeTypes } from '@mdx-js/mdx'
|
||||
import rehypeRaw from 'rehype-raw'
|
||||
import remarkGfm from 'remark-gfm'
|
||||
import rehypeSlug from 'rehype-slug'
|
||||
import remarkMath from 'remark-math'
|
||||
import rehypeKatex from 'rehype-katex'
|
||||
import { getHighlighter } from 'shiki'
|
||||
import Image from "next/image"
|
||||
import Link from "next/link"
|
||||
import { cookies } from "next/headers"
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import { faLink } from "@fortawesome/free-solid-svg-icons"
|
||||
import { MDXRemote } from "next-mdx-remote/rsc"
|
||||
import { nodeTypes } from "@mdx-js/mdx"
|
||||
import rehypeRaw from "rehype-raw"
|
||||
import remarkGfm from "remark-gfm"
|
||||
import rehypeSlug from "rehype-slug"
|
||||
import remarkMath from "remark-math"
|
||||
import rehypeKatex from "rehype-katex"
|
||||
import { getHighlighter } from "shiki"
|
||||
|
||||
import 'katex/dist/katex.min.css'
|
||||
import "katex/dist/katex.min.css"
|
||||
|
||||
import { getTheme } from '@/theme/theme.server'
|
||||
import { remarkSyntaxHighlightingPlugin } from '@/blog/remarkSyntaxHighlightingPlugin'
|
||||
import { BlogPostComments } from '@/blog/BlogPostComments'
|
||||
import { getTheme } from "@/theme/theme.server"
|
||||
import { remarkSyntaxHighlightingPlugin } from "@/blog/remarkSyntaxHighlightingPlugin"
|
||||
import { BlogPostComments } from "@/blog/BlogPostComments"
|
||||
|
||||
const Heading = (
|
||||
props: React.DetailedHTMLProps<
|
||||
React.HTMLAttributes<HTMLHeadingElement>,
|
||||
HTMLHeadingElement
|
||||
>
|
||||
>,
|
||||
): JSX.Element => {
|
||||
const { children, id = '' } = props
|
||||
const { children, id = "" } = props
|
||||
return (
|
||||
<h2 {...props} className='group'>
|
||||
<h2 {...props} className="group">
|
||||
<Link
|
||||
href={`#${id}`}
|
||||
className='invisible !text-black group-hover:visible dark:!text-white'
|
||||
className="invisible !text-black group-hover:visible dark:!text-white"
|
||||
>
|
||||
<FontAwesomeIcon className='mr-2 inline h-4 w-4' icon={faLink} />
|
||||
<FontAwesomeIcon className="mr-2 inline h-4 w-4" icon={faLink} />
|
||||
</Link>
|
||||
{children}
|
||||
</h2>
|
||||
@ -43,7 +43,7 @@ export interface BlogPostContentProps {
|
||||
}
|
||||
|
||||
export const BlogPostContent = async (
|
||||
props: BlogPostContentProps
|
||||
props: BlogPostContentProps,
|
||||
): Promise<JSX.Element> => {
|
||||
const { content } = props
|
||||
|
||||
@ -51,12 +51,12 @@ export const BlogPostContent = async (
|
||||
const theme = getTheme()
|
||||
|
||||
const highlighter = await getHighlighter({
|
||||
theme: `${theme}-plus`
|
||||
theme: `${theme}-plus`,
|
||||
})
|
||||
|
||||
return (
|
||||
<div className='prose mb-10'>
|
||||
<div className='px-8'>
|
||||
<div className="prose mb-10">
|
||||
<div className="px-8">
|
||||
<MDXRemote
|
||||
source={content}
|
||||
options={{
|
||||
@ -64,14 +64,14 @@ export const BlogPostContent = async (
|
||||
remarkPlugins: [
|
||||
remarkGfm,
|
||||
[remarkSyntaxHighlightingPlugin, { highlighter }],
|
||||
remarkMath
|
||||
remarkMath,
|
||||
],
|
||||
rehypePlugins: [
|
||||
rehypeSlug,
|
||||
[rehypeRaw, { passThrough: nodeTypes }],
|
||||
rehypeKatex
|
||||
]
|
||||
}
|
||||
rehypeKatex,
|
||||
],
|
||||
},
|
||||
}}
|
||||
components={{
|
||||
h1: Heading,
|
||||
@ -81,27 +81,27 @@ export const BlogPostContent = async (
|
||||
h5: Heading,
|
||||
h6: Heading,
|
||||
img: (properties) => {
|
||||
const { src = '', alt = 'Blog Image' } = properties
|
||||
const source = src.replace('../../public/', '/')
|
||||
const { src = "", alt = "Blog Image" } = properties
|
||||
const source = src.replace("../../public/", "/")
|
||||
return (
|
||||
<span className='flex flex-col items-center justify-center'>
|
||||
<span className="flex flex-col items-center justify-center">
|
||||
<Image
|
||||
src={source}
|
||||
alt={alt}
|
||||
width={1000}
|
||||
height={1000}
|
||||
className='h-auto w-auto'
|
||||
className="h-auto w-auto"
|
||||
/>
|
||||
</span>
|
||||
)
|
||||
},
|
||||
a: (props) => {
|
||||
const { href = '' } = props
|
||||
if (href.startsWith('#')) {
|
||||
const { href = "" } = props
|
||||
if (href.startsWith("#")) {
|
||||
return <a {...props} />
|
||||
}
|
||||
return <a target='_blank' rel='noopener noreferrer' {...props} />
|
||||
}
|
||||
return <a target="_blank" rel="noopener noreferrer" {...props} />
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<BlogPostComments cookiesStore={cookiesStore.toString()} />
|
||||
|
@ -1,35 +1,35 @@
|
||||
import Link from 'next/link'
|
||||
import date from 'date-and-time'
|
||||
import Link from "next/link"
|
||||
import date from "date-and-time"
|
||||
|
||||
import { ShadowContainer } from '@/components/design/ShadowContainer'
|
||||
import { getBlogPosts } from '@/blog/blog'
|
||||
import { ShadowContainer } from "@/components/design/ShadowContainer"
|
||||
import { getBlogPosts } from "@/blog/blog"
|
||||
|
||||
export const BlogPosts = async (): Promise<JSX.Element> => {
|
||||
const posts = await getBlogPosts()
|
||||
|
||||
return (
|
||||
<div className='flex w-full items-center justify-center p-8'>
|
||||
<div className='w-[1600px]' data-cy='blog-posts'>
|
||||
<div className="flex w-full items-center justify-center p-8">
|
||||
<div className="w-[1600px]" data-cy="blog-posts">
|
||||
{posts.map((post, index) => {
|
||||
const postPublishedOn = date.format(
|
||||
new Date(post.frontmatter.publishedOn),
|
||||
'DD/MM/YYYY'
|
||||
"DD/MM/YYYY",
|
||||
)
|
||||
return (
|
||||
<Link
|
||||
href={`/blog/${post.slug}`}
|
||||
key={index}
|
||||
locale='en'
|
||||
locale="en"
|
||||
data-cy={post.slug}
|
||||
>
|
||||
<ShadowContainer className='cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2'>
|
||||
<h2 data-cy='blog-post-title' className='text-xl font-semibold'>
|
||||
<ShadowContainer className="cursor-pointer p-6 transition duration-200 ease-in-out hover:-translate-y-2">
|
||||
<h2 data-cy="blog-post-title" className="text-xl font-semibold">
|
||||
{post.frontmatter.title}
|
||||
</h2>
|
||||
<p data-cy='blog-post-date' className='mt-2'>
|
||||
<p data-cy="blog-post-date" className="mt-2">
|
||||
{postPublishedOn}
|
||||
</p>
|
||||
<p data-cy='blog-post-description' className='mt-3'>
|
||||
<p data-cy="blog-post-description" className="mt-3">
|
||||
{post.frontmatter.description}
|
||||
</p>
|
||||
</ShadowContainer>
|
||||
|
22
blog/blog.ts
22
blog/blog.ts
@ -1,10 +1,10 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
import { cache } from 'react'
|
||||
import matter from 'gray-matter'
|
||||
import { cache } from "react"
|
||||
import matter from "gray-matter"
|
||||
|
||||
export const BLOG_POSTS_PATH = path.join(process.cwd(), 'blog', 'posts')
|
||||
export const BLOG_POSTS_PATH = path.join(process.cwd(), "blog", "posts")
|
||||
|
||||
export interface FrontMatter {
|
||||
title: string
|
||||
@ -23,13 +23,13 @@ export const getBlogPosts = cache(async (): Promise<BlogPost[]> => {
|
||||
const blogPosts = await fs.promises.readdir(BLOG_POSTS_PATH)
|
||||
const blogPostsWithTime = await Promise.all(
|
||||
blogPosts.map(async (blogPostFilename) => {
|
||||
const [slug, extension] = blogPostFilename.split('.')
|
||||
const [slug, extension] = blogPostFilename.split(".")
|
||||
if (slug == null || extension == null) {
|
||||
throw new Error('Invalid blog post filename.')
|
||||
throw new Error("Invalid blog post filename.")
|
||||
}
|
||||
const blogPostPath = path.join(BLOG_POSTS_PATH, `${slug}.${extension}`)
|
||||
const blogPostContent = await fs.promises.readFile(blogPostPath, {
|
||||
encoding: 'utf8'
|
||||
encoding: "utf8",
|
||||
})
|
||||
const { data, content } = matter(blogPostContent) as unknown as {
|
||||
data: FrontMatter
|
||||
@ -40,9 +40,9 @@ export const getBlogPosts = cache(async (): Promise<BlogPost[]> => {
|
||||
slug,
|
||||
content,
|
||||
frontmatter: data,
|
||||
time: date.getTime()
|
||||
time: date.getTime(),
|
||||
}
|
||||
})
|
||||
}),
|
||||
)
|
||||
const blogPostsSortedByPublicationDate = blogPostsWithTime
|
||||
.filter((post) => {
|
||||
@ -61,5 +61,5 @@ export const getBlogPostBySlug = cache(
|
||||
return blogPost.slug === slug && blogPost.frontmatter.isPublished
|
||||
})
|
||||
return blogPost
|
||||
}
|
||||
},
|
||||
)
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '🧼 Clean Code'
|
||||
title: "🧼 Clean Code"
|
||||
description: 'What is "Clean Code", what are "Design Patterns", and why is it so important today? Tips and tricks to make your code more readable and maintainable in the long term.'
|
||||
isPublished: true
|
||||
publishedOn: '2022-02-23T08:00:18.758Z'
|
||||
publishedOn: "2022-02-23T08:00:18.758Z"
|
||||
---
|
||||
|
||||
Hello! 👋
|
||||
@ -110,7 +110,7 @@ const transaction = charge(user, subscription)
|
||||
```typescript
|
||||
interface Car {
|
||||
carModel: string
|
||||
carColor: 'red' | 'blue' | 'yellow'
|
||||
carColor: "red" | "blue" | "yellow"
|
||||
}
|
||||
const printCar = (car: Car): void => {
|
||||
console.log(`${car.carModel} (${car.carColor})`)
|
||||
@ -122,7 +122,7 @@ const printCar = (car: Car): void => {
|
||||
```typescript
|
||||
interface Car {
|
||||
model: string
|
||||
color: 'red' | 'blue' | 'yellow'
|
||||
color: "red" | "blue" | "yellow"
|
||||
}
|
||||
const printCar = (car: Car): void => {
|
||||
console.log(`${car.model} (${car.color})`)
|
||||
@ -170,17 +170,17 @@ We have to keep it as simple as possible, not to implement features that are not
|
||||
### Example (bad way)
|
||||
|
||||
```typescript
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const createFile = async (
|
||||
name: string,
|
||||
isTemporary: boolean = false
|
||||
isTemporary: boolean = false,
|
||||
): Promise<void> => {
|
||||
if (isTemporary) {
|
||||
return await fs.promises.writeFile(path.join('temporary', name), '')
|
||||
return await fs.promises.writeFile(path.join("temporary", name), "")
|
||||
}
|
||||
return await fs.promises.writeFile(name, '')
|
||||
return await fs.promises.writeFile(name, "")
|
||||
}
|
||||
```
|
||||
|
||||
@ -189,15 +189,15 @@ const createFile = async (
|
||||
### Example (good way)
|
||||
|
||||
```typescript
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import fs from "node:fs"
|
||||
import path from "node:path"
|
||||
|
||||
const createFile = async (name: string): Promise<void> => {
|
||||
await fs.promises.writeFile(name, '')
|
||||
await fs.promises.writeFile(name, "")
|
||||
}
|
||||
|
||||
const createTemporaryFile = async (name: string): Promise<void> => {
|
||||
await createFile(path.join('temporary', name))
|
||||
await createFile(path.join("temporary", name))
|
||||
}
|
||||
```
|
||||
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '🗓️ Git version control: Ultimate Guide'
|
||||
description: 'What is `git`, what are the most used commands, best practices, and tips and tricks. The Ultimate guide to master `git` in your daily workflow.'
|
||||
title: "🗓️ Git version control: Ultimate Guide"
|
||||
description: "What is `git`, what are the most used commands, best practices, and tips and tricks. The Ultimate guide to master `git` in your daily workflow."
|
||||
isPublished: true
|
||||
publishedOn: '2022-10-27T14:33:07.465Z'
|
||||
publishedOn: "2022-10-27T14:33:07.465Z"
|
||||
---
|
||||
|
||||
Hello! 👋
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '👋 Hello, world!'
|
||||
description: 'First post of the blog, introduction and explanation of how this blog is made.'
|
||||
title: "👋 Hello, world!"
|
||||
description: "First post of the blog, introduction and explanation of how this blog is made."
|
||||
isPublished: true
|
||||
publishedOn: '2022-02-20T08:00:18.758Z'
|
||||
publishedOn: "2022-02-20T08:00:18.758Z"
|
||||
---
|
||||
|
||||
Hello, world! 👋
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '❌ Mistakes I made as a junior developer'
|
||||
description: 'Here are mistakes I made when I started, to prevent you from making the same mistakes.'
|
||||
title: "❌ Mistakes I made as a junior developer"
|
||||
description: "Here are mistakes I made when I started, to prevent you from making the same mistakes."
|
||||
isPublished: true
|
||||
publishedOn: '2022-03-14T07:42:52.989Z'
|
||||
publishedOn: "2022-03-14T07:42:52.989Z"
|
||||
---
|
||||
|
||||
Hello! 👋
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '🧠 Programming Challenges'
|
||||
description: 'What are Programming Challenges and Competitive Programming and an introduction to Time/Space Complexity with Big O Notation.'
|
||||
title: "🧠 Programming Challenges"
|
||||
description: "What are Programming Challenges and Competitive Programming and an introduction to Time/Space Complexity with Big O Notation."
|
||||
isPublished: true
|
||||
publishedOn: '2023-05-21T10:20:18.837Z'
|
||||
publishedOn: "2023-05-21T10:20:18.837Z"
|
||||
---
|
||||
|
||||
Hello! 👋
|
||||
|
@ -1,8 +1,8 @@
|
||||
---
|
||||
title: '🟢 Thream v1.0.0'
|
||||
description: 'Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun.'
|
||||
title: "🟢 Thream v1.0.0"
|
||||
description: "Your open source platform to stay close with your friends and communities, talk, chat, collaborate, share and have fun."
|
||||
isPublished: true
|
||||
publishedOn: '2022-04-11T10:24:55.206Z'
|
||||
publishedOn: "2022-04-11T10:24:55.206Z"
|
||||
---
|
||||
|
||||
Hello! 👋
|
||||
|
@ -1,7 +1,7 @@
|
||||
import type { Plugin, Transformer } from 'unified'
|
||||
import type { Literal, Node } from 'unist'
|
||||
import { visit } from 'unist-util-visit'
|
||||
import type { Highlighter } from 'shiki'
|
||||
import type { Plugin, Transformer } from "unified"
|
||||
import type { Literal, Node } from "unist"
|
||||
import { visit } from "unist-util-visit"
|
||||
import type { Highlighter } from "shiki"
|
||||
|
||||
export interface RemarkSyntaxHighlightingPluginOptions {
|
||||
highlighter: Highlighter
|
||||
@ -20,11 +20,11 @@ export const remarkSyntaxHighlightingPlugin: Plugin<
|
||||
Literal
|
||||
> = (options) => {
|
||||
const transformer: Transformer<RemarkSyntaxHighlightingNode> = (tree) => {
|
||||
visit<RemarkSyntaxHighlightingNode, string>(tree, 'code', (node) => {
|
||||
node.type = 'html'
|
||||
visit<RemarkSyntaxHighlightingNode, string>(tree, "code", (node) => {
|
||||
node.type = "html"
|
||||
node.children = undefined
|
||||
node.value = options.highlighter.codeToHtml(node.value, {
|
||||
lang: node.lang
|
||||
lang: node.lang,
|
||||
})
|
||||
})
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Link from 'next/link'
|
||||
import Link from "next/link"
|
||||
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
export const FooterText = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
@ -8,12 +8,12 @@ export const FooterText = (): JSX.Element => {
|
||||
return (
|
||||
<p>
|
||||
<Link
|
||||
href='/'
|
||||
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||
href="/"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
>
|
||||
Théo LUDWIG
|
||||
</Link>{' '}
|
||||
| {i18n.translate('common.all-rights-reserved')}
|
||||
</Link>{" "}
|
||||
| {i18n.translate("common.all-rights-reserved")}
|
||||
</p>
|
||||
)
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo } from "react"
|
||||
|
||||
interface FooterVersionProps {
|
||||
version: string
|
||||
@ -12,14 +12,14 @@ export const FooterVersion = (props: FooterVersionProps): JSX.Element => {
|
||||
}, [version])
|
||||
|
||||
return (
|
||||
<p className='mt-1'>
|
||||
Version{' '}
|
||||
<p className="mt-1">
|
||||
Version{" "}
|
||||
<a
|
||||
data-cy='version-link'
|
||||
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||
data-cy="version-link"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
href={versionLink}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{version}
|
||||
</a>
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { FooterText } from './FooterText'
|
||||
import { FooterVersion } from './FooterVersion'
|
||||
import { FooterText } from "./FooterText"
|
||||
import { FooterVersion } from "./FooterVersion"
|
||||
|
||||
export const Footer = async (): Promise<JSX.Element> => {
|
||||
const { readPackage } = await import('read-pkg')
|
||||
const { readPackage } = await import("read-pkg")
|
||||
const { version } = await readPackage()
|
||||
|
||||
return (
|
||||
<footer className='flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black'>
|
||||
<footer className="flex flex-col items-center justify-center border-t-2 border-gray-600 bg-white py-6 text-lg dark:border-gray-400 dark:bg-black">
|
||||
<FooterText />
|
||||
<FooterVersion version={version} />
|
||||
</footer>
|
||||
|
@ -1,15 +1,15 @@
|
||||
export const Arrow = (): JSX.Element => {
|
||||
return (
|
||||
<svg
|
||||
width='12'
|
||||
height='8'
|
||||
viewBox='0 0 12 8'
|
||||
fill='none'
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
width="12"
|
||||
height="8"
|
||||
viewBox="0 0 12 8"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
className='fill-current text-black dark:text-white'
|
||||
d='M9.8024 0.292969L5.61855 4.58597L1.43469 0.292969L0.0566406 1.70697L5.61855 7.41397L11.1805 1.70697L9.8024 0.292969Z'
|
||||
className="fill-current text-black dark:text-white"
|
||||
d="M9.8024 0.292969L5.61855 4.58597L1.43469 0.292969L0.0566406 1.70697L5.61855 7.41397L11.1805 1.70697L9.8024 0.292969Z"
|
||||
/>
|
||||
</svg>
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import Image from 'next/image'
|
||||
import Image from "next/image"
|
||||
|
||||
import type { CookiesStore } from '@/utils/constants'
|
||||
import { useI18n } from '@/i18n/i18n.client'
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
import { useI18n } from "@/i18n/i18n.client"
|
||||
|
||||
export interface LocaleFlagProps {
|
||||
locale: string
|
||||
@ -22,7 +22,7 @@ export const LocaleFlag = (props: LocaleFlagProps): JSX.Element => {
|
||||
src={`/images/locales/${locale}.svg`}
|
||||
alt={locale}
|
||||
/>
|
||||
<p data-cy='locale-flag-text' className='mx-2 text-base'>
|
||||
<p data-cy="locale-flag-text" className="mx-2 text-base">
|
||||
{i18n.translate(`common.${locale}`)}
|
||||
</p>
|
||||
</>
|
||||
|
@ -1,14 +1,14 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import { usePathname } from 'next/navigation'
|
||||
import { useCallback, useEffect, useState, useRef } from 'react'
|
||||
import classNames from 'clsx'
|
||||
import { usePathname } from "next/navigation"
|
||||
import { useCallback, useEffect, useState, useRef } from "react"
|
||||
import classNames from "clsx"
|
||||
|
||||
import type { Locale as LocaleType, CookiesStore } from '@/utils/constants'
|
||||
import { LOCALES } from '@/utils/constants'
|
||||
import type { Locale as LocaleType, CookiesStore } from "@/utils/constants"
|
||||
import { LOCALES } from "@/utils/constants"
|
||||
|
||||
import { Arrow } from './Arrow'
|
||||
import { LocaleFlag } from './LocaleFlag'
|
||||
import { Arrow } from "./Arrow"
|
||||
import { LocaleFlag } from "./LocaleFlag"
|
||||
|
||||
export interface LocalesProps {
|
||||
currentLocale: string
|
||||
@ -38,28 +38,28 @@ export const Locales = (props: LocalesProps): JSX.Element => {
|
||||
}
|
||||
}
|
||||
|
||||
window.document.addEventListener('click', handleClickEvent)
|
||||
window.document.addEventListener("click", handleClickEvent)
|
||||
|
||||
return () => {
|
||||
return window.removeEventListener('click', handleClickEvent)
|
||||
return window.removeEventListener("click", handleClickEvent)
|
||||
}
|
||||
}, [])
|
||||
|
||||
const handleLocale = async (locale: LocaleType): Promise<void> => {
|
||||
const { setLocale } = await import('@/i18n/i18n.server')
|
||||
const { setLocale } = await import("@/i18n/i18n.server")
|
||||
setLocale(locale)
|
||||
}
|
||||
|
||||
if (pathname.startsWith('/blog')) {
|
||||
if (pathname.startsWith("/blog")) {
|
||||
return <></>
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex cursor-pointer flex-col items-center justify-center'>
|
||||
<div className="flex cursor-pointer flex-col items-center justify-center">
|
||||
<div
|
||||
ref={languageClickRef}
|
||||
data-cy='locale-click'
|
||||
className='mr-5 flex items-center'
|
||||
data-cy="locale-click"
|
||||
className="mr-5 flex items-center"
|
||||
onClick={handleHiddenMenu}
|
||||
>
|
||||
<LocaleFlag
|
||||
@ -70,10 +70,10 @@ export const Locales = (props: LocalesProps): JSX.Element => {
|
||||
</div>
|
||||
|
||||
<ul
|
||||
data-cy='locales-list'
|
||||
data-cy="locales-list"
|
||||
className={classNames(
|
||||
'absolute top-14 z-10 mr-4 mt-3 flex w-32 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-lightFlag dark:bg-black dark:shadow-darkFlag',
|
||||
{ hidden: hiddenMenu }
|
||||
"absolute top-14 z-10 mr-4 mt-3 flex w-32 list-none flex-col items-center justify-center rounded-lg bg-white p-0 shadow-lightFlag dark:bg-black dark:shadow-darkFlag",
|
||||
{ hidden: hiddenMenu },
|
||||
)}
|
||||
>
|
||||
{LOCALES.filter((locale) => {
|
||||
@ -82,7 +82,7 @@ export const Locales = (props: LocalesProps): JSX.Element => {
|
||||
return (
|
||||
<li
|
||||
key={locale}
|
||||
className='flex h-12 w-full items-center justify-center hover:bg-[#4f545c] hover:bg-opacity-20'
|
||||
className="flex h-12 w-full items-center justify-center hover:bg-[#4f545c] hover:bg-opacity-20"
|
||||
onClick={async () => {
|
||||
return await handleLocale(locale)
|
||||
}}
|
||||
|
@ -1,9 +1,9 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import classNames from 'clsx'
|
||||
import classNames from "clsx"
|
||||
|
||||
import { useTheme } from '@/theme/theme.client'
|
||||
import type { CookiesStore } from '@/utils/constants'
|
||||
import { useTheme } from "@/theme/theme.client"
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
|
||||
export interface SwitchThemeProps {
|
||||
cookiesStore: CookiesStore
|
||||
@ -14,63 +14,63 @@ export const SwitchTheme = (props: SwitchThemeProps): JSX.Element => {
|
||||
const theme = useTheme(cookiesStore)
|
||||
|
||||
const handleClick = async (): Promise<void> => {
|
||||
const { setTheme } = await import('@/theme/theme.server')
|
||||
const newTheme = theme === 'dark' ? 'light' : 'dark'
|
||||
const { setTheme } = await import("@/theme/theme.server")
|
||||
const newTheme = theme === "dark" ? "light" : "dark"
|
||||
setTheme(newTheme)
|
||||
}
|
||||
|
||||
return (
|
||||
<div
|
||||
className='flex items-center'
|
||||
data-cy='switch-theme-click'
|
||||
className="flex items-center"
|
||||
data-cy="switch-theme-click"
|
||||
onClick={handleClick}
|
||||
>
|
||||
<div className='relative inline-block cursor-pointer touch-pan-x select-none border-0 bg-transparent p-0'>
|
||||
<div className='h-[24px] w-[50px] rounded-[30px] bg-[#4d4d4d] p-0 text-white transition-all duration-200 ease-in-out'>
|
||||
<div className="relative inline-block cursor-pointer touch-pan-x select-none border-0 bg-transparent p-0">
|
||||
<div className="h-[24px] w-[50px] rounded-[30px] bg-[#4d4d4d] p-0 text-white transition-all duration-200 ease-in-out">
|
||||
<div
|
||||
data-cy='switch-theme-dark'
|
||||
data-cy="switch-theme-dark"
|
||||
className={classNames(
|
||||
'absolute bottom-0 left-[8px] top-0 mb-auto mt-auto h-[10px] w-[14px] leading-[0] transition-opacity duration-[250ms] ease-in-out',
|
||||
"absolute bottom-0 left-[8px] top-0 mb-auto mt-auto h-[10px] w-[14px] leading-[0] transition-opacity duration-[250ms] ease-in-out",
|
||||
{
|
||||
'opacity-100': theme === 'dark',
|
||||
'opacity-0': theme === 'light'
|
||||
}
|
||||
"opacity-100": theme === "dark",
|
||||
"opacity-0": theme === "light",
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span className='relative flex h-[10px] w-[10px] items-center justify-center'>
|
||||
<span className="relative flex h-[10px] w-[10px] items-center justify-center">
|
||||
🌜
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
data-cy='switch-theme-light'
|
||||
data-cy="switch-theme-light"
|
||||
className={classNames(
|
||||
'absolute bottom-0 right-[10px] top-0 mb-auto mt-auto h-[10px] w-[10px] leading-[0]',
|
||||
"absolute bottom-0 right-[10px] top-0 mb-auto mt-auto h-[10px] w-[10px] leading-[0]",
|
||||
{
|
||||
'opacity-100': theme === 'light',
|
||||
'opacity-0': theme === 'dark'
|
||||
}
|
||||
"opacity-100": theme === "light",
|
||||
"opacity-0": theme === "dark",
|
||||
},
|
||||
)}
|
||||
>
|
||||
<span className='relative flex h-[10px] w-[10px] items-center justify-center'>
|
||||
<span className="relative flex h-[10px] w-[10px] items-center justify-center">
|
||||
🌞
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className={classNames(
|
||||
'absolute top-[1px] box-border h-[22px] w-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out',
|
||||
"absolute top-[1px] box-border h-[22px] w-[22px] rounded-[50%] bg-[#fafafa] text-white transition-all duration-[250ms] ease-in-out",
|
||||
{
|
||||
'left-[27px]': theme === 'dark',
|
||||
'left-0': theme === 'light'
|
||||
}
|
||||
"left-[27px]": theme === "dark",
|
||||
"left-0": theme === "light",
|
||||
},
|
||||
)}
|
||||
style={{ border: '1px solid #4d4d4d' }}
|
||||
style={{ border: "1px solid #4d4d4d" }}
|
||||
/>
|
||||
<input
|
||||
data-cy='switch-theme-input'
|
||||
type='checkbox'
|
||||
aria-label='Dark mode toggle'
|
||||
className='absolute m-[-1px] h-[1px] w-[1px] overflow-hidden border-0 p-0 hidden'
|
||||
data-cy="switch-theme-input"
|
||||
type="checkbox"
|
||||
aria-label="Dark mode toggle"
|
||||
className="absolute m-[-1px] h-[1px] w-[1px] overflow-hidden border-0 p-0 hidden"
|
||||
defaultChecked
|
||||
/>
|
||||
</div>
|
||||
|
@ -1,39 +1,39 @@
|
||||
import { cookies } from 'next/headers'
|
||||
import Link from 'next/link'
|
||||
import Image from 'next/image'
|
||||
import { cookies } from "next/headers"
|
||||
import Link from "next/link"
|
||||
import Image from "next/image"
|
||||
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
import { Locales } from './Locales'
|
||||
import { SwitchTheme } from './SwitchTheme'
|
||||
import { Locales } from "./Locales"
|
||||
import { SwitchTheme } from "./SwitchTheme"
|
||||
|
||||
export const Header = (): JSX.Element => {
|
||||
const cookiesStore = cookies()
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<header className='sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black'>
|
||||
<Link href='/'>
|
||||
<div className='flex items-center justify-center'>
|
||||
<header className="sticky top-0 z-50 flex w-full justify-between border-b-2 border-gray-600 bg-white px-6 py-2 dark:border-gray-400 dark:bg-black">
|
||||
<Link href="/">
|
||||
<div className="flex items-center justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
width={60}
|
||||
height={60}
|
||||
src='/images/icon_small.png'
|
||||
alt='Théo LUDWIG'
|
||||
src="/images/icon_small.png"
|
||||
alt="Théo LUDWIG"
|
||||
priority
|
||||
/>
|
||||
<strong className='ml-1 hidden font-headline font-semibold text-yellow dark:text-yellow-dark xs:block'>
|
||||
<strong className="ml-1 hidden font-headline font-semibold text-yellow dark:text-yellow-dark xs:block">
|
||||
Théo LUDWIG
|
||||
</strong>
|
||||
</div>
|
||||
</Link>
|
||||
<div className='flex justify-between'>
|
||||
<div className='flex flex-col items-center justify-center px-6'>
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col items-center justify-center px-6">
|
||||
<Link
|
||||
href='/blog'
|
||||
data-cy='header-blog-link'
|
||||
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||
href="/blog"
|
||||
data-cy="header-blog-link"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
>
|
||||
Blog
|
||||
</Link>
|
||||
|
@ -1,4 +1,4 @@
|
||||
import htmlParser from 'html-react-parser'
|
||||
import htmlParser from "html-react-parser"
|
||||
|
||||
export interface InterestParagraphProps {
|
||||
title: string
|
||||
@ -6,14 +6,14 @@ export interface InterestParagraphProps {
|
||||
}
|
||||
|
||||
export const InterestParagraph = (
|
||||
props: InterestParagraphProps
|
||||
props: InterestParagraphProps,
|
||||
): JSX.Element => {
|
||||
const { title, description } = props
|
||||
|
||||
return (
|
||||
<>
|
||||
<p className='my-6 text-center text-gray dark:text-gray-dark'>
|
||||
<strong className='text-lg font-semibold text-yellow dark:text-yellow-dark'>
|
||||
<p className="my-6 text-center text-gray dark:text-gray-dark">
|
||||
<strong className="text-lg font-semibold text-yellow dark:text-yellow-dark">
|
||||
{title}
|
||||
</strong>
|
||||
<br />
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'
|
||||
import type { IconDefinition } from '@fortawesome/free-solid-svg-icons'
|
||||
import { FontAwesomeIcon } from "@fortawesome/react-fontawesome"
|
||||
import type { IconDefinition } from "@fortawesome/free-solid-svg-icons"
|
||||
|
||||
interface InterestItemProps {
|
||||
title: string
|
||||
@ -10,9 +10,9 @@ export const InterestItem = (props: InterestItemProps): JSX.Element => {
|
||||
const { fontAwesomeIcon, title } = props
|
||||
|
||||
return (
|
||||
<li className='interest-item mx-2 my-2 h-8 w-8' title={title}>
|
||||
<li className="interest-item mx-2 my-2 h-8 w-8" title={title}>
|
||||
<FontAwesomeIcon
|
||||
className='block h-full w-full text-yellow dark:text-yellow-dark'
|
||||
className="block h-full w-full text-yellow dark:text-yellow-dark"
|
||||
icon={fontAwesomeIcon}
|
||||
/>
|
||||
</li>
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { faCode, faMicrochip } from '@fortawesome/free-solid-svg-icons'
|
||||
import { faGit } from '@fortawesome/free-brands-svg-icons'
|
||||
import { faCode, faMicrochip } from "@fortawesome/free-solid-svg-icons"
|
||||
import { faGit } from "@fortawesome/free-brands-svg-icons"
|
||||
|
||||
import { InterestItem } from './InterestItem'
|
||||
import { InterestItem } from "./InterestItem"
|
||||
|
||||
export const InterestsList = (): JSX.Element => {
|
||||
return (
|
||||
<div className='my-4 flex justify-center'>
|
||||
<ul className='m-0 flex w-96 list-none justify-around p-0'>
|
||||
<InterestItem title='Developer Full Stack' fontAwesomeIcon={faCode} />
|
||||
<div className="my-4 flex justify-center">
|
||||
<ul className="m-0 flex w-96 list-none justify-around p-0">
|
||||
<InterestItem title="Developer Full Stack" fontAwesomeIcon={faCode} />
|
||||
<InterestItem
|
||||
title='Passionate about High-Tech'
|
||||
title="Passionate about High-Tech"
|
||||
fontAwesomeIcon={faMicrochip}
|
||||
/>
|
||||
<InterestItem title='Open-Source enthusiast' fontAwesomeIcon={faGit} />
|
||||
<InterestItem title="Open-Source enthusiast" fontAwesomeIcon={faGit} />
|
||||
</ul>
|
||||
</div>
|
||||
)
|
||||
|
@ -1,21 +1,21 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
import type { InterestParagraphProps } from './InterestParagraph'
|
||||
import { InterestParagraph } from './InterestParagraph'
|
||||
import { InterestsList } from './InterestsList'
|
||||
import type { InterestParagraphProps } from "./InterestParagraph"
|
||||
import { InterestParagraph } from "./InterestParagraph"
|
||||
import { InterestsList } from "./InterestsList"
|
||||
|
||||
export const Interests = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
let paragraphs = i18n.translate<InterestParagraphProps[]>(
|
||||
'home.interests.paragraphs'
|
||||
"home.interests.paragraphs",
|
||||
)
|
||||
if (!Array.isArray(paragraphs)) {
|
||||
paragraphs = []
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='max-w-full'>
|
||||
<div className="max-w-full">
|
||||
{paragraphs.map((paragraph, index) => {
|
||||
return <InterestParagraph key={index} {...paragraph} />
|
||||
})}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ShadowContainer } from '@/components/design/ShadowContainer'
|
||||
import { GitHubIcon } from '@/components/Profile/SocialMediaList/SocialMediaIcons/GitHubIcon'
|
||||
import { ShadowContainer } from "@/components/design/ShadowContainer"
|
||||
import { GitHubIcon } from "@/components/Profile/SocialMediaList/SocialMediaIcons/GitHubIcon"
|
||||
|
||||
export interface RepositoryProps {
|
||||
name: string
|
||||
@ -11,13 +11,13 @@ export const Repository = (props: RepositoryProps): JSX.Element => {
|
||||
const { name, description, href } = props
|
||||
|
||||
return (
|
||||
<ShadowContainer className='relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2'>
|
||||
<a href={href} target='_blank' rel='noopener noreferrer'>
|
||||
<div className='flex'>
|
||||
<GitHubIcon className='mr-2 h-6' />
|
||||
<span className='text-yellow dark:text-yellow-dark'>{name}</span>
|
||||
<ShadowContainer className="relative !mb-4 max-h-32 cursor-pointer p-6 transition-transform duration-200 ease-in-out hover:-translate-y-2">
|
||||
<a href={href} target="_blank" rel="noopener noreferrer">
|
||||
<div className="flex">
|
||||
<GitHubIcon className="mr-2 h-6" />
|
||||
<span className="text-yellow dark:text-yellow-dark">{name}</span>
|
||||
</div>
|
||||
<p className='my-4'>{description}</p>
|
||||
<p className="my-4">{description}</p>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
)
|
||||
|
@ -1,35 +1,35 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
import { Repository } from './Repository'
|
||||
import { Repository } from "./Repository"
|
||||
|
||||
export const OpenSource = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<div className='mt-0 flex max-w-full flex-col items-center'>
|
||||
<p className='text-center'>
|
||||
{i18n.translate('home.open-source.description')}
|
||||
<div className="mt-0 flex max-w-full flex-col items-center">
|
||||
<p className="text-center">
|
||||
{i18n.translate("home.open-source.description")}
|
||||
</p>
|
||||
<div className='my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2'>
|
||||
<div className="my-6 grid grid-cols-1 gap-6 md:w-10/12 md:grid-cols-2">
|
||||
<Repository
|
||||
name='nodejs/node'
|
||||
description='Node.js JavaScript runtime ✨🐢🚀✨'
|
||||
href='https://github.com/nodejs/node/commits?author=theoludwig'
|
||||
name="nodejs/node"
|
||||
description="Node.js JavaScript runtime ✨🐢🚀✨"
|
||||
href="https://github.com/nodejs/node/commits?author=theoludwig"
|
||||
/>
|
||||
<Repository
|
||||
name='standard/standard'
|
||||
description='🌟 JavaScript Style Guide, with linter & automatic code fixer'
|
||||
href='https://github.com/standard/standard/commits?author=theoludwig'
|
||||
name="standard/standard"
|
||||
description="🌟 JavaScript Style Guide, with linter & automatic code fixer"
|
||||
href="https://github.com/standard/standard/commits?author=theoludwig"
|
||||
/>
|
||||
<Repository
|
||||
name='nrwl/nx'
|
||||
description='Smart, Fast and Extensible Build System'
|
||||
href='https://github.com/nrwl/nx/commits?author=theoludwig'
|
||||
name="nrwl/nx"
|
||||
description="Smart, Fast and Extensible Build System"
|
||||
href="https://github.com/nrwl/nx/commits?author=theoludwig"
|
||||
/>
|
||||
<Repository
|
||||
name='vercel/next.js'
|
||||
description='The React Framework'
|
||||
href='https://github.com/vercel/next.js/commits?author=theoludwig'
|
||||
name="vercel/next.js"
|
||||
description="The React Framework"
|
||||
href="https://github.com/vercel/next.js/commits?author=theoludwig"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import Image from 'next/image'
|
||||
import Image from "next/image"
|
||||
|
||||
import { ShadowContainer } from '@/components/design/ShadowContainer'
|
||||
import { ShadowContainer } from "@/components/design/ShadowContainer"
|
||||
|
||||
export interface PortfolioItemProps {
|
||||
title: string
|
||||
@ -13,29 +13,29 @@ export const PortfolioItem = (props: PortfolioItemProps): JSX.Element => {
|
||||
const { title, description, link, image } = props
|
||||
|
||||
return (
|
||||
<ShadowContainer className='relative cursor-pointer items-center sm:ml-10'>
|
||||
<ShadowContainer className="relative cursor-pointer items-center sm:ml-10">
|
||||
<a
|
||||
className='group inline-flex justify-center'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className="group inline-flex justify-center"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
href={link}
|
||||
aria-label={title}
|
||||
>
|
||||
<div className='flex justify-center'>
|
||||
<div className="flex justify-center">
|
||||
<Image
|
||||
quality={100}
|
||||
className='h-auto w-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5'
|
||||
className="h-auto w-auto transition-opacity duration-500 group-hover:opacity-20 dark:group-hover:opacity-5"
|
||||
width={300}
|
||||
height={300}
|
||||
src={image}
|
||||
alt={title}
|
||||
/>
|
||||
</div>
|
||||
<div className='absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100'>
|
||||
<h3 className='my-6 text-xl font-semibold text-yellow dark:text-yellow-dark'>
|
||||
<div className="absolute bottom-0 h-auto overflow-hidden text-center opacity-0 transition-opacity duration-500 group-hover:opacity-100">
|
||||
<h3 className="my-6 text-xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
{title}
|
||||
</h3>
|
||||
<p className='my-6'>{description}</p>
|
||||
<p className="my-6">{description}</p>
|
||||
</div>
|
||||
</a>
|
||||
</ShadowContainer>
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
import type { PortfolioItemProps } from './PortfolioItem'
|
||||
import { PortfolioItem } from './PortfolioItem'
|
||||
import type { PortfolioItemProps } from "./PortfolioItem"
|
||||
import { PortfolioItem } from "./PortfolioItem"
|
||||
|
||||
export const Portfolio = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
let items = i18n.translate<PortfolioItemProps[]>('home.portfolio.items')
|
||||
let items = i18n.translate<PortfolioItemProps[]>("home.portfolio.items")
|
||||
if (!Array.isArray(items)) {
|
||||
items = []
|
||||
}
|
||||
|
||||
return (
|
||||
<div className='flex w-full flex-wrap justify-center px-3'>
|
||||
<div className="flex w-full flex-wrap justify-center px-3">
|
||||
{items.map((item, index) => {
|
||||
return <PortfolioItem key={index} {...item} />
|
||||
})}
|
||||
|
@ -1,18 +1,18 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
export const ProfileDescriptionBottom = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<p className='mb-8 mt-8 text-base font-normal text-gray dark:text-gray-dark'>
|
||||
{i18n.translate('home.about.description-bottom')}
|
||||
{i18n.locale === 'fr-FR' ? (
|
||||
<p className="mb-8 mt-8 text-base font-normal text-gray dark:text-gray-dark">
|
||||
{i18n.translate("home.about.description-bottom")}
|
||||
{i18n.locale === "fr-FR" ? (
|
||||
<>
|
||||
<br />
|
||||
<br />
|
||||
<a
|
||||
href='/curriculum-vitae/index.html'
|
||||
className='text-yellow hover:underline dark:text-yellow-dark'
|
||||
href="/curriculum-vitae/index.html"
|
||||
className="text-yellow hover:underline dark:text-yellow-dark"
|
||||
>
|
||||
Curriculum vitæ
|
||||
</a>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
export const ProfileInformation = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<div className='mb-6 border-b-2 border-gray-600 pb-2 font-headline dark:border-gray-400'>
|
||||
<h1 className='mb-2 text-4xl font-semibold text-yellow dark:text-yellow-dark'>
|
||||
<div className="mb-6 border-b-2 border-gray-600 pb-2 font-headline dark:border-gray-400">
|
||||
<h1 className="mb-2 text-4xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
Théo LUDWIG
|
||||
</h1>
|
||||
<h2 className='mb-3 text-base'>
|
||||
{i18n.translate('home.about.description')}
|
||||
<h2 className="mb-3 text-base">
|
||||
{i18n.translate("home.about.description")}
|
||||
</h2>
|
||||
</div>
|
||||
)
|
||||
|
@ -8,14 +8,14 @@ export const ProfileItem = (props: ProfileItemProps): JSX.Element => {
|
||||
const { title, value, link } = props
|
||||
|
||||
return (
|
||||
<li className='mb-3 before:table after:clear-both after:table'>
|
||||
<strong className='float-left block w-28 text-sm font-bold text-black dark:text-white'>
|
||||
<li className="mb-3 before:table after:clear-both after:table">
|
||||
<strong className="float-left block w-28 text-sm font-bold text-black dark:text-white">
|
||||
{title}
|
||||
</strong>
|
||||
<span className='mb-4 ml-0 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32'>
|
||||
<span className="mb-4 ml-0 block text-sm font-normal text-gray dark:text-gray-dark sm:mb-0 sm:ml-32">
|
||||
{link != null ? (
|
||||
<a
|
||||
className='text-gray hover:underline dark:text-gray-dark'
|
||||
className="text-gray hover:underline dark:text-gray-dark"
|
||||
href={link}
|
||||
>
|
||||
{value}
|
||||
|
@ -1,12 +1,12 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo } from "react"
|
||||
|
||||
import { useI18n } from '@/i18n/i18n.client'
|
||||
import { BIRTH_DATE, BIRTH_DATE_STRING, getAge } from '@/utils/getAge'
|
||||
import type { CookiesStore } from '@/utils/constants'
|
||||
import { useI18n } from "@/i18n/i18n.client"
|
||||
import { BIRTH_DATE, BIRTH_DATE_STRING, getAge } from "@/utils/getAge"
|
||||
import type { CookiesStore } from "@/utils/constants"
|
||||
|
||||
import { ProfileItem } from './ProfileItem'
|
||||
import { ProfileItem } from "./ProfileItem"
|
||||
|
||||
export interface ProfileListProps {
|
||||
cookiesStore: CookiesStore
|
||||
@ -22,25 +22,25 @@ export const ProfileList = (props: ProfileListProps): JSX.Element => {
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<ul className='m-0 list-none p-0'>
|
||||
<ul className="m-0 list-none p-0">
|
||||
<ProfileItem
|
||||
title={i18n.translate('home.about.pronouns')}
|
||||
value={i18n.translate('home.about.pronouns-value')}
|
||||
title={i18n.translate("home.about.pronouns")}
|
||||
value={i18n.translate("home.about.pronouns-value")}
|
||||
/>
|
||||
<ProfileItem
|
||||
title={i18n.translate('home.about.birth-date')}
|
||||
title={i18n.translate("home.about.birth-date")}
|
||||
value={`${BIRTH_DATE_STRING} (${age} ${i18n.translate(
|
||||
'home.about.years-old'
|
||||
"home.about.years-old",
|
||||
)})`}
|
||||
/>
|
||||
<ProfileItem
|
||||
title={i18n.translate('home.about.nationality')}
|
||||
value='Alsace, France'
|
||||
title={i18n.translate("home.about.nationality")}
|
||||
value="Alsace, France"
|
||||
/>
|
||||
<ProfileItem
|
||||
title='Email'
|
||||
value='contact@theoludwig.fr'
|
||||
link='mailto:contact@theoludwig.fr'
|
||||
title="Email"
|
||||
value="contact@theoludwig.fr"
|
||||
link="mailto:contact@theoludwig.fr"
|
||||
/>
|
||||
</ul>
|
||||
)
|
||||
|
@ -1,11 +1,11 @@
|
||||
import Image from 'next/image'
|
||||
import Image from "next/image"
|
||||
|
||||
import Logo from 'public/images/logo.png'
|
||||
import Logo from "public/images/logo.png"
|
||||
|
||||
export const ProfileLogo = (): JSX.Element => {
|
||||
return (
|
||||
<div className='max-h-[370px] max-w-[370px] px-2 py-6'>
|
||||
<Image quality={100} src={Logo} alt='Théo LUDWIG' priority />
|
||||
<div className="max-h-[370px] max-w-[370px] px-2 py-6">
|
||||
<Image quality={100} src={Logo} alt="Théo LUDWIG" priority />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const EmailIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>Email</title>
|
||||
<path d='M15.61 12c0 1.99-1.62 3.61-3.61 3.61-1.99 0-3.61-1.62-3.61-3.61 0-1.99 1.62-3.61 3.61-3.61 1.99 0 3.61 1.62 3.61 3.61M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12c2.424 0 4.761-.722 6.76-2.087l.034-.024-1.617-1.879-.027.017A9.494 9.494 0 0112 21.54c-5.26 0-9.54-4.28-9.54-9.54 0-5.26 4.28-9.54 9.54-9.54 5.26 0 9.54 4.28 9.54 9.54a9.63 9.63 0 01-.225 2.05c-.301 1.239-1.169 1.618-1.82 1.568-.654-.053-1.42-.52-1.426-1.661V12A6.076 6.076 0 0012 5.93 6.076 6.076 0 005.93 12 6.076 6.076 0 0012 18.07a6.02 6.02 0 004.3-1.792 3.9 3.9 0 003.32 1.805c.874 0 1.74-.292 2.437-.821.719-.547 1.256-1.336 1.553-2.285.047-.154.135-.504.135-.507l.002-.013c.175-.76.253-1.52.253-2.457 0-6.617-5.383-12-12-12' />
|
||||
<path d="M15.61 12c0 1.99-1.62 3.61-3.61 3.61-1.99 0-3.61-1.62-3.61-3.61 0-1.99 1.62-3.61 3.61-3.61 1.99 0 3.61 1.62 3.61 3.61M12 0C5.383 0 0 5.383 0 12s5.383 12 12 12c2.424 0 4.761-.722 6.76-2.087l.034-.024-1.617-1.879-.027.017A9.494 9.494 0 0112 21.54c-5.26 0-9.54-4.28-9.54-9.54 0-5.26 4.28-9.54 9.54-9.54 5.26 0 9.54 4.28 9.54 9.54a9.63 9.63 0 01-.225 2.05c-.301 1.239-1.169 1.618-1.82 1.568-.654-.053-1.42-.52-1.426-1.661V12A6.076 6.076 0 0012 5.93 6.076 6.076 0 005.93 12 6.076 6.076 0 0012 18.07a6.02 6.02 0 004.3-1.792 3.9 3.9 0 003.32 1.805c.874 0 1.74-.292 2.437-.821.719-.547 1.256-1.336 1.553-2.285.047-.154.135-.504.135-.507l.002-.013c.175-.76.253-1.52.253-2.457 0-6.617-5.383-12-12-12" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const GitHubIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>GitHub</title>
|
||||
<path d='M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12' />
|
||||
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const GitLabIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>GitLab</title>
|
||||
<path d='M4.845.904c-.435 0-.82.28-.955.692C2.639 5.449 1.246 9.728.07 13.335a1.437 1.437 0 00.522 1.607l11.071 8.045c.2.145.472.144.67-.004l11.073-8.04a1.436 1.436 0 00.522-1.61c-1.285-3.942-2.683-8.256-3.817-11.746a1.004 1.004 0 00-.957-.684.987.987 0 00-.949.69l-2.405 7.408H8.203l-2.41-7.408a.987.987 0 00-.942-.69h-.006zm-.006 1.42l2.173 6.678H2.675zm14.326 0l2.168 6.678h-4.341zm-10.593 7.81h6.862c-1.142 3.52-2.288 7.04-3.434 10.559L8.572 10.135zm-5.514.005h4.321l3.086 9.5zm13.567 0h4.325c-2.467 3.17-4.95 6.328-7.411 9.502 1.028-3.167 2.059-6.334 3.086-9.502zM2.1 10.762l6.977 8.947-7.817-5.682a.305.305 0 01-.112-.341zm19.798 0l.952 2.922a.305.305 0 01-.11.341v.002l-7.82 5.68.026-.035z' />
|
||||
<path d="M4.845.904c-.435 0-.82.28-.955.692C2.639 5.449 1.246 9.728.07 13.335a1.437 1.437 0 00.522 1.607l11.071 8.045c.2.145.472.144.67-.004l11.073-8.04a1.436 1.436 0 00.522-1.61c-1.285-3.942-2.683-8.256-3.817-11.746a1.004 1.004 0 00-.957-.684.987.987 0 00-.949.69l-2.405 7.408H8.203l-2.41-7.408a.987.987 0 00-.942-.69h-.006zm-.006 1.42l2.173 6.678H2.675zm14.326 0l2.168 6.678h-4.341zm-10.593 7.81h6.862c-1.142 3.52-2.288 7.04-3.434 10.559L8.572 10.135zm-5.514.005h4.321l3.086 9.5zm13.567 0h4.325c-2.467 3.17-4.95 6.328-7.411 9.502 1.028-3.167 2.059-6.334 3.086-9.502zM2.1 10.762l6.977 8.947-7.817-5.682a.305.305 0 01-.112-.341zm19.798 0l.952 2.922a.305.305 0 01-.11.341v.002l-7.82 5.68.026-.035z" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -1,15 +1,15 @@
|
||||
import classNames from 'clsx'
|
||||
import classNames from "clsx"
|
||||
|
||||
export const Icon = (props: React.SVGProps<SVGSVGElement>): JSX.Element => {
|
||||
const { children, className, ...rest } = props
|
||||
|
||||
return (
|
||||
<svg
|
||||
xmlns='http://www.w3.org/2000/svg'
|
||||
viewBox='0 0 24 24'
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
viewBox="0 0 24 24"
|
||||
className={classNames(
|
||||
'h-8 w-8 fill-current text-black dark:text-white',
|
||||
className
|
||||
"h-8 w-8 fill-current text-black dark:text-white",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const NPMIcon = (props: React.SVGProps<SVGSVGElement>): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>npm</title>
|
||||
<path d='M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z' />
|
||||
<path d="M1.763 0C.786 0 0 .786 0 1.763v20.474C0 23.214.786 24 1.763 24h20.474c.977 0 1.763-.786 1.763-1.763V1.763C24 .786 23.214 0 22.237 0zM5.13 5.323l13.837.019-.009 13.836h-3.464l.01-10.382h-3.456L12.04 19.17H5.113z" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const TwitchIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>Twitch</title>
|
||||
<path d='M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714z' />
|
||||
<path d="M11.571 4.714h1.715v5.143H11.57zm4.715 0H18v5.143h-1.714zM6 0L1.714 4.286v15.428h5.143V24l4.286-4.286h3.428L22.286 12V0zm14.571 11.143l-3.428 3.428h-3.429l-3 3v-3H6.857V1.714h13.714z" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const TwitterIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
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' />
|
||||
<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>
|
||||
)
|
||||
}
|
||||
|
@ -1,12 +1,12 @@
|
||||
import { Icon } from './Icon'
|
||||
import { Icon } from "./Icon"
|
||||
|
||||
export const YouTubeIcon = (
|
||||
props: React.SVGProps<SVGSVGElement>
|
||||
props: React.SVGProps<SVGSVGElement>,
|
||||
): JSX.Element => {
|
||||
return (
|
||||
<Icon {...props}>
|
||||
<title>YouTube</title>
|
||||
<path d='M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z' />
|
||||
<path d="M23.498 6.186a3.016 3.016 0 00-2.122-2.136C19.505 3.545 12 3.545 12 3.545s-7.505 0-9.377.505A3.017 3.017 0 00.502 6.186C0 8.07 0 12 0 12s0 3.93.502 5.814a3.016 3.016 0 002.122 2.136c1.871.505 9.376.505 9.376.505s7.505 0 9.377-.505a3.015 3.015 0 002.122-2.136C24 15.93 24 12 24 12s0-3.93-.502-5.814zM9.545 15.568V8.432L15.818 12l-6.273 3.568z" />
|
||||
</Icon>
|
||||
)
|
||||
}
|
||||
|
@ -7,13 +7,13 @@ export const SocialMediaItem = (props: SocialMediaItemProps): JSX.Element => {
|
||||
const { link, ariaLabel, children } = props
|
||||
|
||||
return (
|
||||
<li className='mx-4 my-1 inline-block'>
|
||||
<li className="mx-4 my-1 inline-block">
|
||||
<a
|
||||
href={link}
|
||||
aria-label={ariaLabel}
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className='relative inline-block bg-transparent'
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="relative inline-block bg-transparent"
|
||||
>
|
||||
{children}
|
||||
</a>
|
||||
|
@ -1,43 +1,43 @@
|
||||
import { SocialMediaItem } from './SocialMediaItem'
|
||||
import { TwitterIcon } from './SocialMediaIcons/TwitterIcon'
|
||||
import { GitHubIcon } from './SocialMediaIcons/GitHubIcon'
|
||||
import { GitLabIcon } from './SocialMediaIcons/GitLabIcon'
|
||||
import { YouTubeIcon } from './SocialMediaIcons/YouTubeIcon'
|
||||
import { TwitchIcon } from './SocialMediaIcons/TwitchIcon'
|
||||
import { EmailIcon } from './SocialMediaIcons/EmailIcon'
|
||||
import { NPMIcon } from './SocialMediaIcons/NPMIcon'
|
||||
import { SocialMediaItem } from "./SocialMediaItem"
|
||||
import { TwitterIcon } from "./SocialMediaIcons/TwitterIcon"
|
||||
import { GitHubIcon } from "./SocialMediaIcons/GitHubIcon"
|
||||
import { GitLabIcon } from "./SocialMediaIcons/GitLabIcon"
|
||||
import { YouTubeIcon } from "./SocialMediaIcons/YouTubeIcon"
|
||||
import { TwitchIcon } from "./SocialMediaIcons/TwitchIcon"
|
||||
import { EmailIcon } from "./SocialMediaIcons/EmailIcon"
|
||||
import { NPMIcon } from "./SocialMediaIcons/NPMIcon"
|
||||
|
||||
export const SocialMediaList = (): JSX.Element => {
|
||||
return (
|
||||
<ul className='social-media-list m-0 mt-2 list-none py-4 text-center'>
|
||||
<SocialMediaItem link='https://github.com/theoludwig' ariaLabel='GitHub'>
|
||||
<ul className="social-media-list m-0 mt-2 list-none py-4 text-center">
|
||||
<SocialMediaItem link="https://github.com/theoludwig" ariaLabel="GitHub">
|
||||
<GitHubIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem link='https://gitlab.com/theoludwig' ariaLabel='GitLab'>
|
||||
<SocialMediaItem link="https://gitlab.com/theoludwig" ariaLabel="GitLab">
|
||||
<GitLabIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem link='https://www.npmjs.com/~theoludwig' ariaLabel='npm'>
|
||||
<SocialMediaItem link="https://www.npmjs.com/~theoludwig" ariaLabel="npm">
|
||||
<NPMIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem
|
||||
link='https://twitter.com/theoludwig_'
|
||||
ariaLabel='Twitter'
|
||||
link="https://twitter.com/theoludwig_"
|
||||
ariaLabel="Twitter"
|
||||
>
|
||||
<TwitterIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem
|
||||
link='https://www.youtube.com/@theo_ludwig'
|
||||
ariaLabel='YouTube'
|
||||
link="https://www.youtube.com/@theo_ludwig"
|
||||
ariaLabel="YouTube"
|
||||
>
|
||||
<YouTubeIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem
|
||||
link='https://www.twitch.tv/theoludwig'
|
||||
ariaLabel='Twitch'
|
||||
link="https://www.twitch.tv/theoludwig"
|
||||
ariaLabel="Twitch"
|
||||
>
|
||||
<TwitchIcon />
|
||||
</SocialMediaItem>
|
||||
<SocialMediaItem link='mailto:contact@theoludwig.fr' ariaLabel='Email'>
|
||||
<SocialMediaItem link="mailto:contact@theoludwig.fr" ariaLabel="Email">
|
||||
<EmailIcon />
|
||||
</SocialMediaItem>
|
||||
</ul>
|
||||
|
@ -1,15 +1,15 @@
|
||||
import { cookies } from 'next/headers'
|
||||
import { cookies } from "next/headers"
|
||||
|
||||
import { ProfileDescriptionBottom } from './ProfileDescriptionBottom'
|
||||
import { ProfileInformation } from './ProfileInfo'
|
||||
import { ProfileList } from './ProfileList'
|
||||
import { ProfileLogo } from './ProfileLogo'
|
||||
import { ProfileDescriptionBottom } from "./ProfileDescriptionBottom"
|
||||
import { ProfileInformation } from "./ProfileInfo"
|
||||
import { ProfileList } from "./ProfileList"
|
||||
import { ProfileLogo } from "./ProfileLogo"
|
||||
|
||||
export const Profile = (): JSX.Element => {
|
||||
const cookiesStore = cookies()
|
||||
|
||||
return (
|
||||
<div className='flex flex-col items-center justify-center px-10 pt-2 md:flex-row md:pt-10'>
|
||||
<div className="flex flex-col items-center justify-center px-10 pt-2 md:flex-row md:pt-10">
|
||||
<ProfileLogo />
|
||||
<div>
|
||||
<ProfileInformation />
|
||||
|
@ -1,9 +1,9 @@
|
||||
import Image from 'next/image'
|
||||
import Image from "next/image"
|
||||
|
||||
import { getTheme } from '@/theme/theme.server'
|
||||
import { getTheme } from "@/theme/theme.server"
|
||||
|
||||
import type { SkillName } from './skills'
|
||||
import { skills } from './skills'
|
||||
import type { SkillName } from "./skills"
|
||||
import { skills } from "./skills"
|
||||
|
||||
export interface SkillComponentProps {
|
||||
skill: SkillName
|
||||
@ -17,10 +17,10 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
|
||||
const theme = getTheme()
|
||||
|
||||
const getImage = (): string => {
|
||||
if (typeof skillProperties.image === 'string') {
|
||||
if (typeof skillProperties.image === "string") {
|
||||
return skillProperties.image
|
||||
}
|
||||
if (theme === 'light') {
|
||||
if (theme === "light") {
|
||||
return skillProperties.image.light
|
||||
}
|
||||
return skillProperties.image.dark
|
||||
@ -29,20 +29,20 @@ export const SkillComponent = (props: SkillComponentProps): JSX.Element => {
|
||||
return (
|
||||
<a
|
||||
href={skillProperties.link}
|
||||
className='mx-2 max-w-xl text-yellow hover:underline dark:text-yellow-dark'
|
||||
target='_blank'
|
||||
rel='noopener noreferrer'
|
||||
className="mx-2 max-w-xl text-yellow hover:underline dark:text-yellow-dark"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
<div className='text-center'>
|
||||
<div className="text-center">
|
||||
<Image
|
||||
className='inline h-16 w-16'
|
||||
className="inline h-16 w-16"
|
||||
quality={100}
|
||||
width={64}
|
||||
height={64}
|
||||
alt={skill}
|
||||
src={getImage()}
|
||||
/>
|
||||
<p className='mt-1'>{skill}</p>
|
||||
<p className="mt-1">{skill}</p>
|
||||
</div>
|
||||
</a>
|
||||
)
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { ShadowContainer } from '@/components/design/ShadowContainer'
|
||||
import { ShadowContainer } from "@/components/design/ShadowContainer"
|
||||
|
||||
export interface SkillsSectionProps {
|
||||
title: string
|
||||
@ -10,15 +10,15 @@ export const SkillsSection = (props: SkillsSectionProps): JSX.Element => {
|
||||
|
||||
return (
|
||||
<ShadowContainer>
|
||||
<div className='mx-auto w-full px-4'>
|
||||
<div className='flex flex-wrap px-4 py-6'>
|
||||
<div className='flex-1'>
|
||||
<div className='mb-8 border-b border-gray-600 dark:border-white dark:border-opacity-10'>
|
||||
<h3 className='my-3 text-xl font-semibold text-yellow dark:text-yellow-dark'>
|
||||
<div className="mx-auto w-full px-4">
|
||||
<div className="flex flex-wrap px-4 py-6">
|
||||
<div className="flex-1">
|
||||
<div className="mb-8 border-b border-gray-600 dark:border-white dark:border-opacity-10">
|
||||
<h3 className="my-3 text-xl font-semibold text-yellow dark:text-yellow-dark">
|
||||
{title}
|
||||
</h3>
|
||||
</div>
|
||||
<div className='flex flex-wrap justify-around'>{children}</div>
|
||||
<div className="flex flex-wrap justify-around">{children}</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -1,40 +1,40 @@
|
||||
import { getI18n } from '@/i18n/i18n.server'
|
||||
import { getI18n } from "@/i18n/i18n.server"
|
||||
|
||||
import { SkillComponent } from './Skill'
|
||||
import { SkillsSection } from './SkillsSection'
|
||||
import { SkillComponent } from "./Skill"
|
||||
import { SkillsSection } from "./SkillsSection"
|
||||
|
||||
export const Skills = (): JSX.Element => {
|
||||
const i18n = getI18n()
|
||||
|
||||
return (
|
||||
<>
|
||||
<SkillsSection title={i18n.translate('home.skills.languages')}>
|
||||
<SkillComponent skill='TypeScript' />
|
||||
<SkillComponent skill='Python' />
|
||||
<SkillComponent skill='C/C++' />
|
||||
<SkillComponent skill='PHP' />
|
||||
<SkillsSection title={i18n.translate("home.skills.languages")}>
|
||||
<SkillComponent skill="TypeScript" />
|
||||
<SkillComponent skill="Python" />
|
||||
<SkillComponent skill="C/C++" />
|
||||
<SkillComponent skill="PHP" />
|
||||
</SkillsSection>
|
||||
|
||||
<SkillsSection title='Frontend'>
|
||||
<SkillComponent skill='HTML' />
|
||||
<SkillComponent skill='CSS' />
|
||||
<SkillComponent skill='Tailwind CSS' />
|
||||
<SkillComponent skill='React.js (+ Next.js)' />
|
||||
<SkillsSection title="Frontend">
|
||||
<SkillComponent skill="HTML" />
|
||||
<SkillComponent skill="CSS" />
|
||||
<SkillComponent skill="Tailwind CSS" />
|
||||
<SkillComponent skill="React.js (+ Next.js)" />
|
||||
</SkillsSection>
|
||||
|
||||
<SkillsSection title='Backend'>
|
||||
<SkillComponent skill='Laravel' />
|
||||
<SkillComponent skill='Node.js' />
|
||||
<SkillComponent skill='Fastify' />
|
||||
<SkillComponent skill='PostgreSQL' />
|
||||
<SkillsSection title="Backend">
|
||||
<SkillComponent skill="Laravel" />
|
||||
<SkillComponent skill="Node.js" />
|
||||
<SkillComponent skill="Fastify" />
|
||||
<SkillComponent skill="PostgreSQL" />
|
||||
</SkillsSection>
|
||||
|
||||
<SkillsSection title={i18n.translate('home.skills.software-tools')}>
|
||||
<SkillComponent skill='GNU/Linux' />
|
||||
<SkillComponent skill='Arch Linux' />
|
||||
<SkillComponent skill='Visual Studio Code' />
|
||||
<SkillComponent skill='Git' />
|
||||
<SkillComponent skill='Docker' />
|
||||
<SkillsSection title={i18n.translate("home.skills.software-tools")}>
|
||||
<SkillComponent skill="GNU/Linux" />
|
||||
<SkillComponent skill="Arch Linux" />
|
||||
<SkillComponent skill="Visual Studio Code" />
|
||||
<SkillComponent skill="Git" />
|
||||
<SkillComponent skill="Docker" />
|
||||
</SkillsSection>
|
||||
</>
|
||||
)
|
||||
|
@ -5,111 +5,111 @@ export interface Skill {
|
||||
|
||||
export const skills = {
|
||||
JavaScript: {
|
||||
link: 'https://developer.mozilla.org/docs/Web/JavaScript',
|
||||
image: '/images/skills/JavaScript.png'
|
||||
link: "https://developer.mozilla.org/docs/Web/JavaScript",
|
||||
image: "/images/skills/JavaScript.png",
|
||||
},
|
||||
TypeScript: {
|
||||
link: 'https://www.typescriptlang.org/',
|
||||
image: '/images/skills/TypeScript.png'
|
||||
link: "https://www.typescriptlang.org/",
|
||||
image: "/images/skills/TypeScript.png",
|
||||
},
|
||||
Python: {
|
||||
link: 'https://www.python.org/',
|
||||
image: '/images/skills/Python.png'
|
||||
link: "https://www.python.org/",
|
||||
image: "/images/skills/Python.png",
|
||||
},
|
||||
'C/C++': {
|
||||
link: 'https://isocpp.org/',
|
||||
image: '/images/skills/C-Cpp.png'
|
||||
"C/C++": {
|
||||
link: "https://isocpp.org/",
|
||||
image: "/images/skills/C-Cpp.png",
|
||||
},
|
||||
PHP: {
|
||||
link: 'https://www.php.net/',
|
||||
image: '/images/skills/PHP.png'
|
||||
link: "https://www.php.net/",
|
||||
image: "/images/skills/PHP.png",
|
||||
},
|
||||
Laravel: {
|
||||
link: 'https://laravel.com/',
|
||||
image: '/images/skills/Laravel.png'
|
||||
link: "https://laravel.com/",
|
||||
image: "/images/skills/Laravel.png",
|
||||
},
|
||||
Dart: {
|
||||
link: 'https://dart.dev/',
|
||||
image: '/images/skills/Dart.png'
|
||||
link: "https://dart.dev/",
|
||||
image: "/images/skills/Dart.png",
|
||||
},
|
||||
Flutter: {
|
||||
link: 'https://flutter.dev/',
|
||||
image: '/images/skills/Flutter.webp'
|
||||
link: "https://flutter.dev/",
|
||||
image: "/images/skills/Flutter.webp",
|
||||
},
|
||||
HTML: {
|
||||
link: 'https://developer.mozilla.org/docs/Web/HTML',
|
||||
image: '/images/skills/HTML.png'
|
||||
link: "https://developer.mozilla.org/docs/Web/HTML",
|
||||
image: "/images/skills/HTML.png",
|
||||
},
|
||||
CSS: {
|
||||
link: 'https://developer.mozilla.org/docs/Web/CSS',
|
||||
image: '/images/skills/CSS.png'
|
||||
link: "https://developer.mozilla.org/docs/Web/CSS",
|
||||
image: "/images/skills/CSS.png",
|
||||
},
|
||||
'Tailwind CSS': {
|
||||
link: 'https://tailwindcss.com/',
|
||||
image: '/images/skills/TailwindCSS.png'
|
||||
"Tailwind CSS": {
|
||||
link: "https://tailwindcss.com/",
|
||||
image: "/images/skills/TailwindCSS.png",
|
||||
},
|
||||
SASS: {
|
||||
link: 'https://sass-lang.com/',
|
||||
image: '/images/skills/SASS.svg'
|
||||
link: "https://sass-lang.com/",
|
||||
image: "/images/skills/SASS.svg",
|
||||
},
|
||||
'React.js (+ Next.js)': {
|
||||
link: 'https://reactjs.org/',
|
||||
image: '/images/skills/ReactJS.png'
|
||||
"React.js (+ Next.js)": {
|
||||
link: "https://reactjs.org/",
|
||||
image: "/images/skills/ReactJS.png",
|
||||
},
|
||||
'Node.js': {
|
||||
link: 'https://nodejs.org/',
|
||||
image: '/images/skills/NodeJS.png'
|
||||
"Node.js": {
|
||||
link: "https://nodejs.org/",
|
||||
image: "/images/skills/NodeJS.png",
|
||||
},
|
||||
Fastify: {
|
||||
link: 'https://www.fastify.io/',
|
||||
link: "https://www.fastify.io/",
|
||||
image: {
|
||||
light: '/images/skills/Fastify-light.png',
|
||||
dark: '/images/skills/Fastify-dark.png'
|
||||
}
|
||||
light: "/images/skills/Fastify-light.png",
|
||||
dark: "/images/skills/Fastify-dark.png",
|
||||
},
|
||||
},
|
||||
Prisma: {
|
||||
link: 'https://www.prisma.io/',
|
||||
link: "https://www.prisma.io/",
|
||||
image: {
|
||||
light: '/images/skills/Prisma-light.png',
|
||||
dark: '/images/skills/Prisma-dark.png'
|
||||
}
|
||||
light: "/images/skills/Prisma-light.png",
|
||||
dark: "/images/skills/Prisma-dark.png",
|
||||
},
|
||||
},
|
||||
PostgreSQL: {
|
||||
link: 'https://www.postgresql.org/',
|
||||
image: '/images/skills/PostgreSQL.png'
|
||||
link: "https://www.postgresql.org/",
|
||||
image: "/images/skills/PostgreSQL.png",
|
||||
},
|
||||
MySQL: {
|
||||
link: 'https://www.mysql.com/',
|
||||
image: '/images/skills/MySQL.png'
|
||||
link: "https://www.mysql.com/",
|
||||
image: "/images/skills/MySQL.png",
|
||||
},
|
||||
Strapi: {
|
||||
link: 'https://strapi.io/',
|
||||
image: '/images/skills/Strapi.png'
|
||||
link: "https://strapi.io/",
|
||||
image: "/images/skills/Strapi.png",
|
||||
},
|
||||
'Visual Studio Code': {
|
||||
link: 'https://code.visualstudio.com/',
|
||||
image: '/images/skills/VisualStudioCode.png'
|
||||
"Visual Studio Code": {
|
||||
link: "https://code.visualstudio.com/",
|
||||
image: "/images/skills/VisualStudioCode.png",
|
||||
},
|
||||
Git: {
|
||||
link: 'https://git-scm.com/',
|
||||
image: '/images/skills/Git.png'
|
||||
link: "https://git-scm.com/",
|
||||
image: "/images/skills/Git.png",
|
||||
},
|
||||
Ubuntu: {
|
||||
link: 'https://ubuntu.com/',
|
||||
image: '/images/skills/Ubuntu.png'
|
||||
link: "https://ubuntu.com/",
|
||||
image: "/images/skills/Ubuntu.png",
|
||||
},
|
||||
'Arch Linux': {
|
||||
link: 'https://archlinux.org/',
|
||||
image: '/images/skills/ArchLinux.png'
|
||||
"Arch Linux": {
|
||||
link: "https://archlinux.org/",
|
||||
image: "/images/skills/ArchLinux.png",
|
||||
},
|
||||
'GNU/Linux': {
|
||||
link: 'https://www.gnu.org/',
|
||||
image: '/images/skills/GNU-Linux.png'
|
||||
"GNU/Linux": {
|
||||
link: "https://www.gnu.org/",
|
||||
image: "/images/skills/GNU-Linux.png",
|
||||
},
|
||||
Docker: {
|
||||
link: 'https://www.docker.com/',
|
||||
image: '/images/skills/Docker.png'
|
||||
}
|
||||
link: "https://www.docker.com/",
|
||||
image: "/images/skills/Docker.png",
|
||||
},
|
||||
} as const
|
||||
|
||||
export type SkillName = keyof typeof skills
|
||||
|
@ -1,4 +1,4 @@
|
||||
import classNames from 'clsx'
|
||||
import classNames from "clsx"
|
||||
|
||||
export interface LoaderProps {
|
||||
width?: number
|
||||
@ -13,16 +13,16 @@ export const Loader = (props: LoaderProps): JSX.Element => {
|
||||
<div
|
||||
style={{
|
||||
width,
|
||||
height
|
||||
height,
|
||||
}}
|
||||
className={classNames(
|
||||
'animate-spin inline-block border-[3px] border-current border-t-transparent text-yellow dark:text-yellow-dark rounded-full',
|
||||
className
|
||||
"animate-spin inline-block border-[3px] border-current border-t-transparent text-yellow dark:text-yellow-dark rounded-full",
|
||||
className,
|
||||
)}
|
||||
role='status'
|
||||
aria-label='loading'
|
||||
role="status"
|
||||
aria-label="loading"
|
||||
>
|
||||
<span className='sr-only'>Loading...</span>
|
||||
<span className="sr-only">Loading...</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
'use client'
|
||||
"use client"
|
||||
|
||||
import { useEffect, useRef } from 'react'
|
||||
import { useEffect, useRef } from "react"
|
||||
|
||||
export type RevealFadeProps = React.PropsWithChildren
|
||||
|
||||
@ -15,22 +15,22 @@ export const RevealFade = (props: RevealFadeProps): JSX.Element => {
|
||||
for (const entry of entries) {
|
||||
if (entry.isIntersecting) {
|
||||
entry.target.className =
|
||||
'opacity-100 visible translate-y-0 transition-all duration-700 ease-in-out'
|
||||
"opacity-100 visible translate-y-0 transition-all duration-700 ease-in-out"
|
||||
observer.unobserve(entry.target)
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
root: null,
|
||||
rootMargin: '0px',
|
||||
threshold: 0.28
|
||||
}
|
||||
rootMargin: "0px",
|
||||
threshold: 0.28,
|
||||
},
|
||||
)
|
||||
observer.observe(htmlElement.current as HTMLDivElement)
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<div ref={htmlElement} className='invisible -translate-y-7 opacity-0'>
|
||||
<div ref={htmlElement} className="invisible -translate-y-7 opacity-0">
|
||||
{children}
|
||||
</div>
|
||||
)
|
||||
|
@ -1,10 +1,10 @@
|
||||
type SectionHeadingProps = React.ComponentPropsWithRef<'h2'>
|
||||
type SectionHeadingProps = React.ComponentPropsWithRef<"h2">
|
||||
|
||||
export const SectionHeading = (props: SectionHeadingProps): JSX.Element => {
|
||||
const { children, ...rest } = props
|
||||
|
||||
return (
|
||||
<h2 {...rest} className='mb-3 mt-1 text-center text-4xl font-semibold'>
|
||||
<h2 {...rest} className="mb-3 mt-1 text-center text-4xl font-semibold">
|
||||
{children}
|
||||
</h2>
|
||||
)
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { ShadowContainer } from '@/components/design/ShadowContainer'
|
||||
import { SectionHeading } from '@/components/design/Section/SectionHeading'
|
||||
import { ShadowContainer } from "@/components/design/ShadowContainer"
|
||||
import { SectionHeading } from "@/components/design/Section/SectionHeading"
|
||||
|
||||
type SectionProps = React.ComponentPropsWithRef<'section'> & {
|
||||
type SectionProps = React.ComponentPropsWithRef<"section"> & {
|
||||
heading?: string
|
||||
description?: string
|
||||
isMain?: boolean
|
||||
@ -20,13 +20,13 @@ export const Section = (props: SectionProps): JSX.Element => {
|
||||
|
||||
if (isMain) {
|
||||
return (
|
||||
<div className='w-full px-3'>
|
||||
<div className="w-full px-3">
|
||||
<ShadowContainer style={{ marginTop: 50 }}>
|
||||
<section {...rest}>
|
||||
{heading != null ? (
|
||||
<SectionHeading>{heading}</SectionHeading>
|
||||
) : null}
|
||||
<div className='w-full px-3'>{children}</div>
|
||||
<div className="w-full px-3">{children}</div>
|
||||
</section>
|
||||
</ShadowContainer>
|
||||
</div>
|
||||
@ -37,7 +37,7 @@ export const Section = (props: SectionProps): JSX.Element => {
|
||||
return (
|
||||
<section {...rest}>
|
||||
{heading != null ? <SectionHeading>{heading}</SectionHeading> : null}
|
||||
<div className='w-full px-3'>{children}</div>
|
||||
<div className="w-full px-3">{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
@ -52,13 +52,13 @@ export const Section = (props: SectionProps): JSX.Element => {
|
||||
</SectionHeading>
|
||||
) : null}
|
||||
{description != null ? (
|
||||
<p style={{ marginTop: 7 }} className='text-center'>
|
||||
<p style={{ marginTop: 7 }} className="text-center">
|
||||
{description}
|
||||
</p>
|
||||
) : null}
|
||||
<div className='w-full px-3'>
|
||||
<div className="w-full px-3">
|
||||
<ShadowContainer>
|
||||
<div className='w-full px-16 py-4 leading-8'>{children}</div>
|
||||
<div className="w-full px-16 py-4 leading-8">{children}</div>
|
||||
</ShadowContainer>
|
||||
</div>
|
||||
</section>
|
||||
|
@ -1,6 +1,6 @@
|
||||
import classNames from 'clsx'
|
||||
import classNames from "clsx"
|
||||
|
||||
type ShadowContainerProps = React.ComponentPropsWithRef<'div'>
|
||||
type ShadowContainerProps = React.ComponentPropsWithRef<"div">
|
||||
|
||||
export const ShadowContainer = (props: ShadowContainerProps): JSX.Element => {
|
||||
const { children, className, ...rest } = props
|
||||
@ -8,8 +8,8 @@ export const ShadowContainer = (props: ShadowContainerProps): JSX.Element => {
|
||||
return (
|
||||
<div
|
||||
className={classNames(
|
||||
'mb-12 h-full max-w-full break-words rounded-2xl border border-solid border-[#000] shadow-light dark:shadow-dark ',
|
||||
className
|
||||
"mb-12 h-full max-w-full break-words rounded-2xl border border-solid border-[#000] shadow-light dark:shadow-dark ",
|
||||
className,
|
||||
)}
|
||||
{...rest}
|
||||
>
|
||||
|
11
compose.yaml
11
compose.yaml
@ -1,11 +1,12 @@
|
||||
services:
|
||||
theoludwig:
|
||||
container_name: ${COMPOSE_PROJECT_NAME}
|
||||
image: 'theoludwig'
|
||||
restart: 'unless-stopped'
|
||||
image: "theoludwig"
|
||||
restart: "unless-stopped"
|
||||
build:
|
||||
context: './'
|
||||
network_mode: 'host'
|
||||
context: "./"
|
||||
ports:
|
||||
- "${PORT-3000}:${PORT-3000}"
|
||||
environment:
|
||||
PORT: ${PORT-3000}
|
||||
env_file: '.env'
|
||||
env_file: ".env"
|
||||
|
@ -1,20 +1,20 @@
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import fs from 'node:fs'
|
||||
import { fileURLToPath } from "node:url"
|
||||
import fs from "node:fs"
|
||||
|
||||
import { build } from 'vite'
|
||||
import { build } from "vite"
|
||||
|
||||
const curriculumVitae = new URL('./', import.meta.url)
|
||||
const curriculumVitaeDist = new URL('./dist', curriculumVitae)
|
||||
const curriculumVitae = new URL("./", import.meta.url)
|
||||
const curriculumVitaeDist = new URL("./dist", curriculumVitae)
|
||||
const publicCurriculumVitaeOutputURL = new URL(
|
||||
'../public/curriculum-vitae',
|
||||
import.meta.url
|
||||
"../public/curriculum-vitae",
|
||||
import.meta.url,
|
||||
)
|
||||
|
||||
await build({
|
||||
root: fileURLToPath(curriculumVitae),
|
||||
base: '/curriculum-vitae/'
|
||||
base: "/curriculum-vitae/",
|
||||
})
|
||||
|
||||
await fs.promises.cp(curriculumVitaeDist, publicCurriculumVitaeOutputURL, {
|
||||
recursive: true
|
||||
recursive: true,
|
||||
})
|
||||
|
@ -22,8 +22,9 @@
|
||||
"courses": [
|
||||
"Développement Web en Node.js et React.js",
|
||||
"Intégration/Déploiement Continue et Docker",
|
||||
"Projet développement LLM (Large Language Model) et NLP (Natural Language Processing)",
|
||||
"Base de données NoSQL (MongoDB)"
|
||||
"Complexité Algorithmique Théorique et Pratique en C++",
|
||||
// "Projet développement LLM (Large Language Model) et NLP (Natural Language Processing)",
|
||||
"Base de données NoSQL (Redis, MongoDB, Cassandra)"
|
||||
]
|
||||
},
|
||||
{
|
||||
@ -34,7 +35,7 @@
|
||||
"score": "2ème année",
|
||||
"courses": [
|
||||
"Développement Web avec le framework Laravel en PHP",
|
||||
"Qualité de développement et Tests unitaires, d'intégration, fonctionnels/systèmes, d'acceptation etc.",
|
||||
"Qualité de développement et Tests automatisés",
|
||||
"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"
|
||||
|
498
curriculum-vitae/package-lock.json
generated
498
curriculum-vitae/package-lock.json
generated
@ -12,16 +12,32 @@
|
||||
"modern-normalize": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.6.2",
|
||||
"@types/node": "20.10.5",
|
||||
"date-and-time": "3.0.3",
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-html": "3.2.0"
|
||||
"vite": "5.0.10",
|
||||
"vite-plugin-html": "3.2.1"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/aix-ppc64": {
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.10.tgz",
|
||||
"integrity": "sha512-Q+mk96KJ+FZ30h9fsJl+67IjNJm3x2eX+GBWGmocAKgzp27cowCOOqSdscX80s0SpdFXZnIv/+1xD1EctFx96Q==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"aix"
|
||||
],
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.10.tgz",
|
||||
"integrity": "sha512-7W0bK7qfkw1fc2viBfrtAEkDKHatYfHzr/jKAHNr9BvkYDXPcC6bodtm8AyLJNNuqClLNaeTLuwURt4PRT9d7w==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -35,9 +51,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.10.tgz",
|
||||
"integrity": "sha512-1X4CClKhDgC3by7k8aOWZeBXQX8dHT5QAMCAQDArCLaYfkppoARvh0fit3X2Qs+MXDngKcHv6XXyQCpY0hkK1Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -51,9 +67,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/android-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-O/nO/g+/7NlitUxETkUv/IvADKuZXyH4BHf/g/7laqKC4i/7whLpB0gvpPc2zpF0q9Q6FXS3TS75QHac9MvVWw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -67,9 +83,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.10.tgz",
|
||||
"integrity": "sha512-YSRRs2zOpwypck+6GL3wGXx2gNP7DXzetmo5pHXLrY/VIMsS59yKfjPizQ4lLt5vEI80M41gjm2BxrGZ5U+VMA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -83,9 +99,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/darwin-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-alfGtT+IEICKtNE54hbvPg13xGBe4GkVxyGWtzr+yHO7HIiRJppPDhOKq3zstTcVf8msXb/t4eavW3jCDpMSmA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -99,9 +115,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.10.tgz",
|
||||
"integrity": "sha512-dMtk1wc7FSH8CCkE854GyGuNKCewlh+7heYP/sclpOG6Cectzk14qdUIY5CrKDbkA/OczXq9WesqnPl09mj5dg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -115,9 +131,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/freebsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-G5UPPspryHu1T3uX8WiOEUa6q6OlQh6gNl4CO4Iw5PS+Kg5bVggVFehzXBJY6X6RSOMS8iXDv2330VzaObm4Ag==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -131,9 +147,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz",
|
||||
"integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.10.tgz",
|
||||
"integrity": "sha512-j6gUW5aAaPgD416Hk9FHxn27On28H4eVI9rJ4az7oCGTFW48+LcgNDBN+9f8rKZz7EEowo889CPKyeaD0iw9Kg==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
@ -147,9 +163,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.10.tgz",
|
||||
"integrity": "sha512-QxaouHWZ+2KWEj7cGJmvTIHVALfhpGxo3WLmlYfJ+dA5fJB6lDEIg+oe/0//FuyVHuS3l79/wyBxbHr0NgtxJQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -163,9 +179,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ia32": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.10.tgz",
|
||||
"integrity": "sha512-4ub1YwXxYjj9h1UIZs2hYbnTZBtenPw5NfXCRgEkGb0b6OJ2gpkMvDqRDYIDRjRdWSe/TBiZltm3Y3Q8SN1xNg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -179,9 +195,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-loong64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz",
|
||||
"integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.10.tgz",
|
||||
"integrity": "sha512-lo3I9k+mbEKoxtoIbM0yC/MZ1i2wM0cIeOejlVdZ3D86LAcFXFRdeuZmh91QJvUTW51bOK5W2BznGNIl4+mDaA==",
|
||||
"cpu": [
|
||||
"loong64"
|
||||
],
|
||||
@ -195,9 +211,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-mips64el": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz",
|
||||
"integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.10.tgz",
|
||||
"integrity": "sha512-J4gH3zhHNbdZN0Bcr1QUGVNkHTdpijgx5VMxeetSk6ntdt+vR1DqGmHxQYHRmNb77tP6GVvD+K0NyO4xjd7y4A==",
|
||||
"cpu": [
|
||||
"mips64el"
|
||||
],
|
||||
@ -211,9 +227,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-ppc64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz",
|
||||
"integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.10.tgz",
|
||||
"integrity": "sha512-tgT/7u+QhV6ge8wFMzaklOY7KqiyitgT1AUHMApau32ZlvTB/+efeCtMk4eXS+uEymYK249JsoiklZN64xt6oQ==",
|
||||
"cpu": [
|
||||
"ppc64"
|
||||
],
|
||||
@ -227,9 +243,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-riscv64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz",
|
||||
"integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.10.tgz",
|
||||
"integrity": "sha512-0f/spw0PfBMZBNqtKe5FLzBDGo0SKZKvMl5PHYQr3+eiSscfJ96XEknCe+JoOayybWUFQbcJTrk946i3j9uYZA==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
@ -243,9 +259,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-s390x": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz",
|
||||
"integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.10.tgz",
|
||||
"integrity": "sha512-pZFe0OeskMHzHa9U38g+z8Yx5FNCLFtUnJtQMpwhS+r4S566aK2ci3t4NCP4tjt6d5j5uo4h7tExZMjeKoehAA==",
|
||||
"cpu": [
|
||||
"s390x"
|
||||
],
|
||||
@ -259,9 +275,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/linux-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-SpYNEqg/6pZYoc+1zLCjVOYvxfZVZj6w0KROZ3Fje/QrM3nfvT2llI+wmKSrWuX6wmZeTapbarvuNNK/qepSgA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -275,9 +291,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/netbsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-ACbZ0vXy9zksNArWlk2c38NdKg25+L9pr/mVaj9SUq6lHZu/35nx2xnQVRGLrC1KKQqJKRIB0q8GspiHI3J80Q==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -291,9 +307,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/openbsd-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-PxcgvjdSjtgPMiPQrM3pwSaG4kGphP+bLSb+cihuP0LYdZv1epbAIecHVl5sD3npkfYBZ0ZnOjR878I7MdJDFg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -307,9 +323,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/sunos-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-ZkIOtrRL8SEJjr+VHjmW0znkPs+oJXhlJbNwfI37rvgeMtk3sxOQevXPXjmAPZPigVTncvFqLMd+uV0IBSEzqA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -323,9 +339,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-arm64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz",
|
||||
"integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.10.tgz",
|
||||
"integrity": "sha512-+Sa4oTDbpBfGpl3Hn3XiUe4f8TU2JF7aX8cOfqFYMMjXp6ma6NJDztl5FDG8Ezx0OjwGikIHw+iA54YLDNNVfw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
@ -339,9 +355,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-ia32": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz",
|
||||
"integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.10.tgz",
|
||||
"integrity": "sha512-EOGVLK1oWMBXgfttJdPHDTiivYSjX6jDNaATeNOaCOFEVcfMjtbx7WVQwPSE1eIfCp/CaSF2nSrDtzc4I9f8TQ==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
@ -355,9 +371,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@esbuild/win32-x64": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz",
|
||||
"integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.10.tgz",
|
||||
"integrity": "sha512-whqLG6Sc70AbU73fFYvuYzaE4MNMBIlR1Y/IrUeOXFrWHxBEjjbZaQ3IXIQS8wJdAzue2GwYZCjOrgrU1oUHoA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
@ -419,9 +435,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@jridgewell/trace-mapping": {
|
||||
"version": "0.3.19",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.19.tgz",
|
||||
"integrity": "sha512-kf37QtfW+Hwx/buWGMPcR60iF9ziHa6r/CZJIHbmcm4+0qrXiVdxegAH0F6yddEVQ7zdkjcGCgCzUu+BcbhQxw==",
|
||||
"version": "0.3.20",
|
||||
"resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.20.tgz",
|
||||
"integrity": "sha512-R8LcPeWZol2zR8mmH3JeKQ6QRCFb7XgUhV9ZlGhHLGyg4wpPiPZNQOOWhFZhxKw8u//yTbNGI42Bx/3paXEQ+Q==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/resolve-uri": "^3.1.0",
|
||||
@ -476,16 +492,188 @@
|
||||
"node": ">= 8.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm-eabi": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.1.tgz",
|
||||
"integrity": "sha512-6vMdBZqtq1dVQ4CWdhFwhKZL6E4L1dV6jUjuBvsavvNJSppzi6dLBbuV+3+IyUREaj9ZFvQefnQm28v4OCXlig==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-android-arm64": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.1.tgz",
|
||||
"integrity": "sha512-Jto9Fl3YQ9OLsTDWtLFPtaIMSL2kwGyGoVCmPC8Gxvym9TCZm4Sie+cVeblPO66YZsYH8MhBKDMGZ2NDxuk/XQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"android"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-arm64": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.1.tgz",
|
||||
"integrity": "sha512-LtYcLNM+bhsaKAIGwVkh5IOWhaZhjTfNOkGzGqdHvhiCUVuJDalvDxEdSnhFzAn+g23wgsycmZk1vbnaibZwwA==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-darwin-x64": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.1.tgz",
|
||||
"integrity": "sha512-KyP/byeXu9V+etKO6Lw3E4tW4QdcnzDG/ake031mg42lob5tN+5qfr+lkcT/SGZaH2PdW4Z1NX9GHEkZ8xV7og==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"darwin"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm-gnueabihf": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.1.tgz",
|
||||
"integrity": "sha512-Yqz/Doumf3QTKplwGNrCHe/B2p9xqDghBZSlAY0/hU6ikuDVQuOUIpDP/YcmoT+447tsZTmirmjgG3znvSCR0Q==",
|
||||
"cpu": [
|
||||
"arm"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-gnu": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.1.tgz",
|
||||
"integrity": "sha512-u3XkZVvxcvlAOlQJ3UsD1rFvLWqu4Ef/Ggl40WAVCuogf4S1nJPHh5RTgqYFpCOvuGJ7H5yGHabjFKEZGExk5Q==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-arm64-musl": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.1.tgz",
|
||||
"integrity": "sha512-0XSYN/rfWShW+i+qjZ0phc6vZ7UWI8XWNz4E/l+6edFt+FxoEghrJHjX1EY/kcUGCnZzYYRCl31SNdfOi450Aw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-riscv64-gnu": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.1.tgz",
|
||||
"integrity": "sha512-LmYIO65oZVfFt9t6cpYkbC4d5lKHLYv5B4CSHRpnANq0VZUQXGcCPXHzbCXCz4RQnx7jvlYB1ISVNCE/omz5cw==",
|
||||
"cpu": [
|
||||
"riscv64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-gnu": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.1.tgz",
|
||||
"integrity": "sha512-kr8rEPQ6ns/Lmr/hiw8sEVj9aa07gh1/tQF2Y5HrNCCEPiCBGnBUt9tVusrcBBiJfIt1yNaXN6r1CCmpbFEDpg==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-linux-x64-musl": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.1.tgz",
|
||||
"integrity": "sha512-t4QSR7gN+OEZLG0MiCgPqMWZGwmeHhsM4AkegJ0Kiy6TnJ9vZ8dEIwHw1LcZKhbHxTY32hp9eVCMdR3/I8MGRw==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"linux"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-arm64-msvc": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.1.tgz",
|
||||
"integrity": "sha512-7XI4ZCBN34cb+BH557FJPmh0kmNz2c25SCQeT9OiFWEgf8+dL6ZwJ8f9RnUIit+j01u07Yvrsuu1rZGxJCc51g==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-ia32-msvc": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.1.tgz",
|
||||
"integrity": "sha512-yE5c2j1lSWOH5jp+Q0qNL3Mdhr8WuqCNVjc6BxbVfS5cAS6zRmdiw7ktb8GNpDCEUJphILY6KACoFoRtKoqNQg==",
|
||||
"cpu": [
|
||||
"ia32"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@rollup/rollup-win32-x64-msvc": {
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.1.tgz",
|
||||
"integrity": "sha512-PyJsSsafjmIhVgaI1Zdj7m8BB8mMckFah/xbpplObyHfiXzKcI5UOUXRyOdHW7nz4DpMCuzLnF7v5IWHenCwYA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
],
|
||||
"dev": true,
|
||||
"optional": true,
|
||||
"os": [
|
||||
"win32"
|
||||
]
|
||||
},
|
||||
"node_modules/@types/node": {
|
||||
"version": "20.6.2",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.6.2.tgz",
|
||||
"integrity": "sha512-Y+/1vGBHV/cYk6OI1Na/LHzwnlNCAfU3ZNGrc1LdRe/LAIbdDPTTv/HU3M7yXN448aTVDq3eKRm2cg7iKLb8gw==",
|
||||
"dev": true
|
||||
"version": "20.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.10.5.tgz",
|
||||
"integrity": "sha512-nNPsNE65wjMxEKI93yOP+NPGGBJz/PoN3kZsVLee0XMiJolxSekEVD8wRwBUBqkwc7UWop0edW50yrCQW4CyRw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"undici-types": "~5.26.4"
|
||||
}
|
||||
},
|
||||
"node_modules/acorn": {
|
||||
"version": "8.10.0",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz",
|
||||
"integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==",
|
||||
"version": "8.11.2",
|
||||
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.2.tgz",
|
||||
"integrity": "sha512-nc0Axzp/0FILLEVsm4fNwLCwMttvhEI263QtVPQcbpfZZ3ts0hLsZGOpE6czNlid7CJ9MlyH8reXkpsf3YUY4w==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"acorn": "bin/acorn"
|
||||
@ -510,9 +698,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/async": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.4.tgz",
|
||||
"integrity": "sha512-iAB+JbDEGXhyIUavoDl9WP/Jj106Kz9DEn1DPgYw5ruDn0e3Wgi3sKFm55sASdGBNOQB8F59d9qQ7deqrHA8wQ==",
|
||||
"version": "3.2.5",
|
||||
"resolved": "https://registry.npmjs.org/async/-/async-3.2.5.tgz",
|
||||
"integrity": "sha512-baNZyqaaLhyLVKm/DlvdW051MSgO6b8eVfIezl9E5PqWxFgzLm/wQntEW4zOytVburDEr0JlALEpdOFwvErLsg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/balanced-match": {
|
||||
@ -582,9 +770,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/clean-css": {
|
||||
"version": "5.3.2",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.2.tgz",
|
||||
"integrity": "sha512-JVJbM+f3d3Q704rF4bqQ5UUyTtuJ0JRKNbTKVEeujCCBoMdkEi+V+e8oktO9qGQNSvHrFTM6JZRXrUvGR1czww==",
|
||||
"version": "5.3.3",
|
||||
"resolved": "https://registry.npmjs.org/clean-css/-/clean-css-5.3.3.tgz",
|
||||
"integrity": "sha512-D5J+kHaVb/wKSFcyyV75uCn8fiY4sV38XJoe4CUyGQ+mOU/fMVYUdH1hJC+CJQ5uY3EnW27SbJYS4X8BiLrAFg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"source-map": "~0.6.0"
|
||||
@ -792,9 +980,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.18.20",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz",
|
||||
"integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==",
|
||||
"version": "0.19.10",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.10.tgz",
|
||||
"integrity": "sha512-S1Y27QGt/snkNYrRcswgRFqZjaTG5a5xM3EQo97uNBnH505pdzSNe/HLBq1v0RO7iK/ngdbhJB6mDAp0OK+iUA==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"bin": {
|
||||
@ -804,28 +992,29 @@
|
||||
"node": ">=12"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/android-arm": "0.18.20",
|
||||
"@esbuild/android-arm64": "0.18.20",
|
||||
"@esbuild/android-x64": "0.18.20",
|
||||
"@esbuild/darwin-arm64": "0.18.20",
|
||||
"@esbuild/darwin-x64": "0.18.20",
|
||||
"@esbuild/freebsd-arm64": "0.18.20",
|
||||
"@esbuild/freebsd-x64": "0.18.20",
|
||||
"@esbuild/linux-arm": "0.18.20",
|
||||
"@esbuild/linux-arm64": "0.18.20",
|
||||
"@esbuild/linux-ia32": "0.18.20",
|
||||
"@esbuild/linux-loong64": "0.18.20",
|
||||
"@esbuild/linux-mips64el": "0.18.20",
|
||||
"@esbuild/linux-ppc64": "0.18.20",
|
||||
"@esbuild/linux-riscv64": "0.18.20",
|
||||
"@esbuild/linux-s390x": "0.18.20",
|
||||
"@esbuild/linux-x64": "0.18.20",
|
||||
"@esbuild/netbsd-x64": "0.18.20",
|
||||
"@esbuild/openbsd-x64": "0.18.20",
|
||||
"@esbuild/sunos-x64": "0.18.20",
|
||||
"@esbuild/win32-arm64": "0.18.20",
|
||||
"@esbuild/win32-ia32": "0.18.20",
|
||||
"@esbuild/win32-x64": "0.18.20"
|
||||
"@esbuild/aix-ppc64": "0.19.10",
|
||||
"@esbuild/android-arm": "0.19.10",
|
||||
"@esbuild/android-arm64": "0.19.10",
|
||||
"@esbuild/android-x64": "0.19.10",
|
||||
"@esbuild/darwin-arm64": "0.19.10",
|
||||
"@esbuild/darwin-x64": "0.19.10",
|
||||
"@esbuild/freebsd-arm64": "0.19.10",
|
||||
"@esbuild/freebsd-x64": "0.19.10",
|
||||
"@esbuild/linux-arm": "0.19.10",
|
||||
"@esbuild/linux-arm64": "0.19.10",
|
||||
"@esbuild/linux-ia32": "0.19.10",
|
||||
"@esbuild/linux-loong64": "0.19.10",
|
||||
"@esbuild/linux-mips64el": "0.19.10",
|
||||
"@esbuild/linux-ppc64": "0.19.10",
|
||||
"@esbuild/linux-riscv64": "0.19.10",
|
||||
"@esbuild/linux-s390x": "0.19.10",
|
||||
"@esbuild/linux-x64": "0.19.10",
|
||||
"@esbuild/netbsd-x64": "0.19.10",
|
||||
"@esbuild/openbsd-x64": "0.19.10",
|
||||
"@esbuild/sunos-x64": "0.19.10",
|
||||
"@esbuild/win32-arm64": "0.19.10",
|
||||
"@esbuild/win32-ia32": "0.19.10",
|
||||
"@esbuild/win32-x64": "0.19.10"
|
||||
}
|
||||
},
|
||||
"node_modules/estree-walker": {
|
||||
@ -835,9 +1024,9 @@
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/fast-glob": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz",
|
||||
"integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==",
|
||||
"version": "3.3.2",
|
||||
"resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
|
||||
"integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@nodelib/fs.stat": "^2.0.2",
|
||||
@ -851,9 +1040,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/fastq": {
|
||||
"version": "1.15.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz",
|
||||
"integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==",
|
||||
"version": "1.16.0",
|
||||
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.16.0.tgz",
|
||||
"integrity": "sha512-ifCoaXsDrsdkWTtiNJX5uzHDsrck5TzfKKDcuFFTIrrc/BS076qgEIfoIy1VeZqViznfKiysPYTh/QeHtnIsYA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"reusify": "^1.0.4"
|
||||
@ -1106,9 +1295,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/nanoid": {
|
||||
"version": "3.3.6",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz",
|
||||
"integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==",
|
||||
"version": "3.3.7",
|
||||
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
|
||||
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1200,9 +1389,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.29",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz",
|
||||
"integrity": "sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw==",
|
||||
"version": "8.4.32",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.32.tgz",
|
||||
"integrity": "sha512-D/kj5JNu6oo2EIy+XL/26JEDTlIbB8hw85G8StOE6L74RQAVVP5rej6wxCNqyMbR4RkPfqvezVbPw81Ngd6Kcw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
@ -1219,7 +1408,7 @@
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"nanoid": "^3.3.7",
|
||||
"picocolors": "^1.0.0",
|
||||
"source-map-js": "^1.0.2"
|
||||
},
|
||||
@ -1267,18 +1456,31 @@
|
||||
}
|
||||
},
|
||||
"node_modules/rollup": {
|
||||
"version": "3.29.2",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.2.tgz",
|
||||
"integrity": "sha512-CJouHoZ27v6siztc21eEQGo0kIcE5D1gVPA571ez0mMYb25LGYGKnVNXpEj5MGlepmDWGXNjDB5q7uNiPHC11A==",
|
||||
"version": "4.9.1",
|
||||
"resolved": "https://registry.npmjs.org/rollup/-/rollup-4.9.1.tgz",
|
||||
"integrity": "sha512-pgPO9DWzLoW/vIhlSoDByCzcpX92bKEorbgXuZrqxByte3JFk2xSW2JEeAcyLc9Ru9pqcNNW+Ob7ntsk2oT/Xw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"rollup": "dist/bin/rollup"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=14.18.0",
|
||||
"node": ">=18.0.0",
|
||||
"npm": ">=8.0.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@rollup/rollup-android-arm-eabi": "4.9.1",
|
||||
"@rollup/rollup-android-arm64": "4.9.1",
|
||||
"@rollup/rollup-darwin-arm64": "4.9.1",
|
||||
"@rollup/rollup-darwin-x64": "4.9.1",
|
||||
"@rollup/rollup-linux-arm-gnueabihf": "4.9.1",
|
||||
"@rollup/rollup-linux-arm64-gnu": "4.9.1",
|
||||
"@rollup/rollup-linux-arm64-musl": "4.9.1",
|
||||
"@rollup/rollup-linux-riscv64-gnu": "4.9.1",
|
||||
"@rollup/rollup-linux-x64-gnu": "4.9.1",
|
||||
"@rollup/rollup-linux-x64-musl": "4.9.1",
|
||||
"@rollup/rollup-win32-arm64-msvc": "4.9.1",
|
||||
"@rollup/rollup-win32-ia32-msvc": "4.9.1",
|
||||
"@rollup/rollup-win32-x64-msvc": "4.9.1",
|
||||
"fsevents": "~2.3.2"
|
||||
}
|
||||
},
|
||||
@ -1346,9 +1548,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/terser": {
|
||||
"version": "5.19.4",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.19.4.tgz",
|
||||
"integrity": "sha512-6p1DjHeuluwxDXcuT9VR8p64klWJKo1ILiy19s6C9+0Bh2+NWTX6nD9EPppiER4ICkHDVB1RkVpin/YW2nQn/g==",
|
||||
"version": "5.26.0",
|
||||
"resolved": "https://registry.npmjs.org/terser/-/terser-5.26.0.tgz",
|
||||
"integrity": "sha512-dytTGoE2oHgbNV9nTzgBEPaqAWvcJNl66VZ0BkJqlvp71IjO8CxdBx/ykCNb47cLnCmCvRZ6ZR0tLkqvZCdVBQ==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@jridgewell/source-map": "^0.3.3",
|
||||
@ -1387,39 +1589,45 @@
|
||||
"integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/undici-types": {
|
||||
"version": "5.26.5",
|
||||
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/universalify": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz",
|
||||
"integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==",
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz",
|
||||
"integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">= 10.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/vite": {
|
||||
"version": "4.4.9",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz",
|
||||
"integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==",
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/vite/-/vite-5.0.10.tgz",
|
||||
"integrity": "sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"esbuild": "^0.18.10",
|
||||
"postcss": "^8.4.27",
|
||||
"rollup": "^3.27.1"
|
||||
"esbuild": "^0.19.3",
|
||||
"postcss": "^8.4.32",
|
||||
"rollup": "^4.2.0"
|
||||
},
|
||||
"bin": {
|
||||
"vite": "bin/vite.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^14.18.0 || >=16.0.0"
|
||||
"node": "^18.0.0 || >=20.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/vitejs/vite?sponsor=1"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"fsevents": "~2.3.2"
|
||||
"fsevents": "~2.3.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"@types/node": ">= 14",
|
||||
"@types/node": "^18.0.0 || >=20.0.0",
|
||||
"less": "*",
|
||||
"lightningcss": "^1.21.0",
|
||||
"sass": "*",
|
||||
@ -1452,9 +1660,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/vite-plugin-html": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-html/-/vite-plugin-html-3.2.0.tgz",
|
||||
"integrity": "sha512-2VLCeDiHmV/BqqNn5h2V+4280KRgQzCFN47cst3WiNK848klESPQnzuC3okH5XHtgwHH/6s1Ho/YV6yIO0pgoQ==",
|
||||
"version": "3.2.1",
|
||||
"resolved": "https://registry.npmjs.org/vite-plugin-html/-/vite-plugin-html-3.2.1.tgz",
|
||||
"integrity": "sha512-UuNTUvGPa9BxZK1I/VC+hWMSl0eIE5ClEdXza3SGGb09jxxS7oAAjRGalUJNZI9lAPvu0W817GD53K/p78wxQw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@rollup/pluginutils": "^4.2.0",
|
||||
|
@ -13,9 +13,9 @@
|
||||
"modern-normalize": "2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/node": "20.6.2",
|
||||
"@types/node": "20.10.5",
|
||||
"date-and-time": "3.0.3",
|
||||
"vite": "4.4.9",
|
||||
"vite-plugin-html": "3.2.0"
|
||||
"vite": "5.0.10",
|
||||
"vite-plugin-html": "3.2.1"
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { BIRTH_DATE, getAge } from '../../utils/getAge.ts'
|
||||
import { BIRTH_DATE, getAge } from "../../utils/getAge.ts"
|
||||
|
||||
const yearOld = document.getElementById('year-old')
|
||||
const yearOld = document.getElementById("year-old")
|
||||
|
||||
yearOld.textContent = getAge(BIRTH_DATE).toString()
|
||||
|
@ -1,7 +1,7 @@
|
||||
@import 'modern-normalize/modern-normalize.css';
|
||||
@import "modern-normalize/modern-normalize.css";
|
||||
|
||||
body {
|
||||
font-family: 'Montserrat', 'Arial', 'sans-serif';
|
||||
font-family: "Montserrat", "Arial", "sans-serif";
|
||||
background: #f0f0f0;
|
||||
color: #333;
|
||||
line-height: 1.42857143;
|
||||
|
@ -1,19 +1,19 @@
|
||||
import fs from 'node:fs'
|
||||
import fs from "node:fs"
|
||||
|
||||
import { defineConfig } from 'vite'
|
||||
import { parse as JSONCParser } from 'jsonc-parser'
|
||||
import { createHtmlPlugin } from 'vite-plugin-html'
|
||||
import date from 'date-and-time'
|
||||
import { defineConfig } from "vite"
|
||||
import { parse as JSONCParser } from "jsonc-parser"
|
||||
import { createHtmlPlugin } from "vite-plugin-html"
|
||||
import date from "date-and-time"
|
||||
|
||||
const jsonCurriculumVitaeURL = new URL(
|
||||
'./curriculum-vitae.jsonc',
|
||||
import.meta.url
|
||||
"./curriculum-vitae.jsonc",
|
||||
import.meta.url,
|
||||
)
|
||||
const dataCurriculumVitaeStringJSON = await fs.promises.readFile(
|
||||
jsonCurriculumVitaeURL,
|
||||
{
|
||||
encoding: 'utf-8'
|
||||
}
|
||||
encoding: "utf-8",
|
||||
},
|
||||
)
|
||||
const curriculumVitae = JSONCParser(dataCurriculumVitaeStringJSON)
|
||||
|
||||
@ -22,7 +22,7 @@ const curriculumVitae = JSONCParser(dataCurriculumVitaeStringJSON)
|
||||
*/
|
||||
export default defineConfig({
|
||||
build: {
|
||||
assetsDir: './'
|
||||
assetsDir: "./",
|
||||
},
|
||||
plugins: [
|
||||
createHtmlPlugin({
|
||||
@ -30,13 +30,13 @@ export default defineConfig({
|
||||
data: {
|
||||
date,
|
||||
locals: {
|
||||
...curriculumVitae
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
...curriculumVitae,
|
||||
},
|
||||
},
|
||||
},
|
||||
}),
|
||||
],
|
||||
css: {
|
||||
postcss: {}
|
||||
}
|
||||
postcss: {},
|
||||
},
|
||||
})
|
||||
|
@ -1,17 +1,17 @@
|
||||
import { defineConfig } from 'cypress'
|
||||
import { defineConfig } from "cypress"
|
||||
|
||||
export default defineConfig({
|
||||
fixturesFolder: false,
|
||||
video: false,
|
||||
screenshotOnRunFailure: false,
|
||||
e2e: {
|
||||
baseUrl: 'http://127.0.0.1:3000',
|
||||
supportFile: false
|
||||
baseUrl: "http://127.0.0.1:3000",
|
||||
supportFile: false,
|
||||
},
|
||||
component: {
|
||||
devServer: {
|
||||
framework: 'next',
|
||||
bundler: 'webpack'
|
||||
}
|
||||
}
|
||||
framework: "next",
|
||||
bundler: "webpack",
|
||||
},
|
||||
},
|
||||
})
|
||||
|
@ -1,16 +1,16 @@
|
||||
import { getAge } from '@/utils/getAge'
|
||||
import { getAge } from "@/utils/getAge"
|
||||
|
||||
describe('utils/getAge', () => {
|
||||
it('should calculate the right age of a person', () => {
|
||||
cy.clock(new Date('2018-03-20')).then(() => {
|
||||
const birthDate = new Date('1980-02-20')
|
||||
describe("utils/getAge", () => {
|
||||
it("should calculate the right age of a person", () => {
|
||||
cy.clock(new Date("2018-03-20")).then(() => {
|
||||
const birthDate = new Date("1980-02-20")
|
||||
expect(getAge(birthDate)).equal(38)
|
||||
})
|
||||
})
|
||||
|
||||
it('should calculate the right age of a person (taking into account the months)', () => {
|
||||
cy.clock(new Date('2018-03-20')).then(() => {
|
||||
const birthDate = new Date('1980-07-20')
|
||||
it("should calculate the right age of a person (taking into account the months)", () => {
|
||||
cy.clock(new Date("2018-03-20")).then(() => {
|
||||
const birthDate = new Date("1980-07-20")
|
||||
expect(getAge(birthDate)).equal(37)
|
||||
})
|
||||
})
|
||||
|
@ -1,62 +1,60 @@
|
||||
describe('Common > Header', () => {
|
||||
describe("Common > Header", () => {
|
||||
beforeEach(() => {
|
||||
return cy.visit('/')
|
||||
return cy.visit("/")
|
||||
})
|
||||
|
||||
it('should redirect to /blog on click of the blog link', () => {
|
||||
cy.get('[data-cy=header-blog-link]')
|
||||
it("should redirect to /blog on click of the blog link", () => {
|
||||
cy.get("[data-cy=header-blog-link]")
|
||||
.click()
|
||||
.location('pathname')
|
||||
.should('eq', '/blog')
|
||||
.location("pathname")
|
||||
.should("eq", "/blog")
|
||||
})
|
||||
|
||||
it('should always be visible (sticky header)', () => {
|
||||
cy.scrollTo('bottom').get('header').should('be.visible')
|
||||
it("should always be visible (sticky header)", () => {
|
||||
cy.scrollTo("bottom").get("header").should("be.visible")
|
||||
})
|
||||
|
||||
describe('Switch theme color (dark/light)', () => {
|
||||
it('should switch theme from `dark` (default) to `light`', () => {
|
||||
cy.get('[data-cy=switch-theme-dark]').should('be.visible')
|
||||
cy.get('[data-cy=switch-theme-light]').should('not.be.visible')
|
||||
cy.get('body').should(
|
||||
'not.have.css',
|
||||
'background-color',
|
||||
'rgb(255, 255, 255)'
|
||||
describe("Switch theme color (dark/light)", () => {
|
||||
it("should switch theme from `dark` (default) to `light`", () => {
|
||||
cy.get("[data-cy=switch-theme-dark]").should("be.visible")
|
||||
cy.get("[data-cy=switch-theme-light]").should("not.be.visible")
|
||||
cy.get("body").should(
|
||||
"not.have.css",
|
||||
"background-color",
|
||||
"rgb(255, 255, 255)",
|
||||
)
|
||||
|
||||
cy.get('[data-cy=switch-theme-click]').click()
|
||||
cy.get("[data-cy=switch-theme-click]").click()
|
||||
|
||||
cy.get('[data-cy=switch-theme-dark]').should('not.be.visible')
|
||||
cy.get('[data-cy=switch-theme-light]').should('be.visible')
|
||||
cy.get('body').should(
|
||||
'have.css',
|
||||
'background-color',
|
||||
'rgb(255, 255, 255)'
|
||||
cy.get("[data-cy=switch-theme-dark]").should("not.be.visible")
|
||||
cy.get("[data-cy=switch-theme-light]").should("be.visible")
|
||||
cy.get("body").should(
|
||||
"have.css",
|
||||
"background-color",
|
||||
"rgb(255, 255, 255)",
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Switch Language', () => {
|
||||
it('should switch locale from English (default) to French', () => {
|
||||
cy.get('h1').contains('Théo LUDWIG')
|
||||
cy.get('[data-cy=locale-flag-text]').contains('English')
|
||||
cy.get('[data-cy=locales-list]').should('not.be.visible')
|
||||
cy.get('[data-cy=locale-click]').click()
|
||||
cy.get('[data-cy=locales-list]').should('be.visible')
|
||||
cy.get('[data-cy=locales-list] > li:first-child')
|
||||
.contains('French')
|
||||
describe("Switch Language", () => {
|
||||
it("should switch locale from English (default) to French", () => {
|
||||
cy.get("[data-cy=locale-flag-text]").contains("English")
|
||||
cy.get("[data-cy=locales-list]").should("not.be.visible")
|
||||
cy.get("[data-cy=locale-click]").click()
|
||||
cy.get("[data-cy=locales-list]").should("be.visible")
|
||||
cy.get("[data-cy=locales-list] > li:first-child")
|
||||
.contains("French")
|
||||
.click()
|
||||
cy.get('[data-cy=locales-list]').should('not.be.visible')
|
||||
cy.get('[data-cy=locale-flag-text]').contains('French')
|
||||
cy.get('h1').contains('Théo LUDWIG')
|
||||
cy.get("[data-cy=locales-list]").should("not.be.visible")
|
||||
// cy.get("[data-cy=locale-flag-text]").contains("French")
|
||||
})
|
||||
|
||||
it('should close the locale list menu when clicking outside', () => {
|
||||
cy.get('[data-cy=locales-list]').should('not.be.visible')
|
||||
cy.get('[data-cy=locale-click]').click()
|
||||
cy.get('[data-cy=locales-list]').should('be.visible')
|
||||
cy.get('h1').click()
|
||||
cy.get('[data-cy=locales-list]').should('not.be.visible')
|
||||
it("should close the locale list menu when clicking outside", () => {
|
||||
cy.get("[data-cy=locales-list]").should("not.be.visible")
|
||||
cy.get("[data-cy=locale-click]").click()
|
||||
cy.get("[data-cy=locales-list]").should("be.visible")
|
||||
cy.get("h1").click()
|
||||
cy.get("[data-cy=locales-list]").should("not.be.visible")
|
||||
})
|
||||
})
|
||||
})
|
||||
|
@ -1,10 +1,10 @@
|
||||
describe('Page /404', () => {
|
||||
describe("Page /404", () => {
|
||||
beforeEach(() => {
|
||||
return cy.visit('/404', { failOnStatusCode: false })
|
||||
return cy.visit("/404", { failOnStatusCode: false })
|
||||
})
|
||||
|
||||
it('should display the statusCode of 404', () => {
|
||||
cy.get('[data-cy=status-code]').contains('404')
|
||||
it("should display the statusCode of 404", () => {
|
||||
cy.get("[data-cy=status-code]").contains("404")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,14 +1,14 @@
|
||||
describe('Page /blog/[slug]', () => {
|
||||
it('should displays the first blog post (`hello-world`)', () => {
|
||||
cy.visit('/blog/hello-world')
|
||||
cy.get('[data-cy=locale-flag-text]').should('not.exist')
|
||||
cy.get('h1').should('have.text', '👋 Hello, world!')
|
||||
cy.get('.prose a:visible').should('have.attr', 'target', '_blank')
|
||||
describe("Page /blog/[slug]", () => {
|
||||
it("should displays the first blog post (`hello-world`)", () => {
|
||||
cy.visit("/blog/hello-world")
|
||||
cy.get("[data-cy=locale-flag-text]").should("not.exist")
|
||||
cy.get("h1").should("have.text", "👋 Hello, world!")
|
||||
cy.get(".prose a:visible").should("have.attr", "target", "_blank")
|
||||
})
|
||||
|
||||
it("should redirect to /404 if the blog post doesn't exist", () => {
|
||||
cy.visit('/blog/random-blog-post-not-found', { failOnStatusCode: false })
|
||||
cy.get('[data-cy=status-code]').contains('404')
|
||||
cy.visit("/blog/random-blog-post-not-found", { failOnStatusCode: false })
|
||||
cy.get("[data-cy=status-code]").contains("404")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,23 +1,23 @@
|
||||
describe('Page /blog', () => {
|
||||
it('should displays the blog posts sorted from newest to oldest', () => {
|
||||
cy.visit('/blog')
|
||||
cy.get('[data-cy=blog-posts] [data-cy=blog-post-title]')
|
||||
describe("Page /blog", () => {
|
||||
it("should displays the blog posts sorted from newest to oldest", () => {
|
||||
cy.visit("/blog")
|
||||
cy.get("[data-cy=blog-posts] [data-cy=blog-post-title]")
|
||||
.last()
|
||||
.should('have.text', '👋 Hello, world!')
|
||||
cy.get('[data-cy=blog-posts] [data-cy=blog-post-description]')
|
||||
.should("have.text", "👋 Hello, world!")
|
||||
cy.get("[data-cy=blog-posts] [data-cy=blog-post-description]")
|
||||
.last()
|
||||
.should(
|
||||
'have.text',
|
||||
'First post of the blog, introduction and explanation of how this blog is made.'
|
||||
"have.text",
|
||||
"First post of the blog, introduction and explanation of how this blog is made.",
|
||||
)
|
||||
})
|
||||
|
||||
it('should redirect the user to the right blog post', () => {
|
||||
cy.visit('/blog')
|
||||
cy.get('[data-cy=hello-world]')
|
||||
it("should redirect the user to the right blog post", () => {
|
||||
cy.visit("/blog")
|
||||
cy.get("[data-cy=hello-world]")
|
||||
.click()
|
||||
.location('pathname')
|
||||
.should('eq', '/blog/hello-world')
|
||||
.location("pathname")
|
||||
.should("eq", "/blog/hello-world")
|
||||
})
|
||||
})
|
||||
|
||||
|
@ -1,16 +1,16 @@
|
||||
describe('Page /', () => {
|
||||
describe("Page /", () => {
|
||||
beforeEach(() => {
|
||||
return cy.visit('/')
|
||||
return cy.visit("/")
|
||||
})
|
||||
|
||||
it('should reveals the sections while scrolling except the about section', () => {
|
||||
const sectionsReveals = ['#interests', '#skills', '#portfolio']
|
||||
cy.get('#about').should('be.visible')
|
||||
it("should reveals the sections while scrolling except the about section", () => {
|
||||
const sectionsReveals = ["#interests", "#skills", "#portfolio"]
|
||||
cy.get("#about").should("be.visible")
|
||||
for (const section of sectionsReveals) {
|
||||
cy.get(section)
|
||||
.should('not.be.visible')
|
||||
.should("not.be.visible")
|
||||
.scrollIntoView()
|
||||
.should('be.visible')
|
||||
.should("be.visible")
|
||||
}
|
||||
})
|
||||
})
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { mount } from 'cypress/react'
|
||||
import { mount } from "cypress/react"
|
||||
|
||||
import './commands'
|
||||
import '../../app/globals.css'
|
||||
import "./commands"
|
||||
import "../../app/globals.css"
|
||||
|
||||
declare global {
|
||||
namespace Cypress {
|
||||
@ -11,4 +11,4 @@ declare global {
|
||||
}
|
||||
}
|
||||
|
||||
Cypress.Commands.add('mount', mount)
|
||||
Cypress.Commands.add("mount", mount)
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user