Files
qrcode-generator/src/components/qr-forms/feedback-form.tsx
T
Florian Wilde e296b1e6c2 Phase C: die letzten sechs Typen — PDF, Bild, Feedback und Datei-Upload
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.
2026-08-14 23:34:28 +02:00

117 lines
3.6 KiB
TypeScript

"use client";
import type { QrFormProps } from "@/components/qr-forms/types";
import { Checkbox, Field, Select, TextArea, TextInput } from "@/components/ui";
import { FEEDBACK_SCALES } from "@/lib/qr/types/feedback";
export function FeedbackForm({ value, onChange, error, disabled }: QrFormProps) {
const title = typeof value.title === "string" ? value.title : "";
const question = typeof value.question === "string" ? value.question : "";
const scale = value.scale === "faces" ? "faces" : "stars";
const allowComment = value.allowComment !== false;
const commentLabel =
typeof value.commentLabel === "string" ? value.commentLabel : "";
const thankYou = typeof value.thankYou === "string" ? value.thankYou : "";
return (
<div className="space-y-5">
<Field
label="Überschrift der Seite"
htmlFor="feedback-title"
error={error}
hint="Wo hängt der Code? Zum Beispiel „Gastraum“ oder „Werkstatt“."
>
<TextInput
id="feedback-title"
value={title}
invalid={Boolean(error)}
disabled={disabled}
maxLength={80}
placeholder="Wie war dein Besuch?"
onChange={(event) => onChange({ ...value, title: event.target.value })}
/>
</Field>
<Field
label="Frage"
htmlFor="feedback-question"
hint="Steht über den Bewertungssymbolen."
>
<TextInput
id="feedback-question"
value={question}
disabled={disabled}
maxLength={160}
placeholder="Wie zufrieden bist du?"
onChange={(event) =>
onChange({ ...value, question: event.target.value })
}
/>
</Field>
<Field
label="Bewertungsskala"
htmlFor="feedback-scale"
hint="In beiden Fällen wird 1 bis 5 gespeichert."
>
<Select
id="feedback-scale"
value={scale}
disabled={disabled}
onChange={(event) => onChange({ ...value, scale: event.target.value })}
>
{Object.entries(FEEDBACK_SCALES).map(([id, eintrag]) => (
<option key={id} value={id}>
{eintrag.label}
</option>
))}
</Select>
</Field>
<Checkbox
id="feedback-allow-comment"
checked={allowComment}
disabled={disabled}
onChange={(event) =>
onChange({ ...value, allowComment: event.target.checked })
}
label="Zusätzlich einen freien Text zulassen"
hint="Sonst gibt es nur die Note — das senkt die Hemmschwelle."
/>
{allowComment ? (
<Field label="Beschriftung des Textfelds" htmlFor="feedback-comment-label">
<TextInput
id="feedback-comment-label"
value={commentLabel}
disabled={disabled}
maxLength={120}
placeholder="Möchtest du uns noch etwas sagen?"
onChange={(event) =>
onChange({ ...value, commentLabel: event.target.value })
}
/>
</Field>
) : null}
<Field
label="Text nach dem Absenden"
htmlFor="feedback-thanks"
hint="Das Letzte, was jemand von dieser Seite sieht."
>
<TextArea
id="feedback-thanks"
rows={2}
value={thankYou}
disabled={disabled}
maxLength={200}
placeholder="Danke für deine Rückmeldung!"
onChange={(event) =>
onChange({ ...value, thankYou: event.target.value })
}
/>
</Field>
</div>
);
}