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
+203
View File
@@ -0,0 +1,203 @@
import type { SVGProps } from "react";
/**
* Kleines Icon-Set für die QR-Typ-Kacheln und die Oberfläche.
* Einheitlicher Stil: 24×24, Strichstärke 1.75, runde Enden.
*/
const PATHS = {
globe: (
<>
<circle cx="12" cy="12" r="9" />
<path d="M3 12h18" />
<path d="M12 3a15 15 0 0 1 0 18a15 15 0 0 1 0-18" />
</>
),
contact: (
<>
<rect x="3" y="4" width="18" height="16" rx="2" />
<circle cx="10" cy="10.5" r="2.25" />
<path d="M6.5 16.5a4 4 0 0 1 7 0" />
<path d="M16 9.5h3M16 13h3" />
</>
),
smartphone: (
<>
<rect x="6" y="2.5" width="12" height="19" rx="2.5" />
<path d="M10.5 18.5h3" />
</>
),
list: (
<>
<path d="M8 6h12M8 12h12M8 18h12" />
<circle cx="4" cy="6" r="1.1" />
<circle cx="4" cy="12" r="1.1" />
<circle cx="4" cy="18" r="1.1" />
</>
),
share: (
<>
<circle cx="17.5" cy="5.5" r="2.5" />
<circle cx="6.5" cy="12" r="2.5" />
<circle cx="17.5" cy="18.5" r="2.5" />
<path d="M8.8 10.8 15.2 6.7M8.8 13.2l6.4 4.1" />
</>
),
chat: (
<>
<path d="M21 11.5a8 8 0 0 1-11.9 7L3.5 20l1.6-5.3A8 8 0 1 1 21 11.5Z" />
<path d="M8.5 10.5h7M8.5 14h4" />
</>
),
document: (
<>
<path d="M14 3H7a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h10a2 2 0 0 0 2-2V8Z" />
<path d="M14 3v5h5" />
<path d="M9 13h6M9 16.5h4" />
</>
),
image: (
<>
<rect x="3" y="4.5" width="18" height="15" rx="2" />
<circle cx="8.5" cy="10" r="1.6" />
<path d="m4 17 4.8-4.4a1.5 1.5 0 0 1 2 0L20 19" />
</>
),
video: (
<>
<rect x="3" y="5.5" width="12.5" height="13" rx="2" />
<path d="m15.5 10.5 5.5-3v9l-5.5-3Z" />
</>
),
mail: (
<>
<rect x="3" y="5" width="18" height="14" rx="2" />
<path d="m3.5 7 8.5 6 8.5-6" />
</>
),
message: (
<>
<path d="M20 15a2 2 0 0 1-2 2H8l-4 3.5V6a2 2 0 0 1 2-2h12a2 2 0 0 1 2 2Z" />
<path d="M8 8.5h8M8 12h5" />
</>
),
pin: (
<>
<path d="M12 21s7-6.2 7-11a7 7 0 1 0-14 0c0 4.8 7 11 7 11Z" />
<circle cx="12" cy="10" r="2.5" />
</>
),
card: (
<>
<rect x="2.5" y="5" width="19" height="14" rx="2.5" />
<path d="M2.5 10h19" />
<path d="M6.5 14.5h3" />
</>
),
wifi: (
<>
<path d="M2.5 9a15 15 0 0 1 19 0" />
<path d="M5.8 12.6a10 10 0 0 1 12.4 0" />
<path d="M9.1 16.2a5 5 0 0 1 5.8 0" />
<circle cx="12" cy="19.5" r="0.9" fill="currentColor" stroke="none" />
</>
),
star: (
<>
<path d="m12 3.5 2.6 5.4 5.9.8-4.3 4.2 1 5.9-5.2-2.8-5.2 2.8 1-5.9L3.5 9.7l5.9-.8Z" />
</>
),
qr: (
<>
<rect x="3" y="3" width="7" height="7" rx="1.5" />
<rect x="14" y="3" width="7" height="7" rx="1.5" />
<rect x="3" y="14" width="7" height="7" rx="1.5" />
<path d="M14 14h3v3h-3zM18 18h3v3h-3zM14 20.5h1.5" />
</>
),
check: <path d="m4.5 12.5 5 5 10-11" />,
copy: (
<>
<rect x="9" y="9" width="12" height="12" rx="2" />
<path d="M15 5.5A2.5 2.5 0 0 0 12.5 3h-7A2.5 2.5 0 0 0 3 5.5v7A2.5 2.5 0 0 0 5.5 15" />
</>
),
download: (
<>
<path d="M12 3.5v11" />
<path d="m7.5 10.5 4.5 4.5 4.5-4.5" />
<path d="M4 17.5v1.5a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2v-1.5" />
</>
),
trash: (
<>
<path d="M4 6.5h16" />
<path d="M9 6.5V4.8A1.8 1.8 0 0 1 10.8 3h2.4A1.8 1.8 0 0 1 15 4.8v1.7" />
<path d="M6.5 6.5 7.4 20a1.5 1.5 0 0 0 1.5 1.4h6.2a1.5 1.5 0 0 0 1.5-1.4l.9-13.5" />
<path d="M10.5 10.5v7M13.5 10.5v7" />
</>
),
pencil: (
<>
<path d="M4 20.5h4l11-11a2.5 2.5 0 0 0-3.5-3.5l-11 11Z" />
<path d="m14.5 7 2.5 2.5" />
</>
),
arrowLeft: (
<>
<path d="M20 12H4" />
<path d="m10 6-6 6 6 6" />
</>
),
external: (
<>
<path d="M14 4h6v6" />
<path d="M20 4 11 13" />
<path d="M18 14.5V19a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h4.5" />
</>
),
warning: (
<>
<path d="M10.3 4.2 2.6 17.5A2 2 0 0 0 4.3 20.5h15.4a2 2 0 0 0 1.7-3L13.7 4.2a2 2 0 0 0-3.4 0Z" />
<path d="M12 9.5v4.2" />
<circle cx="12" cy="17" r="0.9" fill="currentColor" stroke="none" />
</>
),
scan: (
<>
<path d="M4 8V6a2 2 0 0 1 2-2h2" />
<path d="M16 4h2a2 2 0 0 1 2 2v2" />
<path d="M20 16v2a2 2 0 0 1-2 2h-2" />
<path d="M8 20H6a2 2 0 0 1-2-2v-2" />
<path d="M4 12h16" />
</>
),
lock: (
<>
<rect x="4.5" y="10.5" width="15" height="10.5" rx="2" />
<path d="M8 10.5V7.5a4 4 0 0 1 8 0v3" />
</>
),
} as const;
export type IconName = keyof typeof PATHS;
type IconProps = SVGProps<SVGSVGElement> & {
name: IconName;
};
export function Icon({ name, ...props }: IconProps) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth={1.75}
strokeLinecap="round"
strokeLinejoin="round"
aria-hidden="true"
{...props}
>
{PATHS[name]}
</svg>
);
}