Schliesst Issue #1. Der Wunsch betraf den Kurz-Link, nicht das Ziel: Er soll sich von Anfang an bestimmen und spaeter aendern lassen. Beim Anlegen gibt es dafuer ein Feld, im Bearbeiten-Dialog laesst er sich aendern. Erlaubt sind Kleinbuchstaben, Ziffern, Bindestriche und „/" als Trenner — damit sind Adressen wie kunde-a/sommer moeglich. Umlaute bleiben draussen, weil sie in Adressen unleserlich kodiert werden; Gross- und Kleinschreibung ist beim Aufrufen egal. Beim Aendern bleibt die vorherige Adresse gueltig. Das ist der Kern: Auf gedruckten Codes steht die alte, und genau deren Bestand ist der Sinn eines dynamischen Codes. Frueher vergebene Adressen stehen im Dialog und lassen sich einzeln freigeben — fuer den Tippfehler, mit dem noch nichts gedruckt ist. Solange eine Adresse gehalten wird, kann sie kein anderer Code uebernehmen; sonst liessen sich fremde gedruckte Codes umlenken. Vergeben und Aendern ist standardmaessig Administratoren vorbehalten. Ein Kurz-Link ist eine oeffentliche Adresse, und wer ihn aendert, kann Aushaenge entwerten. Unter Verwaltung -> Einstellungen laesst sich das fuer Bearbeiter freigeben; ohne das Recht erscheint das Feld gar nicht, Codes anlegen und deren Ziel aendern geht weiterhin. Eigene Aktion in der Policy statt Teil von codes:update, weil sie mehr wiegt. Technisch: /r und /s sind Sammelrouten, weil ein Kurz-Link „/" enthalten darf. Die Eindeutigkeit gilt zusaetzlich auf der kleingeschriebenen Form; die Migration bricht ab, falls der Bestand das nicht zulaesst, statt still Daten zu verlieren. Der bestehende exakte Index bleibt — er traegt den haeufigen Fall, den gescannten Code.
85 lines
2.8 KiB
TypeScript
85 lines
2.8 KiB
TypeScript
"use client";
|
|
|
|
import { useState } from "react";
|
|
|
|
import { Card, CardTitle, Checkbox, Notice } from "@/components/ui";
|
|
|
|
export type SettingsDto = {
|
|
editorsMaySetSlug: boolean;
|
|
};
|
|
|
|
export function SettingsAdmin({ initial }: { initial: SettingsDto }) {
|
|
const [wert, setWert] = useState(initial);
|
|
const [fehler, setFehler] = useState<string | null>(null);
|
|
const [hinweis, setHinweis] = useState<string | null>(null);
|
|
const [laeuft, setLaeuft] = useState(false);
|
|
|
|
// Bewusst ohne Speichern-Knopf: Ein einzelner Schalter, der erst nach einem
|
|
// zweiten Klick wirkt, ist eine Stolperfalle.
|
|
const umschalten = async (aktiv: boolean) => {
|
|
setLaeuft(true);
|
|
setFehler(null);
|
|
setHinweis(null);
|
|
const vorher = wert;
|
|
setWert({ ...wert, editorsMaySetSlug: aktiv });
|
|
try {
|
|
const antwort = await fetch("/api/admin/settings", {
|
|
method: "PATCH",
|
|
headers: { "Content-Type": "application/json" },
|
|
body: JSON.stringify({ editorsMaySetSlug: aktiv }),
|
|
});
|
|
const rumpf = await antwort.json().catch(() => null);
|
|
if (!antwort.ok) {
|
|
throw new Error(rumpf?.error ?? "Speichern nicht möglich.");
|
|
}
|
|
setWert(rumpf.settings);
|
|
setHinweis(
|
|
aktiv
|
|
? "Bearbeiter dürfen Kurz-Links jetzt selbst vergeben und ändern."
|
|
: "Kurz-Links vergeben und ändern dürfen jetzt nur noch Administratoren.",
|
|
);
|
|
} catch (error) {
|
|
setWert(vorher);
|
|
setFehler(error instanceof Error ? error.message : String(error));
|
|
} finally {
|
|
setLaeuft(false);
|
|
}
|
|
};
|
|
|
|
return (
|
|
<div className="space-y-5">
|
|
{fehler ? <Notice tone="error">{fehler}</Notice> : null}
|
|
{hinweis ? <Notice tone="success">{hinweis}</Notice> : null}
|
|
|
|
<Card>
|
|
<CardTitle hint="Gilt für alle Gruppen dieser Installation.">
|
|
Kurz-Links
|
|
</CardTitle>
|
|
|
|
<Checkbox
|
|
id="editors-may-set-slug"
|
|
label="Bearbeiter dürfen den Kurz-Link selbst vergeben und ändern"
|
|
hint="Aus: Nur Administratoren. Bearbeiter legen Codes an, der Kurz-Link wird dabei erzeugt."
|
|
checked={wert.editorsMaySetSlug}
|
|
disabled={laeuft}
|
|
onChange={(event) => void umschalten(event.target.checked)}
|
|
/>
|
|
|
|
<div className="mt-4 space-y-2 text-sm text-slate-600">
|
|
<p>
|
|
Ein Kurz-Link ist eine öffentliche Adresse und steht auf gedruckten
|
|
Codes. Wer ihn ändert, kann damit Aushänge und Flyer entwerten —
|
|
die vorherige Adresse bleibt zwar gültig, lässt sich aber auch
|
|
wieder freigeben.
|
|
</p>
|
|
<p>
|
|
Deshalb ist das standardmäßig Administratoren vorbehalten. In einem
|
|
kleinen Team, in dem alle wissen, was ein Kurz-Link bedeutet, ist
|
|
die Freigabe unbedenklich.
|
|
</p>
|
|
</div>
|
|
</Card>
|
|
</div>
|
|
);
|
|
}
|