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:
@@ -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" },
|
||||
];
|
||||
Reference in New Issue
Block a user