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,23 +47,26 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
)} )}
</div> </div>
{link.openInNewTab && ( {link.openInNewTab && (
<svg <>
xmlns="http://www.w3.org/2000/svg" <svg
width="14" xmlns="http://www.w3.org/2000/svg"
height="14" width="14"
viewBox="0 0 24 24" height="14"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" fill="none"
strokeWidth="2" stroke="currentColor"
strokeLinecap="round" strokeWidth="2"
strokeLinejoin="round" strokeLinecap="round"
className="shrink-0 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]" strokeLinejoin="round"
aria-label="Ouvre dans un nouvel onglet" className="shrink-0 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]"
> aria-hidden="true"
<path d="M15 3h6v6" /> >
<path d="M10 14 21 3" /> <path d="M15 3h6v6" />
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> <path d="M10 14 21 3" />
</svg> <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,23 +86,26 @@ export function ServiceCard({ link, index = 0, layout = "horizontal" }: ServiceC
)} )}
</div> </div>
{link.openInNewTab && ( {link.openInNewTab && (
<svg <>
xmlns="http://www.w3.org/2000/svg" <svg
width="14" xmlns="http://www.w3.org/2000/svg"
height="14" width="14"
viewBox="0 0 24 24" height="14"
fill="none" viewBox="0 0 24 24"
stroke="currentColor" fill="none"
strokeWidth="2" stroke="currentColor"
strokeLinecap="round" strokeWidth="2"
strokeLinejoin="round" strokeLinecap="round"
className="mt-1 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]" strokeLinejoin="round"
aria-label="Ouvre dans un nouvel onglet" className="mt-1 text-[var(--text-tertiary)] transition-colors group-hover:text-[var(--text-secondary)]"
> aria-hidden="true"
<path d="M15 3h6v6" /> >
<path d="M10 14 21 3" /> <path d="M15 3h6v6" />
<path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6" /> <path d="M10 14 21 3" />
</svg> <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>
<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))