import { useMemo, useState, type FormEvent } from "react"; import { Star } from "lucide-react"; import type { VisitorPublicFeedbackForm, VisitorPublicFeedbackFormField } from "../lib/api"; import { postPublicFormSubmit } from "../lib/api"; import { getAttendeeParticipant } from "../lib/attendeeAuth"; function isStarRatingField(f: VisitorPublicFeedbackFormField): boolean { const t = (f.type || "").toLowerCase().trim(); if (t === "stars" || t === "star rating" || t === "star ratings") return true; if (t === "select") { const opts = (f.options || []).map((o) => String(o).trim()); return opts.length === 5 && opts.every((v, i) => v === String(i + 1)); } return false; } function starValueValid(raw: unknown): boolean { const n = typeof raw === "number" ? raw : parseInt(String(raw ?? ""), 10); return Number.isFinite(n) && n >= 1 && n <= 5; } export function DynamicFeedbackForm({ eventId, schema, variant = "light", }: { eventId: string; schema: VisitorPublicFeedbackForm; variant?: "light" | "gradient"; }) { const [values, setValues] = useState>({}); const [submitting, setSubmitting] = useState(false); const [err, setErr] = useState(null); const [done, setDone] = useState(false); const fields = useMemo(() => (schema.fields || []).filter((f) => f.label?.trim()), [schema.fields]); const setVal = (label: string, v: unknown) => { setValues((prev) => ({ ...prev, [label]: v })); }; const validate = (): boolean => { for (const f of fields) { const key = String(f.label).trim(); if (!f.required) continue; const raw = values[key]; if ((f.type || "").toLowerCase() === "checkboxes") { if (!Array.isArray(raw) || raw.length === 0) return false; } else if (isStarRatingField(f)) { if (!starValueValid(raw)) return false; } else if (raw === undefined || raw === null || String(raw).trim() === "") { return false; } } return true; }; const onSubmit = async (e: FormEvent) => { e.preventDefault(); setErr(null); if (!validate()) { setErr("Please fill all required fields."); return; } const data: Record = {}; for (const f of fields) { const key = String(f.label).trim(); data[key] = values[key] ?? ""; } setSubmitting(true); try { const participant = getAttendeeParticipant(); const pid = participant && String(participant.event) === String(eventId) ? participant._id : undefined; await postPublicFormSubmit(schema._id, data, pid); setDone(true); } catch (er) { setErr(er instanceof Error ? er.message : "Could not submit."); } finally { setSubmitting(false); } }; if (done && schema.showThankYou !== false) { return (

Thank you

{schema.thankYouMessage?.trim() || "Your response has been recorded."}

); } const fieldShell = variant === "gradient" ? "rounded-xl bg-white p-6 shadow-[0_8px_12px_6px_rgba(0,0,0,0.15),0_4px_4px_rgba(0,0,0,0.3)]" : "rounded-md bg-white p-4 shadow-[0_1px_4px_rgba(12,12,13,0.05)]"; return (
{schema.description?.trim() ? (

{stripHtml(schema.description.trim())}

) : null}
{fields.map((f) => (
))}
{err ?

{err}

: null}
); } /** Minimal strip — form descriptions are plain text from dashboard */ function stripHtml(s: string): string { return s.replace(/<[^>]*>/g, "").trim() || s; } function FieldRemarks({ text }: { text?: string }) { const trimmed = text?.trim(); if (!trimmed) return null; return

{trimmed}

; } function FieldInput({ f, value, onChange, }: { f: VisitorPublicFeedbackFormField; value: unknown; onChange: (label: string, v: unknown) => void; }) { const label = String(f.label || "").trim(); const ph = f.placeholder?.trim() || ""; const opts = (f.options || []).map((o) => String(o).trim()).filter(Boolean); const t = (f.type || "text").toLowerCase(); const baseInput = "w-full rounded-lg border border-[rgba(116,112,112,0.3)] bg-[rgba(157,157,172,0.07)] px-3 py-2 font-inter text-sm text-[#24282f] placeholder:text-[rgba(36,40,47,0.45)]"; if (t === "file") { return (

{label} File upload isn't supported in the mobile app yet.

); } if (isStarRatingField(f)) { const n = typeof value === "number" ? value : parseInt(String(value ?? ""), 10); const selected = Number.isFinite(n) ? n : 0; return (
{label} {f.required ? * : null}
{Array.from({ length: 5 }, (_, i) => { const star = i + 1; return ( ); })}
); } if (t === "select") { return ( ); } if (t === "radio") { return (
{label} {f.required ? * : null}
{(opts.length ? opts : ["Yes", "No"]).map((o) => ( ))}
); } if (t === "checkboxes") { const arr = Array.isArray(value) ? (value as string[]) : []; const toggle = (o: string) => { if (arr.includes(o)) onChange(label, arr.filter((x) => x !== o)); else onChange(label, [...arr, o]); }; return (
{label} {f.required ? * : null}
{opts.map((o) => ( ))}
); } if (t === "checkbox") { return ( ); } if (t === "email" || t === "phone") { return ( ); } if (t === "textarea") { return (