Erscheinungsbild im Admin-Bereich anpassbar

Name, Zusatz in der Fusszeile, Akzentfarbe, Logo und Favicon lassen sich
unter Verwaltung -> Erscheinungsbild einstellen. Sie gelten fuer die ganze
Installation und liegen in einer eigenen Tabelle mit genau einer Zeile.

Zur Farbe: Tailwind 4 uebersetzt bg-indigo-600 zu
background-color: var(--color-indigo-600). Die Anwendung schreibt diese
Variablen zur Laufzeit neu — damit folgen alle rund 60 eingefaerbten Stellen
der gewaehlten Farbe, ohne dass eine einzige Klasse angefasst werden muss.

Uebernommen werden nur Farbton und Saettigung, nicht die Helligkeit. Die
Helligkeitskurve stammt aus der Tailwind-Vorlage. Sonst wuerde ein helles
Gelb die Kopfzeile unlesbar machen, denn auf Stufe 600 steht weisse Schrift.
Die Umrechnung laeuft ueber OKLCH und trifft Tailwinds eigenes Indigo exakt.

Logo und Favicon sind Uploads mit eigener Groessengrenze (2 MB bzw. 512 KB)
und eigener Rechtepruefung: instance:manage statt codes:create. Uploads haben
jetzt ein Feld "purpose" — sonst haette die Aufraeumroutine fuer herrenlose
Dateien Logo und Favicon nach einem Tag geloescht. Ein ersetztes Bild wird
gelaescht, und eine Datei aus einem QR-Code laesst sich nicht als Logo
einhaengen.

Auf den Landingpages gelten Farbe und Favicon, aber weder Logo noch
Navigation — wer scannt, soll die Seite des Inhalts sehen.

Ein erster Entwurf hielt das Erscheinungsbild 15 Sekunden im Speicher, um die
Abfrage je Seitenaufruf zu sparen. Das ging schief: API-Route und Layout
laufen in getrennten Modulinstanzen, das Verwerfen beim Speichern erreichte
die andere Instanz nicht — nach dem Speichern blieb die Oberflaeche
unveraendert. Jetzt ohne Puffer.
This commit is contained in:
Florian Wilde
2026-08-16 02:18:49 +02:00
parent d0057f038c
commit 4046b72626
16 changed files with 1068 additions and 44 deletions
+49 -11
View File
@@ -1,28 +1,66 @@
import type { Metadata } from "next";
import { accentCss } from "@/lib/branding-colors";
import { getBranding } from "@/lib/branding";
import "./globals.css";
export const metadata: Metadata = {
title: {
default: "QR-Code-Generator",
template: "%s · QR-Code-Generator",
},
description:
"QR-Codes erstellen: statisch oder dynamisch, mit eigenen Farben, frei wählbarer Größe und Download als PNG oder SVG.",
};
// Name, Farbe und Favicon kommen aus der Datenbank — nichts davon darf
// vorgerendert werden.
export const dynamic = "force-dynamic";
export async function generateMetadata(): Promise<Metadata> {
const branding = await getBranding();
return {
title: {
default: branding.appName,
template: `%s · ${branding.appName}`,
},
description:
"QR-Codes erstellen: statisch oder dynamisch, mit eigenen Farben, frei wählbarer Größe und Download als PNG oder SVG.",
// Ohne eigenes Favicon bleibt es bei src/app/favicon.ico.
...(branding.faviconUploadId
? {
icons: {
icon: {
url: `/api/uploads/${branding.faviconUploadId}`,
type: branding.faviconMimeType ?? undefined,
},
},
}
: {}),
};
}
/**
* Wurzel-Layout — bewusst nur `html` und `body`.
* Wurzel-Layout — bewusst nur `html`, `body` und die Akzentfarbe.
*
* Kopfzeile, Navigation und Fußzeile der Anwendung stehen eine Ebene tiefer in
* `(app)/layout.tsx`. Denn die öffentlichen Landingpages unter `/s/[slug]`
* bekommen ein Feld voller Fremder zu sehen: dort haben „Verwaltung“ und
* „Meine Codes“ nichts zu suchen.
*
* Die Farbe gehört dagegen hierher, damit auch die Landingpages sie tragen.
*/
export default function RootLayout({ children }: LayoutProps<"/">) {
export default async function RootLayout({ children }: LayoutProps<"/">) {
const branding = await getBranding();
return (
<html lang="de" className="h-full">
<body className="flex min-h-full flex-col">{children}</body>
<body className="flex min-h-full flex-col">
{/*
Überschreibt die Farbvariablen, die Tailwind ohnehin benutzt — damit
wirkt die Akzentfarbe an jeder Stelle der Oberfläche, ohne dass eine
einzige Klasse angefasst werden müsste. Der Inhalt entsteht
ausschließlich aus geprüften Zahlen; eine ungültige Farbe fällt auf
die Voreinstellung zurück.
*/}
<style
dangerouslySetInnerHTML={{ __html: accentCss(branding.accentColor) }}
/>
{children}
</body>
</html>
);
}