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 FROM node:20-alpine AS build
WORKDIR /app WORKDIR /app
COPY package.json package-lock.json ./ COPY package.json package-lock.json ./

View file

@ -6,7 +6,7 @@
"dev": "node node_modules/next/dist/bin/next dev", "dev": "node node_modules/next/dist/bin/next dev",
"build": "node node_modules/next/dist/bin/next build", "build": "node node_modules/next/dist/bin/next build",
"start": "node node_modules/next/dist/bin/next start", "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": { "dependencies": {
"lucide-react": "^1.26.0", "lucide-react": "^1.26.0",

View file

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

View file

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

View file

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

View file

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

View file

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