V1: Next.js-App mit QR-Typ-Registry, Website-Generator und dynamischen Codes

- Erweiterbare Registry aller 15 QR-Typen; nur "Website" ist aktiv, die
  uebrigen erscheinen als Platzhalter mit Badge "Bald verfuegbar".
- defineQrType() kapselt Zod-Schema, Kodierung und Redirect-Ziel je Typ,
  sodass ein V2-Typ nur aus einem Registry-Eintrag plus Formular besteht.
- Serverseitige QR-Erzeugung (PNG/SVG) mit Farben, Groesse, Fehlerkorrektur
  und Rand; Live-Vorschau ueber /api/qr/preview.
- Dynamische Codes: Kurz-URL {BASE_URL}/r/{slug}, 302-Redirect mit
  Scan-Zaehler, CRUD unter /api/codes.
- Prisma 7 mit pg-Driver-Adapter und Initial-Migration fuer PostgreSQL.
- Deutsche Oberflaeche mit Tailwind CSS, helles Theme, responsiv.
This commit is contained in:
Florian Wilde
2026-08-13 15:54:47 +02:00
commit ad4cd92f8e
52 changed files with 12348 additions and 0 deletions
+65
View File
@@ -0,0 +1,65 @@
import { z } from "zod";
export const EC_LEVELS = ["L", "M", "Q", "H"] as const;
export type EcLevel = (typeof EC_LEVELS)[number];
export const EC_LEVEL_LABELS: Record<EcLevel, string> = {
L: "L — niedrig (7 % wiederherstellbar)",
M: "M — mittel (15 %, Standard)",
Q: "Q — hoch (25 %)",
H: "H — sehr hoch (30 %)",
};
export const MIN_SIZE = 256;
export const MAX_SIZE = 2048;
const HEX_COLOR = /^#[0-9a-fA-F]{6}$/;
const hexColor = z
.string()
.trim()
.regex(HEX_COLOR, {
error: "Bitte eine Farbe im Format #RRGGBB angeben, z. B. #1E293B.",
})
.transform((value) => value.toUpperCase());
export const designSchema = z.object({
fgColor: hexColor,
bgColor: hexColor,
size: z.coerce
.number({ error: "Die Größe muss eine Zahl sein." })
.int({ error: "Die Größe muss eine ganze Zahl sein." })
.min(MIN_SIZE, { error: `Die Größe muss mindestens ${MIN_SIZE} px betragen.` })
.max(MAX_SIZE, { error: `Die Größe darf höchstens ${MAX_SIZE} px betragen.` }),
ecLevel: z.enum(EC_LEVELS, {
error: "Ungültiges Fehlerkorrektur-Level. Erlaubt sind L, M, Q und H.",
}),
margin: z.coerce
.number({ error: "Der Rand muss eine Zahl sein." })
.int({ error: "Der Rand muss eine ganze Zahl sein." })
.min(0, { error: "Der Rand darf nicht negativ sein." })
.max(16, { error: "Der Rand darf höchstens 16 Module breit sein." }),
});
export type QrDesign = z.infer<typeof designSchema>;
export const defaultDesign: QrDesign = {
fgColor: "#0F172A",
bgColor: "#FFFFFF",
size: 512,
ecLevel: "M",
margin: 4,
};
/** Farbvorschläge für die schnelle Auswahl in der Oberfläche. */
export const COLOR_PRESETS: ReadonlyArray<{
label: string;
fgColor: string;
bgColor: string;
}> = [
{ label: "Klassisch", fgColor: "#0F172A", bgColor: "#FFFFFF" },
{ label: "Indigo", fgColor: "#3730A3", bgColor: "#FFFFFF" },
{ label: "Waldgrün", fgColor: "#14532D", bgColor: "#F0FDF4" },
{ label: "Bordeaux", fgColor: "#7F1D1D", bgColor: "#FFF7ED" },
{ label: "Marine", fgColor: "#1E3A8A", bgColor: "#EFF6FF" },
];