fix: code review corrections - config memoization, Docker cleanup, a11y, lint
This commit is contained in:
parent
528e8bcbbb
commit
59d49d0299
|
|
@ -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 ./
|
||||||
|
|
|
||||||
|
|
@ -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",
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
|
|
@ -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} />
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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">
|
||||||
|
|
|
||||||
|
|
@ -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))
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue