Damit sind alle 15 QR-Typen gebaut. PDF und Bild brauchen eine hochgeladene Datei. Die liegt in der Datenbank, nicht auf der Platte: damit bleibt es bei einem Volumen, das gesichert werden muss — ein zweites wird beim Sichern zu leicht vergessen. Geprueft wird der Dateiinhalt, nicht die Endung; eine Textdatei namens harmlos.pdf kommt nicht durch. SVG ist bewusst nicht erlaubt, weil es Skripte enthalten darf und von derselben Adresse wie die Anwendung ausgeliefert wuerde. Ausgeliefert wird mit nosniff und einer CSP-Sandbox, damit auch ein PDF mit Skripten nichts anrichtet. Dateien haengen am Code: wird er geloescht, geht sie mit. Eine ausgetauschte oder nie gespeicherte Datei wird nach 24 Stunden weggeraeumt. Eine fremde Datei laesst sich nicht an den eigenen Code haengen — sonst koennte man sie einem anderen Code entziehen. Feedback sammelt Note und Kommentar, ohne Name, IP oder Geraetekennung. Die Auswertung mit Durchschnitt, Verteilung und Kommentaren ist aus der Codeliste erreichbar — ein Feedback-Code ohne Auswertung waere nur die halbe Sache.
67 lines
1.7 KiB
TypeScript
67 lines
1.7 KiB
TypeScript
import type { ReactNode } from "react";
|
|
|
|
/**
|
|
* Rahmen aller öffentlichen Landingpages.
|
|
*
|
|
* Diese Seiten sehen Leute, die einen Code gescannt haben — meist auf dem
|
|
* Handy, oft unterwegs, ohne Bezug zu dieser Anwendung. Deshalb: eine Spalte,
|
|
* große Schrift, keine Navigation, kein Hinweis auf die Verwaltung.
|
|
*/
|
|
export function LandingShell({
|
|
title,
|
|
subtitle,
|
|
children,
|
|
footer,
|
|
}: {
|
|
title: string;
|
|
subtitle?: string | null;
|
|
children: ReactNode;
|
|
footer?: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="flex flex-1 flex-col bg-slate-50">
|
|
<main className="mx-auto w-full max-w-lg flex-1 px-4 py-10 sm:px-6 sm:py-14">
|
|
<header className="mb-7 text-center">
|
|
<h1 className="text-2xl font-bold tracking-tight text-balance text-slate-900 sm:text-3xl">
|
|
{title}
|
|
</h1>
|
|
{subtitle ? (
|
|
<p className="mt-2.5 text-pretty text-slate-600">{subtitle}</p>
|
|
) : null}
|
|
</header>
|
|
{children}
|
|
</main>
|
|
{footer ? (
|
|
<footer className="px-4 pb-8 text-center text-xs text-slate-400">
|
|
{footer}
|
|
</footer>
|
|
) : null}
|
|
</div>
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Ein Fehlerbild im selben Gewand — etwa wenn ein Code gelöscht wurde.
|
|
* Bewusst ohne Schuldzuweisung: wer hier landet, kann nichts dafür.
|
|
*/
|
|
export function LandingNotice({
|
|
title,
|
|
children,
|
|
}: {
|
|
title: string;
|
|
children: ReactNode;
|
|
}) {
|
|
return (
|
|
<div className="grid flex-1 place-items-center bg-slate-50 px-4 py-16">
|
|
<div className="max-w-md text-center">
|
|
<h1 className="text-xl font-bold text-slate-900 sm:text-2xl">
|
|
{title}
|
|
</h1>
|
|
<p className="mt-3 leading-relaxed text-pretty text-slate-600">
|
|
{children}
|
|
</p>
|
|
</div>
|
|
</div>
|
|
);
|
|
}
|