feat(settings): turn Appearance into a hub of per-module subpages

Appearance ("Darstellung") stacked every module's view and ordering
settings on one long page. Split it into a hub that lists the modules as
navigable rows, each opening its own /settings/layout/<module> subpage
with a back link — the per-module settings move verbatim, no restyling.
The module routes now redirect to those subpages instead of an in-page
anchor, and the in-app cogs link straight to them.
This commit is contained in:
Marc-André Bombeck committed 2026-06-30 18:53:50 +02:00
1 parent fa2e2c6ba2
commit a01c9d0435
20 files changed
+451 -205

No files matched your search

+85
View File
@@ -0,0 +1,85 @@
import { notFound } from "next/navigation";
import { LayoutModuleGate } from "@/components/settings/layout-module-gate";
import {
LAYOUT_GROUPS,
LAYOUT_GROUP_IDS,
isLayoutGroupId,
} from "@/components/settings/layout-groups";
import { SettingsHubBackLink } from "@/components/settings/settings-hub-back-link";
import { SettingsShell } from "@/components/settings/settings-shell";
import { resolveServerLocale } from "@/lib/i18n/server-locale";
import { getServerTranslator } from "@/lib/i18n/server-translator";
/**
* v1.25.11 (#148) — per-module Appearance subpage.
*
* `/settings/layout/<module>` renders ONLY that module's section (the existing
* `*Section` component, verbatim) under the Appearance hub, with a
* "← Appearance" back-link. The hub at `/settings/layout` lists the modules and
* links here; the legacy `/settings/<module>` routes 301-redirect here
* (next.config.ts).
*
* Each subpage is pre-rendered for the known module ids via
* `generateStaticParams()`; `dynamicParams = false` 404s any other segment at
* routing time (re-checked with `notFound()` for defence-in-depth). The slug
* list is imported from the SERVER-safe `layout-groups` module so this server
* component never pulls runtime values across a client boundary.
*/
export const dynamicParams = false;
export function generateStaticParams() {
return LAYOUT_GROUP_IDS.map((module) => ({ module }));
}
interface PageProps {
// Next.js 16 made route `params` an async Promise. We `await` it before use.
params: Promise<{ module: string }>;
}
export default async function SettingsLayoutModulePage({ params }: PageProps) {
const { module } = await params;
if (!isLayoutGroupId(module)) {
notFound();
}
const group = LAYOUT_GROUPS.find((entry) => entry.id === module);
if (!group) {
notFound();
}
// Heading lives in the shell (its own grid row spanning only the content
// column). The title is the MODULE's title, resolved server-side from the
// cookie locale (mirroring the onboarding pages) so the shell receives a
// ready string — no client re-translation, so server HTML and the hydration
// render carry the identical heading text.
const locale = await resolveServerLocale();
const { t } = getServerTranslator(locale);
const Body = group.Body;
const headingId = `settings-section-${module}-title`;
return (
<SettingsShell
active="layout"
heading={{
title: t(group.titleKey),
subtitle: t(group.descriptionKey),
headingId,
topSlot: (
<SettingsHubBackLink
href="/settings/layout"
labelKey="settings.sections.layout.backToHub"
/>
),
}}
>
<section aria-labelledby={headingId} className="space-y-6">
<LayoutModuleGate moduleGate={group.moduleGate}>
<Body />
</LayoutModuleGate>
</section>
</SettingsShell>
);
}