Das Feld „Name (optional)" stand nur im Dynamisch-Block. Bei einem statischen Code gab es damit keine Moeglichkeit, ihm einen Namen zum Wiederfinden zu geben. Jetzt gilt es in beiden Betriebsarten und tut zweierlei: Es bestimmt den Dateinamen beim Herunterladen — aus „Plakat Eingang" wird qr-plakat-eingang.png statt qr-example-com-plakat.png — und es laesst sich damit ein statischer Code in „Meine Codes" ablegen. Ein abgelegter statischer Code ist ein Schnappschuss, kein steuerbares Ziel. Aenderbar ist nur der Name; Inhalt und Gestaltung bleiben, wie sie beim Ablegen waren, denn genau das steckt im gedruckten Bild. Er bekommt keinen Kurz-Link, ist unter /r nicht erreichbar und zaehlt keine Scans — in der Liste steht dort „statisch" und ein Strich. Wer nach dem Ablegen den Inhalt aendert, bekommt das Ablegen erneut angeboten; der vorherige Eintrag bleibt als eigener Schnappschuss. Dafuer traegt QrCode jetzt `mode`, und `slug` darf leer sein. In PostgreSQL schliessen sich mehrere NULL in einem eindeutigen Index nicht aus, die Bedingung „je Domain eindeutig" gilt fuer dynamische Codes also unveraendert. Refs #2
114 lines
3.6 KiB
TypeScript
114 lines
3.6 KiB
TypeScript
import type { Metadata } from "next";
|
|
import { headers } from "next/headers";
|
|
|
|
import { qrLandings } from "@/components/landing";
|
|
import { LandingNotice } from "@/components/landing/shell";
|
|
import { ensureDefaultDomain, resolveRequestDomain } from "@/lib/domains";
|
|
import { getQrType } from "@/lib/qr/registry";
|
|
import { isValidSlug } from "@/lib/slug";
|
|
import { findCodeBySlug } from "@/lib/slug-resolve";
|
|
|
|
export const dynamic = "force-dynamic";
|
|
|
|
// Sammelroute, weil ein Kurz-Link „/" enthalten darf.
|
|
type PageProps = { params: Promise<{ slug: string[] }> };
|
|
|
|
/**
|
|
* Öffentliche Landingpage eines dynamischen Codes.
|
|
*
|
|
* Hierher schickt `/r/[slug]` alle Typen, die keine fremde Adresse haben,
|
|
* sondern eine Seite: Linksammlungen, hinterlegte Dateien, Feedback.
|
|
*
|
|
* Der Scan-Zähler wird bewusst **nicht** hier erhöht, sondern in `/r/[slug]`.
|
|
* Sonst zählte jedes Neuladen dieser Seite als weiterer Scan.
|
|
*/
|
|
async function loadCode(abschnitte: string[] | undefined) {
|
|
const slug = (abschnitte ?? []).join("/");
|
|
if (!isValidSlug(slug)) return null;
|
|
|
|
const matched = await resolveRequestDomain(await headers());
|
|
const domain = matched ?? { ...(await ensureDefaultDomain()), isActive: true };
|
|
if (!domain.isActive) return null;
|
|
|
|
return (await findCodeBySlug(domain.id, slug))?.code ?? null;
|
|
}
|
|
|
|
export async function generateMetadata({
|
|
params,
|
|
}: PageProps): Promise<Metadata> {
|
|
const { slug } = await params;
|
|
const code = await loadCode(slug);
|
|
const qrType = code ? getQrType(code.type) : undefined;
|
|
|
|
if (!code || !qrType?.hasLandingPage) {
|
|
return { title: "Nicht gefunden" };
|
|
}
|
|
|
|
let title = qrType.title;
|
|
try {
|
|
title = qrType.summary(code.data);
|
|
} catch {
|
|
// Daten passen nicht mehr zum Schema — der Typname tut es auch.
|
|
}
|
|
// Absolut, damit der Titel nicht mit dem Anwendungsnamen ergänzt wird:
|
|
// Wer hier landet, hat mit dem Generator nichts zu tun.
|
|
return { title: { absolute: title } };
|
|
}
|
|
|
|
export default async function LandingPage({ params }: PageProps) {
|
|
const { slug } = await params;
|
|
const code = await loadCode(slug);
|
|
|
|
if (!code) {
|
|
return (
|
|
<LandingNotice title="Diese Seite gibt es nicht">
|
|
Der Code wurde gelöscht oder hat nie existiert. Bitte wende dich an
|
|
denjenigen, der ihn erstellt hat.
|
|
</LandingNotice>
|
|
);
|
|
}
|
|
|
|
const qrType = getQrType(code.type);
|
|
const Landing = qrType?.hasLandingPage ? qrLandings[qrType.id] : undefined;
|
|
|
|
if (!qrType || !Landing) {
|
|
return (
|
|
<LandingNotice title="Diese Seite gibt es nicht">
|
|
Dieser Code führt nicht auf eine Seite. Bitte wende dich an denjenigen,
|
|
der ihn erstellt hat.
|
|
</LandingNotice>
|
|
);
|
|
}
|
|
|
|
// Alte Datensätze können nach einer Schema-Änderung unbrauchbar sein. Das
|
|
// ist kein Grund, den Besuchern einen Absturz zu zeigen.
|
|
if (!qrType.validate(code.data).ok) {
|
|
return (
|
|
<LandingNotice title="Diese Seite ist gerade nicht verfügbar">
|
|
Die hinterlegten Inhalte sind unvollständig. Bitte wende dich an
|
|
denjenigen, der den Code erstellt hat.
|
|
</LandingNotice>
|
|
);
|
|
}
|
|
|
|
// Gefunden wird ein Code nur ueber seinen Kurz-Link; statische haben keinen
|
|
// und koennen hier deshalb gar nicht ankommen. Der Zweig ist reine Vorsorge.
|
|
if (!code.slug) {
|
|
return (
|
|
<LandingNotice title="Diese Seite gibt es nicht">
|
|
Dieser Code führt nicht auf eine Seite. Bitte wende dich an denjenigen,
|
|
der ihn erstellt hat.
|
|
</LandingNotice>
|
|
);
|
|
}
|
|
|
|
return (
|
|
<Landing
|
|
data={code.data}
|
|
label={code.label}
|
|
slug={code.slug}
|
|
codeId={code.id}
|
|
/>
|
|
);
|
|
}
|