fix: code review corrections - config memoization, Docker cleanup, a11y, lint

This commit is contained in:
maxsoch 2026-07-24 09:24:03 +02:00
parent 528e8bcbbb
commit 59d49d0299
7 changed files with 64 additions and 61 deletions

View file

@ -1,8 +1,3 @@
FROM node:20-alpine AS deps
WORKDIR /app
COPY package.json package-lock.json ./
RUN npm ci --only=production
FROM node:20-alpine AS build
WORKDIR /app
COPY package.json package-lock.json ./

View file

@ -6,7 +6,7 @@
"dev": "node node_modules/next/dist/bin/next dev",
"build": "node node_modules/next/dist/bin/next build",
"start": "node node_modules/next/dist/bin/next start",
"lint": "node node_modules/eslint/bin/eslint.js"
"lint": "node node_modules/eslint/bin/eslint.js ."
},
"dependencies": {
"lucide-react": "^1.26.0",

View file

@ -1,5 +1,6 @@
import type { Metadata } from "next"
import { Inter } from "next/font/google"
import { getConfig } from "@/lib/config"
import "./globals.css"
const inter = Inter({
@ -8,17 +9,19 @@ const inter = Inter({
variable: "--font-inter",
})
export const metadata: Metadata = {
title: "Soch Family",
description: "Les applications de la famille",
}
function getInitialTheme(): string {
export async function generateMetadata(): Promise<Metadata> {
try {
const { loadConfig } = require("@/lib/config")
return loadConfig().theme.mode
const config = getConfig()
return {
title: config.site.name,
description: config.site.title,
icons: config.site.favicon ? { icon: config.site.favicon } : undefined,
}
} catch {
return "dark"
return {
title: "Sochboard",
description: "Dashboard",
}
}
}
@ -27,7 +30,10 @@ export default function RootLayout({
}: Readonly<{
children: React.ReactNode
}>) {
const theme = getInitialTheme()
let theme = "dark"
try {
theme = getConfig().theme.mode
} catch {}
return (
<html

View file

@ -1,12 +1,12 @@
import { loadConfig } from "@/lib/config"
import { getConfig } from "@/lib/config"
import { Dashboard } from "@/components/dashboard/Dashboard"
import { ErrorDisplay } from "@/components/ui/ErrorDisplay"
export const dynamic = "force-dynamic"
function getConfig() {
function getConfigResult() {
try {
return { ok: true as const, config: loadConfig() }
return { ok: true as const, config: getConfig() }
} catch (error) {
return {
ok: false as const,
@ -19,7 +19,7 @@ function getConfig() {
}
export default function Home() {
const result = getConfig()
const result = getConfigResult()
if (!result.ok) {
return <ErrorDisplay message={result.message} />

View file

@ -15,13 +15,6 @@ export function DashboardHeader({ site, initialTheme }: DashboardHeaderProps) {
document.documentElement.classList.toggle("theme-light", isLight)
}, [isLight])
useEffect(() => {
document.documentElement.classList.toggle(
"theme-light",
initialTheme === "light"
)
}, [initialTheme])
function toggleTheme() {
setIsLight((prev) => !prev)
}

View file

@ -47,6 +47,7 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
)}
</div>
{link.openInNewTab && (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@ -58,12 +59,14 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
strokeLinecap="round"
strokeLinejoin="round"
className="shrink-0 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]"
aria-label="Ouvre dans un nouvel onglet"
aria-hidden="true"
>
<path d="M15 3h6v6" />
<path d="M10 14 21 3" />
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
</svg>
<span className="sr-only">Nouvel onglet</span>
</>
)}
</>
) : (
@ -83,6 +86,7 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
)}
</div>
{link.openInNewTab && (
<>
<svg
xmlns="http://www.w3.org/2000/svg"
width="14"
@ -94,12 +98,14 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
strokeLinecap="round"
strokeLinejoin="round"
className="mt-1 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]"
aria-label="Ouvre dans un nouvel onglet"
aria-hidden="true"
>
<path d="M15 3h6v6" />
<path d="M10 14 21 3" />
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" />
</svg>
<span className="sr-only">Nouvel onglet</span>
</>
)}
</div>
<div className="flex flex-col gap-1">

View file

@ -1,3 +1,4 @@
import { cache } from "react"
import { readYamlFile } from "./yaml"
import { dashboardConfigSchema } from "./validation"
import type { DashboardConfig } from "@/types/config"
@ -21,3 +22,5 @@ export function loadConfig(configPath?: string): DashboardConfig {
return result.data
}
export const getConfig = cache((configPath?: string) => loadConfig(configPath))