@nextlyhq/plugin-form-builder 0.0.2-alpha.74 → 0.0.2-alpha.76
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -1
- package/dist/admin/index.d.ts +117 -87
- package/dist/admin/index.js +9497 -4456
- package/dist/admin/index.js.map +1 -1
- package/dist/index.d.ts +45 -9
- package/dist/index.js +4648 -632
- package/dist/index.js.map +1 -1
- package/dist/submission-title-CVixn15Z.js +2699 -0
- package/dist/submission-title-CVixn15Z.js.map +1 -0
- package/dist/{new-submissions-CzlhVGhH.d.ts → types-XdBrUfi1.d.ts} +202 -44
- package/package.json +10 -10
- package/dist/field-references-CtzGg51W.js +0 -1318
- package/dist/field-references-CtzGg51W.js.map +0 -1
package/dist/admin/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":["formatDate","PAGE_SIZE","textareaField","hiddenField","icons","mode"],"sources":["../../src/admin/components/submissions/SubmissionSheet.tsx","../../src/admin/components/submissions/SubmissionsView.tsx","../../src/admin/context/FormBuilderContext.tsx","../../src/admin/components/builder/AddFieldDialog.tsx","../../src/admin/components/builder/ConditionalLogicEditor.tsx","../../src/admin/components/builder/FieldEditor.tsx","../../src/admin/components/builder/FieldCards.tsx","../../src/admin/components/builder/notification-addresses.ts","../../src/admin/components/builder/FormNotificationsTab.tsx","../../src/admin/components/builder/RedirectPagePicker.tsx","../../src/admin/components/builder/FormSettingsTab.tsx","../../src/admin/components/builder/FormPreview.tsx","../../src/admin/FormBuilderView.tsx","../../src/dashboard/mark-read.ts","../../src/dashboard/NewSubmissionsCard.tsx","../../src/admin/index.ts"],"sourcesContent":["\"use client\";\n\n/**\n * Submission Sheet\n *\n * Drawer detail for one submission: the submitted values in field order,\n * metadata, status + internal notes, and — behind the update permission —\n * inline editing of the submitted data. Prev/next walks the current table\n * page without losing filter context.\n *\n * @module admin/components/submissions/SubmissionSheet\n */\n\nimport {\n Badge,\n Button,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Sheet,\n SheetContent,\n SheetDescription,\n SheetHeader,\n SheetTitle,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { ChevronLeft, ChevronRight, Pencil, TriangleAlert } from \"lucide-react\";\nimport { type JSX, useEffect, useState } from \"react\";\n\nimport type { FormField } from \"../../../types\";\nimport { formatExportValue } from \"../../../utils/export-formats\";\n\nimport type { FormOption, SubmissionRow } from \"./SubmissionsView\";\n\ninterface SubmissionSheetProps {\n submission: SubmissionRow;\n form: FormOption;\n canUpdate: boolean;\n onClose: () => void;\n onPrev?: () => void;\n onNext?: () => void;\n onSave: (changes: Record<string, unknown>) => Promise<void>;\n}\n\nfunction formatDate(value: string | null | undefined): string {\n if (!value) return \"\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return \"\";\n return date.toLocaleString(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n });\n}\n\n/** One editable input per field type; unsupported types render read-only. */\nfunction FieldValueInput({\n field,\n value,\n onChange,\n}: {\n field: FormField;\n value: unknown;\n onChange: (value: unknown) => void;\n}) {\n const id = `submission-field-${field.name}`;\n switch (field.type) {\n case \"textarea\":\n return (\n <Textarea\n id={id}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n rows={3}\n />\n );\n case \"checkbox\":\n return (\n <Switch\n id={id}\n checked={Boolean(value)}\n onCheckedChange={checked => onChange(checked)}\n />\n );\n case \"select\":\n case \"radio\": {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <Select\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={onChange}\n >\n <SelectTrigger\n id={id}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map(option => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n }\n case \"number\":\n return (\n <Input\n id={id}\n type=\"number\"\n value={\n typeof value === \"number\" || typeof value === \"string\"\n ? String(value)\n : \"\"\n }\n onChange={e =>\n onChange(e.target.value === \"\" ? \"\" : Number(e.target.value))\n }\n />\n );\n case \"date\":\n case \"time\":\n return (\n <Input\n id={id}\n type={field.type}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n />\n );\n case \"text\":\n case \"email\":\n case \"url\":\n case \"phone\":\n return (\n <Input\n id={id}\n type={field.type === \"email\" ? \"email\" : \"text\"}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n />\n );\n case \"file\":\n case \"hidden\":\n default:\n // Files are media references, hidden fields are machine values, and\n // unknown types may hold structured data — coercing any of them\n // through a text input would corrupt them, so they stay read-only.\n return (\n <p className=\"text-sm text-muted-foreground\">\n {formatExportValue(value, field) || \"—\"}\n </p>\n );\n }\n}\n\nexport function SubmissionSheet({\n submission,\n form,\n canUpdate,\n onClose,\n onPrev,\n onNext,\n onSave,\n}: SubmissionSheetProps): JSX.Element {\n const [editing, setEditing] = useState(false);\n const [draftData, setDraftData] = useState<Record<string, unknown>>({});\n const [status, setStatus] = useState(submission.status);\n const [notes, setNotes] = useState(submission.notes ?? \"\");\n const [saving, setSaving] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n\n // Prev/next swaps the submission while the sheet stays open — every piece\n // of local state must follow the record it belongs to.\n useEffect(() => {\n setEditing(false);\n setDraftData(submission.data ?? {});\n setStatus(submission.status);\n setNotes(submission.notes ?? \"\");\n setSaveError(null);\n }, [submission]);\n\n const fieldNames = new Set(form.fields.map(field => field.name));\n // Forms evolve after submissions exist: keys no longer on the form are\n // shown honestly instead of silently dropped.\n const orphanedEntries = Object.entries(submission.data ?? {}).filter(\n ([key]) => !fieldNames.has(key)\n );\n\n // Only a real value difference counts as a data edit — entering edit mode\n // and saving untouched values must not write `data` or earn an edit stamp.\n const dataChanged =\n editing &&\n form.fields.some(field => {\n const before = (submission.data ?? {})[field.name];\n const after = draftData[field.name];\n return JSON.stringify(before ?? null) !== JSON.stringify(after ?? null);\n });\n\n const dirty =\n dataChanged ||\n status !== submission.status ||\n notes !== (submission.notes ?? \"\");\n\n const handleSave = async () => {\n if (!canUpdate) return;\n setSaving(true);\n setSaveError(null);\n try {\n const changes: Record<string, unknown> = {};\n if (status !== submission.status) {\n changes.status = status;\n // Recovering from spam clears the stale detection reason.\n if (submission.status === \"spam\" && status !== \"spam\") {\n changes.spamReason = null;\n }\n }\n if (notes !== (submission.notes ?? \"\")) changes.notes = notes;\n if (dataChanged) changes.data = draftData;\n if (Object.keys(changes).length > 0) await onSave(changes);\n setEditing(false);\n } catch (err) {\n setSaveError(err instanceof Error ? err.message : \"Save failed\");\n } finally {\n setSaving(false);\n }\n };\n\n return (\n <Sheet\n open\n onOpenChange={open => {\n if (!open) onClose();\n }}\n >\n <SheetContent\n side=\"right\"\n className=\"w-[560px] sm:max-w-[560px] p-0 flex flex-col\"\n >\n <SheetHeader className=\"p-4 border-b border-border\">\n <div className=\"flex items-center justify-between gap-2\">\n <SheetTitle className=\"min-w-0 truncate\">\n Submission — {form.name}\n </SheetTitle>\n <div className=\"flex shrink-0 items-center gap-1 mr-6\">\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n disabled={!onPrev}\n onClick={onPrev}\n aria-label=\"Previous submission\"\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n disabled={!onNext}\n onClick={onNext}\n aria-label=\"Next submission\"\n >\n <ChevronRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n <SheetDescription>\n Submitted {formatDate(submission.submittedAt)}\n </SheetDescription>\n </SheetHeader>\n\n <div className=\"flex-1 overflow-y-auto p-4 space-y-6\">\n {/* Spam banner */}\n {submission.status === \"spam\" && (\n // Full-strength destructive border so the spam banner boundary is perceivable over its tinted fill.\n <div className=\"flex items-center gap-2 border border-destructive bg-destructive/5 p-3 text-sm text-destructive\">\n <TriangleAlert className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n Flagged as spam\n {submission.spamReason ? ` (${submission.spamReason})` : \"\"}. Set\n the status to New to recover it.\n </div>\n )}\n\n {/* Submitted values */}\n <div className=\"space-y-3\">\n <div className=\"flex items-center justify-between\">\n <p className=\"text-sm font-medium text-foreground\">\n Submitted values\n </p>\n {canUpdate && !editing && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setEditing(true)}\n >\n <Pencil className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Edit\n </Button>\n )}\n </div>\n\n <div className=\"space-y-3 border border-border p-3\">\n {form.fields.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n The form's fields are unavailable.\n </p>\n )}\n {form.fields.map(field => (\n <div key={field.name} className=\"space-y-1\">\n <Label htmlFor={`submission-field-${field.name}`}>\n {field.label || field.name}\n </Label>\n {editing ? (\n <FieldValueInput\n field={field}\n value={draftData[field.name]}\n onChange={value =>\n setDraftData(prev => ({\n ...prev,\n [field.name]: value,\n }))\n }\n />\n ) : (\n <p className=\"text-sm text-foreground wrap-break-word\">\n {formatExportValue(\n (submission.data ?? {})[field.name],\n field\n ) || <span className=\"text-muted-foreground\">—</span>}\n </p>\n )}\n </div>\n ))}\n </div>\n\n {orphanedEntries.length > 0 && (\n <div className=\"space-y-2 border border-border bg-muted/40 p-3\">\n <p className=\"text-xs font-medium text-muted-foreground\">\n No longer on the form\n </p>\n {orphanedEntries.map(([key, value]) => (\n <p key={key} className=\"text-sm text-muted-foreground\">\n <span className=\"font-mono\">{key}</span>:{\" \"}\n {formatExportValue(value)}\n </p>\n ))}\n </div>\n )}\n\n {submission.editedAt && (\n <p className=\"text-xs text-muted-foreground\">\n Edited {formatDate(submission.editedAt)}\n {submission.editedBy ? ` by ${submission.editedBy}` : \"\"} — the\n values above are not necessarily what the visitor sent.\n </p>\n )}\n </div>\n\n {/* Status + notes */}\n <div className=\"grid grid-cols-1 gap-4 border-t border-border pt-4 sm:grid-cols-2\">\n <div className=\"space-y-1.5\">\n <Label htmlFor=\"submission-status\">Status</Label>\n {/* Status/notes writes go through the same update permission\n as data edits — without it, everything here is read-only. */}\n <Select\n value={status}\n onValueChange={setStatus}\n disabled={!canUpdate}\n >\n <SelectTrigger\n id=\"submission-status\"\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"new\">New</SelectItem>\n <SelectItem value=\"read\">Read</SelectItem>\n <SelectItem value=\"archived\">Archived</SelectItem>\n <SelectItem value=\"spam\">Spam</SelectItem>\n </SelectContent>\n </Select>\n </div>\n <div className=\"space-y-1.5 sm:col-span-2\">\n <Label htmlFor=\"submission-notes\">Internal notes</Label>\n <Textarea\n id=\"submission-notes\"\n value={notes}\n onChange={e => setNotes(e.target.value)}\n rows={3}\n placeholder=\"Visible to admins only\"\n disabled={!canUpdate}\n />\n </div>\n </div>\n\n {/* Metadata */}\n <div className=\"space-y-1 border-t border-border pt-4 text-xs text-muted-foreground\">\n <p>\n IP: {submission.ipAddress || \"—\"}\n <span className=\"mx-1.5 text-muted-foreground\">·</span>\n ID: <span className=\"font-mono\">{submission.id}</span>\n </p>\n {submission.userAgent && (\n <p className=\"break-all\">Agent: {submission.userAgent}</p>\n )}\n {submission.spamReason && submission.status !== \"spam\" && (\n <p>\n Previously flagged:{\" \"}\n <Badge\n variant=\"outline\"\n className=\"rounded-none border-border px-1 py-0 text-[10px]\"\n >\n {submission.spamReason}\n </Badge>\n </p>\n )}\n </div>\n </div>\n\n <div className=\"flex items-center justify-between gap-3 border-t border-border bg-muted p-4\">\n {saveError ? (\n <p className=\"text-xs text-destructive\">{saveError}</p>\n ) : (\n <span />\n )}\n <div className=\"flex items-center gap-3\">\n <Button type=\"button\" variant=\"outline\" onClick={onClose}>\n Close\n </Button>\n <Button\n type=\"button\"\n onClick={() => void handleSave()}\n disabled={!canUpdate || !dirty || saving}\n >\n {saving ? \"Saving…\" : \"Save\"}\n </Button>\n </div>\n </div>\n </SheetContent>\n </Sheet>\n );\n}\n\nexport default SubmissionSheet;\n","\"use client\";\n\n/**\n * Submissions View\n *\n * The submissions list, replacing the generic collection table via the\n * List-view override. Submission data is stored keyed by field name, so\n * when one form is selected the table shows real per-field columns; across\n * all forms (where fields differ) it falls back to generic columns with a\n * data summary. Spam is a first-class status tab — flagged submissions are\n * reviewable and recoverable, never invisible.\n *\n * @module admin/components/submissions/SubmissionsView\n */\n\nimport {\n DataTableView,\n type NextlyColumn,\n type RowAction,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Badge,\n Button,\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Tabs,\n TabsList,\n TabsTrigger,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@nextlyhq/ui\";\nimport { ChevronLeft, ChevronRight, Columns3, Download } from \"lucide-react\";\nimport {\n type JSX,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { FormField } from \"../../../types\";\nimport { formatExportValue } from \"../../../utils/export-formats\";\n\nimport { SubmissionSheet } from \"./SubmissionSheet\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface SubmissionRow {\n id: string;\n form: string | { id: string; name?: string };\n data: Record<string, unknown>;\n status: string;\n spamReason?: string | null;\n notes?: string | null;\n ipAddress?: string | null;\n userAgent?: string | null;\n submittedAt?: string | null;\n editedAt?: string | null;\n editedBy?: string | null;\n /** The submission's version as the list read it; see `submissionPatch`. */\n _rev?: string;\n}\n\n/**\n * What a change to a submission sends: the change, with the version its row\n * had when the list read it, so a change made from an older copy is refused\n * rather than replacing what someone saved since.\n */\nexport function submissionPatch(\n row: SubmissionRow | undefined,\n changes: Record<string, unknown>\n): Record<string, unknown> {\n return typeof row?._rev === \"string\"\n ? { ...changes, _rev: row._rev }\n : changes;\n}\n\n/** What a refused change says when someone saved the submission since. */\nconst SUBMISSION_CHANGED_SINCE =\n \"Someone changed this submission since the list loaded. The list now shows their change; make yours again.\";\n\nexport interface FormOption {\n id: string;\n name: string;\n fields: FormField[];\n}\n\ninterface SubmissionsViewProps {\n collectionSlug?: string;\n}\n\ntype StatusTab = \"all\" | \"new\" | \"read\" | \"archived\" | \"spam\";\n\nconst STATUS_TABS: Array<{ value: StatusTab; label: string }> = [\n { value: \"all\", label: \"All\" },\n { value: \"new\", label: \"New\" },\n { value: \"read\", label: \"Read\" },\n { value: \"archived\", label: \"Archived\" },\n { value: \"spam\", label: \"Spam\" },\n];\n\nconst PAGE_SIZE = 25;\n\n/** How many per-field columns are visible before the rest hide behind the selector. */\nconst VISIBLE_FIELD_COLUMNS = 4;\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction formIdOf(row: SubmissionRow): string {\n return typeof row.form === \"object\" ? row.form.id : row.form;\n}\n\nfunction formatDate(value: string | null | undefined): string {\n if (!value) return \"\";\n const date = new Date(value);\n if (Number.isNaN(date.getTime())) return \"\";\n return date.toLocaleString(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n });\n}\n\n/** Status → badge styling. Tokens only; spam reads as a warning state. */\nfunction StatusBadge({ status }: { status: string }) {\n const label = status.charAt(0).toUpperCase() + status.slice(1);\n return (\n <Badge\n variant=\"outline\"\n className={`rounded-none px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider ${\n // Full-strength status borders (destructive/primary) so each badge boundary is perceivable; the default falls back to border-border, keeping the states distinct.\n status === \"spam\"\n ? \"border-destructive text-destructive\"\n : status === \"new\"\n ? \"border-primary text-primary\"\n : \"border-border text-muted-foreground\"\n }`}\n >\n {label}\n </Badge>\n );\n}\n\n/** Compact \"key: value\" preview of a submission for the all-forms table. */\nfunction summarizeData(data: Record<string, unknown>): string {\n return Object.entries(data)\n .slice(0, 3)\n .map(([key, value]) => `${key}: ${formatExportValue(value)}`)\n .join(\" · \");\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport function SubmissionsView({\n collectionSlug,\n}: SubmissionsViewProps): JSX.Element {\n const [slugs, setSlugs] = useState<{ forms: string; submissions: string }>();\n const [forms, setForms] = useState<FormOption[]>([]);\n const [selectedFormId, setSelectedFormId] = useState<string>(() => {\n // Deep links (?form=<id>) preselect the form filter.\n if (typeof window === \"undefined\") return \"\";\n return new URLSearchParams(window.location.search).get(\"form\") ?? \"\";\n });\n const [statusTab, setStatusTab] = useState<StatusTab>(\"all\");\n const [rows, setRows] = useState<SubmissionRow[]>([]);\n const [total, setTotal] = useState(0);\n const [page, setPage] = useState(1);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n const [openId, setOpenId] = useState<string | null>(null);\n const [canUpdate, setCanUpdate] = useState(false);\n const [canDelete, setCanDelete] = useState(false);\n\n const submissionsSlug =\n slugs?.submissions ?? collectionSlug ?? \"form-submissions\";\n\n // --- bootstrap: resolved slugs, the forms list, and the user's rights ----\n useEffect(() => {\n let cancelled = false;\n void (async () => {\n try {\n const configRes = await fetch(\n \"/admin/api/plugins/@nextlyhq/plugin-form-builder/builder-config\",\n { credentials: \"include\" }\n );\n const config = configRes.ok\n ? ((await configRes.json()) as {\n slugs?: { forms: string; submissions: string };\n })\n : null;\n const resolved = config?.slugs ?? {\n forms: \"forms\",\n submissions: \"form-submissions\",\n };\n if (cancelled) return;\n setSlugs(resolved);\n\n // Page through ALL forms — a capped single fetch would silently\n // drop forms from the filter and the per-field column lookup. The\n // entries API reads the page size from `limit` and answers its\n // default page when that is absent, so the size goes there: under\n // any other name the first page comes back short and the loop below\n // reads it as the last.\n const allForms: FormOption[] = [];\n const formsPageSize = 200;\n for (let formsPage = 1; ; formsPage += 1) {\n const formsRes = await fetch(\n `/admin/api/collections/${resolved.forms}/entries?limit=${formsPageSize}&page=${formsPage}`,\n { credentials: \"include\" }\n );\n if (!formsRes.ok) break;\n const json = (await formsRes.json()) as {\n items?: Array<{ id: string; name?: string; fields?: FormField[] }>;\n };\n const items = json.items ?? [];\n allForms.push(\n ...items.map(item => ({\n id: item.id,\n name: item.name ?? item.id,\n fields: Array.isArray(item.fields) ? item.fields : [],\n }))\n );\n if (items.length < formsPageSize) break;\n }\n if (!cancelled) setForms(allForms);\n\n const permsRes = await fetch(\"/admin/api/me/permissions\", {\n credentials: \"include\",\n });\n if (permsRes.ok) {\n const perms = (await permsRes.json()) as {\n permissions?: string[];\n isSuperAdmin?: boolean;\n };\n const has = (slug: string) =>\n Boolean(perms.isSuperAdmin) ||\n (perms.permissions ?? []).includes(slug);\n if (!cancelled) {\n setCanUpdate(has(`update-${resolved.submissions}`));\n setCanDelete(has(`delete-${resolved.submissions}`));\n }\n }\n } catch {\n // Bootstrap failures degrade to the default slugs — without them\n // the list fetch would wait forever and the loading state never end.\n if (!cancelled) {\n setSlugs(\n current =>\n current ?? { forms: \"forms\", submissions: \"form-submissions\" }\n );\n }\n }\n })();\n return () => {\n cancelled = true;\n };\n }, []);\n\n // --- the list fetch ------------------------------------------------------\n // Monotonic request sequence: rapid filter changes overlap fetches, and\n // only the LATEST request may commit rows — an older response finishing\n // last must never overwrite the current filter's data.\n const fetchSeqRef = useRef(0);\n const fetchRows = useCallback(async () => {\n if (!slugs) return;\n const seq = ++fetchSeqRef.current;\n setLoading(true);\n setError(null);\n try {\n const where: Record<string, unknown> = {};\n if (selectedFormId) where.form = { equals: selectedFormId };\n // \"All\" means every human status; spam only shows when asked for —\n // the same default the export route applies.\n if (statusTab === \"all\") where.status = { not_equals: \"spam\" };\n else where.status = { equals: statusTab };\n\n // `limit`, the entries API's page size: the pager counts pages of\n // PAGE_SIZE, so a page the server cut to its default would skip rows.\n const params = new URLSearchParams({\n page: String(page),\n limit: String(PAGE_SIZE),\n sortBy: \"submittedAt\",\n sortOrder: \"desc\",\n where: JSON.stringify(where),\n });\n const res = await fetch(\n `/admin/api/collections/${slugs.submissions}/entries?${params}`,\n { credentials: \"include\" }\n );\n if (!res.ok)\n throw new Error(`Failed to load submissions (${res.status})`);\n const json = (await res.json()) as {\n items?: SubmissionRow[];\n meta?: { total?: number };\n };\n if (seq !== fetchSeqRef.current) return;\n setRows(json.items ?? []);\n setTotal(json.meta?.total ?? (json.items ?? []).length);\n } catch (err) {\n if (seq !== fetchSeqRef.current) return;\n setError(\n err instanceof Error ? err.message : \"Failed to load submissions\"\n );\n } finally {\n if (seq === fetchSeqRef.current) setLoading(false);\n }\n }, [slugs, selectedFormId, statusTab, page]);\n\n useEffect(() => {\n void fetchRows();\n }, [fetchRows]);\n\n const selectedForm = useMemo(\n () => forms.find(form => form.id === selectedFormId),\n [forms, selectedFormId]\n );\n\n const formNameById = useMemo(\n () => new Map(forms.map(form => [form.id, form.name])),\n [forms]\n );\n\n // --- columns --------------------------------------------------------------\n // Hidden field columns, per form. Wide forms start with everything past\n // the first few hidden; the toolbar's Columns menu toggles the rest in.\n const [hiddenFieldColumns, setHiddenFieldColumns] = useState<Set<string>>(\n new Set()\n );\n useEffect(() => {\n setHiddenFieldColumns(\n new Set(\n (selectedForm?.fields ?? [])\n .slice(VISIBLE_FIELD_COLUMNS)\n .map(field => field.name)\n )\n );\n }, [selectedForm]);\n\n const columns = useMemo<NextlyColumn<SubmissionRow>[]>(() => {\n if (selectedForm) {\n // One form selected: real per-field columns.\n const fieldColumns = selectedForm.fields\n .filter(field => !hiddenFieldColumns.has(field.name))\n .map<NextlyColumn<SubmissionRow>>(field => ({\n name: `data.${field.name}`,\n header: field.label || field.name,\n accessor: row => formatExportValue(row.data?.[field.name], field),\n }));\n return [\n ...fieldColumns,\n {\n name: \"status\",\n header: \"Status\",\n accessor: row => row.status,\n cell: ({ row }) => <StatusBadge status={row.status} />,\n },\n {\n name: \"submittedAt\",\n header: \"Submitted\",\n accessor: row => formatDate(row.submittedAt),\n },\n ];\n }\n return [\n {\n name: \"form\",\n header: \"Form\",\n accessor: row => formNameById.get(formIdOf(row)) ?? formIdOf(row),\n },\n {\n name: \"data\",\n header: \"Submission\",\n accessor: row => summarizeData(row.data ?? {}),\n },\n {\n name: \"status\",\n header: \"Status\",\n accessor: row => row.status,\n cell: ({ row }) => <StatusBadge status={row.status} />,\n },\n {\n name: \"submittedAt\",\n header: \"Submitted\",\n accessor: row => formatDate(row.submittedAt),\n },\n ];\n }, [selectedForm, formNameById, hiddenFieldColumns]);\n\n // --- actions ---------------------------------------------------------------\n // The rows as last read, for the version a change sends.\n const rowsNow = useRef(rows);\n rowsNow.current = rows;\n const patchSubmission = useCallback(\n async (id: string, body: Record<string, unknown>) => {\n const res = await fetch(\n `/admin/api/collections/${submissionsSlug}/entries/${id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(\n submissionPatch(\n rowsNow.current.find(row => row.id === id),\n body\n )\n ),\n }\n );\n if (!res.ok) {\n const refusal = (await res.json().catch(() => null)) as {\n error?: { code?: string };\n } | null;\n // Changed since the list read it: read the list again, so the row\n // shows the change and carries its version, and say so.\n if (refusal?.error?.code === \"STALE_VERSION\") {\n await fetchRows();\n throw new Error(SUBMISSION_CHANGED_SINCE);\n }\n throw new Error(`Update failed (${res.status})`);\n }\n await fetchRows();\n },\n [submissionsSlug, fetchRows]\n );\n\n const deleteSubmission = useCallback(\n async (id: string) => {\n const res = await fetch(\n `/admin/api/collections/${submissionsSlug}/entries/${id}`,\n { method: \"DELETE\", credentials: \"include\" }\n );\n if (!res.ok) throw new Error(`Delete failed (${res.status})`);\n setOpenId(null);\n await fetchRows();\n },\n [submissionsSlug, fetchRows]\n );\n\n const rowActions = useCallback(\n (row: SubmissionRow): RowAction<SubmissionRow>[] => {\n const actions: RowAction<SubmissionRow>[] = [];\n if (row.status === \"spam\" && canUpdate) {\n actions.push({\n id: \"not-spam\",\n label: \"Not spam\",\n // A refusal is said beside the list, as a failed read is: the\n // row is read again, and without this the click would seem to do\n // nothing.\n onSelect: target =>\n void patchSubmission(target.id, {\n status: \"new\",\n spamReason: null,\n }).catch((reason: unknown) =>\n setError(\n reason instanceof Error ? reason.message : \"Update failed.\"\n )\n ),\n });\n }\n if (canDelete) {\n actions.push({\n id: \"delete\",\n label: \"Delete\",\n destructive: true,\n onSelect: target => void deleteSubmission(target.id),\n });\n }\n return actions;\n },\n [canUpdate, canDelete, patchSubmission, deleteSubmission]\n );\n\n // --- export ----------------------------------------------------------------\n const exportHref = useCallback(\n (format: \"csv\" | \"json\") => {\n const params = new URLSearchParams({ format });\n if (selectedFormId) params.set(\"form\", selectedFormId);\n if (statusTab !== \"all\") params.set(\"status\", statusTab);\n return `/admin/api/plugins/@nextlyhq/plugin-form-builder/submissions/export?${params}`;\n },\n [selectedFormId, statusTab]\n );\n\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));\n // The open record is tracked by ID: after a save refetch the row may\n // move or leave the filtered view entirely — index tracking would silently\n // show a different submission, while a missing ID just closes the drawer.\n const openIndex = openId ? rows.findIndex(row => row.id === openId) : -1;\n const openRow = openIndex === -1 ? null : rows[openIndex];\n\n return (\n <div className=\"space-y-4\">\n {/* Toolbar: form filter, status tabs, export */}\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center\">\n <Select\n value={selectedFormId || \"__all\"}\n onValueChange={value => {\n setSelectedFormId(value === \"__all\" ? \"\" : value);\n setPage(1);\n setOpenId(null);\n }}\n >\n <SelectTrigger\n aria-label=\"Filter by form\"\n className=\"w-full sm:w-56 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__all\">All forms</SelectItem>\n {forms.map(form => (\n <SelectItem key={form.id} value={form.id}>\n {form.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n\n <Tabs\n value={statusTab}\n onValueChange={value => {\n setStatusTab(value as StatusTab);\n setPage(1);\n setOpenId(null);\n }}\n >\n {/* No corner override: square is the primitive's guarantee, not\n this view's decision. */}\n <TabsList>\n {STATUS_TABS.map(tab => (\n <TabsTrigger key={tab.value} value={tab.value}>\n {tab.label}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n </div>\n\n <div className=\"flex items-center gap-2\">\n {selectedForm && selectedForm.fields.length > 0 && (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\">\n <Columns3 className=\"h-4 w-4\" aria-hidden=\"true\" />\n Columns\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"shadow-none border-border\"\n >\n {selectedForm.fields.map(field => (\n <DropdownMenuCheckboxItem\n key={field.name}\n checked={!hiddenFieldColumns.has(field.name)}\n onCheckedChange={checked =>\n setHiddenFieldColumns(prev => {\n const next = new Set(prev);\n if (checked) next.delete(field.name);\n else next.add(field.name);\n return next;\n })\n }\n // Keep the menu open while toggling several columns.\n onSelect={event => event.preventDefault()}\n >\n {field.label || field.name}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n )}\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\">\n <Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n Export\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"shadow-none border-border\"\n >\n {selectedFormId ? (\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <a href={exportHref(\"csv\")} download>\n CSV\n </a>\n </DropdownMenuItem>\n ) : (\n <Tooltip>\n <TooltipTrigger asChild>\n {/* CSV columns come from one form's fields, so it needs a\n form selected; the tooltip says so instead of hiding it. */}\n <DropdownMenuItem\n disabled\n onSelect={event => event.preventDefault()}\n >\n CSV\n </DropdownMenuItem>\n </TooltipTrigger>\n <TooltipContent side=\"left\">\n Select a form first — CSV columns come from its fields.\n </TooltipContent>\n </Tooltip>\n )}\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <a href={exportHref(\"json\")} download>\n JSON\n </a>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n\n <DataTableView<SubmissionRow>\n columns={columns}\n rows={rows}\n loading={loading}\n error={error}\n emptyMessage={\n statusTab === \"spam\"\n ? \"No spam — nothing has been flagged.\"\n : \"No submissions yet.\"\n }\n ariaLabel=\"Submissions\"\n onRowClick={row => setOpenId(row.id)}\n rowActions={rowActions}\n />\n\n {/* Pagination */}\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span role=\"status\">\n {total === 0\n ? \"0 submissions\"\n : `${(page - 1) * PAGE_SIZE + 1}–${Math.min(page * PAGE_SIZE, total)} of ${total}`}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n disabled={page <= 1}\n onClick={() => setPage(current => current - 1)}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8 rounded-none\"\n disabled={page >= totalPages}\n onClick={() => setPage(current => current + 1)}\n aria-label=\"Next page\"\n >\n <ChevronRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n\n {/* Detail drawer with prev/next over the current page */}\n {openRow && (\n <SubmissionSheet\n submission={openRow}\n form={\n forms.find(form => form.id === formIdOf(openRow)) ?? {\n id: formIdOf(openRow),\n name: formNameById.get(formIdOf(openRow)) ?? \"Form\",\n fields: [],\n }\n }\n canUpdate={canUpdate}\n onClose={() => setOpenId(null)}\n onPrev={\n openIndex > 0 ? () => setOpenId(rows[openIndex - 1].id) : undefined\n }\n onNext={\n openIndex < rows.length - 1\n ? () => setOpenId(rows[openIndex + 1].id)\n : undefined\n }\n onSave={async changes => {\n await patchSubmission(openRow.id, changes);\n }}\n />\n )}\n </div>\n );\n}\n\nexport default SubmissionsView;\n","\"use client\";\n\n/**\n * Form Builder Context\n *\n * Provides state management for the Form Builder UI.\n * Manages form fields, selection state, and form metadata.\n *\n * @module admin/context/FormBuilderContext\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport {\n type JSX,\n createContext,\n useContext,\n useCallback,\n useMemo,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport type {\n AnyFormField,\n FormFieldTypeId,\n FormNotification,\n FormSettings,\n} from \"../../types\";\nimport {\n DEFAULT_FORM_SETTINGS,\n normalizeFormSettings,\n} from \"../../utils/form-settings\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The canonical settings shape lives in `types.ts` (the same shape the\n * collection group declares and the submit handler consumes); re-exported\n * here so admin components keep one import site.\n */\nexport type { FormSettings } from \"../../types\";\n\n/**\n * The notification rule type lives in `types.ts` (the same shape the send\n * path consumes); re-exported here so admin components keep one import site.\n */\nexport type { FormNotification } from \"../../types\";\n\nexport interface FormBuilderState {\n /** Form fields array */\n fields: AnyFormField[];\n /** Currently selected field ID (name) */\n selectedFieldId: string | null;\n /** Active tab: builder, preview, settings, or notifications */\n activeTab: \"builder\" | \"preview\" | \"settings\" | \"notifications\";\n /** Whether form has unsaved changes */\n isDirty: boolean;\n /** Form metadata */\n formData: {\n id?: string;\n name: string;\n slug: string;\n description?: string;\n status: \"draft\" | \"published\" | \"closed\";\n /** What a closed form tells a visitor who reaches it. */\n closedMessage?: string;\n };\n /** Form settings */\n settings: FormSettings;\n /** Email notifications */\n notifications: FormNotification[];\n}\n\nexport interface FormBuilderActions {\n /** Set all fields */\n setFields: (fields: AnyFormField[]) => void;\n /** Add a new field */\n addField: (field: AnyFormField, index?: number) => void;\n /** Update a field by name */\n updateField: (fieldName: string, updates: Partial<AnyFormField>) => void;\n /** Delete a field by name */\n deleteField: (fieldName: string) => void;\n /** Move a field from one index to another */\n moveField: (fromIndex: number, toIndex: number) => void;\n /** Duplicate a field */\n duplicateField: (fieldName: string) => void;\n /** Select a field */\n selectField: (fieldName: string | null) => void;\n /** Set active tab */\n setActiveTab: (\n tab: \"builder\" | \"preview\" | \"settings\" | \"notifications\"\n ) => void;\n /** Update form metadata */\n updateFormData: (updates: Partial<FormBuilderState[\"formData\"]>) => void;\n /** Update form settings */\n updateSettings: (updates: Partial<FormSettings>) => void;\n /** Add a notification */\n addNotification: (notification: FormNotification) => void;\n /** Duplicate a notification (the copy starts disabled) */\n duplicateNotification: (id: string) => void;\n /** Update a notification */\n updateNotification: (id: string, updates: Partial<FormNotification>) => void;\n /** Delete a notification */\n deleteNotification: (id: string) => void;\n /** Seed default notifications into an empty list without dirtying the form */\n seedNotifications: (rules: FormNotification[]) => void;\n /** Mark form as saved (clears dirty flag) */\n markAsSaved: () => void;\n}\n\nexport interface FormBuilderContextValue\n extends FormBuilderState,\n FormBuilderActions {}\n\nexport interface FormBuilderProviderProps {\n /** Initial form data (for editing existing forms) */\n initialData?: {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\" | \"closed\";\n closedMessage?: string;\n fields?: AnyFormField[];\n settings?: Partial<FormSettings>;\n notifications?: FormNotification[];\n };\n /** Child components */\n children: ReactNode;\n /**\n * The document belongs to someone else right now, so nothing here may change\n * it.\n *\n * Held here rather than at each control because the builder has dozens of\n * them across four tabs, and a rule spelled at every one is a rule one new\n * control forgets. Reading is untouched: the editor still browses fields,\n * switches tabs and opens settings, which is exactly what the strip above\n * offers them.\n */\n readOnly?: boolean;\n}\n\n/**\n * The actions that survive a read-only document.\n *\n * Everything else on the context changes the form, and every one of those marks\n * it dirty - the same question asked twice, so this is the complement of it\n * rather than a second list of what to block. `markAsSaved` is the save's own\n * bookkeeping and cannot run while the save is withheld anyway.\n */\nconst READS_ONLY = new Set([\"selectField\", \"setActiveTab\", \"markAsSaved\"]);\n\n/** Default form settings (the canonical defaults from the settings reader) */\nexport const DEFAULT_SETTINGS: FormSettings = DEFAULT_FORM_SETTINGS;\n\n/** Create a new notification rule with default values */\nexport function createNotification(): FormNotification {\n const id = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;\n return {\n id,\n name: \"New notification\",\n enabled: true,\n recipientType: \"static\",\n to: \"\",\n cc: [],\n bcc: [],\n };\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst FormBuilderContext = createContext<FormBuilderContextValue | null>(null);\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Generate a readable unique field name: the type itself for the first\n * field of that type, then a numeric suffix (`email`, `email_2`, ...).\n * Names key submission data, so they should read like keys a developer\n * would have chosen, not timestamps.\n */\nexport function generateFieldName(\n type: FormFieldTypeId,\n existingNames: readonly string[] = []\n): string {\n // A plugin type id can be hyphenated (`star-rating`) or start with a digit,\n // but a field name must match ^[a-zA-Z][a-zA-Z0-9_]*$ (it keys submission\n // data). Normalize to a safe key; built-in type ids are already valid and\n // pass through unchanged.\n const base =\n type\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .replace(/^_+/, \"\")\n .replace(/^([0-9])/, \"field_$1\")\n .replace(/_+$/, \"\") || \"field\";\n const taken = new Set(existingNames);\n if (!taken.has(base)) return base;\n let suffix = 2;\n while (taken.has(`${base}_${suffix}`)) suffix += 1;\n return `${base}_${suffix}`;\n}\n\n/**\n * Generate a human-readable label from field type, using the shared catalog\n * so a new field's default label matches what the picker called the type. A\n * plugin type not in the built-in catalog falls back to a title-cased id.\n */\nexport function generateFieldLabel(type: FormFieldTypeId): string {\n const entry = FORM_FIELD_TYPE_CATALOG.find(row => row.type === type);\n if (entry) return entry.label;\n const titleCased = type\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n return titleCased || \"New Field\";\n}\n\n/**\n * Create a new field from a field type with default values.\n */\nexport function createFieldFromType(\n type: FormFieldTypeId,\n existingNames: readonly string[] = []\n): AnyFormField {\n const name = generateFieldName(type, existingNames);\n const label = generateFieldLabel(type);\n\n const baseField = {\n name,\n label,\n required: false,\n };\n\n switch (type) {\n case \"text\":\n return { ...baseField, type: \"text\" };\n\n case \"email\":\n return { ...baseField, type: \"email\" };\n\n case \"number\":\n return { ...baseField, type: \"number\" };\n\n case \"phone\":\n return { ...baseField, type: \"phone\" };\n\n case \"url\":\n return { ...baseField, type: \"url\" };\n\n case \"textarea\":\n return { ...baseField, type: \"textarea\", rows: 4 };\n\n case \"select\":\n return {\n ...baseField,\n type: \"select\",\n options: [\n { label: \"Option 1\", value: \"option_1\" },\n { label: \"Option 2\", value: \"option_2\" },\n ],\n };\n\n case \"checkbox\":\n return { ...baseField, type: \"checkbox\" };\n\n case \"radio\":\n return {\n ...baseField,\n type: \"radio\",\n options: [\n { label: \"Option 1\", value: \"option_1\" },\n { label: \"Option 2\", value: \"option_2\" },\n ],\n };\n\n case \"file\":\n return { ...baseField, type: \"file\" };\n\n case \"date\":\n return { ...baseField, type: \"date\" };\n\n case \"time\":\n return { ...baseField, type: \"time\" };\n\n case \"hidden\":\n return { ...baseField, type: \"hidden\" };\n\n default:\n // A plugin-contributed type keeps its own id; the plugin owns the editor\n // component and value semantics, so no built-in defaults are seeded.\n return { ...baseField, type };\n }\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * FormBuilderProvider - Provides form builder state to child components\n *\n * Wraps the form builder UI to provide centralized state management\n * for fields, selection, and form metadata.\n *\n * @example\n * ```tsx\n * <FormBuilderProvider initialData={existingForm}>\n * <FormBuilderView />\n * </FormBuilderProvider>\n * ```\n */\nexport function FormBuilderProvider({\n initialData,\n children,\n readOnly = false,\n}: FormBuilderProviderProps): JSX.Element {\n // State\n const [fields, setFieldsState] = useState<AnyFormField[]>(\n initialData?.fields || []\n );\n const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null);\n const [activeTab, setActiveTab] = useState<\n \"builder\" | \"preview\" | \"settings\" | \"notifications\"\n >(\"builder\");\n const [isDirty, setIsDirty] = useState(false);\n const [formData, setFormData] = useState<FormBuilderState[\"formData\"]>({\n id: initialData?.id,\n name: initialData?.name || \"\",\n slug: initialData?.slug || \"\",\n description: initialData?.description,\n status: initialData?.status || \"draft\",\n closedMessage: initialData?.closedMessage,\n });\n // Stored settings may carry legacy keys from earlier builder versions —\n // the normalizer migrates them on read (nothing rewrites until save).\n const [settings, setSettings] = useState<FormSettings>(() =>\n normalizeFormSettings(initialData?.settings)\n );\n const [notifications, setNotifications] = useState<FormNotification[]>(\n initialData?.notifications || []\n );\n\n // Actions\n const setFields = useCallback((newFields: AnyFormField[]) => {\n setFieldsState(newFields);\n setIsDirty(true);\n }, []);\n\n const addField = useCallback((field: AnyFormField, index?: number) => {\n setFieldsState(prev => {\n if (index !== undefined && index >= 0 && index <= prev.length) {\n const newFields = [...prev];\n newFields.splice(index, 0, field);\n return newFields;\n }\n return [...prev, field];\n });\n setSelectedFieldId(field.name);\n setIsDirty(true);\n }, []);\n\n const updateField = useCallback(\n (fieldName: string, updates: Partial<AnyFormField>) => {\n setFieldsState(prev =>\n prev.map(f => (f.name === fieldName ? { ...f, ...updates } : f))\n );\n // If the field name is being changed, also update selectedFieldId.\n // This prevents the sidebar from disappearing when renaming a field —\n // including when the change is TO the empty string, which is where a\n // rename that clears the input first passes through: skipping it there\n // would stop the renamed field matching its own selection, collapsing\n // the card (and unmounting the editor) mid-rename.\n if (updates.name !== undefined && updates.name !== fieldName) {\n setSelectedFieldId(updates.name);\n }\n setIsDirty(true);\n },\n []\n );\n\n const deleteField = useCallback(\n (fieldName: string) => {\n setFieldsState(prev => prev.filter(f => f.name !== fieldName));\n if (selectedFieldId === fieldName) {\n setSelectedFieldId(null);\n }\n setIsDirty(true);\n },\n [selectedFieldId]\n );\n\n const moveField = useCallback((fromIndex: number, toIndex: number) => {\n setFieldsState(prev => {\n if (\n fromIndex < 0 ||\n fromIndex >= prev.length ||\n toIndex < 0 ||\n toIndex >= prev.length\n ) {\n return prev;\n }\n const newFields = [...prev];\n const [removed] = newFields.splice(fromIndex, 1);\n newFields.splice(toIndex, 0, removed);\n return newFields;\n });\n setIsDirty(true);\n }, []);\n\n const duplicateField = useCallback(\n (fieldName: string) => {\n // Computed from the current fields (not inside the state updater):\n // updaters must stay pure, and the selection below needs the new name\n // deterministically.\n const fieldIndex = fields.findIndex(f => f.name === fieldName);\n if (fieldIndex === -1) return;\n\n const field = fields[fieldIndex];\n const newFieldName = generateFieldName(\n field.type,\n fields.map(f => f.name)\n );\n const newField: AnyFormField = {\n ...field,\n name: newFieldName,\n label: `${field.label} (Copy)`,\n };\n\n const newFields = [...fields];\n newFields.splice(fieldIndex + 1, 0, newField);\n setFieldsState(newFields);\n\n // Select the duplicated field so its card expands\n setSelectedFieldId(newFieldName);\n setIsDirty(true);\n },\n [fields]\n );\n\n const selectField = useCallback((fieldName: string | null) => {\n setSelectedFieldId(fieldName);\n }, []);\n\n const updateFormData = useCallback(\n (updates: Partial<FormBuilderState[\"formData\"]>) => {\n setFormData(prev => ({ ...prev, ...updates }));\n setIsDirty(true);\n },\n []\n );\n\n const updateSettings = useCallback((updates: Partial<FormSettings>) => {\n setSettings(prev => ({ ...prev, ...updates }));\n setIsDirty(true);\n }, []);\n\n const addNotification = useCallback((notification: FormNotification) => {\n setNotifications(prev => [...prev, notification]);\n setIsDirty(true);\n }, []);\n\n const duplicateNotification = useCallback((id: string) => {\n // The non-deterministic id is generated outside the updater (updaters\n // must stay pure and may re-run); the copy itself derives from `prev`\n // so rapid duplications never work from a stale snapshot.\n const copyId = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;\n setNotifications(prev => {\n const index = prev.findIndex(n => n.id === id);\n if (index === -1) return prev;\n const source = prev[index];\n const copy: FormNotification = {\n ...source,\n id: copyId,\n name: `${source.name} (Copy)`,\n // Copies start disabled so duplicating never doubles the emails a\n // live rule sends until the copy is deliberately turned on.\n enabled: false,\n };\n const next = [...prev];\n next.splice(index + 1, 0, copy);\n return next;\n });\n setIsDirty(true);\n }, []);\n\n const seedNotifications = useCallback((rules: FormNotification[]) => {\n // Only fills an empty list, and does not mark the form dirty: the seed\n // is a starting point the user hasn't authored, not an unsaved edit.\n setNotifications(prev => (prev.length > 0 ? prev : rules));\n }, []);\n\n const updateNotification = useCallback(\n (id: string, updates: Partial<FormNotification>) => {\n setNotifications(prev =>\n prev.map(n => (n.id === id ? { ...n, ...updates } : n))\n );\n setIsDirty(true);\n },\n []\n );\n\n const deleteNotification = useCallback((id: string) => {\n setNotifications(prev => prev.filter(n => n.id !== id));\n setIsDirty(true);\n }, []);\n\n const markAsSaved = useCallback(() => {\n setIsDirty(false);\n }, []);\n\n // Memoized context value\n const value = useMemo<FormBuilderContextValue>(\n () => ({\n // State\n fields,\n selectedFieldId,\n activeTab,\n isDirty,\n formData,\n settings,\n notifications,\n // Actions\n setFields,\n addField,\n updateField,\n deleteField,\n moveField,\n duplicateField,\n selectField,\n setActiveTab,\n updateFormData,\n updateSettings,\n addNotification,\n duplicateNotification,\n updateNotification,\n deleteNotification,\n seedNotifications,\n markAsSaved,\n }),\n [\n fields,\n selectedFieldId,\n activeTab,\n isDirty,\n formData,\n settings,\n notifications,\n setFields,\n addField,\n updateField,\n deleteField,\n moveField,\n duplicateField,\n selectField,\n updateFormData,\n updateSettings,\n addNotification,\n duplicateNotification,\n updateNotification,\n deleteNotification,\n seedNotifications,\n markAsSaved,\n ]\n );\n\n /**\n * The same value with every document-changing action withheld.\n *\n * 🔴 Fail-closed, and deliberately so: it withholds every function EXCEPT the\n * ones named, rather than naming the ones to withhold. An action added here\n * later is inert under a colleague's claim without anyone remembering this\n * exists, which is the opposite of how a list of things-to-disable ages.\n *\n * What stays is navigation and the save's own bookkeeping. Neither changes\n * the document, and both are what \"you can read it\" has to mean.\n */\n const guarded = useMemo<FormBuilderContextValue>(() => {\n if (!readOnly) return value;\n return Object.fromEntries(\n Object.entries(value).map(([name, member]) =>\n typeof member === \"function\" && !READS_ONLY.has(name)\n ? [name, () => undefined]\n : [name, member]\n )\n ) as FormBuilderContextValue;\n }, [value, readOnly]);\n\n return (\n <FormBuilderContext.Provider value={guarded}>\n {children}\n </FormBuilderContext.Provider>\n );\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * useFormBuilder - Access form builder context\n *\n * Hook to access form builder state and actions from within the builder UI.\n * Must be used within a FormBuilderProvider.\n *\n * @returns Form builder context value\n * @throws Error if used outside of FormBuilderProvider\n *\n * @example\n * ```tsx\n * function FieldList() {\n * const { fields, selectField, deleteField } = useFormBuilder();\n *\n * return (\n * <ul>\n * {fields.map(field => (\n * <li key={field.name} onClick={() => selectField(field.name)}>\n * {field.label}\n * <button onClick={() => deleteField(field.name)}>Delete</button>\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useFormBuilder(): FormBuilderContextValue {\n const context = useContext(FormBuilderContext);\n\n if (!context) {\n throw new Error(\"useFormBuilder must be used within a FormBuilderProvider\");\n }\n\n return context;\n}\n\n/**\n * useOptionalFormBuilder - Access form builder context safely\n *\n * Like useFormBuilder but returns null if not within a provider.\n * Useful for components that may be rendered outside of the form builder.\n *\n * @returns Form builder context value or null\n */\nexport function useOptionalFormBuilder(): FormBuilderContextValue | null {\n return useContext(FormBuilderContext);\n}\n","\"use client\";\n\n/**\n * Add Field Dialog\n *\n * Type selection for new form fields, rendered from the shared field-type\n * catalog via the SDK's FieldTypePicker so the form surface shows the same\n * labels, icons, and hints as every other field picker in the admin.\n *\n * @module admin/components/builder/AddFieldDialog\n */\n\nimport { FieldTypePicker } from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Button,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n} from \"@nextlyhq/ui\";\nimport type { FieldTypeCatalogEntry } from \"nextly/field-catalog\";\nimport { type JSX, useState } from \"react\";\n\nimport type { FormFieldTypeId } from \"../../../types\";\n\nexport interface AddFieldDialogProps {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n /**\n * The form surface's catalog — built-ins already filtered by the host's\n * excludes plus any plugin field types that opted into the forms surface;\n * `null` while the host configuration is still loading. `type` is the open\n * `FormFieldTypeId` so a plugin entry shares this shape with the built-ins.\n */\n entries: readonly FieldTypeCatalogEntry<FormFieldTypeId>[] | null;\n /** Called with the chosen type; the parent creates and selects the field. */\n onAdd: (type: FormFieldTypeId) => void;\n}\n\nexport function AddFieldDialog({\n open,\n onOpenChange,\n entries,\n onAdd,\n}: AddFieldDialogProps): JSX.Element {\n const [selected, setSelected] = useState<FormFieldTypeId>(\"text\");\n\n const selectable = entries?.some(entry => entry.type === selected)\n ? selected\n : entries?.[0]?.type;\n\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent className=\"sm:max-w-2xl\">\n <DialogHeader>\n <DialogTitle>Add field</DialogTitle>\n <DialogDescription>\n Pick the field type. Everything else is edited on the field card.\n </DialogDescription>\n </DialogHeader>\n\n {entries === null ? (\n <p className=\"text-sm text-muted-foreground\" role=\"status\">\n Loading the available field types…\n </p>\n ) : entries.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n All field types are disabled by this site's form-builder\n configuration.\n </p>\n ) : (\n <FieldTypePicker\n entries={entries}\n value={selectable ?? \"text\"}\n onChange={setSelected}\n columns={3}\n ariaLabel=\"Field type\"\n />\n )}\n\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n disabled={!selectable}\n onClick={() => {\n if (selectable) onAdd(selectable);\n onOpenChange(false);\n }}\n >\n Add field\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n","\"use client\";\n\n/**\n * Conditional Logic Editor\n *\n * Component for configuring conditional show/hide logic for form fields.\n * Allows users to define conditions based on other field values.\n *\n * @module admin/components/builder/ConditionalLogicEditor\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport {\n ConditionRow,\n type ConditionOperatorName,\n type ConditionSource,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n FormLabelWithTooltip,\n Button,\n Checkbox,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { type JSX, useCallback, useMemo } from \"react\";\n\nimport type {\n AnyFormField,\n ConditionalLogic,\n ConditionalLogicCondition,\n} from \"../../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ConditionalLogicEditorProps {\n /** The field being edited (built-in or plugin-contributed). */\n field: AnyFormField;\n /** All fields in the form (for field references) */\n allFields: AnyFormField[];\n /** Callback when conditional logic is updated */\n onUpdate: (updates: Partial<AnyFormField>) => void;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * The comparisons `evaluateConditions` implements.\n *\n * The shared row offers seventeen operators, and this runtime understands\n * seven of them. Every operator offered has to be one this evaluator can\n * answer: a field configured with `isTrue` would store fine, render fine, and\n * then never match at form time, with nothing anywhere to say why. So the\n * vocabulary is pinned here rather than taken from the row's default.\n *\n * Typed as the shared row's operator names AND as this module's stored\n * comparisons, so the day either list moves the other stops compiling.\n */\nconst SUPPORTED_COMPARISONS = [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n] as const satisfies readonly ConditionOperatorName[] &\n readonly ConditionalLogicCondition[\"comparison\"][];\n\nconst SUPPORTED_SET: ReadonlySet<string> = new Set(SUPPORTED_COMPARISONS);\n\n/**\n * Whether an operator the row reported is one this runtime can evaluate.\n *\n * `operatorsFor` already narrows what the row offers, so this should always\n * hold; it is a guard rather than an assertion so that if the two ever drift,\n * the condition is left as it was instead of stored in a shape the evaluator\n * will not understand.\n */\nfunction isSupportedComparison(\n operator: string\n): operator is ConditionalLogicCondition[\"comparison\"] {\n return SUPPORTED_SET.has(operator);\n}\n\n// The field types whose values are text, named as `FormFieldType` spells them.\n// `tel` and `password` were guesses and match nothing; the builder's telephone\n// field is `phone`, so it was falling through to the full comparison set and\n// offering \"greater than\" for a phone number.\nconst TEXT_TYPES = new Set([\n \"text\",\n \"textarea\",\n \"email\",\n \"url\",\n \"phone\",\n \"hidden\",\n]);\n\n/**\n * The comparisons worth offering for a source type, within what the evaluator\n * supports.\n *\n * Narrower than the full seven where the type makes some of them meaningless:\n * \"contains\" on a number compares substrings of a formatted value, which is\n * not what anyone building that condition means.\n */\nfunction comparisonsForType(\n sourceType: string | undefined\n): ConditionOperatorName[] {\n if (sourceType === \"checkbox\") return [\"equals\", \"notEquals\"];\n if (sourceType === \"number\") {\n return [\"equals\", \"notEquals\", \"greaterThan\", \"lessThan\"];\n }\n if (sourceType !== undefined && TEXT_TYPES.has(sourceType)) {\n return [\"equals\", \"notEquals\", \"contains\", \"isEmpty\", \"isNotEmpty\"];\n }\n return [...SUPPORTED_COMPARISONS];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * A form field as the shared row needs to see it.\n *\n * Choice fields hand over their options so the value editor becomes a dropdown\n * of exactly those, rather than a text box in which an option can be misspelled\n * into a condition that never matches.\n */\nfunction toSource(field: AnyFormField): ConditionSource {\n // Only the choice field types carry an option list, and `AnyFormField` is a\n // union over every type, so the property has to be probed rather than read.\n // An option with an empty value is dropped: a Select cannot render one, and\n // it is a placeholder row in the editor rather than a value to compare with.\n const options =\n \"options\" in field && Array.isArray(field.options)\n ? field.options\n .filter(option => option.value !== \"\")\n .map(option => ({ value: option.value, label: option.label }))\n : undefined;\n return {\n name: field.name,\n label: field.label || field.name,\n type: field.type,\n options,\n };\n}\n\n/**\n * A stored condition's value as the row edits it.\n *\n * Storage types this `unknown`, and the row edits scalars. An object that got\n * in some other way is shown as nothing rather than as `[object Object]`,\n * which is not a value anyone typed and not one the evaluator can use.\n */\nfunction toRowValue(value: unknown): string | number | boolean | undefined {\n if (value === null || value === undefined) return undefined;\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n return undefined;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ConditionalLogicEditor - Configure conditional show/hide logic\n *\n * Allows users to define when a field should be shown or hidden\n * based on the values of other fields in the form.\n *\n * @example\n * ```tsx\n * <ConditionalLogicEditor\n * field={selectedField}\n * allFields={allFields}\n * onUpdate={handleUpdate}\n * />\n * ```\n */\nexport function ConditionalLogicEditor({\n field,\n allFields,\n onUpdate,\n}: ConditionalLogicEditorProps): JSX.Element {\n // Get current conditional logic or create default.\n // Wrapped in useMemo so dependent useCallbacks don't re-create on every render.\n const logic: ConditionalLogic = useMemo(\n () =>\n field.conditionalLogic || {\n enabled: false,\n action: \"show\",\n operator: \"AND\",\n conditions: [],\n },\n [field.conditionalLogic]\n );\n\n // Get other fields that can be referenced (exclude current field)\n const availableFields = allFields.filter(f => f.name !== field.name);\n\n // Update the entire conditional logic object\n const updateLogic = useCallback(\n (updates: Partial<ConditionalLogic>) => {\n onUpdate({\n conditionalLogic: { ...logic, ...updates },\n });\n },\n [logic, onUpdate]\n );\n\n // Toggle enabled state\n const handleToggleEnabled = useCallback(() => {\n updateLogic({ enabled: !logic.enabled });\n }, [logic.enabled, updateLogic]);\n\n // Add a new condition\n const handleAddCondition = useCallback(() => {\n const firstField = availableFields[0];\n if (!firstField) return;\n\n const newCondition: ConditionalLogicCondition = {\n field: firstField.name,\n comparison: \"equals\",\n value: \"\",\n };\n\n updateLogic({\n conditions: [...logic.conditions, newCondition],\n });\n }, [availableFields, logic.conditions, updateLogic]);\n\n // Update a specific condition\n const handleUpdateCondition = useCallback(\n (index: number, updates: Partial<ConditionalLogicCondition>) => {\n const newConditions = logic.conditions.map((c, i) =>\n i === index ? { ...c, ...updates } : c\n );\n updateLogic({ conditions: newConditions });\n },\n [logic.conditions, updateLogic]\n );\n\n // Remove a condition\n const handleRemoveCondition = useCallback(\n (index: number) => {\n const newConditions = logic.conditions.filter((_, i) => i !== index);\n updateLogic({ conditions: newConditions });\n },\n [logic.conditions, updateLogic]\n );\n\n return (\n <div className=\"space-y-6 pt-2\">\n {/* Enable toggle */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id=\"logic-enabled\"\n checked={logic.enabled}\n onCheckedChange={handleToggleEnabled}\n />\n <FormLabelWithTooltip\n label=\"Enable conditional logic\"\n htmlFor=\"logic-enabled\"\n description=\"Show or hide this field based on other field values.\"\n />\n </div>\n </div>\n\n {logic.enabled && (\n // Semantic border token so this top divider is visible at the 3:1 UI minimum.\n <div className=\"space-y-4 pt-4 border-t border-border\">\n {/* Action and operator row */}\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <Select\n value={logic.action}\n onValueChange={value =>\n updateLogic({ action: value as \"show\" | \"hide\" })\n }\n >\n <SelectTrigger className=\"w-[100px] h-9 bg-transparent border-input dark:bg-muted/50\">\n <SelectValue placeholder=\"Show/Hide\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"show\">Show</SelectItem>\n <SelectItem value=\"hide\">Hide</SelectItem>\n </SelectContent>\n </Select>\n <span>this field when</span>\n <Select\n value={logic.operator}\n onValueChange={value =>\n updateLogic({ operator: value as \"AND\" | \"OR\" })\n }\n >\n <SelectTrigger className=\"w-[80px] h-9 bg-transparent border-input dark:bg-muted/50\">\n <SelectValue placeholder=\"All/Any\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"AND\">All</SelectItem>\n <SelectItem value=\"OR\">Any</SelectItem>\n </SelectContent>\n </Select>\n <span>of the following conditions are met:</span>\n </div>\n\n {/* Conditions list */}\n <div className=\"space-y-3 pt-4\">\n {logic.conditions.length === 0 ? (\n // Semantic border token so this empty-state boundary is visible at the 3:1 UI minimum.\n <div className=\"p-4 bg-muted rounded-none border border-dashed border-border text-center text-xs text-muted-foreground\">\n No conditions defined. Add a condition to get started.\n </div>\n ) : (\n logic.conditions.map((condition, index) => (\n // Semantic border token so this condition card boundary is visible at the 3:1 UI minimum.\n <div\n key={index}\n className=\"flex flex-col gap-2 p-3 rounded-none bg-muted border border-border relative group\"\n >\n {/* Source, comparison and value, shared with the schema\n builder. Stacked rather than in three columns: this card\n sits in a builder sidebar, where three columns leave each\n control too narrow to read its own selection. */}\n <ConditionRow\n className=\"grid grid-cols-1 gap-2\"\n condition={{\n field: condition.field,\n operator: condition.comparison,\n value: toRowValue(condition.value),\n }}\n sources={availableFields.map(toSource)}\n operatorsFor={comparisonsForType}\n onChange={next => {\n if (!next || !isSupportedComparison(next.operator))\n return;\n handleUpdateCondition(index, {\n field: next.field,\n comparison: next.operator,\n value: toRowValue(next.value),\n });\n }}\n />\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleRemoveCondition(index)}\n // Semantic border token so the remove button's boundary is visible at the 3:1 UI minimum.\n className=\"absolute -top-2 -right-2 h-6 w-6 rounded-full bg-background border border-border opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove condition\"\n >\n <span className=\"text-xs\">×</span>\n </Button>\n </div>\n ))\n )}\n </div>\n\n {/* Add condition button */}\n {availableFields.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddCondition}\n className=\"w-full mt-4 border-dashed\"\n >\n + Add Condition\n </Button>\n ) : (\n // Full-strength warning border so this notice boundary is perceivable over its tinted fill.\n // The 600 shade rather than the base token: the base measures 4.37:1\n // once its own 10% fill composites over the page container, short of\n // the 4.5:1 text needs. The 600 shade holds 5.13:1 at its worst\n // surface in either mode.\n <p className=\"mt-4 text-center text-xs text-warning-600 font-medium bg-warning/10 p-2 rounded-none border border-warning\">\n Add more fields to the form to create conditions.\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * Field Editor\n *\n * Properties panel for editing the selected field's configuration.\n * Provides tabbed interface for General, Validation, and Conditional settings.\n *\n * @module admin/components/builder/FieldEditor\n * @since 0.1.0\n */\n\n\"use client\";\nimport {\n drawsAnyValidationRule,\n FieldOptionsEditor,\n ValidationRulesEditor,\n withOptionIds,\n type FieldOptionsEditorProps,\n type ValidationRuleValues,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n FormLabelWithTooltip,\n Input,\n Checkbox,\n Tabs,\n TabsContent,\n TabsList,\n TabsTrigger,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { validationRulesForFieldType } from \"nextly/field-catalog\";\nimport { type JSX, useState, useCallback, useMemo } from \"react\";\n\nimport type {\n AnyFieldValidation,\n AnyFormField,\n FormField,\n SelectFormField,\n RadioFormField,\n} from \"../../../types\";\nimport { isKnownFormField } from \"../../../types\";\n// From the metadata module rather than from `generate-schema`, which\n// implements it: the editor needs only to know WHICH rules bite for a\n// type, and importing that from the module that builds schemas pulls the\n// whole schema implementation and `zod` into every admin client bundle\n// before anyone opens a field.\nimport { enforcedValidationRules } from \"../../../utils/enforced-validation\";\n\nimport { ConditionalLogicEditor } from \"./ConditionalLogicEditor\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FieldEditorProps {\n /** The field being edited (built-in or plugin-contributed). */\n field: AnyFormField;\n /** All fields in the form (for conditional logic references) */\n allFields: AnyFormField[];\n /** Callback when field is updated */\n onUpdate: (updates: Partial<AnyFormField>) => void;\n}\n\n// ============================================================================\n// Helper Components\n// ============================================================================\n\n/**\n * General properties tab - common field settings\n */\nfunction GeneralTab({\n field,\n allFields,\n onUpdate,\n}: {\n field: AnyFormField;\n allFields: AnyFormField[];\n onUpdate: (updates: Partial<AnyFormField>) => void;\n}) {\n // Check if other fields have conditional logic referencing this field\n const hasConditionalReferences = useMemo(() => {\n return allFields.some(f => {\n if (f.name === field.name || !f.conditionalLogic?.enabled) return false;\n return f.conditionalLogic.conditions.some(c => c.field === field.name);\n });\n }, [allFields, field.name]);\n\n return (\n <div className=\"space-y-6 py-4\">\n {/* Field Name */}\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Field Name (ID)\"\n htmlFor=\"field-name\"\n description=\"Used as the key in submission data. Should be unique and contain no spaces.\"\n />\n <Input\n id=\"field-name\"\n type=\"text\"\n value={field.name}\n onChange={e => onUpdate({ name: e.target.value })}\n pattern=\"^[a-zA-Z][a-zA-Z0-9_]*$\"\n className=\"bg-transparent\"\n />\n {hasConditionalReferences && (\n <p className=\"text-[11px] text-warning font-medium\">\n ⚠️ Changing this may break conditional logic referencing this field\n </p>\n )}\n </div>\n\n {/* Label - not shown for hidden fields */}\n {field.type !== \"hidden\" && (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Label\"\n htmlFor=\"field-label\"\n description=\"The text displayed above the field in the form.\"\n />\n <Input\n id=\"field-label\"\n type=\"text\"\n value={field.label}\n onChange={e => onUpdate({ label: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n )}\n\n {/* Placeholder - for text-like fields */}\n {(field.type === \"text\" ||\n field.type === \"email\" ||\n field.type === \"phone\" ||\n field.type === \"url\" ||\n field.type === \"number\" ||\n field.type === \"textarea\") && (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Placeholder\"\n htmlFor=\"field-placeholder\"\n description=\"The faint text shown inside the field when empty.\"\n />\n <Input\n id=\"field-placeholder\"\n type=\"text\"\n value={field.placeholder || \"\"}\n onChange={e => onUpdate({ placeholder: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n )}\n\n {/* Help Text - not shown for hidden fields */}\n {field.type !== \"hidden\" && (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Help Text\"\n htmlFor=\"field-help\"\n description=\"Additional instructions displayed below the field.\"\n />\n <Input\n id=\"field-help\"\n type=\"text\"\n value={field.helpText || \"\"}\n onChange={e => onUpdate({ helpText: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n )}\n\n {/* Required - not shown for hidden or checkbox fields */}\n {field.type !== \"hidden\" && field.type !== \"checkbox\" && (\n <div className=\"flex items-center gap-3 pt-1\">\n <Checkbox\n id=\"field-required\"\n checked={field.required || false}\n onCheckedChange={checked =>\n onUpdate({ required: checked === true })\n }\n />\n <FormLabelWithTooltip\n label=\"Required field\"\n htmlFor=\"field-required\"\n description=\"Forces the user to fill this field before submitting.\"\n />\n </div>\n )}\n\n {/* Width */}\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Width\"\n htmlFor=\"field-width\"\n description=\"How much space this field takes in a horizontal row.\"\n />\n <Select\n value={field.admin?.width || \"100%\"}\n onValueChange={value =>\n onUpdate({\n admin: {\n ...field.admin,\n width: value as \"25%\" | \"33%\" | \"50%\" | \"66%\" | \"75%\" | \"100%\",\n },\n })\n }\n >\n <SelectTrigger className=\"w-full bg-transparent border-input dark:bg-muted/50\">\n <SelectValue placeholder=\"Select width\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"100%\">Full Width (100%)</SelectItem>\n <SelectItem value=\"75%\">Three Quarters (75%)</SelectItem>\n <SelectItem value=\"66%\">Two Thirds (66%)</SelectItem>\n <SelectItem value=\"50%\">Half (50%)</SelectItem>\n <SelectItem value=\"33%\">One Third (33%)</SelectItem>\n <SelectItem value=\"25%\">One Quarter (25%)</SelectItem>\n </SelectContent>\n </Select>\n </div>\n\n {/* Type-specific options — built-ins only; a plugin field owns its own\n editor component and carries no built-in type-specific settings. */}\n {isKnownFormField(field) && (\n <TypeSpecificOptions field={field} onUpdate={onUpdate} />\n )}\n </div>\n );\n}\n\n/**\n * Type-specific field options\n */\nfunction TypeSpecificOptions({\n field,\n onUpdate,\n}: {\n field: FormField;\n onUpdate: (updates: Partial<FormField>) => void;\n}) {\n switch (field.type) {\n case \"select\":\n case \"radio\":\n return <OptionsEditor field={field} onUpdate={onUpdate} />;\n\n case \"textarea\": {\n const textareaField = field;\n return (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Rows\"\n htmlFor=\"field-rows\"\n description=\"Height of the textarea in text lines.\"\n />\n <Input\n id=\"field-rows\"\n type=\"number\"\n min={2}\n max={20}\n value={textareaField.rows || 4}\n onChange={e => onUpdate({ rows: parseInt(e.target.value) || 4 })}\n className=\"w-24 bg-transparent\"\n />\n </div>\n );\n }\n\n case \"file\": {\n const fileField = field;\n return (\n <>\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Accepted File Types\"\n htmlFor=\"field-accept\"\n description=\"Comma-separated MIME types (e.g., image/*, application/pdf).\"\n />\n <Input\n id=\"field-accept\"\n type=\"text\"\n value={fileField.accept || \"\"}\n onChange={e => onUpdate({ accept: e.target.value })}\n placeholder=\"image/*,application/pdf\"\n className=\"bg-transparent\"\n />\n </div>\n <div className=\"flex items-center gap-3 pt-1\">\n <Checkbox\n id=\"field-multiple\"\n checked={fileField.multiple || false}\n onCheckedChange={checked =>\n onUpdate({ multiple: checked === true })\n }\n />\n <FormLabelWithTooltip\n label=\"Allow multiple files\"\n htmlFor=\"field-multiple\"\n description=\"Users can upload more than one file at once.\"\n />\n </div>\n <div className=\"flex items-center gap-3 pt-1\">\n <Checkbox\n id=\"field-attach-to-email\"\n checked={fileField.attachToEmail || false}\n onCheckedChange={checked =>\n onUpdate({ attachToEmail: checked === true })\n }\n />\n <FormLabelWithTooltip\n label=\"Attach to notification emails\"\n htmlFor=\"field-attach-to-email\"\n description=\"Uploaded files will be attached to all notification emails for this form.\"\n />\n </div>\n </>\n );\n }\n\n case \"hidden\": {\n const hiddenField = field;\n return (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Default Value\"\n htmlFor=\"field-default\"\n description=\"This value will be submitted silently with the form.\"\n />\n <Input\n id=\"field-default\"\n type=\"text\"\n value={hiddenField.defaultValue || \"\"}\n onChange={e => onUpdate({ defaultValue: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n );\n }\n\n case \"date\": {\n const dateField = field;\n return (\n <div className=\"grid grid-cols-2 gap-4\">\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Minimum Date\"\n htmlFor=\"min-date\"\n description=\"Lower bound for date selection.\"\n />\n <Input\n id=\"min-date\"\n type=\"date\"\n value={dateField.min || \"\"}\n onChange={e => onUpdate({ min: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Maximum Date\"\n htmlFor=\"max-date\"\n description=\"Upper bound for date selection.\"\n />\n <Input\n id=\"max-date\"\n type=\"date\"\n value={dateField.max || \"\"}\n onChange={e => onUpdate({ max: e.target.value })}\n className=\"bg-transparent\"\n />\n </div>\n </div>\n );\n }\n\n case \"number\": {\n const numField = field;\n return (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Step\"\n htmlFor=\"field-step\"\n description=\"Increment/decrement step (e.g., 0.01 for decimals).\"\n />\n <Input\n id=\"field-step\"\n type=\"number\"\n value={numField.validation?.step || \"\"}\n onChange={e =>\n onUpdate({\n validation: {\n ...numField.validation,\n step: parseFloat(e.target.value) || undefined,\n },\n })\n }\n placeholder=\"1\"\n className=\"w-24 bg-transparent\"\n />\n </div>\n );\n }\n\n default:\n return null;\n }\n}\n\ntype KitOption = FieldOptionsEditorProps[\"options\"][number];\n\n/**\n * Options editor for select/radio fields, delegating to the SDK's\n * FieldOptionsEditor (drag reorder, auto-generated values, CSV/JSON import,\n * duplicate-value reporting). The kit works on id-carrying rows for\n * drag-and-drop; the form stores plain {label, value} pairs, so ids live in\n * local state and are stripped before every write.\n */\nfunction OptionsEditor({\n field,\n onUpdate,\n}: {\n field: FormField;\n onUpdate: (updates: Partial<FormField>) => void;\n}) {\n const optionsField = field as SelectFormField | RadioFormField;\n\n const [kitOptions, setKitOptions] = useState<KitOption[]>(() =>\n withOptionIds(optionsField.options || [])\n );\n\n const handleOptionsChange = useCallback(\n (next: KitOption[]) => {\n setKitOptions(next);\n onUpdate({\n options: next.map(({ label, value }) => ({ label, value })),\n });\n },\n [onUpdate]\n );\n\n return (\n <div className=\"space-y-3 pt-6\">\n <FormLabelWithTooltip\n label=\"Options\"\n description=\"List of choices available for this field.\"\n />\n <FieldOptionsEditor\n options={kitOptions}\n onOptionsChange={handleOptionsChange}\n />\n </div>\n );\n}\n\n/**\n * Validation tab - field validation settings.\n *\n * Which rules a type accepts is ASKED of core rather than decided here. This\n * previously branched on `field.type === \"text\" || field.type === \"textarea\"`\n * and similar, which is a list that cannot see a type it was not written to\n * know about — a plugin-contributed field type was offered nothing at all, and\n * `url` and `phone` were handled by yet another list further down.\n *\n * The controls themselves are the shared kit's, so this surface and the schema\n * builder no longer drift on labels, help text or which rules exist. What stays\n * here is what is genuinely this surface's: its own tab chrome, its file-size\n * control (stored on the field rather than in `validation`), and the mapping of\n * its own storage key — this builder stores the custom message as\n * `errorMessage` where core names the rule `message`, and a shared component\n * that silently reconciled the two would make every stored value wrong.\n */\nfunction ValidationTab({\n field,\n onUpdate,\n}: {\n field: FormField;\n onUpdate: (updates: Partial<FormField>) => void;\n}) {\n // Read across field types rather than narrowing to one: the whole point of\n // the shared editor is that this tab no longer branches on `field.type`.\n const validation: AnyFieldValidation = useMemo(\n () => field.validation ?? {},\n [field.validation]\n );\n // What the type MEANS, narrowed to what this runtime enforces FOR THAT TYPE.\n // Both halves are asked rather than restated, so neither can drift behind the\n // other. The narrowing has to be per type: a single set of enforced rules\n // only says a rule is honoured somewhere, which would offer `min`/`max` on a\n // date whose schema reads its bounds from `field.min`/`field.max` and never\n // consults `validation` — a control storing a bound nothing reads, which is\n // worse than no control because the author believes the rule is in force.\n const allowed = useMemo(() => {\n const enforced = enforcedValidationRules(field.type);\n return validationRulesForFieldType(field.type).filter(rule =>\n enforced.includes(rule)\n );\n }, [field.type]);\n\n const applyRules = useCallback(\n (next: Partial<ValidationRuleValues>) => {\n const { message, ...rules } = next;\n onUpdate({\n validation: {\n ...validation,\n ...rules,\n // This surface's own key for the rule core calls `message`.\n ...(message === undefined ? {} : { errorMessage: message }),\n },\n });\n },\n [validation, onUpdate]\n );\n\n return (\n <div className=\"space-y-6 pt-2\">\n <ValidationRulesEditor\n allowed={allowed}\n value={{\n minLength: validation.minLength,\n maxLength: validation.maxLength,\n // No row bounds here: this surface neither stores nor enforces them,\n // and `allowed` above never admits them, so passing them would be a\n // value the editor could not draw and the runtime could not read.\n min: validation.min,\n max: validation.max,\n pattern: validation.pattern,\n message: validation.errorMessage,\n }}\n onChange={applyRules}\n />\n\n {/* Not a validation rule: a file's size limit is stored on the field\n itself rather than in `validation`, so it stays with this surface. */}\n {field.type === \"file\" && (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip\n label=\"Max File Size (bytes)\"\n htmlFor=\"max-file-size\"\n description=\"Maximum allowed size in bytes (10MB ≈ 10485760).\"\n />\n <Input\n id=\"max-file-size\"\n type=\"number\"\n min={0}\n value={field.maxFileSize ?? \"\"}\n onChange={e =>\n onUpdate({\n maxFileSize: e.target.value\n ? parseInt(e.target.value)\n : undefined,\n })\n }\n placeholder=\"10485760\"\n className=\"bg-transparent\"\n />\n </div>\n )}\n\n {/* Asked, not restated: naming the types with no options here would be a\n third copy of the reasoning this tab just removed. */}\n {!drawsAnyValidationRule(allowed) && field.type !== \"file\" && (\n // Semantic border token so this info-box boundary is visible at the 3:1 UI minimum.\n <div className=\"p-3 bg-muted rounded-none text-xs text-muted-foreground text-center border border-dashed border-border\">\n No additional validation options for this field type.\n </div>\n )}\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * FieldEditor - Field properties configuration panel\n *\n * Provides a tabbed interface for editing field properties:\n * - General: name, label, placeholder, help text, required, width, type-specific options\n * - Validation: error messages, min/max length, patterns, etc.\n * - Conditional: show/hide logic based on other field values\n *\n * @example\n * ```tsx\n * <FieldEditor\n * field={selectedField}\n * allFields={allFields}\n * onUpdate={handleUpdate}\n * onDelete={handleDelete}\n * onDuplicate={handleDuplicate}\n * />\n * ```\n */\nexport function FieldEditor({\n field,\n allFields,\n onUpdate,\n}: FieldEditorProps): JSX.Element {\n const [activeTab, setActiveTab] = useState<string>(\"general\");\n\n return (\n <div className=\"flex flex-col\">\n {/* Tabs */}\n <Tabs\n value={activeTab}\n onValueChange={setActiveTab}\n className=\"flex-1 flex flex-col\"\n >\n {/* Semantic border token so the tab-bar bottom edge is visible at the 3:1 UI minimum. */}\n <div className=\"border-b bg-muted border-border\">\n {/* Layout only. The underline, its colours and the square corner come\n from the shared primitive; `w-full` spreads the triggers evenly and\n the active tint is this panel's own surface treatment. */}\n <TabsList className=\"w-full justify-start gap-0\">\n <TabsTrigger\n value=\"general\"\n className=\"w-full data-[state=active]:bg-background/50\"\n >\n General\n </TabsTrigger>\n <TabsTrigger\n value=\"validation\"\n className=\"w-full data-[state=active]:bg-background/50\"\n >\n Validation\n </TabsTrigger>\n <TabsTrigger\n value=\"conditional\"\n className=\"w-full data-[state=active]:bg-background/50\"\n >\n Conditional\n </TabsTrigger>\n </TabsList>\n </div>\n\n {/* Content */}\n <div className=\"p-4\">\n <TabsContent value=\"general\" className=\"mt-0\">\n <GeneralTab\n field={field}\n allFields={allFields}\n onUpdate={onUpdate}\n />\n </TabsContent>\n\n <TabsContent value=\"validation\" className=\"mt-0\">\n {isKnownFormField(field) ? (\n <ValidationTab field={field} onUpdate={onUpdate} />\n ) : (\n <p className=\"py-4 text-sm text-muted-foreground\">\n This field type manages its own validation.\n </p>\n )}\n </TabsContent>\n\n <TabsContent value=\"conditional\" className=\"mt-0\">\n <ConditionalLogicEditor\n field={field}\n allFields={allFields}\n onUpdate={onUpdate}\n />\n </TabsContent>\n </div>\n </Tabs>\n </div>\n );\n}\n\nexport default FieldEditor;\n","\"use client\";\n\n/**\n * Field Cards\n *\n * The form builder's field list: one collapsible card per field, edited\n * inline. This is the whole field-editing surface — there is no separate\n * palette or properties sidebar.\n *\n * Reordering is triple-redundant on purpose: drag handles (pointer), the\n * same handle with Space + arrow keys (dnd-kit keyboard sensor), and\n * explicit Move up / Move down menu items. Deleting a field that another\n * field's condition or a notification references is blocked with the list\n * of referrers instead of silently breaking them.\n *\n * @module admin/components/builder/FieldCards\n */\n\nimport {\n closestCenter,\n DndContext,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport { usePluginFieldTypeEntries } from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@nextlyhq/ui\";\nimport * as Lucide from \"lucide-react\";\nimport type {\n FieldTypeCatalogEntry,\n FormFieldCatalogType,\n} from \"nextly/field-catalog\";\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport { type JSX, useCallback, useMemo, useRef, useState } from \"react\";\n\nimport type { AnyFormField, FormFieldTypeId } from \"../../../types\";\nimport { buildFieldReferenceMap } from \"../../../utils/field-references\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { AddFieldDialog } from \"./AddFieldDialog\";\nimport { FieldEditor } from \"./FieldEditor\";\n\n// ---------------------------------------------------------------------------\n// Catalog helpers\n// ---------------------------------------------------------------------------\n\nconst CATALOG_BY_TYPE = new Map<\n string,\n FieldTypeCatalogEntry<FormFieldCatalogType>\n>(FORM_FIELD_TYPE_CATALOG.map(entry => [entry.type, entry]));\n\n/** Resolve a catalog icon name against lucide; unknown names fall back. */\nfunction CatalogIcon({\n name,\n className,\n}: {\n name: string | undefined;\n className?: string;\n}) {\n const icons = Lucide as unknown as Record<string, React.ElementType>;\n const Icon = (name && icons[name]) || Lucide.Type;\n return <Icon className={className} aria-hidden=\"true\" />;\n}\n\n// ---------------------------------------------------------------------------\n// One field card\n// ---------------------------------------------------------------------------\n\ninterface FieldCardProps {\n field: AnyFormField;\n index: number;\n total: number;\n expanded: boolean;\n onToggle: () => void;\n allFields: AnyFormField[];\n deleteBlockers: string[];\n onUpdate: (updates: Partial<AnyFormField>) => void;\n onDuplicate: () => void;\n onDelete: () => void;\n onMove: (direction: -1 | 1) => void;\n}\n\nfunction FieldCard({\n field,\n index,\n total,\n expanded,\n onToggle,\n allFields,\n deleteBlockers,\n onUpdate,\n onDuplicate,\n onDelete,\n onMove,\n}: FieldCardProps) {\n const {\n attributes,\n listeners,\n setNodeRef,\n setActivatorNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: field.name });\n\n const entry = CATALOG_BY_TYPE.get(field.type);\n const headerId = `field-card-header-${field.name}`;\n const bodyId = `field-card-body-${field.name}`;\n const deleteBlocked = deleteBlockers.length > 0;\n\n return (\n <div\n ref={setNodeRef}\n style={{ transform: CSS.Transform.toString(transform), transition }}\n className={\n // Dragging uses full-strength border-primary so the lifted card stands out; the resting state uses border-border, keeping the two distinct.\n isDragging\n ? \"border border-primary bg-card opacity-80 relative z-10\"\n : \"border border-border bg-card\"\n }\n >\n {/* Header row: every control is a real, separate interactive element —\n no nested-button markup. */}\n <div className=\"flex items-center gap-2 px-3 py-2.5\">\n <button\n type=\"button\"\n ref={setActivatorNodeRef}\n {...attributes}\n {...listeners}\n className=\"flex h-8 w-8 shrink-0 items-center justify-center rounded-none text-muted-foreground hover:text-foreground hover:bg-primary/5 cursor-grab active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n aria-label={`Reorder ${field.label || field.name}. Press Space to lift, arrow keys to move, Space to drop.`}\n >\n <Lucide.GripVertical className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n aria-controls={bodyId}\n id={headerId}\n className=\"flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n >\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center bg-primary/5 text-primary\">\n <CatalogIcon name={entry?.icon} className=\"h-3.5 w-3.5\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-foreground\">\n {field.label || field.name}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {entry?.label ?? field.type}\n <span className=\"mx-1.5 text-muted-foreground\">·</span>\n <span className=\"font-mono\">{field.name}</span>\n </span>\n </span>\n {field.required && (\n <span className=\"shrink-0 border border-border bg-primary/5 px-1.5 py-0.5 text-[10px] font-semibold uppercase tracking-wider text-primary\">\n Required\n </span>\n )}\n <Lucide.ChevronDown\n className={`h-4 w-4 shrink-0 text-muted-foreground transition-transform ${expanded ? \"rotate-180\" : \"\"}`}\n aria-hidden=\"true\"\n />\n </button>\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"h-8 w-8 shrink-0 rounded-none text-muted-foreground hover:text-foreground\"\n aria-label={`Actions for ${field.label || field.name}`}\n >\n <Lucide.MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"w-56 shadow-none border-border\"\n >\n <DropdownMenuItem\n disabled={index === 0}\n onClick={() => onMove(-1)}\n className=\"gap-2 cursor-pointer\"\n >\n <Lucide.ArrowUp className=\"h-4 w-4 text-muted-foreground\" />\n Move up\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={index === total - 1}\n onClick={() => onMove(1)}\n className=\"gap-2 cursor-pointer\"\n >\n <Lucide.ArrowDown className=\"h-4 w-4 text-muted-foreground\" />\n Move down\n </DropdownMenuItem>\n <DropdownMenuItem\n onClick={onDuplicate}\n className=\"gap-2 cursor-pointer\"\n >\n <Lucide.Copy className=\"h-4 w-4 text-muted-foreground\" />\n Duplicate\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n {deleteBlocked ? (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <div>\n <DropdownMenuItem\n disabled\n className=\"gap-2 text-muted-foreground\"\n >\n <Lucide.Trash2 className=\"h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </div>\n </TooltipTrigger>\n <TooltipContent side=\"left\" className=\"max-w-64\">\n Used by {deleteBlockers.join(\", \")}. Remove those references\n first.\n </TooltipContent>\n </Tooltip>\n ) : (\n <DropdownMenuItem\n onClick={onDelete}\n className=\"gap-2 cursor-pointer text-destructive focus:text-destructive\"\n >\n <Lucide.Trash2 className=\"h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n\n {/* Inline properties: the card IS the properties panel. */}\n {expanded && (\n <div\n id={bodyId}\n role=\"region\"\n aria-labelledby={headerId}\n className=\"border-t border-border\"\n >\n <FieldEditor\n field={field}\n allFields={allFields}\n onUpdate={onUpdate}\n />\n </div>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// The list\n// ---------------------------------------------------------------------------\n\nexport interface FieldCardsProps {\n /**\n * Form-surface types the host has NOT excluded (the plugin's field\n * enable/disable option). `null` while the host configuration is still\n * loading — the Add dialog waits rather than flashing the unfiltered set.\n * Types outside this set stay renderable on existing fields but cannot be\n * added.\n */\n enabledTypes: readonly FormFieldCatalogType[] | null;\n /**\n * Type ids the host disabled (`config.fields[type] === false`). Applied to\n * plugin field types too, so the host exclude layer is not bypassed for\n * contributed types.\n */\n disabledFieldTypes?: ReadonlySet<string>;\n /** Creates a field of the given type and returns it (context helper). */\n onAddField: (type: FormFieldTypeId) => void;\n}\n\nexport function FieldCards({\n enabledTypes,\n disabledFieldTypes,\n onAddField,\n}: FieldCardsProps): JSX.Element {\n const {\n fields,\n notifications,\n selectedFieldId,\n selectField,\n updateField,\n deleteField,\n duplicateField,\n moveField,\n } = useFormBuilder();\n\n const [isAddOpen, setIsAddOpen] = useState(false);\n\n // A card's React key cannot be the field's name: the name is the one\n // property the Field Name input rewrites on every keystroke, and a list\n // keyed by it unmounts the renamed card mid-word, destroying the input and\n // the focus in it. Keys are minted per card here and follow the field\n // through a rename (handleUpdate below), so editing a name never remounts\n // the card.\n const cardKeysRef = useRef(new Map<string, string>());\n const nextCardKeyRef = useRef(0);\n\n const cardKeyFor = (name: string): string => {\n let key = cardKeysRef.current.get(name);\n if (key === undefined) {\n key = `card-${++nextCardKeyRef.current}`;\n cardKeysRef.current.set(name, key);\n }\n return key;\n };\n\n const handleUpdate = useCallback(\n (field: AnyFormField, updates: Partial<AnyFormField>) => {\n // Move the card's key to the field's incoming name before the state\n // update re-renders the list, so the renamed field keeps its mounted\n // card.\n if (updates.name !== undefined && updates.name !== field.name) {\n const key = cardKeysRef.current.get(field.name);\n if (key !== undefined) {\n cardKeysRef.current.delete(field.name);\n cardKeysRef.current.set(updates.name, key);\n }\n }\n updateField(field.name, updates);\n },\n [updateField]\n );\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })\n );\n\n // Plugin field types that opted into the forms surface, offered alongside the\n // enabled built-ins. A plugin reusing a built-in id never shadows it.\n const pluginEntries = usePluginFieldTypeEntries(\"forms\");\n\n const entries = useMemo(() => {\n if (enabledTypes === null) return null;\n const allowed = new Set<string>(enabledTypes);\n const builtinTypes = new Set<string>(\n FORM_FIELD_TYPE_CATALOG.map(entry => entry.type)\n );\n const builtins: FieldTypeCatalogEntry<FormFieldTypeId>[] =\n FORM_FIELD_TYPE_CATALOG.filter(entry => allowed.has(entry.type));\n return [\n ...builtins,\n // A plugin type a built-in shadows, or one the host disabled, is excluded.\n ...pluginEntries.filter(\n entry =>\n !builtinTypes.has(entry.type) && !disabledFieldTypes?.has(entry.type)\n ),\n ];\n }, [enabledTypes, pluginEntries, disabledFieldTypes]);\n\n // One O(N^2) pass per fields/notifications change; each card then looks\n // its blockers up in O(1) instead of re-walking everything per render.\n const referenceMap = useMemo(\n () => buildFieldReferenceMap(fields, notifications),\n [fields, notifications]\n );\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = fields.findIndex(f => f.name === active.id);\n const to = fields.findIndex(f => f.name === over.id);\n if (from !== -1 && to !== -1) moveField(from, to);\n },\n [fields, moveField]\n );\n\n return (\n <div className=\"space-y-3\">\n {fields.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 border border-dashed border-border bg-card px-6 py-14 text-center\">\n <span className=\"flex h-10 w-10 items-center justify-center bg-primary/10 text-primary\">\n <Lucide.ListPlus className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-medium text-foreground\">No fields yet</p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Add the first field to start building this form.\n </p>\n </div>\n <Button type=\"button\" size=\"sm\" onClick={() => setIsAddOpen(true)}>\n Add field\n </Button>\n </div>\n ) : (\n <>\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext\n items={fields.map(f => f.name)}\n strategy={verticalListSortingStrategy}\n >\n <ul className=\"space-y-2\" aria-label=\"Form fields\">\n {fields.map((field, index) => (\n <li key={cardKeyFor(field.name)}>\n <FieldCard\n field={field}\n index={index}\n total={fields.length}\n expanded={selectedFieldId === field.name}\n onToggle={() =>\n selectField(\n selectedFieldId === field.name ? null : field.name\n )\n }\n allFields={fields}\n deleteBlockers={(referenceMap.get(field.name) ?? []).map(\n ref => ref.label\n )}\n onUpdate={updates => handleUpdate(field, updates)}\n onDuplicate={() => duplicateField(field.name)}\n onDelete={() => deleteField(field.name)}\n onMove={direction => moveField(index, index + direction)}\n />\n </li>\n ))}\n </ul>\n </SortableContext>\n </DndContext>\n\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => setIsAddOpen(true)}\n className=\"w-full border-dashed\"\n >\n <Lucide.Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n Add field\n </Button>\n </>\n )}\n\n <AddFieldDialog\n open={isAddOpen}\n onOpenChange={setIsAddOpen}\n entries={entries}\n onAdd={onAddField}\n />\n </div>\n );\n}\n","import type { FormNotification } from \"../../../types\";\nimport { parseFieldRef } from \"../../../utils/field-references\";\n\n/**\n * Deliberately loose email shape check (something@something.tld): the goal is\n * catching typos before they fail at delivery, not RFC 5322 conformance.\n */\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nexport function isValidEmail(value: string): boolean {\n return EMAIL_PATTERN.test(value.trim());\n}\n\n// Re-exported rather than re-implemented. Whether a value is a field reference\n// is the SEND path's question — a value the editor treats as one and delivery\n// does not goes out with its braces intact — so both read the same parser.\nexport { parseFieldRef };\n\n/** The address fields a notification can get wrong. */\nexport type AddressField = \"senderEmail\" | \"to\" | \"replyTo\";\n\nexport type AddressErrors = Partial<Record<AddressField, string>>;\n\nconst MESSAGE = \"Enter a valid email address.\";\n\n/**\n * Whether ONE address is unusable, by the single rule the whole feature uses.\n *\n * A blank address is not an error — every one of these is optional or has an\n * inherited default — and a `{{field}}` reference is not an address at all: it\n * resolves against each submission at send time, so it cannot be checked now.\n */\nexport function addressError(\n field: AddressField,\n value: string | undefined\n): string | undefined {\n if (field === \"replyTo\" && parseFieldRef(value) !== null) return undefined;\n if (!value?.trim()) return undefined;\n return isValidEmail(value) ? undefined : MESSAGE;\n}\n\n/**\n * Every unusable address in a notification.\n *\n * The editor asks this about one field as it is left; the page asks it about\n * every rule before it saves. The same function answers both, because the\n * alternative is a field that rejects an address and a save that accepts it —\n * which is what happened while the editor held the only copy of the answer.\n *\n * `to` is checked only when the recipient is a literal address: a `field`\n * recipient names a form field, which is not an address to validate.\n */\nexport function addressErrorsIn(notification: FormNotification): AddressErrors {\n const errors: AddressErrors = {};\n\n const sender = addressError(\"senderEmail\", notification.senderEmail);\n if (sender) errors.senderEmail = sender;\n\n if (notification.recipientType === \"static\") {\n const to = addressError(\"to\", notification.to);\n if (to) errors.to = to;\n }\n\n const replyTo = addressError(\"replyTo\", notification.replyTo);\n if (replyTo) errors.replyTo = replyTo;\n\n return errors;\n}\n\n/** The rules a save must refuse, named so the message can say which. */\nexport function notificationsWithBadAddresses(\n notifications: readonly FormNotification[]\n): string[] {\n return notifications\n .filter(n => Object.keys(addressErrorsIn(n)).length > 0)\n .map(n => n.name.trim() || \"Untitled notification\");\n}\n\n/**\n * What to tell an author who pressed Save with a malformed address, or `null`\n * when there is nothing to tell them.\n *\n * The wording lives beside the rule that produces it so the caller has one\n * question to ask and one branch to write — a save path that composed this\n * itself would be a second place deciding what counts as wrong.\n */\nexport function badAddressMessage(\n notifications: readonly FormNotification[]\n): string | null {\n const names = notificationsWithBadAddresses(notifications);\n if (names.length === 0) return null;\n return names.length === 1\n ? `${names[0]} has an invalid email address`\n : `${names.length} notifications have invalid email addresses`;\n}\n\n/**\n * The one reason a save must refuse the notifications, or `null`.\n *\n * A single question so the save has a single branch. Naming comes first: an\n * address complaint that says \"Untitled notification\" twice cannot be acted on,\n * whereas a named rule can then be pointed at.\n */\nexport function notificationsBlockingSave(\n notifications: readonly FormNotification[]\n): string | null {\n const unnamed = unnamedNotificationCount(notifications);\n if (unnamed > 0) {\n return unnamed === 1\n ? \"A notification needs a name\"\n : `${unnamed} notifications need a name`;\n }\n return badAddressMessage(notifications);\n}\n\n/**\n * Rules a save must refuse for having no name.\n *\n * The sheet this editor replaced disabled its own commit while the name was\n * blank. There is no commit here any more — the page's action bar is the only\n * one — so the precondition moves to it rather than disappearing with the\n * button that used to carry it. A nameless rule leaves its summary and its\n * menu labelled with nothing.\n */\nexport function unnamedNotificationCount(\n notifications: readonly FormNotification[]\n): number {\n return notifications.filter(n => !n.name.trim()).length;\n}\n","\"use client\";\n\n/**\n * Form Notifications Tab\n *\n * Per-form email notification rules: a list of cards, each editing in a\n * side sheet. A rule targets a static address or the visitor (via one of\n * the form's email fields), can set Reply-To from a field so replies reach\n * the visitor, and can carry a single send-condition evaluated against the\n * submitted data. An autoresponder is just a rule whose recipient is the\n * visitor — there is no separate feature.\n *\n * @module admin/components/builder/FormNotificationsTab\n */\n\nimport {\n Badge,\n Card,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n FieldShell,\n Grid,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n} from \"@nextlyhq/ui\";\nimport {\n Copy,\n ExternalLink,\n Filter,\n Mail,\n MoreHorizontal,\n Pencil,\n Plus,\n Trash2,\n TriangleAlert,\n X,\n} from \"lucide-react\";\nimport { type JSX, useCallback, useEffect, useMemo, useState } from \"react\";\n\nimport type { ConditionalLogicCondition } from \"../../../types\";\nimport {\n useFormBuilder,\n createNotification,\n type FormNotification,\n} from \"../../context/FormBuilderContext\";\n\nimport {\n addressError,\n addressErrorsIn,\n isValidEmail,\n parseFieldRef,\n type AddressErrors,\n type AddressField,\n} from \"./notification-addresses\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/** Host-level notification defaults surfaced by the builder-config route. */\nexport interface NotificationDefaults {\n defaultFrom?: string;\n defaultToEmail?: string;\n}\n\ninterface FormNotificationsTabProps {\n /** `null` while the builder-config request is still settling. */\n defaults: NotificationDefaults | null;\n}\n\ninterface ProviderOption {\n id: string;\n name: string;\n type: string;\n isDefault: boolean;\n}\n\n/**\n * Where an email opens in the admin's Email hub. Written out rather than\n * imported because a plugin builds against the SDK, not the admin's routes.\n */\nfunction emailHubEditHref(templateId: string): string {\n return `/admin/email/messages/edit/${encodeURIComponent(templateId)}`;\n}\n\ninterface TemplateOption {\n id: string;\n name: string;\n slug: string;\n isActive: boolean;\n /** \"template\" or \"layout\" — layouts wrap templates and cannot be sent. */\n kind?: string;\n}\n\ninterface FieldOption {\n name: string;\n label: string;\n type: string;\n}\n\n// ============================================================================\n// API helpers\n// ============================================================================\n\nasync function fetchProviders(): Promise<ProviderOption[]> {\n try {\n const res = await fetch(\"/admin/api/email-providers?pageSize=100&page=1\", {\n credentials: \"include\",\n });\n if (!res.ok) return [];\n const json = (await res.json()) as { providers?: ProviderOption[] };\n return json.providers ?? [];\n } catch {\n return [];\n }\n}\n\n/**\n * Whether the signed-in person may open an email in the Email hub's editor.\n * The editor's route needs the grant that saves a template, so a link shown\n * without it would open a page that refuses them.\n */\nasync function fetchCanEditTemplates(): Promise<boolean> {\n try {\n const res = await fetch(\"/admin/api/me/permissions\", {\n credentials: \"include\",\n });\n if (!res.ok) return false;\n const json = (await res.json()) as {\n permissions?: string[];\n isSuperAdmin?: boolean;\n };\n const held = json.permissions ?? [];\n // The permission itself: the retired `manage-email-templates` opens\n // nothing on the server, so it shows no link here.\n return (\n Boolean(json.isSuperAdmin) || held.includes(\"update-email-templates\")\n );\n } catch {\n return false;\n }\n}\n\nasync function fetchTemplates(): Promise<TemplateOption[]> {\n try {\n const res = await fetch(\"/admin/api/email-templates\", {\n credentials: \"include\",\n });\n if (!res.ok) return [];\n const json = (await res.json()) as { templates?: TemplateOption[] };\n return json.templates ?? [];\n } catch {\n return [];\n }\n}\n\n// ============================================================================\n// Field-reference helpers\n// ============================================================================\n\nfunction toFieldRef(fieldName: string): string {\n return `{{${fieldName}}}`;\n}\n\n/**\n * Options for address dropdowns: the form's email fields, plus the\n * currently-referenced field even when it is not an email type, so a legacy\n * value keeps displaying instead of silently vanishing from the select.\n */\nfunction buildEmailFieldOptions(\n fields: readonly FieldOption[],\n currentRef: string | null\n): FieldOption[] {\n const emailFields = fields.filter(field => field.type === \"email\");\n if (currentRef && !emailFields.some(field => field.name === currentRef)) {\n const current = fields.find(field => field.name === currentRef);\n if (current) return [...emailFields, current];\n }\n return emailFields;\n}\n\n// ============================================================================\n// Condition labels\n// ============================================================================\n\nconst COMPARISON_LABELS: Record<\n ConditionalLogicCondition[\"comparison\"],\n string\n> = {\n equals: \"Equals\",\n notEquals: \"Does not equal\",\n contains: \"Contains\",\n isEmpty: \"Is empty\",\n isNotEmpty: \"Is not empty\",\n greaterThan: \"Greater than\",\n lessThan: \"Less than\",\n};\n\nconst VALUELESS_COMPARISONS: ReadonlySet<\n ConditionalLogicCondition[\"comparison\"]\n> = new Set([\"isEmpty\", \"isNotEmpty\"]);\n\n// ============================================================================\n// Notification Card\n// ============================================================================\n\ninterface NotificationCardProps {\n notification: FormNotification;\n providerName: string;\n fields: readonly FieldOption[];\n /** Whether this row's editor is showing. */\n expanded: boolean;\n /** The editor region's id, so the summary can point at what it opens. */\n editorId: string;\n onToggle: () => void;\n onDuplicate: () => void;\n onDelete: () => void;\n onToggleEnabled: (enabled: boolean) => void;\n /** The editor, rendered under the summary while `expanded`. */\n children: React.ReactNode;\n}\n\nfunction describeRecipient(\n notification: FormNotification,\n fields: readonly FieldOption[]\n): { text: string; missing: boolean } {\n if (notification.recipientType === \"field\") {\n const ref = parseFieldRef(notification.to);\n if (!ref) return { text: \"No recipient field selected\", missing: true };\n const field = fields.find(f => f.name === ref);\n if (!field) {\n // The referenced field was deleted (allowed while the rule was\n // disabled) — say so instead of presenting the dead name as valid.\n return {\n text: `Recipient field \"${ref}\" no longer exists`,\n missing: true,\n };\n }\n return {\n text: `To the visitor (${field.label})`,\n missing: false,\n };\n }\n if (!notification.to.trim()) {\n return { text: \"No recipient address\", missing: true };\n }\n return { text: `To ${notification.to}`, missing: false };\n}\n\nfunction NotificationCard({\n notification,\n providerName,\n fields,\n expanded,\n editorId,\n onToggle,\n onDuplicate,\n onDelete,\n onToggleEnabled,\n children,\n}: NotificationCardProps) {\n const recipient = describeRecipient(notification, fields);\n const ccCount =\n (notification.cc?.length ?? 0) + (notification.bcc?.length ?? 0);\n\n // Deleting a referenced field is only blocked for enabled rules, so a\n // disabled rule can legitimately hold references to fields that no longer\n // exist — surface that on the card instead of sending broken email later.\n const fieldNames = new Set(fields.map(f => f.name));\n const replyToRef = parseFieldRef(notification.replyTo);\n const hasStaleReference =\n (replyToRef !== null && !fieldNames.has(replyToRef)) ||\n (notification.condition !== undefined &&\n !fieldNames.has(notification.condition.field));\n\n const warnings: string[] = [];\n if (!notification.templateSlug) {\n warnings.push(\"No template — will not send\");\n }\n if (hasStaleReference) {\n warnings.push(\"References a deleted field — edit to repair\");\n }\n\n return (\n // `Card` rather than its classes written out again: the primitive owns the\n // surface colour, the radius, the foreground and the transition, and a\n // second spelling of them drifts the moment the shared surface changes.\n //\n // `overflow-hidden` because an expanded editor paints a full-width tinted\n // child; without clipping it squares off the corners the card just rounded.\n <Card className=\"overflow-hidden p-0\">\n {/* The dimming is the SUMMARY's, not the card's. A disabled rule reads as\n inactive at a glance, but an expanded editor inside a faded card fades\n every label, input and validation message the author is reading while\n they fix it — and takes the already-muted help text below its intended\n contrast. */}\n <div\n className={`flex items-center gap-2 px-3 py-2.5 ${notification.enabled ? \"\" : \"opacity-60\"}`}\n >\n <span className=\"flex h-7 w-7 shrink-0 items-center justify-center bg-primary/5 text-primary\">\n <Mail className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n </span>\n\n <button\n type=\"button\"\n onClick={onToggle}\n aria-expanded={expanded}\n aria-controls={editorId}\n className=\"flex min-w-0 flex-1 items-center gap-3 rounded-none py-1 text-left cursor-pointer focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary\"\n // The NAME stays put while the state changes. `aria-expanded` above\n // already says open or closed, and a control whose name moves with\n // its state is one a screen-reader user cannot refer to twice — and\n // one no test can hold a handle on across a click.\n aria-label={`Edit notification ${notification.name}`}\n >\n <span className=\"min-w-0 flex-1\">\n <span className=\"flex items-center gap-2\">\n <span className=\"truncate text-sm font-medium text-foreground\">\n {notification.name}\n </span>\n {notification.condition && (\n <Badge\n variant=\"outline\"\n className=\"shrink-0 gap-1 rounded-none border-border px-1.5 py-0 text-[10px] font-semibold uppercase tracking-wider text-muted-foreground\"\n >\n <Filter className=\"h-2.5 w-2.5\" aria-hidden=\"true\" />\n Conditional\n </Badge>\n )}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n <span className={recipient.missing ? \"text-destructive\" : \"\"}>\n {recipient.text}\n </span>\n {ccCount > 0 && (\n <span>\n <span className=\"mx-1.5 text-muted-foreground\">·</span>+\n {ccCount} cc/bcc\n </span>\n )}\n <span className=\"mx-1.5 text-muted-foreground\">·</span>\n {providerName}\n </span>\n </span>\n {warnings.length > 0 && (\n <span className=\"flex shrink-0 flex-col items-end gap-0.5\">\n {warnings.map(warning => (\n <span\n key={warning}\n className=\"flex items-center gap-1 text-xs font-medium text-destructive\"\n >\n <TriangleAlert className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {warning}\n </span>\n ))}\n </span>\n )}\n </button>\n\n <Switch\n checked={notification.enabled}\n onCheckedChange={onToggleEnabled}\n aria-label={`${notification.enabled ? \"Disable\" : \"Enable\"} notification ${notification.name}`}\n />\n\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 shrink-0 rounded-none text-muted-foreground hover:text-foreground\"\n aria-label={`Notification actions for ${notification.name}`}\n >\n <MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent\n align=\"end\"\n className=\"w-56 shadow-none border-border\"\n >\n <DropdownMenuItem\n onClick={onToggle}\n className=\"gap-2 cursor-pointer\"\n >\n <Pencil className=\"h-4 w-4 text-muted-foreground\" />\n {expanded ? \"Collapse\" : \"Edit\"}\n </DropdownMenuItem>\n <DropdownMenuItem\n onClick={onDuplicate}\n className=\"gap-2 cursor-pointer\"\n >\n <Copy className=\"h-4 w-4 text-muted-foreground\" />\n Duplicate\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onClick={onDelete}\n className=\"gap-2 cursor-pointer text-destructive focus:text-destructive\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n\n {/* Unmounted while collapsed rather than hidden. The editor holds a\n draft of the rule in its own state, seeded from the row; keeping a\n closed one mounted would keep a stale draft alive behind a summary\n that has moved on. */}\n {expanded && children}\n </Card>\n );\n}\n\n// ============================================================================\n// Recipient chip list (cc / bcc)\n// ============================================================================\n\ninterface AddressChipListProps {\n id: string;\n label: string;\n addresses: string[];\n placeholder: string;\n onChange: (addresses: string[]) => void;\n}\n\nfunction AddressChipList({\n id,\n label,\n addresses,\n placeholder,\n onChange,\n}: AddressChipListProps) {\n const [draft, setDraft] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n\n const add = useCallback(() => {\n const email = draft.trim();\n if (!email) return;\n // Nothing here goes through form submission, so constraint validation\n // never runs — reject malformed addresses before they can be persisted\n // and fail at delivery.\n if (!isValidEmail(email)) {\n setError(\"Enter a valid email address.\");\n return;\n }\n onChange([...addresses, email]);\n setDraft(\"\");\n setError(null);\n }, [draft, addresses, onChange]);\n\n return (\n <div className=\"space-y-1.5\">\n <Label htmlFor={id}>{label}</Label>\n {addresses.length > 0 && (\n <div className=\"flex flex-wrap gap-2\">\n {addresses.map((email, index) => (\n <Badge\n key={`${email}-${index}`}\n variant=\"outline\"\n className=\"gap-1.5 rounded-none border-border bg-primary/5 px-2 py-0.5 text-xs font-medium\"\n >\n <span>{email}</span>\n <button\n type=\"button\"\n className=\"text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-primary\"\n onClick={() =>\n onChange(addresses.filter((_, i) => i !== index))\n }\n aria-label={`Remove ${email}`}\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n </Badge>\n ))}\n </div>\n )}\n <div className=\"flex items-center gap-2\">\n <Input\n id={id}\n type=\"email\"\n value={draft}\n onChange={e => {\n setDraft(e.target.value);\n setError(null);\n }}\n onKeyDown={e => {\n if (e.key === \"Enter\") {\n e.preventDefault();\n add();\n }\n }}\n placeholder={placeholder}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? `${id}-error` : undefined}\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={add}\n className=\"shrink-0 px-3\"\n >\n Add\n </Button>\n </div>\n {error && (\n <p id={`${id}-error`} className=\"text-xs text-destructive\">\n {error}\n </p>\n )}\n </div>\n );\n}\n\ninterface RecipientFieldsProps {\n form: FormNotification;\n fields: readonly FieldOption[];\n defaults: NotificationDefaults | null;\n update: <K extends keyof FormNotification>(\n key: K,\n value: FormNotification[K]\n ) => void;\n /**\n * Switching target kinds invalidates the previous `to` value shape, so both\n * keys move together. It goes back to the editor rather than being done here\n * because the editor owns the write-through to the form.\n */\n onRecipientTypeChange: (kind: \"static\" | \"field\") => void;\n addressErrors: AddressErrors;\n validateAddress: (key: AddressField, value: string | undefined) => void;\n replyToMode: ReplyToMode;\n setReplyToMode: (mode: ReplyToMode) => void;\n}\n\n/**\n * Who the notification goes to: the recipient itself, the reply-to, and the\n * optional cc/bcc lists.\n *\n * Extracted from the editor because it is the one part of a notification that\n * is entirely about addresses — the editor around it is about identity,\n * sending and conditions — and because leaving it inline made a single\n * component long enough that no reader could hold it at once.\n */\nfunction RecipientFields({\n form,\n fields,\n defaults,\n update,\n onRecipientTypeChange,\n addressErrors,\n validateAddress,\n replyToMode,\n setReplyToMode,\n}: RecipientFieldsProps) {\n // Derived here rather than passed in: each of these is a function of `form`\n // and `fields`, which this component already has, and computing them in the\n // caller would be the same answer worked out somewhere else.\n const toRef = parseFieldRef(form.to);\n const toFieldOptions = buildEmailFieldOptions(fields, toRef);\n const replyToRef = parseFieldRef(form.replyTo);\n const replyToFieldOptions = buildEmailFieldOptions(fields, replyToRef);\n\n return (\n <>\n <div className=\"space-y-4 pt-4 border-t border-border\">\n <Grid cols={2} responsive>\n <FieldShell label=\"Send to\" htmlFor=\"notification-recipient-type\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={form.recipientType}\n onValueChange={value => {\n // Switching target kinds invalidates the previous `to`\n // value shape, so it resets rather than leaking a\n // {{ref}} into the static input (or vice versa).\n onRecipientTypeChange(value as \"static\" | \"field\");\n }}\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"static\">A specific address</SelectItem>\n <SelectItem value=\"field\">\n The visitor (email field)\n </SelectItem>\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n\n {form.recipientType === \"field\" ? (\n <div className=\"space-y-1.5\">\n <FieldShell label=\"Visitor email field\" htmlFor=\"notification-to\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={toRef ?? \"__none\"}\n onValueChange={value =>\n update(\"to\", value === \"__none\" ? \"\" : toFieldRef(value))\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__none\">Select a field</SelectItem>\n {toFieldOptions.map(f => (\n <SelectItem key={f.name} value={f.name}>\n {f.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n {toFieldOptions.length === 0 && (\n <p className=\"text-xs text-muted-foreground\">\n Add an email field to the form first.\n </p>\n )}\n </div>\n ) : (\n <FieldShell\n label=\"Recipient address\"\n htmlFor=\"notification-to\"\n width=\"half\"\n error={addressErrors.to}\n >\n <Input\n type=\"email\"\n value={form.to}\n onChange={e => update(\"to\", e.target.value)}\n onBlur={e => validateAddress(\"to\", e.target.value)}\n placeholder={defaults?.defaultToEmail || \"admin@example.com\"}\n />\n </FieldShell>\n )}\n </Grid>\n\n {/* Reply-To */}\n <Grid cols={2} responsive>\n <FieldShell label=\"Reply-To\" htmlFor=\"notification-replyto-mode\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={replyToMode}\n onValueChange={value => {\n const mode = value as ReplyToMode;\n setReplyToMode(mode);\n // A mode change always clears the stored value (the old\n // shape can't be represented in the new mode); it stays\n // absent — not an empty string — until the user picks a\n // field or types an address.\n update(\"replyTo\", undefined);\n }}\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"none\">None</SelectItem>\n <SelectItem value=\"field\">\n The visitor (email field)\n </SelectItem>\n <SelectItem value=\"custom\">A custom address</SelectItem>\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n\n {replyToMode === \"field\" && (\n <FieldShell\n label=\"Visitor email field\"\n htmlFor=\"notification-replyto\"\n >\n {({ id, describedBy, invalid }) => (\n <Select\n value={replyToRef ?? \"__none\"}\n onValueChange={value =>\n update(\n \"replyTo\",\n value === \"__none\" ? undefined : toFieldRef(value)\n )\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__none\">Select a field</SelectItem>\n {replyToFieldOptions.map(f => (\n <SelectItem key={f.name} value={f.name}>\n {f.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n )}\n {replyToMode === \"custom\" && (\n <FieldShell\n label=\"Reply-To address\"\n htmlFor=\"notification-replyto\"\n width=\"half\"\n error={addressErrors.replyTo}\n >\n <Input\n type=\"email\"\n value={form.replyTo ?? \"\"}\n onBlur={e => validateAddress(\"replyTo\", e.target.value)}\n onChange={e => update(\"replyTo\", e.target.value || undefined)}\n placeholder=\"replies@example.com\"\n />\n </FieldShell>\n )}\n </Grid>\n {replyToMode === \"field\" && (\n <p className=\"text-xs text-muted-foreground -mt-2\">\n Replying to this email answers the person who submitted the form.\n </p>\n )}\n\n <AddressChipList\n id=\"notification-cc\"\n label=\"CC (optional)\"\n addresses={form.cc}\n placeholder=\"cc@example.com\"\n onChange={cc => update(\"cc\", cc)}\n />\n <AddressChipList\n id=\"notification-bcc\"\n label=\"BCC (optional)\"\n addresses={form.bcc}\n placeholder=\"bcc@example.com\"\n onChange={bcc => update(\"bcc\", bcc)}\n />\n </div>\n </>\n );\n}\n\ninterface SendConditionFieldProps {\n condition: ConditionalLogicCondition | undefined;\n fields: readonly FieldOption[];\n update: <K extends keyof FormNotification>(\n key: K,\n value: FormNotification[K]\n ) => void;\n}\n\n/**\n * The optional rule that decides whether a notification sends at all.\n *\n * Its own component because it is the one part of the editor with a shape of\n * its own — absent, or a field/comparison/value triple — and inline it made\n * the editor read as two unrelated forms sharing a scope.\n */\nfunction SendConditionField({\n condition,\n fields,\n update,\n}: SendConditionFieldProps) {\n return (\n <>\n <div className=\"space-y-3 pt-4 border-t border-border\">\n <div className=\"flex items-center justify-between\">\n <div>\n <p className=\"text-sm font-medium text-foreground\">\n Send condition\n </p>\n <p className=\"text-xs text-muted-foreground\">\n Only send this notification when a submitted value matches.\n </p>\n </div>\n {!condition && (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() =>\n update(\"condition\", {\n field: fields[0]?.name ?? \"\",\n comparison: \"equals\",\n value: \"\",\n })\n }\n disabled={fields.length === 0}\n >\n <Plus className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Add condition\n </Button>\n )}\n </div>\n\n {condition && (\n <div className=\"flex flex-wrap items-end gap-2 border border-border bg-muted/40 p-3\">\n <FieldShell\n label=\"Field\"\n htmlFor=\"notification-condition-field\"\n className=\"min-w-36 flex-1\"\n >\n {({ id, describedBy, invalid }) => (\n <Select\n value={condition.field || \"__none\"}\n onValueChange={value =>\n update(\"condition\", {\n ...condition,\n field: value === \"__none\" ? \"\" : value,\n })\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__none\">Select a field</SelectItem>\n {fields.map(f => (\n <SelectItem key={f.name} value={f.name}>\n {f.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n\n <FieldShell\n label=\"Comparison\"\n htmlFor=\"notification-condition-comparison\"\n className=\"min-w-36 flex-1\"\n >\n {({ id, describedBy, invalid }) => (\n <Select\n value={condition.comparison}\n onValueChange={value =>\n update(\"condition\", {\n ...condition,\n comparison:\n value as ConditionalLogicCondition[\"comparison\"],\n })\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(COMPARISON_LABELS).map(([value, label]) => (\n <SelectItem key={value} value={value}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n\n {!VALUELESS_COMPARISONS.has(condition.comparison) && (\n <FieldShell\n label=\"Value\"\n htmlFor=\"notification-condition-value\"\n className=\"min-w-36 flex-1\"\n >\n <Input\n type=\"text\"\n value={\n typeof condition.value === \"string\" ||\n typeof condition.value === \"number\"\n ? String(condition.value)\n : \"\"\n }\n onChange={e =>\n update(\"condition\", {\n ...condition,\n value: e.target.value,\n })\n }\n />\n </FieldShell>\n )}\n\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-9 w-9 rounded-none text-muted-foreground hover:text-destructive\"\n onClick={() => update(\"condition\", undefined)}\n aria-label=\"Remove send condition\"\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n )}\n </div>\n </>\n );\n}\n\ninterface NotificationIdentityFieldsProps {\n form: FormNotification;\n providers: ProviderOption[];\n defaults: NotificationDefaults | null;\n templates: TemplateOption[];\n /** May open a template in the Email hub's editor. */\n canEditTemplates: boolean;\n update: <K extends keyof FormNotification>(\n key: K,\n value: FormNotification[K]\n ) => void;\n addressErrors: AddressErrors;\n validateAddress: (key: AddressField, value: string | undefined) => void;\n}\n\n/**\n * What the notification IS: its name, which provider and template carry it,\n * and who it comes from.\n *\n * Separated from the recipients and the condition because those answer\n * different questions — where it goes, and whether it goes at all — and one\n * component answering all three was long enough that no reader could hold it.\n */\nfunction NotificationIdentityFields({\n form,\n providers,\n defaults,\n templates,\n canEditTemplates,\n update,\n addressErrors,\n validateAddress,\n}: NotificationIdentityFieldsProps) {\n // Derived here, from props this component already holds. Passing them in\n // would be the same answer worked out in the caller.\n const defaultProvider = providers.find(p => p.isDefault);\n const selectedTemplate =\n canEditTemplates && form.templateSlug\n ? templates.find(t => t.slug === form.templateSlug)\n : undefined;\n const defaultProviderLabel = defaultProvider\n ? `System default (${defaultProvider.name})`\n : \"System default\";\n const senderPlaceholder = defaults?.defaultFrom || \"Provider default\";\n const senderHelp = defaults?.defaultFrom\n ? `Leave blank to send from ${defaults.defaultFrom} (the configured default).`\n : \"Leave blank to use the template or provider default address.\";\n\n return (\n <>\n {/* Name */}\n <FieldShell label=\"Name\" htmlFor=\"notification-name\" width=\"half\">\n <Input\n type=\"text\"\n value={form.name}\n onChange={e => update(\"name\", e.target.value)}\n placeholder=\"e.g. Admin notification\"\n />\n </FieldShell>\n\n {/* Provider & Template. A container-query grid rather than the\n viewport's `sm:` breakpoint: the admin content region is\n narrower than the window whenever both sidebars are open, so a\n viewport breakpoint promises columns this sheet does not have. */}\n <Grid cols={2} responsive>\n {/* Both `Select`-driven: FieldShell's render-function `children`\n applies the computed id/aria-describedby/aria-invalid to\n SelectTrigger, the actual focusable element, rather than to\n `Select`'s root (which accepts a fixed prop list and forwards\n none of the rest). */}\n <FieldShell label=\"Email provider\" htmlFor=\"notification-provider\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={form.providerId ?? \"__default\"}\n onValueChange={value =>\n update(\"providerId\", value === \"__default\" ? undefined : value)\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__default\">\n {defaultProviderLabel}\n </SelectItem>\n {providers.map(p => (\n <SelectItem key={p.id} value={p.id}>\n {p.name}\n {p.isDefault ? \" (Default)\" : \"\"}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n\n <div className=\"space-y-1.5\">\n <FieldShell label=\"Email template\" htmlFor=\"notification-template\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={form.templateSlug ?? \"__none\"}\n onValueChange={value =>\n update(\"templateSlug\", value === \"__none\" ? undefined : value)\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__none\">Select a template</SelectItem>\n {templates\n .filter(t => t.isActive && t.kind !== \"layout\")\n .map(t => (\n <SelectItem key={t.id} value={t.slug}>\n {t.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n {!form.templateSlug && (\n <p className=\"flex items-center gap-1 text-xs text-destructive\">\n <TriangleAlert className=\"h-3 w-3\" aria-hidden=\"true\" />\n Required — a notification without a template is never sent.\n </p>\n )}\n {selectedTemplate && (\n // A new tab, so unsaved changes to this form aren't lost by\n // leaving the builder to edit the email.\n <a\n href={emailHubEditHref(selectedTemplate.id)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n className=\"inline-flex items-center gap-1 text-xs text-primary hover:underline\"\n >\n Open {selectedTemplate.name} in Email\n <ExternalLink className=\"h-3 w-3\" aria-hidden=\"true\" />\n </a>\n )}\n </div>\n </Grid>\n\n {/* Sender */}\n <FieldShell\n label=\"Sender email\"\n htmlFor=\"notification-sender\"\n width=\"half\"\n description={addressErrors.senderEmail ? undefined : senderHelp}\n error={addressErrors.senderEmail}\n >\n <Input\n type=\"email\"\n value={form.senderEmail ?? \"\"}\n onChange={e => update(\"senderEmail\", e.target.value || undefined)}\n onBlur={e => validateAddress(\"senderEmail\", e.target.value)}\n placeholder={senderPlaceholder}\n />\n </FieldShell>\n </>\n );\n}\n\n// ============================================================================\n// Notification editor\n// ============================================================================\n\ntype ReplyToMode = \"none\" | \"field\" | \"custom\";\n\ninterface NotificationEditorProps {\n /**\n * The rule as the form holds it. Read on every render rather than copied\n * into local state: the summary row edits the same rule (its Enabled switch\n * writes straight to the form), and a second copy here would go stale the\n * moment it did — the next edit would then send the whole stale rule back\n * and undo it.\n */\n notification: FormNotification;\n /** Ties the row's toggle to this region for assistive tech. */\n editorId: string;\n providers: ProviderOption[];\n templates: TemplateOption[];\n canEditTemplates: boolean;\n fields: readonly FieldOption[];\n defaults: NotificationDefaults | null;\n /**\n * Every edit, as it happens. There is no save button here on purpose: the\n * page's own action bar commits the form, and a second one beside it could\n * only mean the same thing said twice or two different things unlabelled.\n */\n onChange: (notification: FormNotification) => void;\n}\n\nfunction initialReplyToMode(replyTo: string | undefined): ReplyToMode {\n if (!replyTo) return \"none\";\n return parseFieldRef(replyTo) ? \"field\" : \"custom\";\n}\n\nfunction NotificationEditor({\n notification,\n editorId,\n providers,\n templates,\n canEditTemplates,\n fields,\n defaults,\n onChange,\n}: NotificationEditorProps) {\n const form = notification;\n const [replyToMode, setReplyToMode] = useState<ReplyToMode>(\n initialReplyToMode(notification.replyTo)\n );\n // Save-time address errors, keyed by field. Nothing here goes through\n // form submission (every control is a type=\"button\" callback), so\n // constraint validation never runs — this is its replacement.\n // Seeded from the rule as it stands, so a row opened onto an address saved\n // earlier shows the problem straight away rather than waiting to be touched.\n const [addressErrors, setAddressErrors] = useState<AddressErrors>(() =>\n addressErrorsIn(notification)\n );\n\n const update = useCallback(\n <K extends keyof FormNotification>(key: K, value: FormNotification[K]) => {\n onChange({ ...notification, [key]: value });\n setAddressErrors(prev =>\n key in prev ? { ...prev, [key]: undefined } : prev\n );\n },\n [notification, onChange]\n );\n\n const changeRecipientType = useCallback(\n (kind: \"static\" | \"field\") => {\n onChange({ ...notification, recipientType: kind, to: \"\" });\n },\n [notification, onChange]\n );\n\n /**\n * Address validity, checked when the field is LEFT rather than on a save\n * press.\n *\n * Nothing here goes through form submission — every control is a\n * `type=\"button\"` callback — so constraint validation never runs and this is\n * its replacement. Blur rather than change, because an address is invalid\n * for most of the time it is being typed.\n */\n const validateAddress = useCallback(\n (key: AddressField, value: string | undefined) => {\n setAddressErrors(prev => ({ ...prev, [key]: addressError(key, value) }));\n },\n []\n );\n\n const condition = form.condition;\n\n return (\n <div id={editorId} className=\"border-t border-border bg-muted/20 px-4 py-5\">\n <div className=\"space-y-6\">\n <NotificationIdentityFields\n form={form}\n providers={providers}\n defaults={defaults}\n templates={templates}\n canEditTemplates={canEditTemplates}\n update={update}\n addressErrors={addressErrors}\n validateAddress={validateAddress}\n />\n {/* Recipients */}\n <RecipientFields\n form={form}\n fields={fields}\n defaults={defaults}\n update={update}\n onRecipientTypeChange={changeRecipientType}\n addressErrors={addressErrors}\n validateAddress={validateAddress}\n replyToMode={replyToMode}\n setReplyToMode={setReplyToMode}\n />\n {/* Send condition */}\n <SendConditionField\n condition={condition}\n fields={fields}\n update={update}\n />\n {/* Enabled */}\n <div className=\"flex items-center justify-between pt-4 border-t border-border\">\n <div>\n <Label htmlFor=\"notification-enabled\">Enabled</Label>\n <p className=\"text-xs text-muted-foreground\">\n Turn off to keep the rule without sending.\n </p>\n </div>\n <Switch\n id=\"notification-enabled\"\n checked={form.enabled}\n onCheckedChange={checked => update(\"enabled\", checked)}\n />\n </div>\n </div>\n </div>\n );\n}\n\n// ============================================================================\n// Main Component\n// ============================================================================\n\n/**\n * FormNotificationsTab - the form's notification rules.\n *\n * Each rule references an existing email provider and template rather than\n * duplicating provider credentials or embedding a second template editor.\n */\nexport function FormNotificationsTab({\n defaults,\n}: FormNotificationsTabProps): JSX.Element {\n const {\n notifications,\n fields,\n addNotification,\n duplicateNotification,\n updateNotification,\n deleteNotification,\n } = useFormBuilder();\n\n const [providers, setProviders] = useState<ProviderOption[]>([]);\n const [templates, setTemplates] = useState<TemplateOption[]>([]);\n const [canEditTemplates, setCanEditTemplates] = useState(false);\n // Which row is open. One at a time: several open at once turns the list\n // into a wall of fields and loses the overview the summaries exist to give.\n const [expandedId, setExpandedId] = useState<string | null>(null);\n\n useEffect(() => {\n void fetchProviders().then(setProviders);\n void fetchTemplates().then(setTemplates);\n void fetchCanEditTemplates().then(setCanEditTemplates);\n }, []);\n\n const fieldList = useMemo<FieldOption[]>(\n () => fields.map(f => ({ name: f.name, label: f.label, type: f.type })),\n [fields]\n );\n\n /**\n * A new rule is added immediately and opened, rather than composed in a\n * draft and committed on a button.\n *\n * That is the trade the inline editor makes: there is no save press to\n * attach a creation to, so the row exists from the moment it is asked for.\n * An unwanted one is removed the same way any other is, and an incomplete\n * one is inert — a notification without a template never sends, which the\n * row says on its face.\n */\n const addAndOpen = useCallback(() => {\n const created = createNotification();\n addNotification(created);\n setExpandedId(created.id);\n }, [addNotification]);\n\n const toggleExpanded = useCallback((id: string) => {\n setExpandedId(current => (current === id ? null : id));\n }, []);\n\n const getProviderName = useCallback(\n (providerId?: string) => {\n if (!providerId) {\n const def = providers.find(p => p.isDefault);\n return def ? `${def.name} (default)` : \"System default\";\n }\n return providers.find(p => p.id === providerId)?.name ?? \"Unknown\";\n },\n [providers]\n );\n\n return (\n // The measure belongs to the page's shell, not to this tab.\n <div>\n <div className=\"flex items-center justify-between mb-8 pb-4 border-b border-border\">\n <div>\n <h3 className=\"text-xl font-semibold text-foreground\">\n Notifications\n </h3>\n <p className=\"text-sm text-muted-foreground mt-1\">\n Emails sent when someone submits this form.\n </p>\n </div>\n <Button type=\"button\" onClick={addAndOpen}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n Add notification\n </Button>\n </div>\n\n {/* List */}\n {notifications.length === 0 ? (\n <div className=\"flex flex-col items-center justify-center p-12 mt-6 border-2 border-dashed border-border rounded-none bg-muted\">\n <div className=\"flex items-center justify-center w-12 h-12 rounded-none border border-border bg-background mb-4 text-primary\">\n <Mail className=\"h-6 w-6\" aria-hidden=\"true\" />\n </div>\n <p className=\"text-base font-medium text-foreground mb-1\">\n No notifications yet\n </p>\n <p className=\"text-sm text-muted-foreground mb-6 text-center max-w-sm\">\n Add a notification to email someone when this form is submitted.\n </p>\n <Button type=\"button\" onClick={addAndOpen}>\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n Add notification\n </Button>\n </div>\n ) : (\n <div className=\"space-y-2\">\n {notifications.map(notification => (\n <NotificationCard\n key={notification.id}\n notification={notification}\n providerName={getProviderName(notification.providerId)}\n fields={fieldList}\n expanded={expandedId === notification.id}\n editorId={`notification-editor-${notification.id}`}\n onToggle={() => toggleExpanded(notification.id)}\n onDuplicate={() => duplicateNotification(notification.id)}\n onDelete={() => deleteNotification(notification.id)}\n onToggleEnabled={enabled =>\n updateNotification(notification.id, { enabled })\n }\n >\n <NotificationEditor\n notification={notification}\n editorId={`notification-editor-${notification.id}`}\n providers={providers}\n templates={templates}\n canEditTemplates={canEditTemplates}\n fields={fieldList}\n defaults={defaults}\n onChange={next => updateNotification(notification.id, next)}\n />\n </NotificationCard>\n ))}\n </div>\n )}\n </div>\n );\n}\n\nexport default FormNotificationsTab;\n","\"use client\";\n\n/**\n * Picks the document a form redirects to after submission.\n *\n * The plugin cannot reach the admin's own relationship input: `@nextlyhq/admin`\n * is not on the plugin import boundary, and `plugin-sdk/admin` does not\n * re-export it. This is the narrow version of that control — one value, chosen\n * from the collections the site configured as redirect targets.\n *\n * @module admin/components/builder/RedirectPagePicker\n */\n\nimport {\n Input,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { encodeSelectParam } from \"nextly/query\";\nimport { type JSX, useEffect, useState } from \"react\";\n\nimport {\n documentReachability,\n type Reachability,\n parseRedirectReference,\n} from \"../../../utils/redirect-reference\";\n\n/** Rows per request. Search, not paging, is how the rest are reached. */\nconst PAGE_SIZE = 50;\n\n/**\n * The conventional title fields, tried in order when a collection does not say\n * which of its fields names a document.\n */\nconst FALLBACK_TITLE_FIELDS = [\"title\", \"name\", \"label\", \"slug\"] as const;\n\n/**\n * The fields this control reads for one collection, in the order\n * `documentLabel` prefers them.\n *\n * A collection declares what names its documents through `admin.useAsTitle`,\n * and that declaration leads here: a collection titled by `headline` is listed\n * by its headlines rather than by opaque ids, which is the difference between\n * choosing a redirect target and guessing at one. The conventional names still\n * follow it, so a row whose configured title field happens to be empty reads\n * as something recognisable instead of falling straight through to an id.\n *\n * `id` is always first because the control stores it.\n *\n * One declaration, used to build the request AND to choose the label, so a\n * field added to one cannot go missing from the other.\n */\nexport function labelFieldsFor(useAsTitle?: string): readonly string[] {\n const configured = useAsTitle?.trim();\n if (!configured) return [\"id\", ...FALLBACK_TITLE_FIELDS];\n return [\n \"id\",\n configured,\n ...FALLBACK_TITLE_FIELDS.filter(field => field !== configured),\n ];\n}\n\n/**\n * The `select` query value that projects a read down to `fields`.\n *\n * The dispatcher reads this parameter with `JSON.parse` and keeps only the\n * keys whose values are booleans (`parseSelectParam`,\n * packages/nextly/src/dispatcher/helpers/validation.ts). A value it cannot\n * parse becomes `undefined` — no error, no warning, and a response carrying\n * every field of every row. A comma-separated list is precisely such a value,\n * so the encoding is the whole difference between projecting a read and\n * appearing to project it.\n */\n/**\n * The fields to REQUEST, which is the label fields plus what decides\n * reachability.\n *\n * Kept apart from `labelFieldsFor` on purpose. `status` decides whether a page\n * is marked as a draft; it must never become a document's name, and\n * `documentLabel` walks whatever list it is given — so a page with no title\n * would otherwise be offered to an author as \"draft\".\n *\n * Derived from the label list rather than written out again, so a field added\n * for labelling is still requested.\n */\nexport function selectFieldsFor(useAsTitle?: string): readonly string[] {\n // `firstPublishedAt` as well as `status`: it is what says the collection has\n // a publish lifecycle at all, and without it a page whose collection merely\n // has a field called `status` would be marked as a draft. See\n // `hasPublishLifecycle`.\n return [...labelFieldsFor(useAsTitle), \"status\", \"firstPublishedAt\"];\n}\n\n/**\n * The `select` parameter, ready to sit in a hand-built query string.\n *\n * The format itself comes from core rather than from here. This module once\n * spelled it out, having first shipped a comma list the server accepted and\n * discarded — every field of up to fifty documents per collection, to fill a\n * dropdown reading five.\n */\nexport function selectParam(fields: readonly string[]): string {\n return encodeURIComponent(encodeSelectParam(fields));\n}\n\n/** One selectable document, reduced to what the control shows and stores. */\ninterface Choice {\n collection: string;\n id: string;\n label: string;\n /**\n * Whether a visitor could reach this page today. Unpublished pages are\n * offered on purpose — a form is usually configured beside the page it\n * points at — but an author choosing one should be able to see that it is\n * not live yet.\n *\n * Kept as the three-way answer rather than a boolean: a localized page whose\n * translation may be public is not the same as one that is definitely a\n * draft, and only the definite case earns a marker.\n */\n reachability: Reachability;\n}\n\ninterface RedirectPagePickerProps {\n /** Collections configured as redirect targets, in configuration order. */\n collections: readonly string[];\n /** The stored relationship value, in whatever shape it was saved. */\n value: unknown;\n onChange: (next: { relationTo: string; value: string } | undefined) => void;\n}\n\n/**\n * The document a stored value points at, as `collection:id`.\n *\n * The reading is `parseRedirectReference`, not a second copy of it: the picker\n * and the submit handler have to agree about which document a stored value\n * names, and two readers that agree today would drift — silently, because a\n * control showing the wrong page still shows a page.\n */\nexport function selectionKey(\n value: unknown,\n collections: readonly string[]\n): string | undefined {\n const reference = parseRedirectReference(value, collections);\n return reference ? `${reference.collection}:${reference.id}` : undefined;\n}\n\n/** What to call a document, preferring the field an author would recognise. */\nexport function documentLabel(\n row: Record<string, unknown>,\n fields: readonly string[] = labelFieldsFor()\n): string {\n for (const field of fields.filter(name => name !== \"id\")) {\n // A collection only has to point `useAsTitle` at a field that EXISTS, so\n // the configured title can be a number or a boolean. Skipping those falls\n // through to a conventional field or to the id, which is the opaque label\n // reading the configuration was meant to replace. `0` and `false` are\n // titles like any other, so emptiness is decided after stringifying, not\n // by truthiness.\n const candidate = row[field];\n const text =\n typeof candidate === \"string\"\n ? candidate\n : typeof candidate === \"number\" && Number.isFinite(candidate)\n ? String(candidate)\n : typeof candidate === \"boolean\"\n ? String(candidate)\n : undefined;\n if (text !== undefined && text.trim()) return text;\n }\n return typeof row.id === \"string\" ? row.id : \"Untitled\";\n}\n\n/**\n * One collection's matching documents, or null when it could not be read.\n *\n * `limit`, not `pageSize` — the other name is accepted and ignored, leaving\n * the default of 10. `status=all` because a form is usually configured\n * alongside the page it points at, and filtering to published shows \"create\n * one first\" while the page sits in the next tab. `depth=0` because this reads\n * a handful of scalar fields and would otherwise inherit the collection API's\n * expansion, pulling every nested relation of every row to fill a dropdown.\n */\nasync function fetchChoices(\n collection: string,\n query: string,\n page: number,\n meta: CollectionMeta\n): Promise<{ rows: Choice[]; hasMore: boolean } | null> {\n const search = query ? `&search=${encodeURIComponent(query)}` : \"\";\n try {\n // `select` as well as `depth=0`. Without one the server sends every scalar\n // and JSON field of up to fifty documents per collection — for\n // page-builder documents that is the whole block tree — to fill a dropdown\n // that reads a handful of them.\n const response = await fetch(\n `/admin/api/collections/${collection}/entries` +\n `?limit=${PAGE_SIZE}&page=${page}&status=all&depth=0` +\n `&select=${selectParam(selectFieldsFor(meta.titleField))}${search}`,\n { credentials: \"include\" }\n );\n if (!response.ok) return null;\n const body = (await response.json()) as {\n items?: Record<string, unknown>[];\n meta?: { totalPages?: number };\n };\n return {\n rows: (body.items ?? [])\n .filter(row => typeof row.id === \"string\")\n .map(row => ({\n collection,\n id: row.id as string,\n label: documentLabel(row, labelFieldsFor(meta.titleField)),\n reachability: documentReachability(\n row as { id: string },\n meta.localized\n ),\n })),\n // Reported rather than assumed, so the control can offer the rest\n // instead of truncating where the author cannot see it.\n hasMore: page < (body.meta?.totalPages ?? 1),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Existing choices plus new ones, de-duplicated on the pair that identifies a\n * document. Order is preserved so a list does not reshuffle under the author.\n */\nfunction mergeChoices(\n existing: readonly Choice[],\n incoming: readonly Choice[]\n): Choice[] {\n const seen = new Set(existing.map(c => `${c.collection}:${c.id}`));\n const merged = [...existing];\n for (const choice of incoming) {\n const key = `${choice.collection}:${choice.id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n merged.push(choice);\n }\n return merged;\n}\n\n/** One option, keyed and valued by the pair the caller stores. */\nfunction renderChoice(choice: Choice) {\n return (\n <SelectItem\n key={`${choice.collection}:${choice.id}`}\n value={`${choice.collection}:${choice.id}`}\n >\n <span className=\"inline-flex items-center gap-2\">\n <span>{choice.label}</span>\n {/* Unpublished pages are offered deliberately — a form is usually\n configured beside the page it points at — so the marker is what\n keeps \"offered\" from reading as \"live\". A published form saved\n against one is refused, and an author who cannot see which pages\n are drafts has no way to predict that. */}\n {choice.reachability === \"unreachable\" && (\n <span className=\"shrink-0 rounded-sm border px-1 py-px text-[10px] uppercase tracking-wide text-muted-foreground\">\n Draft\n </span>\n )}\n </span>\n </SelectItem>\n );\n}\n\n/**\n * Which collections the picker needs metadata for, as the key the lookup is\n * cached on.\n *\n * The configured ones, plus the collection the STORED target names when that\n * is no longer among them. A polymorphic value can point at a collection since\n * removed from `redirectRelationships`, and the picker recovers and displays\n * that target on purpose — looking up only the configured ones would label it\n * by the conventional names while the listing uses each collection's own title\n * field, showing one document under two rules.\n */\nexport function metadataCollections(\n collections: readonly string[],\n value: unknown\n): string {\n const stored = selectionKey(value, collections)?.split(\":\")[0];\n const wanted =\n stored && !collections.includes(stored)\n ? [...collections, stored]\n : collections;\n return wanted.join(\",\");\n}\n\n/**\n * The field a collection says names its documents, or undefined when it does\n * not say or cannot be asked.\n *\n * Undefined covers both \"this collection declares no title field\" and \"the\n * metadata could not be read\", because the control does the same thing in\n * either case: fall back to the conventional names. Nothing on screen depends\n * on telling them apart, and the listing itself still reports its own\n * failures.\n */\ninterface CollectionMeta {\n /** The field the collection says names its documents. */\n titleField: string | undefined;\n /**\n * The collection's `localized` setting, or undefined when it could not be\n * read. Undefined is NOT false: a localized collection read as plain marks\n * reachable translations as drafts.\n */\n localized: boolean | undefined;\n}\n\nasync function fetchCollectionMeta(\n collection: string\n): Promise<CollectionMeta> {\n try {\n // The bare document, not a `{ message, item }` envelope — that shape is\n // for mutations, and this is a read.\n const response = await fetch(`/admin/api/collections/${collection}`, {\n credentials: \"include\",\n });\n if (!response.ok) return { titleField: undefined, localized: undefined };\n const body = (await response.json()) as {\n admin?: { useAsTitle?: unknown };\n localized?: unknown;\n };\n const configured = body.admin?.useAsTitle;\n return {\n titleField:\n typeof configured === \"string\" && configured.trim()\n ? configured.trim()\n : undefined,\n localized:\n typeof body.localized === \"boolean\" ? body.localized : undefined,\n };\n } catch {\n return { titleField: undefined, localized: undefined };\n }\n}\n\n/**\n * The fields to read and to label from, per configured collection.\n *\n * Resolved once per collection list rather than per search: what a collection\n * calls its title does not change while an author types, and folding this into\n * the listing effect would spend a request per keystroke to re-learn a\n * constant.\n *\n * `null` means not resolved yet. The listing waits for it, because a first\n * page fetched under the fallback fields would have to be refetched or else\n * left labelled differently from every page after it.\n */\nfunction useTitleFields(key: string) {\n const [fields, setFields] = useState<Record<string, CollectionMeta> | null>(\n null\n );\n\n useEffect(() => {\n let cancelled = false;\n const wanted = key ? key.split(\",\") : [];\n setFields(null);\n\n void Promise.all(\n wanted.map(async collection => {\n return [collection, await fetchCollectionMeta(collection)] as const;\n })\n ).then(pairs => {\n if (!cancelled) setFields(Object.fromEntries(pairs));\n });\n\n return () => {\n cancelled = true;\n };\n }, [key]);\n\n return fields;\n}\n\n/**\n * The matching documents for every configured collection, and which of them\n * could not be read.\n */\nfunction useChoices(\n key: string,\n applied: string,\n titleFields: Record<string, CollectionMeta> | null\n) {\n const [choices, setChoices] = useState<Choice[] | null>(null);\n const [failed, setFailed] = useState<readonly string[]>([]);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n // Per collection, not shared. A shared counter advances every collection\n // when any one of them has more, so a collection whose page N failed is\n // never asked for page N again — and if its N+1 succeeds the warning clears\n // too, leaving those documents absent with nothing on screen saying so.\n const [pages, setPages] = useState<Record<string, number>>({});\n\n // A new query is a new list, not more of the old one.\n //\n // The SAME reference is returned when there is nothing to clear. Writing a\n // fresh `{}` every time is a state change even when the map is already\n // empty, and the fetch effect below depends on `pages` — so each query\n // change ran the fetch once with the old map, cancelled it, and ran it\n // again. Every collection was requested twice per search and the first\n // answer discarded.\n useEffect(() => {\n setPages(current => (Object.keys(current).length === 0 ? current : {}));\n }, [key, applied]);\n\n useEffect(() => {\n // Nothing to request until each collection's title field is known —\n // see `useTitleFields`. `choices` stays null, which the control already\n // renders as loading.\n if (titleFields === null) return;\n\n let cancelled = false;\n const wanted = key ? key.split(\",\") : [];\n setLoading(true);\n\n void Promise.all(\n wanted.map(collection =>\n fetchChoices(\n collection,\n applied,\n pages[collection] ?? 1,\n titleFields[collection] ?? {\n titleField: undefined,\n localized: undefined,\n }\n )\n )\n ).then(results => {\n if (cancelled) return;\n setLoading(false);\n\n // WHICH collections failed, not merely whether any did: one unreadable\n // collection beside a readable-but-empty one otherwise reports \"nothing\n // here\" about a collection nobody could see.\n setFailed(wanted.filter((_, index) => results[index] === null));\n\n const loaded = results.filter(\n (result): result is { rows: Choice[]; hasMore: boolean } =>\n result !== null\n );\n setHasMore(loaded.some(result => result.hasMore));\n\n const rows = loaded.flatMap(result => result.rows);\n const first = wanted.every(collection => (pages[collection] ?? 1) === 1);\n // Merged by identity rather than appended. A retry re-requests a page\n // that already succeeded for its siblings, and appending would list\n // those documents twice — duplicate options, and duplicate React keys.\n // Keyed, a repeated page is a no-op whatever caused the repeat.\n setChoices(current => mergeChoices(first ? [] : (current ?? []), rows));\n });\n\n return () => {\n cancelled = true;\n };\n }, [key, applied, pages, titleFields]);\n\n return {\n choices,\n setChoices,\n failed,\n hasMore,\n loading,\n /**\n * Advances only the collections that have more to give, and RETRIES the\n * ones whose last page failed rather than stepping over it.\n *\n * Guarded on the in-flight flag: two clicks before the first resolves\n * either batch into a double increment or cancel the pending effect, and a\n * skipped page is then absent from a list that reports having loaded it.\n */\n loadMore: () => {\n if (loading) return;\n setPages(current => {\n const next: Record<string, number> = { ...current };\n for (const collection of key ? key.split(\",\") : []) {\n // A failed collection stays on its page so the retry asks for the\n // page it never got.\n if (failed.includes(collection)) continue;\n next[collection] = (current[collection] ?? 1) + 1;\n }\n return next;\n });\n },\n /** Ask the failed collections again, without advancing anyone. */\n retryFailed: () => {\n if (!loading) setPages(current => ({ ...current }));\n },\n };\n}\n\n/**\n * Adds the selected document when the listing did not reach it.\n *\n * Keyed on the selection rather than on the collection list, because the\n * stored value arrives with the form and often AFTER this mounts — a hook\n * watching only the collections would capture the moment when nothing was\n * selected and never look again.\n *\n * A control that silently drops its own value is worse than one that cannot\n * list everything: the author sees blank, re-picks, and saves a different\n * destination than the one that was stored.\n */\nfunction useSelectedChoice(\n selected: string | undefined,\n choices: Choice[] | null,\n setChoices: (update: (current: Choice[] | null) => Choice[]) => void,\n titleFields: Record<string, CollectionMeta> | null\n) {\n // Whether the stored selection could not be read. A controlled `Select`\n // holding a value with no matching option renders BLANK, which looks\n // identical to \"nothing chosen\" — so an author with a perfectly good\n // destination is invited to pick over it. Reported rather than swallowed.\n const [unreadable, setUnreadable] = useState(false);\n\n useEffect(() => {\n if (!selected || choices === null) return;\n const separator = selected.indexOf(\":\");\n const collection = selected.slice(0, separator);\n const id = selected.slice(separator + 1);\n // Collection AND id: two configured collections can hold the same id, and\n // matching on id alone skips the fetch while the option that is actually\n // selected is absent.\n if (\n !id ||\n choices.some(\n choice => choice.id === id && choice.collection === collection\n )\n ) {\n setUnreadable(false);\n return;\n }\n\n let cancelled = false;\n void (async () => {\n try {\n // `status=all` for the same reason the listing carries it: a stored\n // target may be a draft, and a published-only read answers 404.\n const response = await fetch(\n `/admin/api/collections/${collection}/entries/${id}?depth=0&status=all`,\n { credentials: \"include\" }\n );\n if (!response.ok) {\n if (!cancelled) setUnreadable(true);\n return;\n }\n // A by-id read answers with the document ITSELF. Only mutations carry\n // the `{ message, item }` envelope.\n const row = (await response.json()) as Record<string, unknown>;\n if (cancelled) return;\n if (!row || typeof row.id !== \"string\") {\n setUnreadable(true);\n return;\n }\n setUnreadable(false);\n setChoices(current => [\n {\n collection,\n id: row.id as string,\n // The same fields the listing labels by. Labelling the recovered\n // document differently would show one document under two names\n // depending on whether the listing happened to reach it.\n label: documentLabel(\n row,\n labelFieldsFor(titleFields?.[collection]?.titleField)\n ),\n reachability: documentReachability(\n row as { id: string },\n titleFields?.[collection]?.localized\n ),\n },\n ...(current ?? []),\n ]);\n } catch {\n if (!cancelled) setUnreadable(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [selected, choices, setChoices, titleFields]);\n\n return unreadable;\n}\n\n/**\n * The options, grouped by the collection each one came from.\n *\n * The groups are derived from the CHOICES, not from the configured list. A\n * stored target whose collection was removed since the form was saved is\n * recovered by id and belongs in the list — filtering by configuration\n * dropped it, leaving the control blank over a value that is still stored,\n * with the unreadable warning cleared because the recovery had succeeded.\n *\n * That divergence was visible in the code before it was visible on screen: the\n * single-collection branch rendered every choice and the grouped branch\n * filtered them, so the same state produced different pickers depending on how\n * many collections a site happened to configure. One path now.\n */\nexport interface ChoiceGroup {\n collection: string;\n label: string;\n /** True when this collection is no longer a configured redirect target. */\n removed: boolean;\n choices: Choice[];\n}\n\n/**\n * The choices arranged into groups, in configuration order, with collections\n * that are no longer configured last and marked.\n *\n * Derived from the CHOICES rather than from the configured list, which is the\n * whole point: a stored target whose collection was removed is recovered by id\n * and belongs in the list.\n */\nexport function groupChoices(\n choices: readonly Choice[],\n collections: readonly string[]\n): ChoiceGroup[] {\n const present = [...new Set(choices.map(choice => choice.collection))];\n const order = [\n ...collections.filter(name => present.includes(name)),\n ...present.filter(name => !collections.includes(name)),\n ];\n\n return order.map(collection => {\n const removed = !collections.includes(collection);\n return {\n collection,\n // Named as unavailable rather than hidden: the value is still stored and\n // the server refuses a save into a collection with no pattern, so an\n // author needs to see what is set in order to change it.\n label: removed ? `${collection} — no longer configured` : collection,\n removed,\n choices: choices.filter(choice => choice.collection === collection),\n };\n });\n}\n\nfunction Options({\n choices,\n collections,\n}: {\n choices: Choice[];\n collections: readonly string[];\n}) {\n const groups = groupChoices(choices, collections);\n if (groups.length <= 1 && !groups[0]?.removed) {\n return <>{choices.map(renderChoice)}</>;\n }\n return (\n <>\n {groups.map(group => (\n <SelectGroup key={group.collection}>\n <SelectLabel>{group.label}</SelectLabel>\n {group.choices.map(renderChoice)}\n </SelectGroup>\n ))}\n </>\n );\n}\n\n/**\n * What to say when there is nothing to choose, and why.\n *\n * `collections` here is the READABLE ones. Passing the full configured list\n * asserted that a collection which could not be read is empty — printed\n * directly beneath the notice saying it could not be read.\n */\nfunction Empty({\n applied,\n collections,\n}: {\n applied: string;\n collections: readonly string[];\n}) {\n return (\n <p className=\"text-[12px] text-muted-foreground\">\n {applied\n ? `No documents match “${applied}”.`\n : collections.length === 0\n ? \"No collection is configured as a redirect target.\"\n : `No documents to redirect to yet. Create one in ${collections.join(\" or \")} first.`}\n </p>\n );\n}\n\n/**\n * The things this control has to say that its options cannot.\n *\n * Three separate states, kept apart deliberately. A collection that could not\n * be read, a saved page that cannot be IDENTIFIED because no collection is\n * configured to look it up in, and a saved page whose lookup FAILED are three\n * different situations, and each one previously appeared as an empty control —\n * which reads as \"nothing is set\" and invites an author to overwrite a\n * destination they were never shown.\n */\nfunction PickerNotices({\n failed,\n loading,\n onRetry,\n unidentifiable,\n unreadableSelection,\n}: {\n failed: readonly string[];\n loading: boolean;\n onRetry: () => void;\n unidentifiable: boolean;\n unreadableSelection: boolean;\n}) {\n return (\n <>\n {failed.length > 0 && (\n <p className=\"text-[12px] text-destructive\">\n Could not read {failed.join(\" or \")}. You may not have permission to\n list {failed.length > 1 ? \"them\" : \"it\"}, or the request failed.{\" \"}\n <button\n type=\"button\"\n onClick={onRetry}\n disabled={loading}\n className=\"underline underline-offset-2 disabled:no-underline\"\n >\n Try again\n </button>\n </p>\n )}\n\n {unidentifiable && (\n <p className=\"text-[12px] text-muted-foreground\">\n A page is saved on this form, but it cannot be identified without a\n configured redirect collection. Choosing a different confirmation\n replaces it.\n </p>\n )}\n\n {unreadableSelection && (\n <p className=\"text-[12px] text-destructive\">\n This form has a page selected that could not be read. Leave it as it\n is unless you mean to change it — saving a new choice replaces the\n stored one.\n </p>\n )}\n </>\n );\n}\n\nexport function RedirectPagePicker({\n collections,\n value,\n onChange,\n}: RedirectPagePickerProps): JSX.Element {\n const [query, setQuery] = useState(\"\");\n const [applied, setApplied] = useState(\"\");\n\n // Typing should not issue a request per keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setApplied(query), 250);\n return () => clearTimeout(timer);\n }, [query]);\n\n // `collections` is an array prop, so a new identity every render would\n // refetch forever; the join is the value that actually decides the request.\n const titleFields = useTitleFields(metadataCollections(collections, value));\n const {\n choices,\n setChoices,\n failed,\n hasMore,\n loading,\n loadMore,\n retryFailed,\n } = useChoices(collections.join(\",\"), applied, titleFields);\n const selected = selectionKey(value, collections);\n // A value is stored and this control cannot say WHICH document it is: a bare\n // id names no collection, and with none configured there is nowhere to look\n // it up. Distinct from an unreadable selection, which is a failed request\n // rather than missing information.\n const unidentifiable = value != null && selected === undefined;\n const selectionUnreadable = useSelectedChoice(\n selected,\n choices,\n setChoices,\n titleFields\n );\n\n if (choices === null) {\n return <p className=\"text-[12px] text-muted-foreground\">Loading pages…</p>;\n }\n\n return (\n <div className=\"space-y-2\">\n {/* Search rather than paging: a dropdown cannot usefully list a large\n collection, and a ceiling on how many pages to walk would leave the\n documents past it unreachable however high it were set. */}\n {/* Nothing to search when no collection is listed — the only entry in\n the control then is the stored target, recovered by id. */}\n {collections.length > 0 && (\n <Input\n aria-label=\"Search pages\"\n value={query}\n onChange={event => setQuery(event.target.value)}\n placeholder=\"Search…\"\n />\n )}\n\n {/* Reported BESIDE the choices rather than instead of them: a collection\n the author cannot read is a different problem from one that is empty,\n and the collections that did load are still choosable. */}\n <PickerNotices\n failed={failed}\n loading={loading}\n onRetry={retryFailed}\n unidentifiable={unidentifiable}\n unreadableSelection={selectionUnreadable}\n />\n\n {choices.length === 0 ? (\n failed.length < collections.length && (\n <Empty\n applied={applied}\n collections={collections.filter(name => !failed.includes(name))}\n />\n )\n ) : (\n <Select\n value={selected ?? \"\"}\n onValueChange={next => {\n const separator = next.indexOf(\":\");\n if (separator < 0) return onChange(undefined);\n onChange({\n relationTo: next.slice(0, separator),\n value: next.slice(separator + 1),\n });\n }}\n >\n <SelectTrigger aria-label=\"Redirect page\" className=\"w-full\">\n <SelectValue placeholder=\"Choose a page\" />\n </SelectTrigger>\n <SelectContent>\n <Options choices={choices} collections={collections} />\n </SelectContent>\n </Select>\n )}\n\n {/* The rest of the matches, reachable rather than silently absent. A cap\n with no way past it makes a document beyond it unselectable however\n high the cap is set. */}\n {hasMore && (\n <button\n type=\"button\"\n onClick={loadMore}\n disabled={loading}\n className=\"text-[12px] text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:no-underline disabled:opacity-60\"\n >\n {loading ? \"Loading…\" : \"Load more\"}\n </button>\n )}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * Form Settings Tab\n *\n * Per-form behavior on the canonical settings shape: every control here is\n * consumed somewhere (the submit handler or the confirmation flow) — a\n * setting that does nothing does not get a toggle. Spam controls are\n * per-form OVERRIDES of the plugin's global config: blank means inherit,\n * and the inherited value is shown rather than hidden.\n *\n * @module admin/components/builder/FormSettingsTab\n */\n\nimport {\n FormSection,\n Input,\n Label,\n RadioGroup,\n RadioGroupItem,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport type React from \"react\";\n\nimport { parseRedirectReference } from \"../../../utils/redirect-reference\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { RedirectPagePicker } from \"./RedirectPagePicker\";\n\n// ---------------------------------------------------------------------------\n// Layout helpers\n// ---------------------------------------------------------------------------\n\nfunction SettingRow({\n label,\n description,\n htmlFor,\n children,\n}: {\n label: string;\n description?: string;\n htmlFor?: string;\n children: React.ReactNode;\n}) {\n return (\n <div className=\"flex flex-col sm:flex-row sm:items-center justify-between gap-6\">\n <div className=\"space-y-1\">\n <Label\n htmlFor={htmlFor}\n className=\"text-[13px] font-semibold text-foreground tracking-tight\"\n >\n {label}\n </Label>\n {description && (\n <p className=\"text-[12px] text-muted-foreground leading-relaxed\">\n {description}\n </p>\n )}\n </div>\n <div className=\"shrink-0 flex items-center justify-end min-w-25\">\n {children}\n </div>\n </div>\n );\n}\n\n/**\n * Tri-state override select: inherit from the plugin config (showing the\n * effective value), or force on/off for this form.\n */\nfunction InheritToggle({\n id,\n value,\n inherited,\n onChange,\n}: {\n id: string;\n value: boolean | undefined;\n inherited: boolean | undefined;\n onChange: (value: boolean | undefined) => void;\n}) {\n const inheritedLabel =\n inherited === undefined\n ? \"Inherit\"\n : `Inherit (${inherited ? \"on\" : \"off\"})`;\n return (\n <Select\n value={value === undefined ? \"inherit\" : value ? \"on\" : \"off\"}\n onValueChange={selected =>\n onChange(selected === \"inherit\" ? undefined : selected === \"on\")\n }\n >\n <SelectTrigger\n id={id}\n className=\"w-40 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"inherit\">{inheritedLabel}</SelectItem>\n <SelectItem value=\"on\">On</SelectItem>\n <SelectItem value=\"off\">Off</SelectItem>\n </SelectContent>\n </Select>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport interface SpamDefaults {\n honeypot?: boolean;\n recaptchaEnabled?: boolean;\n}\n\ninterface FormSettingsTabProps {\n /** Plugin-level spam defaults (from builder-config); null while loading. */\n spamDefaults: SpamDefaults | null;\n /**\n * Collections a form may redirect to (from builder-config); null while\n * loading. Empty means the site configured none, and the option is not\n * offered — choosing it could only ever produce a form with no destination.\n */\n redirectCollections: string[] | null;\n /**\n * The configuration request failed, as distinct from returning nothing. An\n * empty list is a valid answer; a failure is not an answer at all, and\n * saying \"no collection is configured\" on a 403 states something false.\n */\n redirectConfigFailed: boolean;\n}\n\n/**\n * The \"Redirect to a page\" choice and its picker.\n *\n * Shown when the site configures a redirect collection, AND whenever this form\n * ALREADY redirects to a page. Hiding a stored confirmation leaves the radio\n * group holding a value none of its items carry, so nothing appears selected —\n * while that stored value is still posted on the next save. The author would\n * be looking at a form that says one thing and saves another.\n *\n * Configuration can be removed after a form was saved, and the builder-config\n * request can fail; both arrive here as an empty list, which is why the stored\n * value rather than the configuration decides whether this renders.\n */\n/**\n * What the \"Redirect to a page\" option should show.\n *\n * Four outcomes, named because they were four nested conditions and the\n * difference between two of them is not visual — an empty configuration is an\n * ANSWER, a failed configuration read is not, and stating the first when the\n * second happened tells an author something false about their site.\n */\n/**\n * Whether these settings actually NAME a page — not whether the page-redirect\n * option happens to be selected.\n *\n * Conflating the two let an author choose the option while the configuration\n * was unknown, which classified the state as \"a page is stored\" with nothing\n * stored: a picker with no collections to offer, and a save that could only be\n * refused. Selecting a confirmation is not a destination.\n *\n * A BARE id counts. `parseRedirectReference` resolves one only when exactly\n * one collection is configured, which is the right rule for \"which document is\n * this\" — but the question here is \"is anything stored at all\", and the answer\n * must not depend on configuration that may have failed to load or been\n * removed. Those are precisely the states where an author needs to be shown\n * what is already saved.\n */\nexport function hasStoredRedirectPage(settings: {\n redirectPage?: unknown;\n}): boolean {\n const value = settings.redirectPage;\n if (typeof value === \"string\") return value.trim().length > 0;\n return parseRedirectReference(value, []) !== null;\n}\n\nexport type RedirectOptionState =\n /** Not offered: nothing stored, nothing configured, nothing unknown. */\n | \"hidden\"\n /** Offered but not selectable: the choices could not be loaded. */\n | \"unknown\"\n /** Offered because a page IS stored, though none can be chosen now. */\n | \"stored-only\"\n /** Offered with choices. */\n | \"ready\";\n\nexport function redirectOptionState(input: {\n /** A page is actually stored — not merely that this option is selected. */\n hasStoredPage: boolean;\n collections: string[] | null;\n configFailed: boolean;\n}): RedirectOptionState {\n const configured = input.collections !== null && input.collections.length > 0;\n if (configured) return \"ready\";\n // A STORED page outranks a failed request: it is a fact about this form,\n // and the author needs to see it to change it.\n if (input.hasStoredPage) return \"stored-only\";\n return input.configFailed ? \"unknown\" : \"hidden\";\n}\n\nfunction PageRedirectOption({\n redirectCollections,\n configFailed,\n settings,\n updateSettings,\n}: {\n redirectCollections: string[] | null;\n configFailed: boolean;\n settings: { confirmationType?: string; redirectPage?: unknown };\n updateSettings: (patch: { redirectPage?: unknown }) => void;\n}) {\n const state = redirectOptionState({\n hasStoredPage: hasStoredRedirectPage(settings),\n collections: redirectCollections,\n configFailed,\n });\n if (state === \"hidden\") return null;\n\n return (\n <div className=\"flex items-start gap-3\">\n {/* Selectable only when there are collections to pick from. In every\n other state choosing it leads to a save the server must refuse: the\n rule requires a page, and a page can only come from a configured\n collection. `stored-only` shows what is already saved; it is not an\n invitation to switch a different form into that state. */}\n <RadioGroupItem\n value=\"relationship\"\n id=\"settings-confirm-page\"\n className=\"mt-0.5\"\n disabled={state !== \"ready\"}\n />\n <div className=\"w-full space-y-2\">\n <Label htmlFor=\"settings-confirm-page\">Redirect to a page</Label>\n\n {state !== \"ready\" && (\n <p className=\"text-[12px] text-muted-foreground\">\n {configFailed\n ? \"The redirect configuration could not be loaded, so the pages you can choose from are not listed. Reload to try again.\"\n : \"No collection is configured as a redirect target right now. The page below is still saved, and stays until you choose a different confirmation.\"}\n </p>\n )}\n\n {/* Mounted whenever a page is stored, including with NO configured\n collections: the picker is what recovers the stored target by id\n and labels it, so leaving it out there tells an author a page is\n saved without telling them which. */}\n {state === \"stored-only\" || state === \"ready\"\n ? settings.confirmationType === \"relationship\" && (\n <RedirectPagePicker\n collections={redirectCollections ?? []}\n value={settings.redirectPage}\n onChange={next => updateSettings({ redirectPage: next })}\n />\n )\n : null}\n </div>\n </div>\n );\n}\n\nexport function FormSettingsTab({\n spamDefaults,\n redirectCollections,\n redirectConfigFailed,\n}: FormSettingsTabProps): React.JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n\n return (\n // The measure belongs to the page's shell, not to this tab.\n <div className=\"flex flex-col gap-10\">\n {/* Submission */}\n <FormSection label=\"Submission\">\n <SettingRow\n label=\"Submit button text\"\n description=\"Label on the form's primary action button\"\n htmlFor=\"settings-submit-text\"\n >\n <Input\n id=\"settings-submit-text\"\n type=\"text\"\n className=\"w-56\"\n value={settings.submitButtonText ?? \"\"}\n onChange={e => updateSettings({ submitButtonText: e.target.value })}\n />\n </SettingRow>\n\n <SettingRow\n label=\"Allow multiple submissions\"\n description=\"When off, the same visitor (by IP) can submit this form only once. Best-effort: IP-based, so shared networks count as one visitor.\"\n htmlFor=\"settings-multiple\"\n >\n <Switch\n id=\"settings-multiple\"\n checked={settings.allowMultipleSubmissions ?? true}\n onCheckedChange={checked =>\n updateSettings({ allowMultipleSubmissions: checked })\n }\n />\n </SettingRow>\n </FormSection>\n\n {/* After submission */}\n <FormSection label=\"After submission\">\n <RadioGroup\n value={settings.confirmationType ?? \"message\"}\n onValueChange={value =>\n updateSettings({\n confirmationType: value as\n | \"message\"\n | \"redirect\"\n | \"relationship\",\n })\n }\n className=\"flex flex-col gap-3\"\n >\n <div className=\"flex items-start gap-3\">\n <RadioGroupItem\n value=\"message\"\n id=\"settings-confirm-message\"\n className=\"mt-0.5\"\n />\n <div className=\"w-full space-y-2\">\n <Label htmlFor=\"settings-confirm-message\">Show a message</Label>\n {(settings.confirmationType ?? \"message\") === \"message\" && (\n <Textarea\n aria-label=\"Success message\"\n value={settings.successMessage ?? \"\"}\n onChange={e =>\n updateSettings({ successMessage: e.target.value })\n }\n rows={3}\n placeholder=\"Thank you for your submission!\"\n />\n )}\n </div>\n </div>\n <div className=\"flex items-start gap-3\">\n <RadioGroupItem\n value=\"redirect\"\n id=\"settings-confirm-redirect\"\n className=\"mt-0.5\"\n />\n <div className=\"w-full space-y-2\">\n <Label htmlFor=\"settings-confirm-redirect\">\n Redirect to a URL\n </Label>\n {settings.confirmationType === \"redirect\" && (\n <Input\n aria-label=\"Redirect URL\"\n type=\"url\"\n value={settings.redirectUrl ?? \"\"}\n onChange={e =>\n updateSettings({\n redirectUrl: e.target.value || undefined,\n })\n }\n placeholder=\"https://example.com/thanks\"\n />\n )}\n </div>\n </div>\n <PageRedirectOption\n redirectCollections={redirectCollections}\n configFailed={redirectConfigFailed}\n settings={settings}\n updateSettings={updateSettings}\n />\n </RadioGroup>\n </FormSection>\n\n {/* Spam protection */}\n <FormSection label=\"Spam protection\">\n <SettingRow\n label=\"Honeypot\"\n description=\"Invisible trap field for bots. Inherits the plugin default unless overridden here.\"\n htmlFor=\"settings-honeypot\"\n >\n <InheritToggle\n id=\"settings-honeypot\"\n value={settings.honeypotEnabled}\n inherited={spamDefaults?.honeypot}\n onChange={honeypotEnabled => updateSettings({ honeypotEnabled })}\n />\n </SettingRow>\n\n <SettingRow\n label=\"reCAPTCHA\"\n description=\"Challenge-based bot check. Inherits the plugin default unless overridden here.\"\n htmlFor=\"settings-captcha\"\n >\n <InheritToggle\n id=\"settings-captcha\"\n value={settings.captchaEnabled}\n inherited={spamDefaults?.recaptchaEnabled}\n onChange={captchaEnabled => updateSettings({ captchaEnabled })}\n />\n </SettingRow>\n\n {settings.captchaEnabled === true && (\n <SettingRow\n label=\"reCAPTCHA site key\"\n description=\"The client-facing site key for this form\"\n htmlFor=\"settings-captcha-key\"\n >\n <Input\n id=\"settings-captcha-key\"\n type=\"text\"\n // Full width below the breakpoint: 288px does not fit inside the\n // section's 24px insets on a ~360px panel, and the card clips\n // rather than scrolling.\n className=\"w-full font-mono sm:w-72\"\n value={settings.captchaSiteKey ?? \"\"}\n onChange={e =>\n updateSettings({\n captchaSiteKey: e.target.value || undefined,\n })\n }\n />\n </SettingRow>\n )}\n </FormSection>\n </div>\n );\n}\n\nexport default FormSettingsTab;\n","\"use client\";\n\n/**\n * Form Preview\n *\n * An interactive simulation of the form: real enabled inputs, conditional\n * logic evaluated live against what you type (the same evaluator the\n * runtime uses), the form's actual settings (button text, confirmation\n * behavior), and a device-width toggle. It is honest about what it is —\n * a faithful simulation inside the admin, not the published form; nothing\n * here submits anywhere.\n *\n * @module admin/components/builder/FormPreview\n */\n\nimport {\n Badge,\n Button,\n Checkbox,\n Input,\n Card,\n Label,\n RadioGroup,\n RadioGroupItem,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Tabs,\n TabsList,\n TabsTrigger,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { Eye, Monitor, Paperclip, RotateCcw, Smartphone } from \"lucide-react\";\nimport { type JSX, useMemo, useState } from \"react\";\n\nimport type { AnyFormField, CustomFormField, FormField } from \"../../../types\";\nimport { isKnownFormField } from \"../../../types\";\nimport { evaluateConditions } from \"../../../utils/evaluate-conditions\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { hasStoredRedirectPage } from \"./FormSettingsTab\";\n\n// ---------------------------------------------------------------------------\n// Field rendering\n// ---------------------------------------------------------------------------\n\nfunction PreviewFieldInput({\n field,\n value,\n onChange,\n}: {\n field: FormField;\n value: unknown;\n onChange: (value: unknown) => void;\n}) {\n const id = `preview-${field.name}`;\n switch (field.type) {\n case \"textarea\":\n return (\n <Textarea\n id={id}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n rows={\"rows\" in field ? (field.rows ?? 4) : 4}\n placeholder={field.placeholder}\n />\n );\n case \"checkbox\":\n // A real checkbox, not a toggle switch — the preview should look like\n // what a frontend form renders for this type.\n return (\n <Checkbox\n id={id}\n checked={Boolean(value)}\n onCheckedChange={checked => onChange(checked === true)}\n />\n );\n case \"select\": {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <Select\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={onChange}\n >\n <SelectTrigger\n id={id}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue placeholder={field.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {options.map(option => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n }\n case \"radio\": {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <RadioGroup\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={onChange}\n className=\"flex flex-col gap-2\"\n >\n {options.map(option => (\n <div key={option.value} className=\"flex items-center gap-2\">\n <RadioGroupItem\n value={option.value}\n id={`${id}-${option.value}`}\n />\n <Label htmlFor={`${id}-${option.value}`} className=\"font-normal\">\n {option.label}\n </Label>\n </div>\n ))}\n </RadioGroup>\n );\n }\n case \"number\":\n return (\n <Input\n id={id}\n type=\"number\"\n value={\n typeof value === \"number\" || typeof value === \"string\"\n ? String(value)\n : \"\"\n }\n onChange={e =>\n onChange(e.target.value === \"\" ? \"\" : Number(e.target.value))\n }\n placeholder={field.placeholder}\n />\n );\n case \"date\":\n case \"time\":\n return (\n <Input\n id={id}\n type={field.type}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n />\n );\n case \"file\":\n // Nothing uploads from a preview; the affordance is shown, disabled.\n return (\n <div className=\"flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n <Paperclip className=\"h-4 w-4\" aria-hidden=\"true\" />\n File upload (disabled in preview)\n </div>\n );\n default:\n return (\n <Input\n id={id}\n type={\n field.type === \"email\"\n ? \"email\"\n : field.type === \"url\"\n ? \"url\"\n : field.type === \"phone\"\n ? \"tel\"\n : \"text\"\n }\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n placeholder={field.placeholder}\n />\n );\n }\n}\n\n/**\n * Preview for a plugin-contributed field. The plugin owns the real input, which\n * renders from its own component in the live form; the builder preview shows a\n * labeled placeholder so the field's presence and order stay visible.\n */\nfunction PluginFieldPreview({ field }: { field: CustomFormField }) {\n return (\n <div className=\"space-y-1.5\">\n <Label htmlFor={`preview-${field.name}`}>\n {field.label || field.name}\n {field.required && <span className=\"ml-1 text-destructive\">*</span>}\n </Label>\n <div className=\"flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n Custom field ({field.type}) — renders from its plugin component in the\n live form.\n </div>\n {field.helpText && (\n <p className=\"text-xs text-muted-foreground\">{field.helpText}</p>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport interface FormPreviewProps {\n /** Array of form fields to preview (built-in or plugin-contributed). */\n fields: AnyFormField[];\n /** Form metadata */\n formData?: {\n name?: string;\n slug?: string;\n description?: string;\n };\n}\n\n/**\n * What the visitor is told after a successful submission.\n *\n * Its own component because the fall-through belongs to the MESSAGE branch: a\n * confirmation this does not name is previewed as a thank-you note, which is\n * how the page redirect came to be previewed as something it is not. Each\n * confirmation that is not a message has to be named here.\n */\nfunction Confirmation({\n settings,\n}: {\n settings: {\n confirmationType?: string;\n redirectUrl?: string;\n successMessage?: string;\n redirectPage?: unknown;\n };\n}) {\n const panel = \"border border-border bg-muted/40 p-4 text-sm text-foreground\";\n\n if (settings.confirmationType === \"redirect\") {\n return (\n <p className={panel}>\n The visitor would now be redirected to{\" \"}\n <span className=\"font-mono\">\n {settings.redirectUrl || \"(no redirect URL set)\"}\n </span>\n .\n </p>\n );\n }\n\n if (settings.confirmationType === \"relationship\") {\n // Whether a page is actually named, not merely that this confirmation is\n // chosen. Claiming a redirect with no page saved describes something that\n // cannot happen: the save is refused, and a form that reached this state\n // another way resolves to no redirect at all.\n const named = hasStoredRedirectPage(settings);\n return (\n <p className={panel}>\n {named\n ? \"The visitor would now be redirected to the linked page. Its URL is built when the form is submitted, from the pattern the site configured for that collection.\"\n : \"No page is selected yet, so this form has no destination. Choose one under Settings, or pick a different confirmation.\"}\n </p>\n );\n }\n\n return (\n <p className={panel}>\n {settings.successMessage || \"Thank you for your submission!\"}\n </p>\n );\n}\n\nexport function FormPreview({\n fields,\n formData,\n}: FormPreviewProps): JSX.Element {\n const { settings } = useFormBuilder();\n const [values, setValues] = useState<Record<string, unknown>>({});\n const [device, setDevice] = useState<\"desktop\" | \"mobile\">(\"desktop\");\n const [confirmed, setConfirmed] = useState(false);\n const [missingRequired, setMissingRequired] = useState<Set<string>>(\n new Set()\n );\n\n // Conditional logic runs against the live values with the SAME evaluator\n // the runtime uses — typing into the preview shows/hides fields for real.\n // Visibility is computed in field order against the values of fields that\n // are themselves visible: a hidden field's leftover value must not keep\n // satisfying a downstream condition (chained show/hide would misbehave).\n const visibleFields = useMemo(() => {\n const effectiveValues: Record<string, unknown> = {};\n const visible: AnyFormField[] = [];\n for (const field of fields) {\n if (field.type === \"hidden\") continue;\n const isVisible =\n !field.conditionalLogic?.enabled ||\n evaluateConditions(field.conditionalLogic, effectiveValues);\n if (isVisible) {\n visible.push(field);\n if (field.name in values) {\n effectiveValues[field.name] = values[field.name];\n }\n }\n }\n return visible;\n }, [fields, values]);\n\n const hiddenCount = fields.filter(field => field.type === \"hidden\").length;\n\n const reset = () => {\n setValues({});\n setConfirmed(false);\n setMissingRequired(new Set());\n };\n\n // The real form would refuse an empty required field, so the simulation\n // does too — an admin previewing required fields sees the requirement.\n const simulateSubmit = () => {\n const missing = new Set(\n visibleFields\n .filter(field => {\n // A plugin field renders a non-interactive placeholder in the preview\n // (its real input lives in the plugin's component in the live form),\n // so it can never be filled here — excluded like `file`, otherwise the\n // simulated submit would block forever with no way to satisfy it.\n if (!field.required || field.type === \"file\") return false;\n if (!isKnownFormField(field)) return false;\n const value = values[field.name];\n return value === undefined || value === \"\" || value === false;\n })\n .map(field => field.name)\n );\n setMissingRequired(missing);\n if (missing.size === 0) setConfirmed(true);\n };\n\n return (\n // One surface for the whole tab. The chrome and the stage are the same\n // panel seen twice, and leaving the chrome unbacked put it on the page's\n // grey while the stage below it had a frame — reading as a control strip\n // that had lost its box.\n <Card className=\"overflow-hidden p-0\">\n {/* Preview chrome: honest framing + device toggle + reset */}\n <div className=\"flex flex-col gap-3 border-b border-border px-6 py-4 sm:flex-row sm:items-center sm:justify-between\">\n <div className=\"flex items-center gap-2 text-sm text-muted-foreground\">\n <Eye className=\"h-4 w-4\" aria-hidden=\"true\" />\n Preview — a simulation of this form. Nothing here submits anywhere.\n </div>\n {/* Wraps rather than holding one row. The card around this clips, and\n at a ~360px panel the inset leaves less width than the device\n toggle and Reset need side by side — so an unwrapped row loses its\n right edge instead of overflowing visibly. */}\n <div className=\"flex flex-wrap items-center justify-end gap-2\">\n <Tabs\n value={device}\n onValueChange={value => setDevice(value as \"desktop\" | \"mobile\")}\n >\n {/* No corner override: the primitive already guarantees square,\n and a call site restating it is how the guarantee gets forked. */}\n <TabsList>\n <TabsTrigger value=\"desktop\" className=\"gap-1.5\">\n <Monitor className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Desktop\n </TabsTrigger>\n <TabsTrigger value=\"mobile\" className=\"gap-1.5\">\n <Smartphone className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Mobile\n </TabsTrigger>\n </TabsList>\n </Tabs>\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={reset}>\n <RotateCcw className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Reset\n </Button>\n </div>\n </div>\n\n {/* The simulated form, on a tinted stage so the sheet inside reads as a\n page rather than as more of this panel. The stage draws no border of\n its own — the card around it already does. */}\n <div className=\"flex justify-center bg-muted/30 p-6\">\n <div\n className={`w-full space-y-5 border border-border bg-background p-6 ${\n device === \"mobile\" ? \"max-w-95\" : \"max-w-2xl\"\n }`}\n >\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {formData?.name || \"Untitled form\"}\n </h3>\n {formData?.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {formData.description}\n </p>\n )}\n </div>\n\n {confirmed ? (\n <div className=\"space-y-4\">\n <Confirmation settings={settings} />\n <Button type=\"button\" variant=\"outline\" onClick={reset}>\n Fill again\n </Button>\n </div>\n ) : (\n <>\n {visibleFields.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No visible fields — add fields in the Builder tab.\n </p>\n )}\n {visibleFields.map(field =>\n !isKnownFormField(field) ? (\n <PluginFieldPreview key={field.name} field={field} />\n ) : (\n <div key={field.name} className=\"space-y-1.5\">\n {field.type !== \"checkbox\" && (\n <Label htmlFor={`preview-${field.name}`}>\n {field.label || field.name}\n {field.required && (\n <span className=\"ml-1 text-destructive\">*</span>\n )}\n </Label>\n )}\n {field.type === \"checkbox\" ? (\n <div className=\"flex items-center gap-2\">\n <PreviewFieldInput\n field={field}\n value={values[field.name]}\n onChange={value =>\n setValues(prev => ({\n ...prev,\n [field.name]: value,\n }))\n }\n />\n <Label htmlFor={`preview-${field.name}`}>\n {field.label || field.name}\n {field.required && (\n <span className=\"ml-1 text-destructive\">*</span>\n )}\n </Label>\n </div>\n ) : (\n <PreviewFieldInput\n field={field}\n value={values[field.name]}\n onChange={value =>\n setValues(prev => ({ ...prev, [field.name]: value }))\n }\n />\n )}\n {field.helpText && (\n <p className=\"text-xs text-muted-foreground\">\n {field.helpText}\n </p>\n )}\n {missingRequired.has(field.name) && (\n <p className=\"text-xs text-destructive\">\n This field is required.\n </p>\n )}\n </div>\n )\n )}\n\n <Button\n type=\"button\"\n onClick={simulateSubmit}\n disabled={visibleFields.length === 0}\n >\n {settings.submitButtonText || \"Submit\"}\n </Button>\n </>\n )}\n\n {hiddenCount > 0 && !confirmed && (\n <p className=\"text-xs text-muted-foreground\">\n <Badge\n variant=\"outline\"\n className=\"mr-1.5 rounded-none border-border px-1 py-0 text-[10px]\"\n >\n {hiddenCount}\n </Badge>\n hidden {hiddenCount === 1 ? \"field\" : \"fields\"} will be submitted\n invisibly.\n </p>\n )}\n </div>\n </div>\n </Card>\n );\n}\n\nexport default FormPreview;\n","\"use client\";\n\n/**\n * Form Builder View\n *\n * Custom admin view that provides a visual drag-and-drop form builder.\n * Layout mirrors the Collection Builder for visual consistency.\n *\n * @module admin/FormBuilderView\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport type { DocumentLockGates } from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Button,\n Card,\n FieldShell,\n FormActions,\n Input,\n Textarea,\n toast,\n Tabs,\n TabsList,\n TabsTrigger,\n Select,\n SelectTrigger,\n SelectValue,\n SelectContent,\n SelectItem,\n} from \"@nextlyhq/ui\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport type { FormFieldCatalogType } from \"nextly/field-catalog\";\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport { type JSX, useCallback, useEffect, useRef, useState } from \"react\";\n\nimport type { FormField, FormFieldTypeId } from \"../types\";\n\nimport { FieldCards } from \"./components/builder/FieldCards\";\nimport {\n FormNotificationsTab,\n type NotificationDefaults,\n} from \"./components/builder/FormNotificationsTab\";\nimport { FormPreview } from \"./components/builder/FormPreview\";\nimport {\n FormSettingsTab,\n type SpamDefaults,\n} from \"./components/builder/FormSettingsTab\";\nimport { notificationsBlockingSave } from \"./components/builder/notification-addresses\";\nimport {\n FormBuilderProvider,\n useFormBuilder,\n createFieldFromType,\n createNotification,\n type FormNotification,\n} from \"./context/FormBuilderContext\";\n\n/**\n * The key a form's version travels under, as the admin reads and sends it.\n * Spelled here because the plugin reaches the admin only through the SDK,\n * which does not publish it.\n */\nconst DOCUMENT_VERSION_KEY = \"_rev\";\n\n/**\n * What a save sends: an existing form's save names the version the editor's\n * copy started from, where it has one, so a save from an older copy is refused.\n */\nfunction saveBody(\n saveData: Record<string, unknown>,\n isCreating: boolean,\n startedFrom: string | undefined\n): Record<string, unknown> {\n return isCreating || startedFrom === undefined\n ? saveData\n : { ...saveData, [DOCUMENT_VERSION_KEY]: startedFrom };\n}\n\n/** The version a save answered, or the copy's own where it names none. */\nfunction versionAfterSave(\n saved: Record<string, unknown>,\n previous: string | undefined\n): string | undefined {\n const answered = saved[DOCUMENT_VERSION_KEY];\n return typeof answered === \"string\" ? answered : previous;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FormBuilderViewProps {\n id?: string;\n entryId?: string;\n collection?: string;\n collectionSlug?: string;\n isCreating?: boolean;\n initialData?: {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\" | \"closed\";\n closedMessage?: string;\n fields?: unknown[];\n settings?: Record<string, unknown>;\n notifications?: unknown[];\n /**\n * The form's version as read, which a save sends back so one made from an\n * older version is refused rather than replacing what someone saved since.\n */\n _rev?: string;\n };\n onSave?: (data: unknown) => void;\n onSuccess?: (entry?: Record<string, unknown>) => void;\n onCancel?: () => void;\n /**\n * What a colleague's claim on this form withholds, handed over by the admin\n * page that renders this view.\n *\n * The strip above this builder says in words that unsaved changes cannot be\n * saved while someone else holds the document. Committing anyway would make\n * that sentence false and would overwrite the holder's row, so the save reads\n * it too.\n */\n documentLock?: DocumentLockGates;\n}\n\n// ============================================================================\n// Helper\n// ============================================================================\n\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/[\\s_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n// ============================================================================\n// Inner Component (uses context)\n// ============================================================================\n\nfunction FormBuilderViewInner({\n isCreating = false,\n collectionSlug = \"forms\",\n entryId,\n onSave,\n onSuccess,\n onCancel,\n documentLock,\n version,\n}: Pick<\n FormBuilderViewProps,\n | \"isCreating\"\n | \"collectionSlug\"\n | \"entryId\"\n | \"onSave\"\n | \"onSuccess\"\n | \"onCancel\"\n | \"documentLock\"\n> & {\n /** The form's version as read; see `initialData._rev`. */\n version?: string;\n}) {\n // The version the next save sends: the one read, then the one each save\n // answered, so a second save is not refused for this editor's first.\n const startedFrom = useRef(version);\n const {\n fields,\n activeTab,\n isDirty,\n formData,\n settings,\n notifications,\n setActiveTab,\n addField,\n selectField,\n seedNotifications,\n updateFormData,\n markAsSaved,\n } = useFormBuilder();\n\n const queryClient = useQueryClient();\n\n const [isSaving, setIsSaving] = useState(false);\n const [saveError, setSaveError] = useState<string | null>(null);\n\n // The host's resolved field enable/disable map. `null` until the config\n // request settles, so the Add dialog never flashes the unfiltered set. If\n // the request errors, every type is offered: the exclude list is an\n // authoring preference (the server accepts any field type in the form's\n // JSON regardless), so hiding all types on a transient failure would cost\n // more than a temporarily unfiltered picker.\n const [enabledTypes, setEnabledTypes] = useState<\n FormFieldCatalogType[] | null\n >(null);\n\n // Type ids the host explicitly disabled (`config.fields[type] === false`),\n // including plugin type ids — so the host exclude layer applies to\n // contributed field types too, not just built-ins.\n const [disabledFieldTypes, setDisabledFieldTypes] = useState<Set<string>>(\n () => new Set()\n );\n\n // The host's notification defaults (plugin options). `null` until the\n // config request settles; `{}` when the request failed or nothing is\n // configured, so consumers can distinguish \"loading\" from \"no defaults\".\n const [notificationDefaults, setNotificationDefaults] =\n useState<NotificationDefaults | null>(null);\n\n // Plugin-level spam defaults, so the Settings tab can show what\n // \"inherit\" resolves to. `null` while the config request settles.\n const [spamDefaults, setSpamDefaults] = useState<SpamDefaults | null>(null);\n\n // Collections a form may redirect to. `null` while the config request\n // settles, so the Settings tab can tell \"not configured\" from \"not known\n // yet\" and not flash the option away.\n const [redirectCollections, setRedirectCollections] = useState<\n string[] | null\n >(null);\n\n // Whether the configuration request FAILED, kept apart from what it\n // returned. A 403 or a 500 mapped to an empty list is indistinguishable from\n // \"this site configures no redirect targets\" — so the Settings tab would\n // state that as fact, hide the option, and offer no way to tell the\n // difference or try again.\n const [redirectConfigFailed, setRedirectConfigFailed] = useState(false);\n\n useEffect(() => {\n let cancelled = false;\n const allTypes = FORM_FIELD_TYPE_CATALOG.map(entry => entry.type);\n void fetch(\n \"/admin/api/plugins/@nextlyhq/plugin-form-builder/builder-config\",\n { credentials: \"include\" }\n )\n .then(response => (response.ok ? response.json() : null))\n .then(\n (\n config: {\n fields?: Record<string, boolean>;\n notifications?: NotificationDefaults;\n spamProtection?: SpamDefaults;\n redirectCollections?: string[];\n } | null\n ) => {\n if (cancelled) return;\n setEnabledTypes(\n config?.fields\n ? allTypes.filter(type => config.fields?.[type] !== false)\n : allTypes\n );\n setDisabledFieldTypes(\n new Set(\n Object.entries(config?.fields ?? {})\n .filter(([, enabled]) => enabled === false)\n .map(([type]) => type)\n )\n );\n setNotificationDefaults(config?.notifications ?? {});\n setSpamDefaults(config?.spamProtection ?? {});\n // `config === null` is the failure branch: the response was not ok.\n setRedirectConfigFailed(config === null);\n setRedirectCollections(config?.redirectCollections ?? []);\n }\n )\n .catch(() => {\n if (cancelled) return;\n setEnabledTypes(allTypes);\n setNotificationDefaults({});\n setSpamDefaults({});\n setRedirectConfigFailed(true);\n setRedirectCollections([]);\n });\n return () => {\n cancelled = true;\n };\n }, []);\n\n // Seed a new form with the default admin-notification rule once the host\n // defaults are known. Guarded by a ref so deleting the seeded rule is\n // final — the effect must never re-seed a list the user emptied.\n const seededRef = useRef(false);\n useEffect(() => {\n if (!isCreating || notificationDefaults === null || seededRef.current) {\n return;\n }\n seededRef.current = true;\n seedNotifications([\n {\n ...createNotification(),\n name: \"Admin notification\",\n to: notificationDefaults.defaultToEmail ?? \"\",\n },\n ]);\n }, [isCreating, notificationDefaults, seedNotifications]);\n\n const handleAddField = useCallback(\n (type: FormFieldTypeId) => {\n const newField = createFieldFromType(\n type,\n fields.map(f => f.name)\n );\n addField(newField);\n selectField(newField.name);\n },\n [fields, addField, selectField]\n );\n\n // ── Save / Cancel ─────────────────────────────────────────────────────────\n const handleSave = useCallback(async () => {\n // The commit is where the refusal lives, following the rule the entry\n // editor sets: a write passes one gate rather than each control being\n // disabled separately. The button below reads the same fact, so the\n // interface does not offer what this would refuse.\n //\n // Today that button is this callback's only caller, so the two cannot be\n // told apart by a test. What the gate is for is the next caller - a\n // shortcut, a second action bar - arriving without one.\n if (documentLock?.actionsDisabled === true) return;\n\n // Check if there are fields\n if (fields.length === 0) {\n toast.error(\"Please add at least one field to the form\");\n return;\n }\n\n // A notification is written to the form as it is typed, so nothing between\n // the editor and here refuses one. The sheet this replaced disabled its own\n // commit for a blank name, and the field rejects a malformed address on\n // blur — both preconditions move to this save, which is now the only\n // commit, and both are answered by the module the editor itself asks.\n const blocked = notificationsBlockingSave(notifications);\n if (blocked) {\n toast.error(blocked);\n return;\n }\n\n const { id: _id, ...formDataWithoutId } = formData;\n const saveData = {\n ...formDataWithoutId,\n fields,\n settings,\n notifications,\n };\n\n if (onSave) {\n onSave(saveData);\n return;\n }\n\n setIsSaving(true);\n setSaveError(null);\n try {\n const url = isCreating\n ? `/admin/api/collections/${collectionSlug}/entries`\n : `/admin/api/collections/${collectionSlug}/entries/${entryId}`;\n const response = await fetch(url, {\n method: isCreating ? \"POST\" : \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(\n saveBody(saveData, isCreating, startedFrom.current)\n ),\n });\n if (!response.ok) {\n throw new Error(\n saveErrorMessage(\n await response.json().catch(() => ({})),\n response.statusText\n )\n );\n }\n\n const result = (await response.json()) as {\n message?: string;\n item?: Record<string, unknown>;\n } & Record<string, unknown>;\n const savedEntry: Record<string, unknown> = result.item ?? result;\n startedFrom.current = versionAfterSave(savedEntry, startedFrom.current);\n void queryClient.invalidateQueries({\n queryKey: [\"entries\", \"list\", collectionSlug],\n });\n void queryClient.invalidateQueries({ queryKey: [\"entries\", \"count\"] });\n if (!isCreating && entryId) {\n void queryClient.invalidateQueries({\n queryKey: [\"entries\", \"detail\", collectionSlug, entryId],\n });\n }\n markAsSaved();\n toast.success(\n isCreating ? \"Form created successfully\" : \"Form updated successfully\"\n );\n if (onSuccess) onSuccess(savedEntry);\n } catch (error) {\n const message =\n error instanceof Error ? error.message : \"Failed to save form\";\n setSaveError(message);\n toast.error(message);\n } finally {\n setIsSaving(false);\n }\n }, [\n formData,\n fields,\n settings,\n notifications,\n onSave,\n onSuccess,\n isCreating,\n collectionSlug,\n entryId,\n queryClient,\n markAsSaved,\n // Read inside, so it belongs here. Without it the callback closes over the\n // claim as it stood when the form mounted, and a colleague taking the\n // document over mid-session would leave this still committing.\n documentLock?.actionsDisabled,\n ]);\n\n const handleCancel = useCallback(() => {\n if (onCancel) onCancel();\n else if (typeof window !== \"undefined\") window.history.back();\n }, [onCancel]);\n\n const handleNameChange = useCallback(\n (name: string) => {\n updateFormData({ name });\n if (!formData.slug || formData.slug === slugify(formData.name)) {\n updateFormData({ slug: slugify(name) });\n }\n },\n [formData.name, formData.slug, updateFormData]\n );\n\n // Main tabs\n const mainTabs = [\n {\n value: \"builder\" as const,\n label: \"Builder\",\n count: fields.length > 0 ? fields.length : null,\n },\n {\n value: \"preview\" as const,\n label: \"Preview\",\n count: null,\n },\n { value: \"settings\" as const, label: \"Settings\", count: null },\n {\n value: \"notifications\" as const,\n label: \"Notifications\",\n count: notifications.length > 0 ? notifications.length : null,\n },\n ];\n\n // ── Render ────────────────────────────────────────────────────────────────\n // The host page owns the measure, the centring and the padding — this view\n // renders as content inside its frame, under its breadcrumb. Declaring a\n // shell here would nest one inset inside another and put this heading out of\n // line with that breadcrumb. Nothing below hand-rolls a card, a width cap or\n // a hack to escape either.\n return (\n <>\n {/* ── Page header ── */}\n <div className=\"mb-6\">\n <h1 className=\"text-2xl font-semibold text-foreground tracking-tight\">\n {isCreating ? \"Create Form\" : \"Edit Form\"}\n </h1>\n <p className=\"text-sm text-muted-foreground mt-1\">\n {isCreating\n ? \"Design a new form with drag-and-drop fields.\"\n : \"Modify your form fields and settings.\"}\n </p>\n </div>\n\n {/* ── Form metadata ──\n A card, so the fields that identify the form read as one group and\n have a surface to sit on. The builder canvas below is deliberately\n NOT carded: it draws its own drop zone, and a frame around a frame\n reads as a nested box rather than as structure. */}\n {/* `pb-6` against `pt-5`: the box padding is equal at 20px, but the\n first thing inside is a LABEL and the last is a control. A label's\n line box carries 3px of leading above its glyphs, so the ink starts\n lower than the box does while the control's border ends flush —\n measured 24px of visible space above and 21px below. The extra step\n on the bottom is what makes the two read as equal. */}\n <Card className=\"mb-6 px-6 pt-5 pb-6\">\n <div className=\"flex flex-wrap gap-4\">\n <FieldShell\n label=\"Form Name\"\n htmlFor=\"form-name\"\n className=\"flex-1 min-w-[200px]\"\n >\n <Input\n type=\"text\"\n value={formData.name || \"\"}\n onChange={e => handleNameChange(e.target.value)}\n placeholder=\"e.g., Contact Form\"\n />\n </FieldShell>\n\n <FieldShell\n label=\"Slug\"\n htmlFor=\"form-slug\"\n className=\"flex-1 min-w-[160px]\"\n >\n <Input\n type=\"text\"\n value={formData.slug || \"\"}\n onChange={e => updateFormData({ slug: e.target.value })}\n placeholder=\"e.g., contact-form\"\n className=\"placeholder:text-muted-foreground\"\n />\n </FieldShell>\n\n {/* Status is a Radix Select: its Root accepts a fixed prop list and\n forwards none of the rest to the trigger DOM node, so a\n FieldShell clone's id/aria-describedby/aria-invalid would land\n on a component that drops them rather than on the focusable\n trigger. The render-function form of `children` sidesteps\n that: FieldShell hands the computed wiring to this function,\n which applies it to SelectTrigger — the actual focusable,\n ARIA-bearing element — instead of a clone that can never reach\n past `Select`'s root. */}\n <div className=\"w-36\">\n <FieldShell label=\"Status\" htmlFor=\"form-status\">\n {({ id, describedBy, invalid }) => (\n <Select\n value={formData.status || \"draft\"}\n onValueChange={value =>\n updateFormData({\n status: value as \"draft\" | \"published\" | \"closed\",\n })\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={describedBy}\n aria-invalid={invalid}\n >\n <SelectValue placeholder=\"Status\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"draft\">Draft</SelectItem>\n <SelectItem value=\"published\">Published</SelectItem>\n <SelectItem value=\"closed\">Closed</SelectItem>\n </SelectContent>\n </Select>\n )}\n </FieldShell>\n </div>\n </div>\n\n {/* Shown only once the author has chosen Closed. The explanation is\n written at the moment the decision is made, which is when they know\n what it should say; a box that is always there would sit empty on\n the great majority of forms, which are never closed. */}\n {formData.status === \"closed\" && (\n <div className=\"mt-4\">\n <FieldShell\n label=\"Message for visitors\"\n htmlFor=\"form-closed-message\"\n // Prose, not a value: the half width the other fields take is\n // sized for a name or a slug, and wrapping a sentence every six\n // words makes it harder to read back what was written.\n width=\"full\"\n description=\"Shown to anyone who reaches this form now that it is closed. Left empty, they are told only that it is not accepting submissions.\"\n >\n <Textarea\n value={formData.closedMessage ?? \"\"}\n onChange={e =>\n updateFormData({ closedMessage: e.target.value })\n }\n placeholder=\"e.g., Applications closed on 31 March. Thank you to everyone who applied.\"\n rows={3}\n />\n </FieldShell>\n </div>\n )}\n </Card>\n\n {/* ── Main tab navigation ──\n On the page rather than inside the card above: the rail switches what\n is BELOW it, so grouping it with the metadata would attach it to the\n wrong thing. */}\n <div className=\"border-b border-border mb-6\">\n <Tabs\n value={activeTab}\n onValueChange={v => setActiveTab(v as typeof activeTab)}\n >\n {/* Layout only. The underline, the active and hover colours, the\n focus ring and the disabled state all come from the shared\n primitive; restating them here is how two copies of one\n appearance start drifting.\n\n The strip can overflow its container, so it asks for scrolling\n through `scrollable` rather than by putting `overflow-x-auto` on\n the list: that spelling makes the LIST the scroll container, which\n costs the rail its indicator. */}\n <TabsList scrollable className=\"bg-transparent justify-start gap-0\">\n {mainTabs.map(tab => (\n <TabsTrigger\n key={tab.value}\n value={tab.value}\n className=\"shrink-0 whitespace-nowrap\"\n >\n {tab.label}\n {/* `rounded-sm`, the adornment step, matching the shared\n Badge and the identically sized notification count.\n Square is the TAB's corner and it exists so the\n underline runs flush to the trigger's edges; a chip\n inside the label inherits none of that reasoning, and\n carrying it made the count read as a second tab. */}\n {tab.count !== null && (\n <span\n className={`inline-flex items-center justify-center min-w-[18px] h-[18px] px-1 text-[10px] font-semibold rounded-sm ml-2 transition-colors ${\n activeTab === tab.value\n ? \"bg-primary/5 text-primary\"\n : \"bg-primary/5 text-muted-foreground\"\n }`}\n >\n {tab.count}\n </span>\n )}\n </TabsTrigger>\n ))}\n </TabsList>\n </Tabs>\n </div>\n\n {/* ── Tab content ── */}\n\n {/* Builder tab */}\n {activeTab === \"builder\" && (\n <FieldCards\n enabledTypes={enabledTypes}\n disabledFieldTypes={disabledFieldTypes}\n onAddField={handleAddField}\n />\n )}\n\n {/* Preview tab */}\n {activeTab === \"preview\" && (\n <FormPreview fields={fields} formData={formData} />\n )}\n\n {/* Settings tab */}\n {activeTab === \"settings\" && (\n <FormSettingsTab\n spamDefaults={spamDefaults}\n redirectCollections={redirectCollections}\n redirectConfigFailed={redirectConfigFailed}\n />\n )}\n\n {/* Notifications tab */}\n {activeTab === \"notifications\" && (\n <FormNotificationsTab defaults={notificationDefaults} />\n )}\n\n {/* Save error */}\n {saveError && (\n // Full-strength destructive border so the error box boundary is perceivable over its tinted fill.\n <div className=\"mt-6 p-3 text-sm text-destructive bg-destructive/10 border border-destructive rounded-none\">\n {saveError}\n </div>\n )}\n\n {/* A form commits as one document, so there is exactly one action bar,\n fed the dirty flag the form state already tracks rather than a\n second computation of it. */}\n <FormActions dirty={isDirty}>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleCancel}\n disabled={isSaving}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={() => {\n void handleSave();\n }}\n disabled={isSaving || documentLock?.actionsDisabled === true}\n className=\"flex items-center gap-1.5\"\n >\n {isSaving ? (\n <>\n <svg\n className=\"animate-spin h-3.5 w-3.5\"\n viewBox=\"0 0 24 24\"\n fill=\"none\"\n >\n <circle\n className=\"opacity-25\"\n cx=\"12\"\n cy=\"12\"\n r=\"10\"\n stroke=\"currentColor\"\n strokeWidth=\"4\"\n />\n <path\n className=\"opacity-75\"\n fill=\"currentColor\"\n d=\"M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z\"\n />\n </svg>\n Saving…\n </>\n ) : isCreating ? (\n \"Create\"\n ) : (\n \"Save Changes\"\n )}\n </Button>\n </FormActions>\n </>\n );\n}\n\n// ============================================================================\n// Main Component (provides context)\n// ============================================================================\n\n/**\n * What to tell an author when a save is refused.\n *\n * A validation refusal carries the generic `\"Validation failed.\"` at the top\n * level and puts the actionable part in `data.errors`. Reporting only the top\n * level tells an author their form was rejected and nothing about what to\n * change — every rule in the collection produces that same sentence.\n */\nexport function saveErrorMessage(body: unknown, statusText: string): string {\n const error = (\n body as {\n error?: {\n code?: string;\n message?: string;\n data?: {\n errors?: { message?: string }[];\n changedBy?: { name?: unknown };\n };\n };\n }\n )?.error;\n\n // Refused because someone saved the form since: who, where the refusal\n // names them, and what to do. The edits stay in the editor.\n if (error?.code === \"STALE_VERSION\") {\n const name = error.data?.changedBy?.name;\n const who =\n typeof name === \"string\" && name.trim() !== \"\" ? name.trim() : \"Someone\";\n return `${who} changed this form since you opened it. Reload to see their change; your edits are still here, not saved.`;\n }\n\n const fieldIssues = (error?.data?.errors ?? [])\n .map(issue => issue.message)\n .filter((message): message is string => Boolean(message));\n\n return (\n fieldIssues.join(\" \") ||\n error?.message ||\n `Failed to save form: ${statusText}`\n );\n}\n\nexport function FormBuilderView({\n id,\n entryId,\n collection,\n collectionSlug,\n isCreating,\n initialData,\n onSave,\n onSuccess,\n onCancel,\n documentLock,\n}: FormBuilderViewProps): JSX.Element {\n const resolvedEntryId = entryId || id || initialData?.id;\n const resolvedCollectionSlug = collectionSlug || collection || \"forms\";\n const resolvedIsCreating = isCreating ?? !resolvedEntryId;\n const providerKey = resolvedEntryId || \"new\";\n\n return (\n <FormBuilderProvider\n key={providerKey}\n readOnly={documentLock?.readOnly === true}\n initialData={{\n id: resolvedEntryId,\n name: initialData?.name,\n slug: initialData?.slug,\n description: initialData?.description,\n status: initialData?.status,\n closedMessage: initialData?.closedMessage,\n fields: initialData?.fields as FormField[],\n settings: initialData?.settings as Record<string, unknown>,\n notifications: initialData?.notifications as FormNotification[],\n }}\n >\n <FormBuilderViewInner\n isCreating={resolvedIsCreating}\n collectionSlug={resolvedCollectionSlug}\n entryId={resolvedEntryId}\n onSave={onSave}\n onSuccess={onSuccess}\n onCancel={onCancel}\n documentLock={documentLock}\n version={initialData?._rev}\n />\n </FormBuilderProvider>\n );\n}\n\nexport default FormBuilderView;\n","/**\n * Marking a submission read when it is opened from the New form submissions\n * card: the status the submissions list sets, through the same collection\n * update the list's own status changes are sent through, as the reader.\n *\n * Sent as an update of the submissions that match the id AND are still new,\n * so a submission someone archived, flagged or read since the card loaded is\n * matched by nothing and keeps its status. The update is judged as every\n * update of the collection is (`update-<slug>` and its rule): a reader who\n * may not update submissions is refused, and the submission stays new.\n *\n * @module dashboard/mark-read\n */\n\nimport { NEW_STATUS } from \"./new-submissions\";\n\n/** The status an opened submission moves to, as the submissions list names it. */\nconst READ_STATUS = \"read\";\n\n/** The admin's API root, which the submissions list sends its updates to. */\nconst ADMIN_API = \"/admin/api\";\n\n/** The request that marks submission `id` of `collection` read while it is still new. */\nexport function markReadRequest(\n collection: string,\n id: string\n): { url: string; init: RequestInit } {\n return {\n url: `${ADMIN_API}/collections/${encodeURIComponent(collection)}/entries`,\n init: {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n // The row's link navigates away at once; kept alive, the update is\n // still sent when that navigation leaves the page.\n keepalive: true,\n // An update by query writes one value to whatever matches, with no\n // version of its own for each document, so it says it sets the status\n // whatever is stored: the `where` above is what keeps it to a\n // submission still new.\n body: JSON.stringify({\n where: { id: { equals: id }, status: { equals: NEW_STATUS } },\n data: { status: READ_STATUS, _overwrite: true },\n }),\n },\n };\n}\n\n/**\n * Marks submission `id` read, answering whether the update was taken. A\n * refusal or a failed request answers `false` and leaves it new, which the\n * card and the list then still say; it never rejects.\n */\nexport async function markRead(\n collection: string,\n id: string\n): Promise<boolean> {\n const { url, init } = markReadRequest(collection, id);\n try {\n return (await fetch(url, init)).ok;\n } catch {\n return false;\n }\n}\n","\"use client\";\n\n/**\n * The New form submissions card's body: the newest new submissions as core's\n * list rows, each opening its submission and marking it read, with the\n * card's footer leading to every submission, and how many are new beside its\n * title. While nothing is new it says so, with a link to every submission,\n * and its title carries no count.\n *\n * Drawn only with data: the frame draws loading and every failure itself.\n * The empty state is drawn here rather than by the frame, because its link\n * is the submissions collection the site installed, which only the card's\n * data knows; the frame's would name the slug the plugin declared. Built from\n * the widget kit alone, so it reads like core's list cards beside it.\n *\n * @module dashboard/NewSubmissionsCard\n */\n\nimport {\n formatWidgetTime,\n FrameState,\n useRefreshDashboard,\n useWidgetClock,\n Widget,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport type { JSX, MouseEvent } from \"react\";\n\nimport { markRead } from \"./mark-read\";\nimport type { NewSubmissionRow, NewSubmissionsData } from \"./new-submissions\";\n\n/** The mouse button that opens a link in a new tab. */\nconst MIDDLE_BUTTON = 1;\n\n/** What a row says under its title: its form, and the first line of its first text answer. */\nfunction metaOf(row: NewSubmissionRow): readonly string[] {\n return [row.form, row.excerpt].filter(\n (part): part is string => part !== undefined\n );\n}\n\n/** The row whose link a click landed on, if it landed on one. */\nfunction openedRow(\n event: MouseEvent,\n rows: readonly NewSubmissionRow[]\n): NewSubmissionRow | undefined {\n if (!(event.target instanceof Element)) return undefined;\n const href = event.target.closest(\"a[href]\")?.getAttribute(\"href\");\n return href === null || href === undefined\n ? undefined\n : rows.find(row => row.href === href);\n}\n\n/**\n * Nothing new, with a way to every submission. Offered to whoever sees the\n * card: the card is shown only to a reader who may read the submissions\n * collection the site installed, which is where the link leads.\n */\nfunction NothingNew({ allHref }: { allHref: string }): JSX.Element {\n return (\n <FrameState\n state={{\n kind: \"empty\",\n empty: {\n title: \"No new submissions\",\n action: { label: \"All submissions\", href: allHref },\n },\n }}\n />\n );\n}\n\n/** The card's rows, as many as its size holds. */\nexport function NewSubmissionsCard({\n data,\n}: {\n data: NewSubmissionsData;\n}): JSX.Element {\n // One instant for every row, moved on each minute while the card is\n // mounted, so \"just now\" becomes \"1m ago\" without the card being reloaded.\n const now = useWidgetClock();\n const refresh = useRefreshDashboard();\n // The title carries how many are new, the total the header's part counts\n // from the same read (\"1000+\" where that is a floor); nothing while none\n // are, as the empty state says so.\n Widget.useTitleCount(\n data.total > 0 ? data.total : undefined,\n data.atLeast === true\n );\n if (data.total === 0) return <NothingNew allHref={data.allHref} />;\n // Opening a row, by a click or a middle click into a new tab, marks its\n // submission read; a click anywhere else in the card does nothing. Once it\n // is taken, the cards and the header's count are asked again: a dashboard\n // left open behind a new tab would otherwise go on listing it.\n const opened = (event: MouseEvent) => {\n const row = openedRow(event, data.items);\n if (row === undefined) return;\n void markRead(data.collection, row.id).then(taken => {\n if (taken) refresh();\n });\n };\n return (\n // Laid out as if absent (`contents`), so the list sits in the card's body\n // exactly as it would alone; it is here to hear a row being opened.\n <div\n className=\"contents\"\n onClickCapture={opened}\n onAuxClickCapture={event => {\n if (event.button === MIDDLE_BUTTON) opened(event);\n }}\n >\n <Widget.List\n rows={data.items}\n title={row => row.title}\n href={row => row.href}\n meta={metaOf}\n trailing={row =>\n row.at === undefined ? undefined : (\n <time dateTime={row.at}>{formatWidgetTime(row.at, now)}</time>\n )\n }\n viewAllHref={data.allHref}\n />\n </div>\n );\n}\n","/**\n * Form Builder Admin Components\n *\n * Entry point for admin UI components used by the Nextly admin panel.\n * These components are referenced via component paths in collection configs.\n *\n * Component paths use the format: \"@nextlyhq/plugin-form-builder/admin#ExportName\"\n *\n * ## Auto-Registration\n *\n * This module automatically registers its components with the admin panel\n * when imported. No manual registration is required - just add the plugin\n * to your Nextly config and the admin will detect and load the components.\n *\n * @module admin\n * @since 0.1.0\n */\n\n// ============================================================================\n// Main View Components\n// ============================================================================\n\n// ============================================================================\n// Component Registration\n// ============================================================================\n\nimport {\n registerComponents,\n registerKnownPlugin,\n type ComponentPath,\n} from \"@nextlyhq/plugin-sdk/admin\";\n\nimport { SubmissionsView } from \"./components/submissions/SubmissionsView\";\nimport { FormBuilderView } from \"./FormBuilderView\";\n\nexport { FormBuilderView, type FormBuilderViewProps } from \"./FormBuilderView\";\nexport { SubmissionsView } from \"./components/submissions/SubmissionsView\";\nexport { SubmissionSheet } from \"./components/submissions/SubmissionSheet\";\n\n// ============================================================================\n// Dashboard Widgets\n// ============================================================================\n\n// The New form submissions card's body, at the path its widget declares\n// (\"@nextlyhq/plugin-form-builder/admin#NewSubmissionsCard\"). A site's\n// generated component map imports it from here.\nexport { NewSubmissionsCard } from \"../dashboard/NewSubmissionsCard\";\n\n// ============================================================================\n// Builder Sub-Components\n// ============================================================================\n\nexport {\n FieldCards,\n AddFieldDialog,\n FieldEditor,\n FormPreview,\n type FieldCardsProps,\n type AddFieldDialogProps,\n type FieldEditorProps,\n type FormPreviewProps,\n} from \"./components/builder\";\n\n// ============================================================================\n// Context & Hooks\n// ============================================================================\n\nexport {\n FormBuilderProvider,\n useFormBuilder,\n useOptionalFormBuilder,\n createFieldFromType,\n createNotification,\n generateFieldName,\n generateFieldLabel,\n DEFAULT_SETTINGS,\n type FormBuilderState,\n type FormBuilderActions,\n type FormBuilderContextValue,\n type FormBuilderProviderProps,\n type FormSettings,\n type FormNotification,\n} from \"./context/FormBuilderContext\";\n\n// ============================================================================\n// Tab Components\n// ============================================================================\n\nexport { FormSettingsTab } from \"./components/builder/FormSettingsTab\";\nexport { FormNotificationsTab } from \"./components/builder/FormNotificationsTab\";\n\n/** Component path for FormBuilderView */\nexport const FORM_BUILDER_VIEW_PATH: ComponentPath =\n \"@nextlyhq/plugin-form-builder/admin#FormBuilderView\";\n\n/** Component path for SubmissionsView */\nexport const SUBMISSIONS_VIEW_PATH: ComponentPath =\n \"@nextlyhq/plugin-form-builder/admin#SubmissionsView\";\n\n// ============================================================================\n// Auto-Registration with Admin\n// ============================================================================\n\n/**\n * Eagerly register components at module load time.\n *\n * This ensures components are available immediately when the admin\n * tries to resolve them, without needing to wait for async registration.\n */\nregisterComponents({\n [FORM_BUILDER_VIEW_PATH]: FormBuilderView,\n [SUBMISSIONS_VIEW_PATH]: SubmissionsView,\n});\n\n/**\n * Also register with the known plugin system for consistency.\n *\n * This supports the auto-registration flow where the admin detects\n * component paths and triggers registration. Since we eagerly register\n * above, this callback is effectively a no-op but keeps the plugin\n * compatible with both eager and lazy registration patterns.\n */\nregisterKnownPlugin(\n \"@nextlyhq/plugin-form-builder\",\n // eslint-disable-next-line @typescript-eslint/require-await -- registerKnownPlugin's second arg is typed `() => Promise<void>` (strict, not a union); body is synchronous\n async () => {\n // Components already registered above, but re-register for safety\n registerComponents({\n [FORM_BUILDER_VIEW_PATH]: FormBuilderView,\n [SUBMISSIONS_VIEW_PATH]: SubmissionsView,\n });\n }\n);\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","names":["PAGE_SIZE","icons","readJson","valueText","TRIGGER","ConditionRow","TRIGGER","ROUTES","NotificationRow","PageHeader"],"sources":["../../src/admin/use-admin-permissions.ts","../../src/admin/components/editor/useSubmissionCount.ts","../../src/admin/components/submissions/bulk-actions.ts","../../src/admin/components/submissions/date-filter.ts","../../src/admin/components/submissions/ExportMenu.tsx","../../src/admin/components/submissions/SelectionBar.tsx","../../src/admin/components/submissions/submission-row.ts","../../src/admin/components/submissions/submission-columns.tsx","../../src/admin/components/submissions/DateFilter.tsx","../../src/admin/components/submissions/SubmissionsToolbar.tsx","../../src/admin/components/submissions/use-columns-left-out.ts","../../src/admin/components/submissions/use-status-counts.ts","../../src/admin/components/submissions/SubmissionsView.tsx","../../src/admin/context/field-names.ts","../../src/admin/context/use-been-live.ts","../../src/admin/context/change-history.ts","../../src/admin/context/use-change-history.ts","../../src/admin/context/use-field-identity.ts","../../src/admin/context/FormBuilderContext.tsx","../../src/admin/components/builder/field-badges.ts","../../src/admin/components/builder/ConditionalLogicEditor.tsx","../../src/admin/components/builder/panel-field.ts","../../src/admin/components/builder/forms-field-surface.tsx","../../src/admin/components/builder/FieldInspector.tsx","../../src/admin/components/builder/FieldCards.tsx","../../src/admin/components/builder/settings-rows.tsx","../../src/admin/components/builder/FormIdentitySection.tsx","../../src/admin/components/notifications/notification-address.ts","../../src/admin/components/notifications/notification-sources.ts","../../src/admin/use-builder-config.ts","../../src/admin/components/notifications/notification-summary.ts","../../src/admin/components/notifications/NotificationConditions.tsx","../../src/admin/components/builder/notification-addresses.ts","../../src/admin/components/notifications/NotificationEmail.tsx","../../src/admin/components/submissions/SubmissionEmails.tsx","../../src/admin/components/notifications/NotificationRecentSends.tsx","../../src/admin/components/notifications/NotificationsList.tsx","../../src/admin/components/notifications/NotificationRecipients.tsx","../../src/admin/components/notifications/NotificationPage.tsx","../../src/admin/components/builder/FormNotificationsTab.tsx","../../src/admin/components/builder/BotCheckRows.tsx","../../src/admin/components/builder/ClosingRows.tsx","../../src/admin/components/builder/RedirectPagePicker.tsx","../../src/admin/components/builder/FormSettingsTab.tsx","../../src/admin/components/builder/preview-checks.ts","../../src/admin/components/builder/FormPreview.tsx","../../src/admin/components/editor/form-editor-address.ts","../../src/admin/components/editor/FormStatusMenu.tsx","../../src/admin/components/editor/FormEditorBar.tsx","../../src/admin/components/editor/FormEditorHeader.tsx","../../src/admin/form-editor-hooks.ts","../../src/admin/save-refusal.ts","../../src/admin/stale-save.ts","../../src/admin/FormBuilderView.tsx","../../src/admin/components/submissions/browser-label.ts","../../src/admin/components/submissions/submission-fields.tsx","../../src/admin/components/submissions/SubmissionAnswers.tsx","../../src/admin/use-submission-fields.ts","../../src/admin/SubmissionPage.tsx","../../src/dashboard/mark-read.ts","../../src/dashboard/NewSubmissionsCard.tsx","../../src/admin/index.ts"],"sourcesContent":["\"use client\";\n\n/**\n * What the signed-in person may do, as the admin's permissions route answers\n * it: one reading of that answer for every Forms screen, so the editor and the\n * submissions list cannot come to disagree about what someone may see or do.\n *\n * @module admin/use-admin-permissions\n */\nimport { useQuery } from \"@tanstack/react-query\";\n\n/** Whether the person holds a permission, spelled as stored (`read-forms`). */\nexport type PermissionCheck = (permission: string) => boolean;\n\n/** The check the permissions route's answer gives; a super admin holds all. */\nfunction permissionsFrom(body: unknown): PermissionCheck {\n if (typeof body !== \"object\" || body === null) return () => false;\n const superAdmin = \"isSuperAdmin\" in body && body.isSuperAdmin === true;\n const held = new Set<string>(\n \"permissions\" in body && Array.isArray(body.permissions)\n ? body.permissions.filter(\n (each): each is string => typeof each === \"string\"\n )\n : []\n );\n return permission => superAdmin || held.has(permission);\n}\n\n/** Reads the person's permissions; a refused read is an error. */\nexport async function readPermissions(): Promise<PermissionCheck> {\n const response = await fetch(\"/admin/api/me/permissions\", {\n credentials: \"include\",\n });\n if (!response.ok) throw new Error(`permissions ${response.status}`);\n return permissionsFrom(await response.json());\n}\n\n/**\n * The person's permission check, or `undefined` while it is unknown (loading,\n * or the read failed). A screen offers what a permission gates only once the\n * answer says it may, so nothing is offered and then refused.\n */\nexport function useAdminPermissions(): PermissionCheck | undefined {\n const { data } = useQuery({\n queryKey: [\"plugin-form-builder\", \"me-permissions\"],\n queryFn: readPermissions,\n retry: false,\n });\n return data;\n}\n","\"use client\";\n\n/**\n * How many submissions a saved form has, for the count in the editor's bar.\n *\n * @module admin/components/editor/useSubmissionCount\n */\nimport { useQuery } from \"@tanstack/react-query\";\n\n/**\n * The entries query that counts one form's submissions. Spam is left out, as\n * the Submissions list leaves it out of \"All\": the count is what people sent.\n * One row is asked for, since only the total is read.\n */\nfunction submissionCountQuery(formId: string): string {\n return new URLSearchParams({\n limit: \"1\",\n where: JSON.stringify({\n form: { equals: formId },\n status: { not_equals: \"spam\" },\n }),\n }).toString();\n}\n\n/** The total an entries list answered, or `undefined` for none. */\nfunction totalOf(body: unknown): number | undefined {\n if (typeof body !== \"object\" || body === null || !(\"meta\" in body)) {\n return undefined;\n }\n const meta = body.meta;\n if (typeof meta !== \"object\" || meta === null || !(\"total\" in meta)) {\n return undefined;\n }\n return typeof meta.total === \"number\" ? meta.total : undefined;\n}\n\n/**\n * Where every form's submission count is cached. A change to a submission\n * (a delete, a status) invalidates it, so the count read beside the list is\n * the list's own answer, not one taken once as the editor opened.\n */\nexport const SUBMISSION_COUNT_KEY = [\n \"plugin-form-builder\",\n \"submission-count\",\n] as const;\n\n/** Reads one form's count; a failed read is an error, so none is shown. */\nasync function readCount(\n submissionsSlug: string,\n formId: string\n): Promise<number | null> {\n const response = await fetch(\n `/admin/api/collections/${encodeURIComponent(submissionsSlug)}/entries?${submissionCountQuery(formId)}`,\n { credentials: \"include\" }\n );\n if (!response.ok) throw new Error(`count ${response.status}`);\n return totalOf(await response.json()) ?? null;\n}\n\n/**\n * The number of submissions `formId` has in `submissionsSlug`, or `undefined`\n * while it is unknown: not saved yet, still loading, or the read failed. The\n * bar then draws no count rather than a wrong one.\n */\nexport function useSubmissionCount(\n formId: string | undefined,\n submissionsSlug: string | undefined\n): number | undefined {\n const { data } = useQuery({\n queryKey: [...SUBMISSION_COUNT_KEY, submissionsSlug, formId],\n enabled: formId !== undefined && submissionsSlug !== undefined,\n queryFn: () => readCount(submissionsSlug ?? \"\", formId ?? \"\"),\n // The count is a convenience: a failed read shows none, without retries.\n retry: false,\n });\n return data ?? undefined;\n}\n","/**\n * What the submissions list's selection bar does to the ticked rows, through\n * the entries API's bulk routes: one request for every ticked row, judged row\n * by row on the server under the person's own rights (update for a status,\n * delete for Delete), with each row's failure answered beside the others'\n * successes.\n *\n * @module admin/components/submissions/bulk-actions\n */\n\nimport type { SubmissionRow } from \"./submission-row\";\n\n/** A change the bar makes to every ticked submission. */\nexport type BulkChange = \"read\" | \"archived\" | \"spam\" | \"notSpam\" | \"delete\";\n\n/**\n * The patch each status change sends. Leaving Spam clears why it was\n * flagged, as the submission's own page does; Not spam returns it to New.\n */\nconst STATUS_PATCH: Readonly<\n Record<Exclude<BulkChange, \"delete\">, Record<string, unknown>>\n> = {\n read: { status: \"read\", spamReason: null },\n archived: { status: \"archived\", spamReason: null },\n spam: { status: \"spam\" },\n notSpam: { status: \"new\", spamReason: null },\n};\n\n/** A request, as `fetch` takes it. */\nexport interface BulkRequest {\n url: string;\n init: RequestInit;\n}\n\n/** Each row's version as the list read it, so a change made since is refused. */\nfunction versionsOf(rows: readonly SubmissionRow[]): Record<string, string> {\n return Object.fromEntries(\n rows.flatMap(row =>\n typeof row._rev === \"string\" ? [[row.id, row._rev]] : []\n )\n );\n}\n\n/** The request that makes `change` to exactly `rows`. */\nexport function bulkRequest(\n slug: string,\n change: BulkChange,\n rows: readonly SubmissionRow[]\n): BulkRequest {\n const base = `/admin/api/collections/${encodeURIComponent(slug)}/entries`;\n const ids = rows.map(row => row.id);\n const body =\n change === \"delete\"\n ? { ids }\n : { ids, data: STATUS_PATCH[change], revs: versionsOf(rows) };\n return {\n url: `${base}/${change === \"delete\" ? \"bulk-delete\" : \"bulk-update\"}`,\n init: {\n method: \"POST\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(body),\n },\n };\n}\n\n/** One row the server refused, as the bulk routes answer it. */\ninterface RowRefusal {\n id: string;\n code?: string;\n}\n\n/** What a bulk change did: the rows changed, and the rows refused. */\nexport interface BulkOutcome {\n done: string[];\n refused: RowRefusal[];\n}\n\n/** Sends `request` and reads which rows it changed; a refused request throws. */\nexport async function sendBulk(request: BulkRequest): Promise<BulkOutcome> {\n const response = await fetch(request.url, request.init);\n const body = (await response.json().catch(() => null)) as {\n items?: Array<{ id?: unknown }>;\n errors?: RowRefusal[];\n error?: { message?: string };\n } | null;\n if (!response.ok) {\n throw new Error(\n body?.error?.message ?? `The change failed (${response.status}).`\n );\n }\n return {\n done: (body?.items ?? []).flatMap(item =>\n typeof item.id === \"string\" ? [item.id] : []\n ),\n refused: body?.errors ?? [],\n };\n}\n\n/** \"1 submission\" or \"N submissions\". */\nfunction submissions(count: number): string {\n return count === 1 ? \"1 submission\" : `${count} submissions`;\n}\n\n/** What a change says it did, by the number of rows it changed. */\nconst DONE: Readonly<Record<BulkChange, (count: string) => string>> = {\n read: count => `Marked ${count} as read.`,\n archived: count => `Archived ${count}.`,\n spam: count => `Moved ${count} to Spam.`,\n notSpam: count => `Moved ${count} out of Spam.`,\n delete: count => `Deleted ${count}.`,\n};\n\n/** Why rows were refused, in words, by the server's code. */\nfunction refusalReason(refused: readonly RowRefusal[]): string {\n if (refused.some(each => each.code === \"STALE_VERSION\")) {\n return \"someone changed them since the list loaded. The list now shows their change\";\n }\n if (refused.some(each => each.code === \"FORBIDDEN\")) {\n return \"your role may not change them\";\n }\n return \"the server refused them\";\n}\n\n/** What the bar tells the person once a change is answered. */\nexport function outcomeText(\n change: BulkChange,\n outcome: BulkOutcome\n): { done?: string; refused?: string } {\n const count = outcome.refused.length;\n const verb = change === \"delete\" ? \"deleted\" : \"changed\";\n return {\n ...(outcome.done.length > 0\n ? { done: DONE[change](submissions(outcome.done.length)) }\n : {}),\n ...(count > 0\n ? {\n refused: `${submissions(count)} couldn't be ${verb}: ${refusalReason(outcome.refused)}.`,\n }\n : {}),\n };\n}\n","/**\n * The submissions list's date filter: a few presets and a range of days, each\n * turned into the moments it covers once, when it is chosen. The list filters\n * `submittedAt` by those moments on the server, so every dialect answers the\n * same rows.\n *\n * Days are the reader's own: \"Today\" starts at their midnight, not the\n * server's, and a range's last day runs to the start of the next.\n *\n * @module admin/components/submissions/date-filter\n */\n\n/** A preset of the date filter. */\nexport type DatePreset = \"any\" | \"today\" | \"last7\" | \"last30\";\n\n/** What the filter is set to: a preset, or a range of days (`YYYY-MM-DD`). */\nexport type DateChoice =\n | { kind: DatePreset }\n | { kind: \"range\"; firstDay?: string; lastDay?: string };\n\n/** The moments a choice covers: sent at or after `from`, before `to`. */\nexport interface SentRange {\n from?: string;\n to?: string;\n}\n\n/** The presets in the order the filter offers them, with how each reads. */\nexport const DATE_PRESETS: ReadonlyArray<{ kind: DatePreset; label: string }> =\n [\n { kind: \"any\", label: \"Any date\" },\n { kind: \"today\", label: \"Today\" },\n { kind: \"last7\", label: \"Last 7 days\" },\n { kind: \"last30\", label: \"Last 30 days\" },\n ];\n\n/** How many days before today each preset starts; none for any date. */\nconst DAYS_BACK: Readonly<Record<DatePreset, number | undefined>> = {\n any: undefined,\n today: 0,\n last7: 6,\n last30: 29,\n};\n\n/** The reader's midnight `days` days after `day`'s (before, when negative). */\nfunction midnight(day: Date, days = 0): Date {\n return new Date(day.getFullYear(), day.getMonth(), day.getDate() + days);\n}\n\n/** A `YYYY-MM-DD` day as the reader's midnight that starts it; none if not one. */\nfunction dayStart(day: string | undefined): Date | undefined {\n const match = day ? /^(\\d{4})-(\\d{2})-(\\d{2})$/.exec(day) : null;\n if (!match) return undefined;\n const [year, month, date] = match.slice(1).map(Number) as [\n number,\n number,\n number,\n ];\n const start = new Date(year, month - 1, date);\n return Number.isNaN(start.getTime()) ? undefined : start;\n}\n\n/**\n * The moments `choice` covers, as of `now`. A preset runs from the start of\n * its first day with no end, as nothing is sent in the future; a range from\n * the start of its first day to the start of the day after its last.\n */\nexport function sentRange(choice: DateChoice, now: Date): SentRange {\n if (choice.kind !== \"range\") {\n const back = DAYS_BACK[choice.kind];\n return back === undefined\n ? {}\n : { from: midnight(now, -back).toISOString() };\n }\n const first = dayStart(choice.firstDay);\n const last = dayStart(choice.lastDay);\n return {\n ...(first ? { from: first.toISOString() } : {}),\n ...(last ? { to: midnight(last, 1).toISOString() } : {}),\n };\n}\n\n/** A `YYYY-MM-DD` day as a person reads it, in their locale. */\nfunction dayText(day: string | undefined): string | undefined {\n const start = dayStart(day);\n return start?.toLocaleDateString(undefined, { dateStyle: \"medium\" });\n}\n\n/** How the filter's button reads for `choice`. */\nexport function dateChoiceLabel(choice: DateChoice): string {\n if (choice.kind !== \"range\") {\n return DATE_PRESETS.find(each => each.kind === choice.kind)?.label ?? \"\";\n }\n const first = dayText(choice.firstDay);\n const last = dayText(choice.lastDay);\n if (first && last) return first === last ? first : `${first} – ${last}`;\n if (first) return `From ${first}`;\n return last ? `Until ${last}` : \"Any date\";\n}\n","\"use client\";\n\n/**\n * Export, as the submissions list offers it: a CSV with a form's labels as\n * its columns, or JSON, of what the list shows or of the ticked rows alone.\n * Each choice is a link to the export route, so the browser downloads the\n * file the server writes.\n *\n * @module admin/components/submissions/ExportMenu\n */\n\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuLabel,\n DropdownMenuTrigger,\n} from \"@nextlyhq/ui\";\nimport { Download } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\nimport {\n exportAddress,\n type SubmissionListScope,\n} from \"./submission-list-scope\";\n\nexport function ExportMenu({\n scope,\n ids,\n heading,\n compact = false,\n}: {\n /** What the list shows; a CSV needs its `form`. */\n scope: SubmissionListScope;\n /** Exactly these submissions, when rows are ticked. */\n ids?: readonly string[];\n /** What the export holds, said at the head of the menu. */\n heading: string;\n /** Drawn at the selection bar's smaller size, beside its other actions. */\n compact?: boolean;\n}): JSX.Element {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n size={compact ? \"sm\" : \"md\"}\n className=\"gap-2 px-3\"\n >\n <Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n Export\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-64\">\n <DropdownMenuLabel className=\"text-xs font-medium text-muted-foreground\">\n {heading}\n </DropdownMenuLabel>\n {scope.form ? (\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <a href={exportAddress(scope, \"csv\", ids)} download>\n CSV, with the form's labels\n </a>\n </DropdownMenuItem>\n ) : (\n // Said in the item itself rather than a tooltip, which a keyboard\n // never reaches on a disabled item.\n <DropdownMenuItem disabled className=\"flex-col items-start gap-0.5\">\n <span>CSV, with the form's labels</span>\n <span className=\"text-xs text-muted-foreground\">\n Pick one form first: its labels are the columns.\n </span>\n </DropdownMenuItem>\n )}\n <DropdownMenuItem asChild className=\"cursor-pointer\">\n <a href={exportAddress(scope, \"json\", ids)} download>\n JSON\n </a>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","\"use client\";\n\n/**\n * The bar the submissions list shows while rows are ticked: how many, and\n * what can be done to those rows alone. Each action is offered only to a\n * person whose role allows it (the server judges each row again), and Delete\n * asks first.\n *\n * @module admin/components/submissions/SelectionBar\n */\n\nimport {\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Button,\n} from \"@nextlyhq/ui\";\nimport { Archive, Ban, CheckCheck, Inbox, Trash2, X } from \"lucide-react\";\nimport { type JSX, type ReactNode, useState } from \"react\";\n\nimport type { BulkChange } from \"./bulk-actions\";\n\n/** What the person may do to the ticked rows. */\nexport interface SelectionRights {\n update: boolean;\n delete: boolean;\n}\n\n/** One action's button. */\nfunction Action({\n icon,\n label,\n disabled,\n onClick,\n}: {\n icon: ReactNode;\n label: string;\n disabled: boolean;\n onClick: () => void;\n}): JSX.Element {\n return (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n className=\"gap-2 px-3\"\n disabled={disabled}\n onClick={onClick}\n >\n {icon}\n {label}\n </Button>\n );\n}\n\n/** Delete's question: the ticked submissions are gone for good once answered. */\nfunction ConfirmDelete({\n count,\n open,\n onOpenChange,\n onConfirm,\n}: {\n count: number;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onConfirm: () => void;\n}): JSX.Element {\n const what = count === 1 ? \"this submission\" : `these ${count} submissions`;\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>\n {count === 1\n ? \"Delete 1 submission?\"\n : `Delete ${count} submissions?`}\n </AlertDialogTitle>\n <AlertDialogDescription>\n {`The answers in ${what} are deleted for good, and can't be brought back.`}\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n {/* Said of what is ticked: one submission is \"it\". */}\n <AlertDialogCancel>\n {count === 1 ? \"Keep it\" : \"Keep them\"}\n </AlertDialogCancel>\n <AlertDialogAction variant=\"destructive\" onClick={onConfirm}>\n Delete\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\nconst ICON = \"h-4 w-4\";\n\nexport function SelectionBar({\n count,\n rights,\n spamTab,\n busy,\n exportMenu,\n onChange,\n onClear,\n}: {\n count: number;\n rights: SelectionRights;\n /** On the Spam tab the bar offers Not spam in Spam's place. */\n spamTab: boolean;\n /** A change is being sent; nothing else is offered until it is answered. */\n busy: boolean;\n /** The export of the ticked rows, when the person may export. */\n exportMenu: ReactNode;\n onChange: (change: BulkChange) => void;\n onClear: () => void;\n}): JSX.Element {\n const [confirming, setConfirming] = useState(false);\n return (\n <div\n role=\"region\"\n aria-label=\"Selected submissions\"\n className=\"flex flex-wrap items-center gap-x-4 gap-y-2 rounded-md border border-border bg-card px-3 py-2 text-sm text-card-foreground\"\n >\n <p className=\"flex items-center gap-1 font-medium\" aria-live=\"polite\">\n {count} selected\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Clear the selection\"\n onClick={onClear}\n >\n <X className={ICON} aria-hidden=\"true\" />\n </Button>\n </p>\n <div className=\"flex flex-wrap items-center gap-2 sm:ml-auto\">\n {rights.update && (\n <>\n <Action\n icon={<CheckCheck className={ICON} aria-hidden=\"true\" />}\n label=\"Mark as read\"\n disabled={busy}\n onClick={() => onChange(\"read\")}\n />\n <Action\n icon={<Archive className={ICON} aria-hidden=\"true\" />}\n label=\"Archive\"\n disabled={busy}\n onClick={() => onChange(\"archived\")}\n />\n {spamTab ? (\n <Action\n icon={<Inbox className={ICON} aria-hidden=\"true\" />}\n label=\"Not spam\"\n disabled={busy}\n onClick={() => onChange(\"notSpam\")}\n />\n ) : (\n <Action\n icon={<Ban className={ICON} aria-hidden=\"true\" />}\n label=\"Spam\"\n disabled={busy}\n onClick={() => onChange(\"spam\")}\n />\n )}\n </>\n )}\n {exportMenu}\n {rights.delete && (\n <Action\n icon={<Trash2 className={ICON} aria-hidden=\"true\" />}\n label=\"Delete\"\n disabled={busy}\n onClick={() => setConfirming(true)}\n />\n )}\n </div>\n <ConfirmDelete\n count={count}\n open={confirming}\n onOpenChange={setConfirming}\n onConfirm={() => onChange(\"delete\")}\n />\n </div>\n );\n}\n","/**\n * A submission as the entries API answers it, and the readings of it the\n * list and a submission's page share, so the two cannot read it differently.\n *\n * @module admin/components/submissions/submission-row\n */\n\nexport interface SubmissionRow {\n id: string;\n form: string | { id: string; name?: string };\n data: Record<string, unknown>;\n status: string;\n /** Who sent it, as the write seam named it (`utils/submission-title`). */\n title?: string | null;\n spamReason?: string | null;\n notes?: string | null;\n ipAddress?: string | null;\n userAgent?: string | null;\n /** The page it was sent from: origin and path. */\n page?: string | null;\n submittedAt?: string | null;\n createdAt?: string | null;\n editedAt?: string | null;\n editedBy?: string | null;\n /** What it was asked, as the write seam stored it. */\n asked?: unknown;\n /** Its version as read, sent back with a change. */\n _rev?: string;\n}\n\n/** A submission's review status, as stored. */\nexport type SubmissionStatus = \"new\" | \"read\" | \"archived\" | \"spam\";\n\n/**\n * Each status as the admin shows it: its name, and the colour of the dot its\n * quiet chip carries (tokens only). New stands out, Spam warns, and Read and\n * Archived recede.\n */\nexport const STATUS_LOOK: Readonly<\n Record<SubmissionStatus, { label: string; dot: string }>\n> = {\n new: { label: \"New\", dot: \"bg-info-solid\" },\n read: { label: \"Read\", dot: \"bg-muted-foreground\" },\n archived: { label: \"Archived\", dot: \"bg-muted-foreground/50\" },\n spam: { label: \"Spam\", dot: \"bg-danger-solid\" },\n};\n\n/** The form a submission belongs to, by id. */\nexport function formIdOf(row: Pick<SubmissionRow, \"form\">): string {\n return typeof row.form === \"object\" ? row.form.id : row.form;\n}\n\n/** A stored date and time, or none for none or an invalid one. */\nfunction dateOf(value: string | null | undefined): Date | undefined {\n if (!value) return undefined;\n const date = new Date(value);\n return Number.isNaN(date.getTime()) ? undefined : date;\n}\n\n/** A date and time as a person reads it; nothing for none or an invalid one. */\nexport function whenText(value: string | null | undefined): string {\n return (\n dateOf(value)?.toLocaleString(undefined, {\n dateStyle: \"medium\",\n timeStyle: \"short\",\n }) ?? \"\"\n );\n}\n\n/**\n * A date and time as a list's narrow column reads it: the year only when it\n * is not this one (\"Nov 12, 6:27 PM\").\n */\nexport function shortWhenText(\n value: string | null | undefined,\n now: Date = new Date()\n): string {\n const date = dateOf(value);\n if (date === undefined) return \"\";\n return date.toLocaleString(undefined, {\n month: \"short\",\n day: \"numeric\",\n ...(date.getFullYear() === now.getFullYear() ? {} : { year: \"numeric\" }),\n hour: \"numeric\",\n minute: \"2-digit\",\n });\n}\n","\"use client\";\n\n/**\n * The submissions list's columns. One form's list has that form's labels as\n * its columns, each answer read as the submission was asked it (a choice by\n * its option's label); the list of every form, whose fields differ, has who\n * sent each, its form and a summary of its first answers. Both end with its\n * status, as a quiet chip with a dot, and when it was sent.\n *\n * @module admin/components/submissions/submission-columns\n */\n\nimport type { NextlyColumn } from \"@nextlyhq/plugin-sdk/admin\";\nimport type { JSX } from \"react\";\n\nimport type { FormField } from \"../../../types\";\nimport { answerReader } from \"../../../utils/export-formats\";\n\nimport {\n formIdOf,\n shortWhenText,\n STATUS_LOOK,\n type SubmissionRow,\n type SubmissionStatus,\n whenText,\n} from \"./submission-row\";\n\n/** A form as the list uses it: its id, name and fields. */\nexport interface FormOption {\n id: string;\n name: string;\n fields: FormField[];\n}\n\n/** A submission's status as a quiet chip with a dot. */\nfunction StatusChip({ status }: { status: string }): JSX.Element {\n const look = STATUS_LOOK[status as SubmissionStatus] ?? {\n label: status,\n dot: \"bg-muted-foreground\",\n };\n return (\n <span className=\"inline-flex h-6 items-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-card px-2 text-xs font-medium text-foreground\">\n <span\n aria-hidden=\"true\"\n className={`size-1.5 shrink-0 rounded-full ${look.dot}`}\n />\n {look.label}\n </span>\n );\n}\n\n/**\n * The row's lead text, bold, with a dot after it while the submission is\n * New, so unread rows stand out as they do in an inbox. The dot is drawn\n * only: the Status column says New in words.\n */\nfunction Lead({\n text,\n unread,\n}: {\n text: string;\n unread: boolean;\n}): JSX.Element {\n return (\n <span className=\"inline-flex max-w-full items-center gap-1.5 font-medium text-foreground\">\n <span className=\"truncate\">{text || \"—\"}</span>\n {unread && (\n <span\n aria-hidden=\"true\"\n className=\"size-1.5 shrink-0 rounded-full bg-info-solid\"\n />\n )}\n </span>\n );\n}\n\n/** How many files a file answer holds, as a list cell reads it. */\nfunction filesText(value: unknown): string {\n const count = Array.isArray(value) ? value.length : value ? 1 : 0;\n if (count === 0) return \"\";\n return count === 1 ? \"1 file\" : `${count} files`;\n}\n\n/**\n * Reads each row's answers as it was asked, once per row however many of its\n * cells read them.\n */\nfunction rowAnswers(fields: readonly FormField[]) {\n const readers = new WeakMap<SubmissionRow, (field: FormField) => string>();\n return (row: SubmissionRow, field: FormField): string => {\n let read = readers.get(row);\n if (read === undefined) {\n read = answerReader(row, fields);\n readers.set(row, read);\n }\n return field.type === \"file\"\n ? filesText(row.data?.[field.name])\n : read(field);\n };\n}\n\n/** The status and sent columns every list ends with. */\nconst TRAILING_COLUMNS: NextlyColumn<SubmissionRow>[] = [\n {\n name: \"status\",\n header: \"Status\",\n accessor: row => row.status,\n cell: ({ row }) => <StatusChip status={row.status} />,\n },\n {\n name: \"submittedAt\",\n header: \"Submitted\",\n accessor: row => shortWhenText(row.submittedAt),\n // The full date and time on hover; the column keeps to the short one.\n cell: ({ row }) => (\n <span\n className=\"whitespace-nowrap text-muted-foreground\"\n title={whenText(row.submittedAt)}\n >\n {shortWhenText(row.submittedAt)}\n </span>\n ),\n },\n];\n\n/**\n * One form's columns: its fields by label, those in `hidden` left out, the\n * first drawn as the row's lead.\n */\nexport function formColumns(\n form: FormOption,\n hidden: ReadonlySet<string>\n): NextlyColumn<SubmissionRow>[] {\n const answer = rowAnswers(form.fields);\n const shown = form.fields.filter(field => !hidden.has(field.name));\n const fieldColumns = shown.map<NextlyColumn<SubmissionRow>>(\n (field, index) => ({\n name: `data.${field.name}`,\n header: field.label || field.name,\n accessor: row => answer(row, field),\n // Past the lead, answers are wide; the card on a phone keeps to the\n // lead, the status and when.\n hideOnMobile: index > 0,\n ...(index === 0\n ? {\n cell: ({ row }) => (\n <Lead text={answer(row, field)} unread={row.status === \"new\"} />\n ),\n }\n : {}),\n })\n );\n return [...fieldColumns, ...TRAILING_COLUMNS];\n}\n\n/** The first email address a submission answered, by its form's fields. */\nfunction emailOf(row: SubmissionRow, form: FormOption | undefined): string {\n const field = form?.fields.find(\n each => each.type === \"email\" && typeof row.data?.[each.name] === \"string\"\n );\n return field ? String(row.data[field.name]) : \"\";\n}\n\n/** The kinds of answer a one-line summary leaves out. */\nconst UNSUMMARISED: ReadonlySet<string> = new Set([\n \"file\",\n \"consent\",\n \"hidden\",\n]);\n\n/**\n * A submission's first few answers, in its form's order, as one line: what\n * it is about at a glance. Who sent it is in the From column, so the answers\n * shown there are left out here.\n */\nfunction summaryOf(row: SubmissionRow, form: FormOption | undefined): string {\n if (!form) return \"\";\n const answer = answerReader(row, form.fields);\n const shown = new Set([row.title ?? \"\", emailOf(row, form)]);\n return form.fields\n .filter(field => !UNSUMMARISED.has(field.type))\n .map(answer)\n .filter(text => text !== \"\" && !shown.has(text))\n .slice(0, 3)\n .join(\" · \");\n}\n\n/**\n * Who sent a submission, and their address, as the From column draws them;\n * with `formName`, the form leads the second line, for a list too narrow for\n * a Form column of its own.\n */\nfunction From({\n row,\n form,\n formName,\n}: {\n row: SubmissionRow;\n form: FormOption | undefined;\n formName?: string;\n}): JSX.Element {\n const email = emailOf(row, form);\n const sender = row.title || email;\n const second = [formName, email && email !== sender ? email : \"\"]\n .filter(Boolean)\n .join(\" · \");\n // Bounded, so a long name or address truncates rather than widening the\n // column past the list's box.\n return (\n <span className=\"flex min-w-0 max-w-64 flex-col\">\n <Lead text={sender} unread={row.status === \"new\"} />\n {second ? (\n <span className=\"truncate text-xs text-muted-foreground\">{second}</span>\n ) : null}\n </span>\n );\n}\n\n/**\n * The columns every form's list can do without when its box is narrow, in the\n * order it gives them up: the Summary first, then the Form column, whose name\n * moves under the sender's.\n */\nexport const ALL_FORMS_OPTIONAL_COLUMNS = 2;\n\n/**\n * The columns of every form's list, with the last `leftOut` of its optional\n * ones ({@link ALL_FORMS_OPTIONAL_COLUMNS}) left out.\n */\nexport function allFormsColumns(\n forms: ReadonlyMap<string, FormOption>,\n leftOut = 0\n): NextlyColumn<SubmissionRow>[] {\n const formOf = (row: SubmissionRow) => forms.get(formIdOf(row));\n const formNameOf = (row: SubmissionRow) => formOf(row)?.name ?? formIdOf(row);\n const withSummary = leftOut < 1;\n const withForm = leftOut < 2;\n const from: NextlyColumn<SubmissionRow> = {\n name: \"from\",\n header: \"From\",\n accessor: row => row.title ?? \"\",\n cell: ({ row }) => (\n <From\n row={row}\n form={formOf(row)}\n formName={withForm ? undefined : formNameOf(row)}\n />\n ),\n };\n const formColumn: NextlyColumn<SubmissionRow> = {\n name: \"form\",\n header: \"Form\",\n accessor: formNameOf,\n };\n const summary: NextlyColumn<SubmissionRow> = {\n name: \"summary\",\n header: \"Summary\",\n hideOnMobile: true,\n accessor: row => summaryOf(row, formOf(row)),\n cell: ({ value }) => (\n <span className=\"line-clamp-2 max-w-56 whitespace-normal text-muted-foreground\">\n {typeof value === \"string\" ? value : \"\"}\n </span>\n ),\n };\n return [\n from,\n ...(withForm ? [formColumn] : []),\n ...(withSummary ? [summary] : []),\n ...TRAILING_COLUMNS,\n ];\n}\n","\"use client\";\n\n/**\n * The submissions list's date filter: a button naming the dates shown, which\n * opens the presets and a range of days to pick from.\n *\n * @module admin/components/submissions/DateFilter\n */\n\nimport {\n Button,\n Input,\n Label,\n Popover,\n PopoverContent,\n PopoverTrigger,\n} from \"@nextlyhq/ui\";\nimport { CalendarDays, Check, ChevronDown } from \"lucide-react\";\nimport { type JSX, useId, useState } from \"react\";\n\nimport { type DateChoice, dateChoiceLabel, DATE_PRESETS } from \"./date-filter\";\n\n/** A range's days as the fields hold them, empty for none. */\nfunction rangeDays(choice: DateChoice): { first: string; last: string } {\n return choice.kind === \"range\"\n ? { first: choice.firstDay ?? \"\", last: choice.lastDay ?? \"\" }\n : { first: \"\", last: \"\" };\n}\n\n/** Whether two days make a range: one at least, the first not after the last. */\nfunction isRange(first: string, last: string): boolean {\n if (first === \"\" && last === \"\") return false;\n return first === \"\" || last === \"\" || first <= last;\n}\n\n/** One preset, ticked when it is the filter's. */\nfunction PresetOption({\n label,\n chosen,\n onChoose,\n}: {\n label: string;\n chosen: boolean;\n onChoose: () => void;\n}): JSX.Element {\n return (\n <button\n type=\"button\"\n aria-pressed={chosen}\n onClick={onChoose}\n className=\"flex w-full items-center justify-between gap-2 rounded-sm px-2 py-1.5 text-left text-sm text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-pressed:font-medium\"\n >\n {label}\n {chosen && <Check className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />}\n </button>\n );\n}\n\n/** The range of days: from and to, applied together. */\nfunction RangeFields({\n choice,\n onApply,\n}: {\n choice: DateChoice;\n onApply: (first: string, last: string) => void;\n}): JSX.Element {\n const id = useId();\n const [days, setDays] = useState(() => rangeDays(choice));\n const ready = isRange(days.first, days.last);\n return (\n <form\n className=\"space-y-3 border-t border-border p-3\"\n aria-labelledby={`${id}-title`}\n onSubmit={event => {\n event.preventDefault();\n if (ready) onApply(days.first, days.last);\n }}\n >\n <p\n id={`${id}-title`}\n className=\"text-xs font-medium text-muted-foreground\"\n >\n Between two days\n </p>\n <div className=\"grid grid-cols-2 gap-2\">\n <div className=\"space-y-1\">\n <Label htmlFor={`${id}-first`}>From</Label>\n <Input\n id={`${id}-first`}\n type=\"date\"\n value={days.first}\n max={days.last || undefined}\n onChange={event =>\n setDays(current => ({ ...current, first: event.target.value }))\n }\n />\n </div>\n <div className=\"space-y-1\">\n <Label htmlFor={`${id}-last`}>To</Label>\n <Input\n id={`${id}-last`}\n type=\"date\"\n value={days.last}\n min={days.first || undefined}\n onChange={event =>\n setDays(current => ({ ...current, last: event.target.value }))\n }\n />\n </div>\n </div>\n <Button type=\"submit\" size=\"sm\" className=\"w-full\" disabled={!ready}>\n Show these days\n </Button>\n </form>\n );\n}\n\nexport function DateFilter({\n choice,\n onChoose,\n}: {\n choice: DateChoice;\n onChoose: (choice: DateChoice) => void;\n}): JSX.Element {\n const [open, setOpen] = useState(false);\n const label = dateChoiceLabel(choice);\n const choose = (next: DateChoice) => {\n onChoose(next);\n setOpen(false);\n };\n return (\n <Popover open={open} onOpenChange={setOpen}>\n <PopoverTrigger asChild>\n <Button\n type=\"button\"\n variant=\"outline\"\n // Named with what it filters, and its visible words inside that\n // name, so a screen reader says what the dates are of.\n aria-label={`Date sent: ${label}`}\n className=\"max-w-full justify-start gap-2 px-3\"\n >\n <CalendarDays\n className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n <span className=\"truncate\">{label}</span>\n <ChevronDown\n className=\"h-4 w-4 shrink-0 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </Button>\n </PopoverTrigger>\n <PopoverContent\n align=\"start\"\n className=\"w-80 max-w-[calc(100vw-2rem)] p-0\"\n >\n <div role=\"group\" aria-label=\"Date sent\" className=\"p-1\">\n {DATE_PRESETS.map(preset => (\n <PresetOption\n key={preset.kind}\n label={preset.label}\n chosen={choice.kind === preset.kind}\n onChoose={() => choose({ kind: preset.kind })}\n />\n ))}\n </div>\n <RangeFields\n choice={choice}\n onApply={(firstDay, lastDay) =>\n choose({\n kind: \"range\",\n ...(firstDay ? { firstDay } : {}),\n ...(lastDay ? { lastDay } : {}),\n })\n }\n />\n </PopoverContent>\n </Popover>\n );\n}\n","\"use client\";\n\n/**\n * The submissions list's toolbar: the form picker (on the list of every form),\n * the status tabs with their counts, the date filter, and at its end the\n * columns menu and whatever the list adds there.\n *\n * @module admin/components/submissions/SubmissionsToolbar\n */\n\nimport {\n Button,\n DropdownMenu,\n DropdownMenuCheckboxItem,\n DropdownMenuContent,\n DropdownMenuTrigger,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { Columns3 } from \"lucide-react\";\nimport type { JSX, ReactNode } from \"react\";\n\nimport type { DateChoice } from \"./date-filter\";\nimport { DateFilter } from \"./DateFilter\";\nimport type { FormOption } from \"./submission-columns\";\nimport type { StatusTab } from \"./submission-list-scope\";\n\nconst STATUS_TABS: ReadonlyArray<{ value: StatusTab; label: string }> = [\n { value: \"all\", label: \"All\" },\n { value: \"new\", label: \"New\" },\n { value: \"read\", label: \"Read\" },\n { value: \"archived\", label: \"Archived\" },\n { value: \"spam\", label: \"Spam\" },\n];\n\n/** The picker's value for every form: a form's id is never this. */\nconst ALL_FORMS = \"__all\";\n\n/** The form picker of the list of every form. */\nfunction FormPicker({\n forms,\n value,\n onChange,\n}: {\n forms: readonly FormOption[];\n value: string;\n onChange: (formId: string) => void;\n}): JSX.Element {\n return (\n <Select\n value={value || ALL_FORMS}\n onValueChange={next => onChange(next === ALL_FORMS ? \"\" : next)}\n >\n <SelectTrigger aria-label=\"Filter by form\" className=\"w-full sm:w-56\">\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value={ALL_FORMS}>All forms</SelectItem>\n {forms.map(form => (\n <SelectItem key={form.id} value={form.id}>\n {form.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\n/**\n * The status filter, each with how many it holds once that is known. A\n * segmented control of pressed buttons rather than tabs: choosing one\n * filters the one list below, and there are no panels for tabs to control.\n */\nfunction StatusFilter({\n value,\n counts,\n onChange,\n}: {\n value: StatusTab;\n counts: Record<StatusTab, number> | undefined;\n onChange: (tab: StatusTab) => void;\n}): JSX.Element {\n return (\n <div\n role=\"group\"\n aria-label=\"Status\"\n className=\"inline-flex max-w-full gap-0.5 overflow-x-auto rounded-md bg-secondary p-0.5\"\n >\n {STATUS_TABS.map(tab => (\n <button\n key={tab.value}\n type=\"button\"\n aria-pressed={value === tab.value}\n onClick={() => onChange(tab.value)}\n className=\"inline-flex h-8 shrink-0 items-center gap-1 rounded-sm px-2 text-sm sm:gap-1.5 sm:px-2.5 font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-card aria-pressed:text-foreground aria-pressed:shadow-sm\"\n >\n {tab.label}\n {counts === undefined ? null : (\n <span className=\"text-xs tabular-nums\">{counts[tab.value]}</span>\n )}\n </button>\n ))}\n </div>\n );\n}\n\n/** Which of a form's fields have columns: a menu of its labels to tick. */\nexport function ColumnsMenu({\n fields,\n hidden,\n onToggle,\n}: {\n fields: FormOption[\"fields\"];\n hidden: ReadonlySet<string>;\n onToggle: (name: string, shown: boolean) => void;\n}): JSX.Element {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"outline\" className=\"gap-2 px-3\">\n <Columns3 className=\"h-4 w-4\" aria-hidden=\"true\" />\n Columns\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n {fields.map(field => (\n <DropdownMenuCheckboxItem\n key={field.name}\n checked={!hidden.has(field.name)}\n onCheckedChange={checked => onToggle(field.name, checked === true)}\n // Keep the menu open while toggling several columns.\n onSelect={event => event.preventDefault()}\n >\n {field.label || field.name}\n </DropdownMenuCheckboxItem>\n ))}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nexport function SubmissionsToolbar({\n forms,\n formId,\n onFormChange,\n status,\n counts,\n onStatusChange,\n date,\n onDateChange,\n end,\n}: {\n /** The forms to pick from; none on a list fixed to one form. */\n forms: readonly FormOption[] | undefined;\n formId: string;\n onFormChange: (formId: string) => void;\n status: StatusTab;\n counts: Record<StatusTab, number> | undefined;\n onStatusChange: (tab: StatusTab) => void;\n date: DateChoice;\n onDateChange: (choice: DateChoice) => void;\n /** Drawn at the toolbar's end: the columns menu, and export where it goes. */\n end: ReactNode;\n}): JSX.Element {\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n {forms === undefined ? null : (\n <FormPicker forms={forms} value={formId} onChange={onFormChange} />\n )}\n <StatusFilter value={status} counts={counts} onChange={onStatusChange} />\n <DateFilter choice={date} onChoose={onDateChange} />\n <div className=\"flex flex-wrap items-center gap-2 sm:ml-auto\">{end}</div>\n </div>\n );\n}\n","\"use client\";\n\n/**\n * How many of a list's optional columns to leave out so its table fits.\n *\n * The admin's table never shrinks below its content: when the columns are\n * wider than the box, it scrolls sideways inside it, and on a narrow window or\n * beside an open side panel the list's last columns (its status and date) end\n * up out of sight. So after each render this measures the table against its\n * box and, while it overflows, leaves out one more optional column, before the\n * browser paints. When the box grows, every column is tried again.\n *\n * Measured rather than set by window width, because what fits depends on the\n * names, the form and the font as much as on the window.\n *\n * @module admin/components/submissions/use-columns-left-out\n */\n\nimport { type RefObject, useEffect, useLayoutEffect, useState } from \"react\";\n\n/** Whether the table inside `box` is drawn and wider than its scroll box. */\nfunction overflows(box: HTMLElement | null): boolean {\n const scroller = box?.querySelector(\"table\")?.parentElement;\n // A table hidden in favour of the cards measures 0: nothing to fit.\n if (!scroller || scroller.clientWidth === 0) return false;\n return scroller.scrollWidth > scroller.clientWidth;\n}\n\n/**\n * The number of optional columns to leave out, at most `optional`. `columnSet`\n * names the columns on offer, and `content` is what the table draws (its\n * rows): a change of either starts again from all of them, since new rows can\n * narrow a column as well as widen it.\n */\nexport function useColumnsLeftOut(\n box: RefObject<HTMLElement | null>,\n optional: number,\n columnSet: string,\n content: unknown\n): number {\n // `width` is the box's, as last observed: a change of it is measured again.\n const [fit, setFit] = useState({ columnSet, content, leftOut: 0, width: 0 });\n // New columns or rows are measured afresh, from all of the columns: derived\n // during render rather than in an effect, so the first paint of the new\n // ones is not the old ones' count. The measurement below then leaves out,\n // before that paint, whatever does not fit.\n const fresh = fit.columnSet !== columnSet || fit.content !== content;\n if (fresh) setFit({ ...fit, columnSet, content, leftOut: 0 });\n const leftOut = fresh ? 0 : fit.leftOut;\n\n // Once the columns, the rows or the box's width change, and again after\n // each column left out: one more out while the table overflows. Each step\n // renders again before paint, so the overflowing table never shows. It\n // ends, since `leftOut` only grows here and stops at `optional`.\n useLayoutEffect(() => {\n if (leftOut < optional && overflows(box.current)) {\n setFit(current => ({ ...current, leftOut: current.leftOut + 1 }));\n }\n }, [box, leftOut, optional, columnSet, content, fit.width]);\n\n // Any change of the box's width is measured again. A narrower box (the\n // window narrowed, a side panel opened) may no longer fit what is shown; a\n // wider one may fit what was left out, so every column is tried again and\n // the measurement above leaves out what still does not fit.\n useEffect(() => {\n const element = box.current;\n if (!element || typeof ResizeObserver === \"undefined\") return;\n let width = element.clientWidth;\n const observer = new ResizeObserver(() => {\n const next = element.clientWidth;\n if (next === width) return;\n const grew = next > width;\n width = next;\n setFit(current => ({\n ...current,\n width: next,\n leftOut: grew ? 0 : current.leftOut,\n }));\n });\n observer.observe(element);\n return () => observer.disconnect();\n }, [box]);\n\n return Math.min(leftOut, optional);\n}\n","\"use client\";\n\n/**\n * How many submissions each status tab of a list holds, with the list's form\n * and dates, for the counts beside the tabs. Each count is the entries API's\n * total for the tab's own filter, built by `listWhere` as the list's rows are,\n * so a tab's count is what clicking it lists.\n *\n * @module admin/components/submissions/use-status-counts\n */\nimport { useQuery } from \"@tanstack/react-query\";\n\nimport { SUBMISSION_COUNT_KEY } from \"../editor/useSubmissionCount\";\n\nimport {\n listWhere,\n type StatusTab,\n type SubmissionListScope,\n} from \"./submission-list-scope\";\n\n/** The tabs counted, in the order they are drawn. */\nconst COUNTED: readonly StatusTab[] = [\n \"all\",\n \"new\",\n \"read\",\n \"archived\",\n \"spam\",\n];\n\n/** The total one tab's filter matches; a failed read is an error. */\nasync function countOf(\n slug: string,\n scope: SubmissionListScope\n): Promise<number> {\n // One row is asked for, since only the total is read.\n const params = new URLSearchParams({\n limit: \"1\",\n where: JSON.stringify(listWhere(scope)),\n });\n const response = await fetch(\n `/admin/api/collections/${encodeURIComponent(slug)}/entries?${params}`,\n { credentials: \"include\" }\n );\n if (!response.ok) throw new Error(`count ${response.status}`);\n const body = (await response.json()) as { meta?: { total?: unknown } };\n const total = body.meta?.total;\n if (typeof total !== \"number\") throw new Error(\"count missing\");\n return total;\n}\n\n/** Every tab's count for `scope`, read together. */\nasync function countAll(\n slug: string,\n scope: Omit<SubmissionListScope, \"status\">\n): Promise<Record<StatusTab, number>> {\n const totals = await Promise.all(\n COUNTED.map(status => countOf(slug, { ...scope, status }))\n );\n return Object.fromEntries(\n COUNTED.map((status, index) => [status, totals[index]])\n ) as Record<StatusTab, number>;\n}\n\n/**\n * Each tab's count for the list's form and dates, or `undefined` while it is\n * unknown (loading, or a read failed): the tabs then show no number rather\n * than a wrong one. Kept under the submission counts' key, so every change\n * the list makes reads them again.\n */\nexport function useStatusCounts(\n slug: string | undefined,\n scope: Omit<SubmissionListScope, \"status\">\n): Record<StatusTab, number> | undefined {\n const { data } = useQuery({\n queryKey: [...SUBMISSION_COUNT_KEY, \"tabs\", slug, scope],\n enabled: slug !== undefined,\n queryFn: () => countAll(slug ?? \"\", scope),\n retry: false,\n });\n return data;\n}\n","\"use client\";\n\n/**\n * Submissions View\n *\n * The submissions list, replacing the generic collection table via the\n * List-view override, and drawn again as a form's own Submissions tab. One\n * form's list has that form's labels as its columns and each answer as it\n * was asked (a choice by its option's label); the list of every form, whose\n * fields differ, shows who sent each, its form and a summary. It filters by\n * status (Spam is a tab of its own: flagged submissions are reviewable and\n * recoverable, never invisible) and by date, and acts on ticked rows alone:\n * Mark as read, Archive, Spam, Export and Delete.\n *\n * There is no way to add a submission here: a submission records what a\n * visitor sent, and the collection declares `disableCreate`.\n *\n * @module admin/components/submissions/SubmissionsView\n */\n\nimport {\n DataTableView,\n navigateTo,\n type RowAction,\n TopBarPlace,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport { Button } from \"@nextlyhq/ui\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n CircleAlert,\n CircleCheck,\n ChevronLeft,\n ChevronRight,\n} from \"lucide-react\";\nimport {\n type JSX,\n type KeyboardEvent,\n type RefObject,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n} from \"react\";\n\nimport type { FormField } from \"../../../types\";\nimport { readPermissions } from \"../../use-admin-permissions\";\nimport { SUBMISSION_COUNT_KEY } from \"../editor/useSubmissionCount\";\n\nimport {\n type BulkChange,\n bulkRequest,\n outcomeText,\n sendBulk,\n} from \"./bulk-actions\";\nimport { type DateChoice, sentRange, type SentRange } from \"./date-filter\";\nimport { ExportMenu } from \"./ExportMenu\";\nimport { SelectionBar } from \"./SelectionBar\";\nimport {\n ALL_FORMS_OPTIONAL_COLUMNS,\n allFormsColumns,\n formColumns,\n type FormOption,\n} from \"./submission-columns\";\nimport {\n listWhere,\n LIST_SORT,\n type StatusTab,\n submissionPageAddress,\n type SubmissionListScope,\n} from \"./submission-list-scope\";\nimport { formIdOf, type SubmissionRow } from \"./submission-row\";\nimport { ColumnsMenu, SubmissionsToolbar } from \"./SubmissionsToolbar\";\nimport { useColumnsLeftOut } from \"./use-columns-left-out\";\nimport { useStatusCounts } from \"./use-status-counts\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\n/**\n * What a change to a submission sends: the change, with the version its row\n * had when the list read it, so a change made from an older copy is refused\n * rather than replacing what someone saved since.\n */\nexport function submissionPatch(\n row: SubmissionRow | undefined,\n changes: Record<string, unknown>\n): Record<string, unknown> {\n return typeof row?._rev === \"string\"\n ? { ...changes, _rev: row._rev }\n : changes;\n}\n\n/** What a refused change says when someone saved the submission since. */\nconst SUBMISSION_CHANGED_SINCE =\n \"Someone changed this submission since the list loaded. The list now shows their change; make yours again.\";\n\ninterface SubmissionsViewProps {\n collectionSlug?: string;\n /**\n * Show only this form's submissions, with no choice of form: the list as a\n * form's own Submissions tab draws it.\n */\n formId?: string;\n}\n\n/** The collections the list reads, as this install named them. */\ninterface Slugs {\n forms: string;\n submissions: string;\n}\n\nconst DEFAULT_SLUGS: Slugs = {\n forms: \"forms\",\n submissions: \"form-submissions\",\n};\n\n/** What the person may do with submissions. */\ninterface Rights {\n update: boolean;\n delete: boolean;\n export: boolean;\n}\n\nconst NO_RIGHTS: Rights = { update: false, delete: false, export: false };\n\nconst PAGE_SIZE = 25;\n\n/** How many per-field columns are visible before the rest hide behind the selector. */\nconst VISIBLE_FIELD_COLUMNS = 4;\n\n// ---------------------------------------------------------------------------\n// Reading\n// ---------------------------------------------------------------------------\n\n/** A form entry as the entries API answers it. */\ntype FormEntry = { id: string; name?: string; fields?: FormField[] };\n\n/** A form as the list uses it: its id, name and fields. */\nfunction formOptionOf(item: FormEntry): FormOption {\n return {\n id: item.id,\n name: item.name ?? item.id,\n fields: Array.isArray(item.fields) ? item.fields : [],\n };\n}\n\n/**\n * Every form, page by page: a capped single fetch would silently drop forms\n * from the filter and the per-field column lookup. The entries API reads the\n * page size from `limit` and answers its default page when that is absent, so\n * the size goes there: under any other name the first page comes back short\n * and the loop reads it as the last.\n */\nasync function readAllForms(formsSlug: string): Promise<FormOption[]> {\n const allForms: FormOption[] = [];\n const formsPageSize = 200;\n for (let formsPage = 1; ; formsPage += 1) {\n const formsRes = await fetch(\n `/admin/api/collections/${formsSlug}/entries?limit=${formsPageSize}&page=${formsPage}`,\n { credentials: \"include\" }\n );\n if (!formsRes.ok) break;\n const json = (await formsRes.json()) as { items?: FormEntry[] };\n const items = json.items ?? [];\n allForms.push(...items.map(formOptionOf));\n if (items.length < formsPageSize) break;\n }\n return allForms;\n}\n\n/** One form, for the list fixed to it; none if it cannot be read. */\nasync function readOneForm(\n formsSlug: string,\n formId: string\n): Promise<FormOption[]> {\n const response = await fetch(\n `/admin/api/collections/${formsSlug}/entries/${encodeURIComponent(formId)}`,\n { credentials: \"include\" }\n );\n if (!response.ok) return [];\n const json = (await response.json()) as FormEntry & { item?: FormEntry };\n return [formOptionOf(json.item ?? json)];\n}\n\n/** The collections' installed names, or the defaults when they cannot be read. */\nasync function readSlugs(): Promise<Slugs> {\n const response = await fetch(\n \"/admin/api/plugins/@nextlyhq/plugin-form-builder/builder-config\",\n { credentials: \"include\" }\n );\n if (!response.ok) return DEFAULT_SLUGS;\n const config = (await response.json()) as { slugs?: Slugs };\n return config.slugs ?? DEFAULT_SLUGS;\n}\n\n/** What the person may do with `submissions`; nothing while unknown. */\nasync function readRights(submissions: string): Promise<Rights> {\n const has = await readPermissions().catch(() => undefined);\n if (has === undefined) return NO_RIGHTS;\n return {\n update: has(`update-${submissions}`),\n delete: has(`delete-${submissions}`),\n export: has(\"export-submissions\"),\n };\n}\n\n/**\n * The installed collection names, the forms the list needs (one form's tab\n * that form alone, the list of every form all of them, for its picker and\n * its columns), and the person's rights. `ready` once all of them are known,\n * or known to be unreadable: until then the list cannot tell which columns\n * and tick boxes to draw.\n */\nfunction useBootstrap(formId: string | undefined) {\n const [slugs, setSlugs] = useState<Slugs>();\n const [forms, setForms] = useState<FormOption[]>([]);\n const [rights, setRights] = useState<Rights>(NO_RIGHTS);\n const [ready, setReady] = useState(false);\n useEffect(() => {\n let cancelled = false;\n setReady(false);\n void (async () => {\n try {\n const resolved = await readSlugs();\n if (cancelled) return;\n setSlugs(resolved);\n // Read together, since the list waits for both.\n const [found, held] = await Promise.all([\n formId === undefined\n ? readAllForms(resolved.forms)\n : readOneForm(resolved.forms, formId),\n readRights(resolved.submissions),\n ]);\n if (cancelled) return;\n setForms(found);\n setRights(held);\n } catch {\n // Bootstrap failures degrade to the default slugs — without them\n // the list fetch would wait forever and the loading state never end.\n if (!cancelled) setSlugs(current => current ?? DEFAULT_SLUGS);\n } finally {\n if (!cancelled) setReady(true);\n }\n })();\n return () => {\n cancelled = true;\n };\n }, [formId]);\n return { slugs, forms, rights, ready };\n}\n\n/** One page of the list's rows for `scope`, read again on `refetch`. */\nfunction useRows(\n slug: string | undefined,\n scope: SubmissionListScope,\n page: number\n) {\n const [rows, setRows] = useState<SubmissionRow[]>([]);\n const [total, setTotal] = useState(0);\n const [loading, setLoading] = useState(true);\n const [error, setError] = useState<string | null>(null);\n // Monotonic request sequence: rapid filter changes overlap fetches, and\n // only the LATEST request may commit rows — an older response finishing\n // last must never overwrite the current filter's data.\n const fetchSeqRef = useRef(0);\n const refetch = useCallback(async () => {\n if (slug === undefined) return;\n const seq = ++fetchSeqRef.current;\n setLoading(true);\n setError(null);\n try {\n // `limit`, the entries API's page size: the pager counts pages of\n // PAGE_SIZE, so a page the server cut to its default would skip rows.\n // The filter is built where a submission's page builds it, so its\n // newer and older move through this same list.\n const params = new URLSearchParams({\n page: String(page),\n limit: String(PAGE_SIZE),\n sort: LIST_SORT,\n where: JSON.stringify(listWhere(scope)),\n });\n const res = await fetch(\n `/admin/api/collections/${slug}/entries?${params}`,\n { credentials: \"include\" }\n );\n if (!res.ok)\n throw new Error(`Failed to load submissions (${res.status})`);\n const json = (await res.json()) as {\n items?: SubmissionRow[];\n meta?: { total?: number };\n };\n if (seq !== fetchSeqRef.current) return;\n setRows(json.items ?? []);\n setTotal(json.meta?.total ?? (json.items ?? []).length);\n } catch (err) {\n if (seq !== fetchSeqRef.current) return;\n setError(\n err instanceof Error ? err.message : \"Failed to load submissions\"\n );\n } finally {\n if (seq === fetchSeqRef.current) setLoading(false);\n }\n }, [slug, scope, page]);\n useEffect(() => {\n void refetch();\n }, [refetch]);\n return { rows, total, loading, error, setError, refetch };\n}\n\n// ---------------------------------------------------------------------------\n// Selection\n// ---------------------------------------------------------------------------\n\n/**\n * The ticked rows of the page on screen. Ticks belong to the rows they were\n * made on: a new filter or page starts with none, and a row that leaves the\n * list (moved to another tab, deleted) leaves the selection with it.\n */\nfunction useSelection(rows: readonly SubmissionRow[], listKey: string) {\n const [ids, setIds] = useState<string[]>([]);\n useEffect(() => setIds([]), [listKey]);\n useEffect(\n () =>\n setIds(current => {\n const kept = current.filter(id => rows.some(row => row.id === id));\n return kept.length === current.length ? current : kept;\n }),\n [rows]\n );\n const toggle = useCallback(\n (row: SubmissionRow) =>\n setIds(current =>\n current.includes(row.id)\n ? current.filter(id => id !== row.id)\n : [...current, row.id]\n ),\n []\n );\n const toggleAll = useCallback(\n (page: SubmissionRow[], allSelected: boolean) =>\n setIds(allSelected ? [] : page.map(row => row.id)),\n []\n );\n const ticked = useMemo(\n () => rows.filter(row => ids.includes(row.id)),\n [rows, ids]\n );\n return { ids, ticked, setIds, toggle, toggleAll };\n}\n\n/** The one form every ticked row belongs to, for a CSV of them; none if mixed. */\nfunction commonForm(rows: readonly SubmissionRow[]): string | undefined {\n const forms = new Set(rows.map(formIdOf));\n return forms.size === 1 ? [...forms][0] : undefined;\n}\n\n/**\n * A row's checkbox answers Space and Enter itself. Without this the key also\n * reaches the row around it, which opens the submission on the same keys, so\n * a keyboard could not tick a row without leaving the list.\n */\nfunction keepKeysOnCheckboxes(event: KeyboardEvent<HTMLDivElement>): void {\n const target = event.target as HTMLElement;\n if (\n target.getAttribute(\"role\") === \"checkbox\" &&\n (event.key === \" \" || event.key === \"Enter\")\n ) {\n event.stopPropagation();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\n/** What the last change of the ticked rows did, said above the list. */\ninterface Notice {\n done?: string;\n refused?: string;\n}\n\nfunction ChangeNotice({ notice }: { notice: Notice }): JSX.Element {\n return (\n <div role=\"status\" className=\"space-y-1 text-sm\">\n {notice.done && (\n <p className=\"flex items-center gap-2 text-foreground\">\n <CircleCheck\n className=\"h-4 w-4 shrink-0 text-success\"\n aria-hidden=\"true\"\n />\n {notice.done}\n </p>\n )}\n {notice.refused && (\n <p className=\"flex items-center gap-2 text-destructive\">\n <CircleAlert className=\"h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n {notice.refused}\n </p>\n )}\n </div>\n );\n}\n\n/** The pager under the list. */\nfunction Pager({\n page,\n total,\n onPage,\n}: {\n page: number;\n total: number;\n onPage: (page: number) => void;\n}): JSX.Element {\n const totalPages = Math.max(1, Math.ceil(total / PAGE_SIZE));\n return (\n <div className=\"flex items-center justify-between text-sm text-muted-foreground\">\n <span role=\"status\">\n {total === 0\n ? \"0 submissions\"\n : `${(page - 1) * PAGE_SIZE + 1}–${Math.min(page * PAGE_SIZE, total)} of ${total}`}\n </span>\n <div className=\"flex items-center gap-2\">\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n disabled={page <= 1}\n onClick={() => onPage(page - 1)}\n aria-label=\"Previous page\"\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <span>\n Page {page} of {totalPages}\n </span>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"icon\"\n className=\"h-8 w-8\"\n disabled={page >= totalPages}\n onClick={() => onPage(page + 1)}\n aria-label=\"Next page\"\n >\n <ChevronRight className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </div>\n </div>\n );\n}\n\n/** What the list says when it has no rows. */\nfunction emptyMessage(scope: SubmissionListScope): string {\n if (scope.from || scope.to) return \"No submissions were sent in these dates.\";\n return scope.status === \"spam\"\n ? \"No spam — nothing has been flagged.\"\n : \"No submissions yet.\";\n}\n\n/**\n * The columns the list draws, fitted to its box (`useColumnsLeftOut`).\n *\n * The list of every form gives up its Summary, then its Form column. A form's\n * own list shows its first few fields by default, as many of them as fit\n * (always the first), and the Columns menu toggles any field in or out. Once\n * the person has chosen there, their choice is drawn as made, even if the\n * table then scrolls: they asked for those columns.\n */\nfunction useFittedColumns(\n box: RefObject<HTMLElement | null>,\n form: FormOption | undefined,\n formsById: ReadonlyMap<string, FormOption>,\n content: unknown\n) {\n const [chosen, setChosen] = useState<{\n form: FormOption;\n hidden: ReadonlySet<string>;\n }>();\n // A choice belongs to the form it was made on.\n const chosenHidden =\n form !== undefined && chosen?.form === form ? chosen.hidden : undefined;\n const byDefault = Math.min(VISIBLE_FIELD_COLUMNS, form?.fields.length ?? 0);\n const optional =\n form === undefined\n ? ALL_FORMS_OPTIONAL_COLUMNS\n : chosenHidden\n ? 0\n : Math.max(0, byDefault - 1);\n const leftOut = useColumnsLeftOut(\n box,\n optional,\n `${form?.id ?? \"\"}:${chosenHidden ? \"chosen\" : \"default\"}`,\n content\n );\n const hidden = useMemo<ReadonlySet<string>>(\n () =>\n chosenHidden ??\n new Set(\n (form?.fields ?? []).slice(byDefault - leftOut).map(field => field.name)\n ),\n [chosenHidden, form, byDefault, leftOut]\n );\n const toggle = useCallback(\n (name: string, shown: boolean) => {\n if (form === undefined) return;\n const next = new Set(hidden);\n if (shown) next.delete(name);\n else next.add(name);\n setChosen({ form, hidden: next });\n },\n [form, hidden]\n );\n const columns = useMemo(\n () =>\n form ? formColumns(form, hidden) : allFormsColumns(formsById, leftOut),\n [form, hidden, formsById, leftOut]\n );\n return { columns, hidden, toggle };\n}\n\nexport function SubmissionsView({\n collectionSlug,\n formId,\n}: SubmissionsViewProps): JSX.Element {\n const { slugs, forms, rights, ready } = useBootstrap(formId);\n const [chosenFormId, setChosenFormId] = useState<string>(() => {\n // Deep links (?form=<id>) preselect the form filter.\n if (typeof window === \"undefined\") return \"\";\n return new URLSearchParams(window.location.search).get(\"form\") ?? \"\";\n });\n // A form's own tab names its form; the list of all submissions offers the\n // choice.\n const selectedFormId = formId ?? chosenFormId;\n const [statusTab, setStatusTab] = useState<StatusTab>(\"all\");\n const [date, setDate] = useState<{ choice: DateChoice; range: SentRange }>({\n choice: { kind: \"any\" },\n range: {},\n });\n const [page, setPage] = useState(1);\n const [notice, setNotice] = useState<Notice>({});\n const [busy, setBusy] = useState(false);\n\n const submissionsSlug =\n slugs?.submissions ?? collectionSlug ?? DEFAULT_SLUGS.submissions;\n\n // What the list shows, one object: its rows, its counts, the page a row\n // opens and the export all read it.\n const scope = useMemo<SubmissionListScope>(\n () => ({\n ...(selectedFormId ? { form: selectedFormId } : {}),\n status: statusTab,\n ...date.range,\n }),\n [selectedFormId, statusTab, date.range]\n );\n const countScope = useMemo(\n () => ({\n ...(selectedFormId ? { form: selectedFormId } : {}),\n ...date.range,\n }),\n [selectedFormId, date.range]\n );\n const { rows, total, loading, error, setError, refetch } = useRows(\n slugs?.submissions,\n scope,\n page\n );\n const counts = useStatusCounts(slugs?.submissions, countScope);\n const selection = useSelection(rows, `${JSON.stringify(scope)}:${page}`);\n\n // A new filter starts at its first page, with nothing said about a change\n // made in the last one.\n const filterBy = useCallback((change: () => void) => {\n change();\n setPage(1);\n setNotice({});\n }, []);\n\n const selectedForm = useMemo(\n () => forms.find(form => form.id === selectedFormId),\n [forms, selectedFormId]\n );\n const formsById = useMemo(\n () => new Map(forms.map(form => [form.id, form])),\n [forms]\n );\n const tableBox = useRef<HTMLDivElement>(null);\n // The rows the table draws: none until the list is ready.\n const shownRows = ready ? rows : null;\n const fitted = useFittedColumns(tableBox, selectedForm, formsById, shownRows);\n // Tick boxes only for someone who can act on a ticked row: without update,\n // delete or export a selection would open a bar with nothing in it.\n const canAct = rights.update || rights.delete || rights.export;\n\n // --- changes ---------------------------------------------------------------\n // The form's submission count, beside the list in the form editor's bar and\n // tab, and the tabs' counts are read again after any change here.\n const queryClient = useQueryClient();\n const changed = useCallback(async () => {\n await refetch();\n void queryClient.invalidateQueries({ queryKey: SUBMISSION_COUNT_KEY });\n }, [refetch, queryClient]);\n\n // The rows as last read, for the version a change sends.\n const rowsNow = useRef(rows);\n rowsNow.current = rows;\n const patchSubmission = useCallback(\n async (id: string, body: Record<string, unknown>) => {\n const res = await fetch(\n `/admin/api/collections/${submissionsSlug}/entries/${id}`,\n {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(\n submissionPatch(\n rowsNow.current.find(row => row.id === id),\n body\n )\n ),\n }\n );\n if (!res.ok) {\n const refusal = (await res.json().catch(() => null)) as {\n error?: { code?: string };\n } | null;\n // Changed since the list read it: read the list again, so the row\n // shows the change and carries its version, and say so.\n if (refusal?.error?.code === \"STALE_VERSION\") {\n await refetch();\n throw new Error(SUBMISSION_CHANGED_SINCE);\n }\n throw new Error(`Update failed (${res.status})`);\n }\n await changed();\n },\n [submissionsSlug, refetch, changed]\n );\n\n const deleteSubmission = useCallback(\n async (id: string) => {\n const res = await fetch(\n `/admin/api/collections/${submissionsSlug}/entries/${id}`,\n { method: \"DELETE\", credentials: \"include\" }\n );\n if (!res.ok) throw new Error(`Delete failed (${res.status})`);\n await changed();\n },\n [submissionsSlug, changed]\n );\n\n const rowActions = useCallback(\n (row: SubmissionRow): RowAction<SubmissionRow>[] => {\n const actions: RowAction<SubmissionRow>[] = [];\n if (row.status === \"spam\" && rights.update) {\n actions.push({\n id: \"not-spam\",\n label: \"Not spam\",\n // A refusal is said beside the list, as a failed read is: the\n // row is read again, and without this the click would seem to do\n // nothing.\n onSelect: target =>\n void patchSubmission(target.id, {\n status: \"new\",\n spamReason: null,\n }).catch((reason: unknown) =>\n setError(\n reason instanceof Error ? reason.message : \"Update failed.\"\n )\n ),\n });\n }\n if (rights.delete) {\n actions.push({\n id: \"delete\",\n label: \"Delete\",\n destructive: true,\n onSelect: target => void deleteSubmission(target.id),\n });\n }\n return actions;\n },\n [rights.update, rights.delete, patchSubmission, deleteSubmission, setError]\n );\n\n // One request for every ticked row; the server judges each under the\n // person's rights and answers the ones it refused beside the rest.\n const changeTicked = useCallback(\n async (change: BulkChange) => {\n setBusy(true);\n try {\n const outcome = await sendBulk(\n bulkRequest(submissionsSlug, change, selection.ticked)\n );\n setNotice(outcomeText(change, outcome));\n // Refused rows stay ticked, to try again or look at.\n selection.setIds(outcome.refused.map(each => each.id));\n } catch (reason) {\n setNotice({\n refused:\n reason instanceof Error ? reason.message : \"The change failed.\",\n });\n } finally {\n setBusy(false);\n await changed();\n }\n },\n [submissionsSlug, selection, changed]\n );\n\n // --- export ----------------------------------------------------------------\n const tickedScope = useMemo<SubmissionListScope>(() => {\n const form = selectedFormId || commonForm(selection.ticked);\n return { status: statusTab, ...(form ? { form } : {}) };\n }, [selectedFormId, selection.ticked, statusTab]);\n const exportAll = rights.export ? (\n <ExportMenu scope={scope} heading=\"Export what is shown\" />\n ) : null;\n\n const tickedCount = selection.ticked.length;\n return (\n <div className=\"space-y-4\">\n {/* The list of every form puts Export in the admin's top bar; a form's\n tab leaves that bar to the form editor, whose last action is Save,\n and puts Export at the end of the toolbar. */}\n {formId === undefined && exportAll ? (\n <TopBarPlace name=\"end\">\n <div className=\"ml-auto flex items-center\">{exportAll}</div>\n </TopBarPlace>\n ) : null}\n\n <SubmissionsToolbar\n forms={formId === undefined ? forms : undefined}\n formId={selectedFormId}\n onFormChange={next => filterBy(() => setChosenFormId(next))}\n status={statusTab}\n counts={counts}\n onStatusChange={next => filterBy(() => setStatusTab(next))}\n date={date.choice}\n onDateChange={choice =>\n filterBy(() =>\n setDate({ choice, range: sentRange(choice, new Date()) })\n )\n }\n end={\n <>\n {selectedForm && selectedForm.fields.length > 0 ? (\n <ColumnsMenu\n fields={selectedForm.fields}\n hidden={fitted.hidden}\n onToggle={fitted.toggle}\n />\n ) : null}\n {formId === undefined ? null : exportAll}\n </>\n }\n />\n\n {tickedCount > 0 && canAct ? (\n <SelectionBar\n count={tickedCount}\n rights={rights}\n spamTab={statusTab === \"spam\"}\n busy={busy}\n exportMenu={\n rights.export ? (\n <ExportMenu\n scope={tickedScope}\n ids={selection.ticked.map(row => row.id)}\n heading={\n tickedCount === 1\n ? \"Export the ticked submission\"\n : `Export the ${tickedCount} ticked submissions`\n }\n compact\n />\n ) : null\n }\n onChange={change => void changeTicked(change)}\n onClear={() => selection.setIds([])}\n />\n ) : null}\n\n {notice.done || notice.refused ? <ChangeNotice notice={notice} /> : null}\n\n <div ref={tableBox} onKeyDownCapture={keepKeysOnCheckboxes}>\n {/* Until the forms and the person's rights are known, the list stays\n loading, with no columns and no rows: drawn earlier, a form's tab\n would show every form's columns, and tick boxes, for a moment\n before its own. */}\n <DataTableView<SubmissionRow>\n columns={ready ? fitted.columns : []}\n rows={shownRows ?? []}\n loading={loading || !ready}\n error={error}\n emptyMessage={emptyMessage(scope)}\n ariaLabel=\"Submissions\"\n selection={\n ready && canAct\n ? {\n selectedIds: selection.ids,\n onToggle: selection.toggle,\n onToggleAll: selection.toggleAll,\n }\n : undefined\n }\n // A submission opens as a page of its own, carrying this list's\n // filter so its newer and older follow it.\n onRowClick={row =>\n navigateTo(submissionPageAddress(submissionsSlug, row.id, scope))\n }\n rowActions={rowActions}\n />\n </div>\n\n <Pager page={page} total={total} onPage={setPage} />\n\n {ready ? (\n <p className=\"text-xs text-muted-foreground\">\n Click a row to open its page.{\" \"}\n {canAct ? \"Tick rows for bulk actions. \" : \"\"}\n {selectedForm\n ? \"Columns use this form's labels and option names.\"\n : \"Pick one form to see its own columns.\"}\n </p>\n ) : null}\n </div>\n );\n}\n\nexport default SubmissionsView;\n","/**\n * A field's stored name: the key its answers are kept under, so it reads like\n * a key a developer would have chosen and is unique within the form.\n *\n * Its own module so the provider and the field identity it holds can both use\n * it without importing each other.\n *\n * @module admin/context/field-names\n */\nimport type { FormFieldTypeId } from \"../../types\";\n\n/**\n * Generate a readable unique field name: the type itself for the first\n * field of that type, then a numeric suffix (`email`, `email_2`, ...).\n * Names key submission data, so they should read like keys a developer\n * would have chosen, not timestamps.\n */\nexport function generateFieldName(\n type: FormFieldTypeId,\n existingNames: readonly string[] = []\n): string {\n // A plugin type id can be hyphenated (`star-rating`) or start with a digit,\n // but a field name must match ^[a-zA-Z][a-zA-Z0-9_]*$ (it keys submission\n // data). Normalize to a safe key; built-in type ids are already valid and\n // pass through unchanged.\n const base =\n type\n .replace(/[^a-zA-Z0-9]+/g, \"_\")\n .replace(/^_+/, \"\")\n .replace(/^([0-9])/, \"field_$1\")\n .replace(/_+$/, \"\") || \"field\";\n const taken = new Set(existingNames);\n if (!taken.has(base)) return base;\n let suffix = 2;\n while (taken.has(`${base}_${suffix}`)) suffix += 1;\n return `${base}_${suffix}`;\n}\n","/**\n * Whether the form's ID is fixed: once the form has ever been live, the site's\n * pages and integrations may have read it by its ID. A form set back to draft\n * has still been live, so this never turns false. While the save that first\n * makes it live runs, the ID is fixed too, so it is published as sent.\n *\n * The stored form records it as `wentLiveAt`, stamped once by the server on\n * the first publish. A form live before that stamp existed is live as loaded,\n * and a save that stores Live makes it so here, where the server's stamp is\n * not read back.\n *\n * @module admin/context/use-been-live\n */\nimport { useCallback, useRef, useState } from \"react\";\n\nimport type { FormBuilderState } from \"./FormBuilderContext\";\n\ntype FormStatus = FormBuilderState[\"formData\"][\"status\"];\n\nexport interface BeenLive {\n /**\n * Whether the form's ID is fixed: it has been live, or a save that makes it\n * live is under way, so what that save sends is the ID it publishes.\n */\n idFixed: boolean;\n /** A save began: the status it sends is the one it will store. */\n beginSave: () => void;\n /** The save that began stored the form. */\n markSaved: () => void;\n /** The save that began was refused or failed: nothing was stored. */\n saveFailed: () => void;\n}\n\n/** Whether the form as loaded has been live. */\nfunction liveAsLoaded(\n initialData: { status?: FormStatus; wentLiveAt?: unknown } | undefined\n): boolean {\n const stamp = initialData?.wentLiveAt;\n const stamped = stamp !== undefined && stamp !== null && stamp !== \"\";\n return stamped || initialData?.status === \"published\";\n}\n\n/**\n * Kept in step with the change history's own save marks: the returned\n * `beginSave` and `markSaved` call the history's, so one save marks both.\n *\n * @param initialData - what the editor opened with; unset for a new form\n * @param currentDocument - the form as edited now\n * @param history - the change history's save marks\n */\nexport function useBeenLive(\n initialData: { status?: FormStatus; wentLiveAt?: unknown } | undefined,\n currentDocument: () => { formData: { status: FormStatus } },\n history: { beginSave: () => void; markSaved: () => void }\n): BeenLive {\n const [beenLive, setBeenLive] = useState(() => liveAsLoaded(initialData));\n // A save that makes the form live is under way: the ID it sends is the one\n // the site will read, so it may not change while the save runs.\n const [publishing, setPublishing] = useState(false);\n // The status the save in flight sends: a status chosen while it runs is\n // not what it stores.\n const sending = useRef<FormStatus | undefined>(undefined);\n const { beginSave: historyBeginSave, markSaved: historyMarkSaved } = history;\n\n const beginSave = useCallback(() => {\n sending.current = currentDocument().formData.status;\n setPublishing(sending.current === \"published\");\n historyBeginSave();\n }, [currentDocument, historyBeginSave]);\n\n const markSaved = useCallback(() => {\n historyMarkSaved();\n const stored = sending.current ?? currentDocument().formData.status;\n sending.current = undefined;\n setPublishing(false);\n // Only ever on: having been live is not undone by a later status.\n if (stored === \"published\") setBeenLive(true);\n }, [currentDocument, historyMarkSaved]);\n\n const saveFailed = useCallback(() => {\n sending.current = undefined;\n setPublishing(false);\n }, []);\n\n return { idFixed: beenLive || publishing, beginSave, markSaved, saveFailed };\n}\n","/**\n * The form editor's unsaved changes: what each one replaced, so the bar can\n * count them and Undo can take the last one back.\n *\n * One record per change, holding the whole form as it was just before it.\n * The count of records IS the number of unsaved changes, and \"unsaved\" is a\n * count above zero, so the bar's count, its Undo and the leave guard read one\n * answer rather than three that could disagree.\n *\n * Pure functions over a plain value, so the rules (what counts as one change,\n * what a save keeps) are tested without a React tree.\n *\n * @module admin/context/change-history\n */\n\n/**\n * Typing into one input is one change, not one per keystroke, while the\n * keystrokes keep coming: an edit to the same thing within this long of the\n * last joins it.\n */\nexport const COALESCE_MS = 2000;\n\n/** The record history keeps: whatever the editor restores on Undo. */\nexport interface ChangeHistory<Doc> {\n /** The form before each unsaved change, oldest first. */\n readonly entries: readonly Doc[];\n /** What the last change edited, when it may be joined by the next. */\n readonly lastKey: string | null;\n /** When the last change was made, in milliseconds. */\n readonly lastAt: number;\n}\n\n/** No unsaved changes. */\nexport function emptyHistory<Doc>(): ChangeHistory<Doc> {\n return { entries: [], lastKey: null, lastAt: 0 };\n}\n\n/**\n * The history after a change made to `before`.\n *\n * - `before` is the very record the last change pushed: the same event made\n * two writes (a name and the address it suggests), which is one change.\n * - `key` names what was edited (a field's place, a setting); the same key\n * within `COALESCE_MS` of the last edit joins it. `null` never joins: a\n * field added, removed or moved, or a status picked, is a change of its own.\n */\nexport function recordChange<Doc>(\n history: ChangeHistory<Doc>,\n before: Doc,\n key: string | null,\n now: number\n): ChangeHistory<Doc> {\n const last = history.entries.at(-1);\n const joins =\n last === before ||\n (key !== null &&\n key === history.lastKey &&\n history.entries.length > 0 &&\n now - history.lastAt < COALESCE_MS);\n return {\n entries: joins ? history.entries : [...history.entries, before],\n lastKey: key,\n lastAt: now,\n };\n}\n\n/**\n * The last change taken back: the form to restore, and the history without\n * it. `undefined` when there is nothing unsaved to take back.\n */\nexport function undoChange<Doc>(\n history: ChangeHistory<Doc>\n): { history: ChangeHistory<Doc>; restore: Doc } | undefined {\n const restore = history.entries.at(-1);\n if (restore === undefined) return undefined;\n return {\n history: {\n entries: history.entries.slice(0, -1),\n lastKey: null,\n lastAt: 0,\n },\n restore,\n };\n}\n\n/**\n * The history once a save has stored the form as it stood when the save\n * began, `mark` records in. The changes made while the save was under way\n * are not in what it stored, so they stay unsaved; the first of them keeps\n * the stored form as what Undo goes back to. With no mark (a save whose start\n * was not recorded) everything counts as stored.\n */\nexport function markSaved<Doc>(\n history: ChangeHistory<Doc>,\n mark: number | undefined\n): ChangeHistory<Doc> {\n const kept =\n mark === undefined\n ? []\n : history.entries.slice(Math.min(mark, history.entries.length));\n return { entries: kept, lastKey: null, lastAt: 0 };\n}\n\n/**\n * The history as a save begins: how many records it stores (`mark`), and a\n * history whose next edit starts a change of its own, so an edit made during\n * the save is never joined to one the save is storing.\n */\nexport function beginSaving<Doc>(history: ChangeHistory<Doc>): {\n history: ChangeHistory<Doc>;\n mark: number;\n} {\n return {\n history: { ...history, lastKey: null },\n mark: history.entries.length,\n };\n}\n\n/**\n * The history with every kept record passed through `rebase`: for a fact the\n * records hold but an edit does not decide, such as which fields the server\n * now stores, so that taking an edit back never takes that fact back too.\n */\nexport function rebaseEntries<Doc>(\n history: ChangeHistory<Doc>,\n rebase: (record: Doc) => Doc\n): ChangeHistory<Doc> {\n return { ...history, entries: history.entries.map(rebase) };\n}\n","\"use client\";\n\n/**\n * The form editor's unsaved changes as a hook: records each change against\n * the form as last rendered, counts them for the bar, takes the last one back,\n * and keeps what a save did not store. The rules are `change-history`'s; this\n * holds them across renders.\n *\n * @module admin/context/use-change-history\n */\nimport { useCallback, useLayoutEffect, useRef, useState } from \"react\";\n\nimport {\n beginSaving,\n emptyHistory,\n markSaved,\n rebaseEntries,\n recordChange,\n undoChange,\n type ChangeHistory,\n} from \"./change-history\";\n\nexport interface ChangeHistoryHandle<Doc> {\n /** How many unsaved changes there are. */\n changeCount: number;\n /**\n * How many times Undo has restored the form. An editor that keeps its own\n * copy of a value (a mode it opened in) is keyed on it, so it reads the\n * restored value afresh rather than showing what was taken back.\n */\n restored: number;\n /** The form as last rendered, which the next change records. */\n current: () => Doc;\n /** Count a change about to be made; `key` names what it edits. */\n record: (key: string | null) => void;\n /** Take the last change back, handing its form to `restore`. */\n undo: () => void;\n /** A save begins: what it stores is what is unsaved now. */\n beginSave: () => void;\n /** The save that began has stored the form. */\n markSaved: () => void;\n /** Pass every kept record through `rebase` (see `rebaseEntries`). */\n rebase: (rebase: (record: Doc) => Doc) => void;\n}\n\n/**\n * @param document - the form as rendered, one object per render, so two\n * writes in one event record it once\n * @param restore - puts a form back, for Undo; stable across renders\n */\nexport function useChangeHistory<Doc>(\n document: Doc,\n restore: (document: Doc) => void\n): ChangeHistoryHandle<Doc> {\n // A ref, because each change records the form as the event found it;\n // `changeCount` is its length, for rendering.\n const history = useRef<ChangeHistory<Doc>>(emptyHistory());\n const [changeCount, setChangeCount] = useState(0);\n const [restored, setRestored] = useState(0);\n // How many changes the save under way is storing (`beginSaving`).\n const saveMark = useRef<number | undefined>(undefined);\n const documentRef = useRef(document);\n useLayoutEffect(() => {\n documentRef.current = document;\n }, [document]);\n\n const settle = useCallback((next: ChangeHistory<Doc>) => {\n history.current = next;\n setChangeCount(next.entries.length);\n }, []);\n\n const current = useCallback(() => documentRef.current, []);\n\n const record = useCallback(\n (key: string | null) =>\n settle(\n recordChange(history.current, documentRef.current, key, Date.now())\n ),\n [settle]\n );\n\n const undo = useCallback(() => {\n const undone = undoChange(history.current);\n if (undone === undefined) return;\n settle(undone.history);\n restore(undone.restore);\n setRestored(count => count + 1);\n }, [settle, restore]);\n\n const beginSave = useCallback(() => {\n const begun = beginSaving(history.current);\n history.current = begun.history;\n saveMark.current = begun.mark;\n }, []);\n\n const saved = useCallback(() => {\n settle(markSaved(history.current, saveMark.current));\n saveMark.current = undefined;\n }, [settle]);\n\n const rebase = useCallback((each: (record: Doc) => Doc) => {\n history.current = rebaseEntries(history.current, each);\n }, []);\n\n return {\n changeCount,\n restored,\n current,\n record,\n undo,\n beginSave,\n markSaved: saved,\n rebase,\n };\n}\n","\"use client\";\n\n/**\n * A field's identity while it is edited, held by the form editor's provider\n * so it outlasts the list that shows it.\n *\n * A form stores no field ids: a field is known by its name, and the name is\n * the one thing a rename rewrites on every keystroke. So each field gets a key\n * here that follows it through renames (the list's React key and the settings\n * panel's id), and a rename that clears the Name input first still moves the\n * references from the name the rest of the form knows the field by.\n *\n * Held here rather than in the list: the list is not drawn while another page\n * of the editor is open, and what it held would be lost on the way back.\n *\n * @module admin/context/use-field-identity\n */\nimport { useCallback, useRef } from \"react\";\n\nimport type { AnyFormField } from \"../../types\";\n\nimport { generateFieldName } from \"./field-names\";\n\nexport interface FieldIdentity {\n /** The key a field is known by while edited, whatever its name becomes. */\n fieldKey: (name: string) => string;\n /**\n * Replace `field` with its edited definition. A new name another field\n * already has is made unique, and the references to the field move from\n * the name the form knows it by, even through an empty one.\n */\n editField: (\n field: AnyFormField,\n next: AnyFormField,\n fields: readonly AnyFormField[]\n ) => void;\n}\n\n/** `next`, with a name no other field has. */\nfunction uniquelyNamed(\n field: AnyFormField,\n next: AnyFormField,\n fields: readonly AnyFormField[]\n): AnyFormField {\n if (next.name === \"\" || next.name === field.name) return next;\n // A name is the key its answers are stored under, so two fields never\n // share one: a rename onto another field's name is made unique the way\n // the picker makes a new name unique.\n const others = fields.filter(each => each !== field).map(each => each.name);\n return others.includes(next.name)\n ? { ...next, name: generateFieldName(next.name, others) }\n : next;\n}\n\n/**\n * @param replaceField - the provider's own replace, which moves the\n * references from the name it is given\n */\nexport function useFieldIdentity(\n replaceField: (\n fieldName: string,\n next: AnyFormField,\n referencesFrom?: string\n ) => void\n): FieldIdentity {\n const keys = useRef(new Map<string, string>());\n const nextKey = useRef(0);\n // The name a field had before its Name was cleared, by key.\n const clearedFrom = useRef(new Map<string, string>());\n\n const fieldKey = useCallback((name: string): string => {\n let key = keys.current.get(name);\n if (key === undefined) {\n nextKey.current += 1;\n key = `field-${nextKey.current}`;\n keys.current.set(name, key);\n }\n return key;\n }, []);\n\n const editField = useCallback(\n (\n field: AnyFormField,\n next: AnyFormField,\n fields: readonly AnyFormField[]\n ) => {\n const updated = uniquelyNamed(field, next, fields);\n const key = fieldKey(field.name);\n if (updated.name !== field.name) {\n // The key moves to the incoming name before the state update\n // re-renders, so the renamed field keeps its row and its panel.\n keys.current.delete(field.name);\n keys.current.set(updated.name, key);\n }\n // The name the rest of the form refers to the field by.\n const referencedAs =\n field.name !== \"\" ? field.name : clearedFrom.current.get(key);\n if (updated.name === \"\" && referencedAs !== undefined) {\n clearedFrom.current.set(key, referencedAs);\n } else if (updated.name !== \"\") {\n clearedFrom.current.delete(key);\n }\n replaceField(field.name, updated, referencedAs);\n },\n [fieldKey, replaceField]\n );\n\n return { fieldKey, editField };\n}\n","\"use client\";\n\n/**\n * Form Builder Context\n *\n * Provides state management for the Form Builder UI.\n * Manages form fields, selection state, and form metadata.\n *\n * @module admin/context/FormBuilderContext\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport {\n type JSX,\n createContext,\n useContext,\n useCallback,\n useMemo,\n useRef,\n useState,\n type ReactNode,\n} from \"react\";\n\nimport {\n FORM_CONFIRMATION_TEMPLATE,\n FORM_SUBMISSION_TEMPLATE,\n} from \"../../emails/starter-messages\";\nimport type {\n AnyFormField,\n FormFieldTypeId,\n FormNotification,\n FormSettings,\n} from \"../../types\";\nimport {\n renameInFieldConditions,\n renameInNotifications,\n} from \"../../utils/field-references\";\nimport {\n DEFAULT_FORM_SETTINGS,\n normalizeFormSettings,\n} from \"../../utils/form-settings\";\n\nimport { generateFieldName } from \"./field-names\";\nimport { useBeenLive } from \"./use-been-live\";\nimport { useChangeHistory } from \"./use-change-history\";\nimport { useFieldIdentity } from \"./use-field-identity\";\n\nexport { generateFieldName } from \"./field-names\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\n/**\n * The canonical settings shape lives in `types.ts` (the same shape the\n * collection group declares and the submit handler consumes); re-exported\n * here so admin components keep one import site.\n */\nexport type { FormSettings } from \"../../types\";\n\n/**\n * The notification rule type lives in `types.ts` (the same shape the send\n * path consumes); re-exported here so admin components keep one import site.\n */\nexport type { FormNotification } from \"../../types\";\n\nexport interface FormBuilderState {\n /** Form fields array */\n fields: AnyFormField[];\n /** Currently selected field ID (name) */\n selectedFieldId: string | null;\n /**\n * Active tab: builder, preview, settings, or notifications.\n * @deprecated The editor shows the page its address names; this is no\n * longer what it shows, and is kept for hosts that read it.\n */\n activeTab: \"builder\" | \"preview\" | \"settings\" | \"notifications\";\n /** Whether form has unsaved changes */\n isDirty: boolean;\n /** Form metadata */\n formData: {\n id?: string;\n name: string;\n slug: string;\n description?: string;\n status: \"draft\" | \"published\" | \"closed\";\n /** What a closed form tells a visitor who reaches it. */\n closedMessage?: string;\n };\n /** Form settings */\n settings: FormSettings;\n /** Email notifications */\n notifications: FormNotification[];\n}\n\nexport interface FormBuilderActions {\n /** Set all fields */\n setFields: (fields: AnyFormField[]) => void;\n /** Add a new field */\n addField: (field: AnyFormField, index?: number) => void;\n /** Update a field by name */\n updateField: (fieldName: string, updates: Partial<AnyFormField>) => void;\n /** Delete a field by name */\n deleteField: (fieldName: string) => void;\n /** Move a field from one index to another */\n moveField: (fromIndex: number, toIndex: number) => void;\n /** Duplicate a field */\n duplicateField: (fieldName: string) => void;\n /** Select a field */\n selectField: (fieldName: string | null) => void;\n /**\n * Set active tab.\n * @deprecated The editor's tabs are links to each page's address.\n */\n setActiveTab: (\n tab: \"builder\" | \"preview\" | \"settings\" | \"notifications\"\n ) => void;\n /** Update form metadata */\n updateFormData: (updates: Partial<FormBuilderState[\"formData\"]>) => void;\n /** Update form settings */\n updateSettings: (updates: Partial<FormSettings>) => void;\n /** Add a notification */\n addNotification: (notification: FormNotification) => void;\n /** Duplicate a notification (the copy starts disabled) */\n duplicateNotification: (id: string) => void;\n /** Update a notification */\n updateNotification: (id: string, updates: Partial<FormNotification>) => void;\n /** Delete a notification */\n deleteNotification: (id: string) => void;\n /** Seed default notifications into an empty list without dirtying the form */\n seedNotifications: (rules: FormNotification[]) => void;\n /** Mark form as saved (clears dirty flag) */\n markAsSaved: () => void;\n}\n\nexport interface FormBuilderContextValue\n extends FormBuilderState,\n FormBuilderActions {}\n\n/**\n * The builder's own field bookkeeping, kept off the exported state and\n * actions so a host that builds or mocks those types is not asked for it.\n * The provider supplies it; the builder's field list reads it through\n * `useFieldBookkeeping`, which the package does not export.\n */\nexport interface FieldBookkeeping {\n /**\n * The names of the fields the stored form holds, as of the last load or\n * save. A field named here has answers stored under its name and type, so\n * its type is fixed; a renamed one keeps its place here under its new name.\n */\n savedFieldNames: ReadonlySet<string>;\n /**\n * Replace a field by name with the whole of its next definition, so a\n * setting the next one leaves out is gone rather than kept from before.\n * Given `referencesFrom`, the name other fields' conditions and the\n * notifications know the field by, those references move to its new name.\n */\n replaceField: (\n fieldName: string,\n next: AnyFormField,\n referencesFrom?: string\n ) => void;\n /** Called as a save begins, before anything is sent. */\n beginSave: () => void;\n /**\n * Records the fields a save stored, by the names they were saved under.\n * Given the names the save was handed, so an edit made while it was under\n * way is not counted as stored; and a name deleted or renamed away since\n * the save began is not either, since a field under it now is another one.\n */\n markFieldsSaved: (names: readonly string[]) => void;\n /** Whether a colleague holds the form, so nothing in it may change. */\n readOnly: boolean;\n /**\n * How many unsaved changes the form holds, across every tab: typing into\n * one input counts once while the keystrokes keep coming. `isDirty` is this\n * above zero.\n */\n changeCount: number;\n /** Take the last unsaved change back, on any tab. */\n undo: () => void;\n /**\n * How many times Undo has restored the form: the key for an editor that\n * keeps its own copy of a value, so it rereads the restored one.\n */\n restored: number;\n /**\n * Whether the form's ID is fixed: it has been live as stored (set back to\n * draft, it still has), or the save that makes it live is under way. An\n * unsaved pick in the bar does not count.\n */\n formIdFixed: boolean;\n /** The save that began was refused or failed: nothing was stored. */\n saveFailed: () => void;\n /**\n * The key a field is known by while it is edited, whatever its name\n * becomes: the list's row key and the settings panel's field id.\n */\n fieldKey: (name: string) => string;\n /**\n * Replace `field` with its edited definition: a name another field has is\n * made unique, and references move from the name the form knows the field\n * by, even through an empty one. Kept here, not in the list, so a rename\n * finished after a trip to another page still moves them.\n */\n editField: (\n field: AnyFormField,\n next: AnyFormField,\n fields: readonly AnyFormField[]\n ) => void;\n}\n\nexport type FormBuilderInternalValue = FormBuilderContextValue &\n FieldBookkeeping;\n\nexport interface FormBuilderProviderProps {\n /** Initial form data (for editing existing forms) */\n initialData?: {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\" | \"closed\";\n /** When the form first went live, as the server stamped it. */\n wentLiveAt?: string | null;\n closedMessage?: string;\n fields?: AnyFormField[];\n settings?: Partial<FormSettings>;\n notifications?: FormNotification[];\n };\n /** Child components */\n children: ReactNode;\n /**\n * The document belongs to someone else right now, so nothing here may change\n * it.\n *\n * Held here rather than at each control because the builder has dozens of\n * them across four tabs, and a rule spelled at every one is a rule one new\n * control forgets. Reading is untouched: the editor still browses fields,\n * switches tabs and opens settings, which is exactly what the strip above\n * offers them.\n */\n readOnly?: boolean;\n}\n\n/**\n * The actions that survive a read-only document.\n *\n * Everything else on the context changes the form, and every one of those marks\n * it dirty - the same question asked twice, so this is the complement of it\n * rather than a second list of what to block. `markAsSaved` is the save's own\n * bookkeeping and cannot run while the save is withheld anyway.\n */\nconst READS_ONLY = new Set([\n \"selectField\",\n \"fieldKey\",\n \"setActiveTab\",\n \"markAsSaved\",\n \"beginSave\",\n \"saveFailed\",\n \"markFieldsSaved\",\n]);\n\n/** What Undo restores: the form, and which of its fields are stored. */\ninterface FormDocument {\n fields: AnyFormField[];\n formData: FormBuilderState[\"formData\"];\n settings: FormSettings;\n notifications: FormNotification[];\n savedFieldNames: ReadonlySet<string>;\n}\n\n/**\n * The metadata keys typed into, whose keystrokes join one change. A status\n * picked from a menu is a change of its own every time.\n */\nconst TYPED_FORM_DATA = new Set([\n \"name\",\n \"slug\",\n \"description\",\n \"closedMessage\",\n]);\n\n/** What an edit to the form's metadata edited, when it may join the next. */\nfunction formDataKey(\n updates: Partial<FormBuilderState[\"formData\"]>\n): string | null {\n const keys = Object.keys(updates);\n return keys.length > 0 && keys.every(key => TYPED_FORM_DATA.has(key))\n ? `form:${keys.sort().join(\",\")}`\n : null;\n}\n\n/**\n * `record` with every field of it the server now stores counted as stored:\n * which fields are stored is the server's to say, not an edit's to undo.\n */\nfunction withStoredFields(\n record: FormDocument,\n stored: ReadonlySet<string>\n): FormDocument {\n const named = record.fields\n .map(field => field.name)\n .filter(name => stored.has(name) && !record.savedFieldNames.has(name));\n return named.length === 0\n ? record\n : {\n ...record,\n savedFieldNames: new Set([...record.savedFieldNames, ...named]),\n };\n}\n\n/** The open field's name, while that field is still among `fields`. */\nfunction keptSelection(\n selected: string | null,\n fields: readonly AnyFormField[]\n): string | null {\n return selected !== null && fields.some(field => field.name === selected)\n ? selected\n : null;\n}\n\n/**\n * What an edit to a field edited: its place in the list, which a rename does\n * not move, so renaming one keystroke at a time is still one change.\n */\nfunction fieldChangeKey(fields: readonly AnyFormField[], name: string): string {\n return `field:${fields.findIndex(field => field.name === name)}`;\n}\n\n/** Default form settings (the canonical defaults from the settings reader) */\nexport const DEFAULT_SETTINGS: FormSettings = DEFAULT_FORM_SETTINGS;\n\n/** Create a new notification rule with default values */\nexport function createNotification(\n kind: \"team\" | \"visitor\" = \"team\",\n fields: readonly AnyFormField[] = []\n): FormNotification {\n const id = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;\n // The visitor's address, and where a team member's reply lands: the form's\n // first Email field, when it has one.\n const email = fields.find(field => field.type === \"email\")?.name;\n const visitorRef = email === undefined ? undefined : `{{${email}}}`;\n const shared = {\n id,\n enabled: true,\n cc: [],\n bcc: [],\n // A new notification attaches nothing until its files are chosen.\n attachFields: [],\n };\n if (kind === \"visitor\") {\n return {\n ...shared,\n kind,\n name: \"Confirmation to the visitor\",\n recipientType: \"field\",\n to: visitorRef ?? \"\",\n // The visitor's starter message, so the confirmation sends at once.\n templateSlug: FORM_CONFIRMATION_TEMPLATE,\n };\n }\n return {\n ...shared,\n kind,\n name: \"Notify your team\",\n recipientType: \"static\",\n // One address, as a caller may set it (a site's default recipient): the\n // list is written only once the addresses are edited, so it never\n // outranks an address set afterwards.\n to: \"\",\n // Replying from the team's inbox answers the visitor.\n ...(visitorRef === undefined ? {} : { replyTo: visitorRef }),\n // The team's starter message, so a new notification sends as soon as it\n // has an address, rather than starting without one and never sending.\n templateSlug: FORM_SUBMISSION_TEMPLATE,\n };\n}\n\n// ============================================================================\n// Context\n// ============================================================================\n\nconst FormBuilderContext = createContext<FormBuilderInternalValue | null>(null);\n\n// ============================================================================\n// Utilities\n// ============================================================================\n\n/**\n * Generate a human-readable label from field type, using the shared catalog\n * so a new field's default label matches what the picker called the type. A\n * plugin type not in the built-in catalog falls back to a title-cased id.\n */\nexport function generateFieldLabel(type: FormFieldTypeId): string {\n const entry = FORM_FIELD_TYPE_CATALOG.find(row => row.type === type);\n if (entry) return entry.label;\n const titleCased = type\n .split(/[-_\\s]+/)\n .filter(Boolean)\n .map(word => word.charAt(0).toUpperCase() + word.slice(1))\n .join(\" \");\n return titleCased || \"New Field\";\n}\n\n/**\n * Create a new field from a field type with default values.\n */\nexport function createFieldFromType(\n type: FormFieldTypeId,\n existingNames: readonly string[] = []\n): AnyFormField {\n const name = generateFieldName(type, existingNames);\n const label = generateFieldLabel(type);\n\n const baseField = {\n name,\n label,\n required: false,\n };\n\n switch (type) {\n case \"text\":\n return { ...baseField, type: \"text\" };\n\n case \"email\":\n return { ...baseField, type: \"email\" };\n\n case \"number\":\n return { ...baseField, type: \"number\" };\n\n case \"phone\":\n return { ...baseField, type: \"phone\" };\n\n case \"url\":\n return { ...baseField, type: \"url\" };\n\n case \"textarea\":\n return { ...baseField, type: \"textarea\", rows: 4 };\n\n case \"select\":\n return {\n ...baseField,\n type: \"select\",\n options: [\n { label: \"Option 1\", value: \"option_1\" },\n { label: \"Option 2\", value: \"option_2\" },\n ],\n };\n\n case \"checkbox\":\n return { ...baseField, type: \"checkbox\" };\n\n case \"radio\":\n return {\n ...baseField,\n type: \"radio\",\n options: [\n { label: \"Option 1\", value: \"option_1\" },\n { label: \"Option 2\", value: \"option_2\" },\n ],\n };\n\n case \"file\":\n return { ...baseField, type: \"file\" };\n\n case \"date\":\n return { ...baseField, type: \"date\" };\n\n case \"time\":\n return { ...baseField, type: \"time\" };\n\n case \"hidden\":\n return { ...baseField, type: \"hidden\" };\n\n case \"consent\":\n // Always required, and seeded with a text to agree to, since a consent\n // with none could not be saved.\n return {\n ...baseField,\n type: \"consent\",\n required: true,\n agreementText: \"I agree to the privacy policy\",\n };\n\n default:\n // A plugin-contributed type keeps its own id; the plugin owns the editor\n // component and value semantics, so no built-in defaults are seeded.\n return { ...baseField, type };\n }\n}\n\n// ============================================================================\n// Provider\n// ============================================================================\n\n/**\n * FormBuilderProvider - Provides form builder state to child components\n *\n * Wraps the form builder UI to provide centralized state management\n * for fields, selection, and form metadata.\n *\n * @example\n * ```tsx\n * <FormBuilderProvider initialData={existingForm}>\n * <FormBuilderView />\n * </FormBuilderProvider>\n * ```\n */\nexport function FormBuilderProvider({\n initialData,\n children,\n readOnly = false,\n}: FormBuilderProviderProps): JSX.Element {\n // State\n const [fields, setFieldsState] = useState<AnyFormField[]>(\n initialData?.fields || []\n );\n const [selectedFieldId, setSelectedFieldId] = useState<string | null>(null);\n const [activeTab, setActiveTab] = useState<\n \"builder\" | \"preview\" | \"settings\" | \"notifications\"\n >(\"builder\");\n // The names that stopped meaning the field they named (deleted, or renamed\n // away) since the save under way began.\n const vacatedRef = useRef(new Set<string>());\n\n const [savedFieldNames, setSavedFieldNames] = useState<ReadonlySet<string>>(\n () => new Set((initialData?.fields ?? []).map(field => field.name))\n );\n const [formData, setFormData] = useState<FormBuilderState[\"formData\"]>({\n id: initialData?.id,\n name: initialData?.name || \"\",\n slug: initialData?.slug || \"\",\n description: initialData?.description,\n status: initialData?.status || \"draft\",\n closedMessage: initialData?.closedMessage,\n });\n // Stored settings may carry legacy keys from earlier builder versions —\n // the normalizer migrates them on read (nothing rewrites until save).\n const [settings, setSettings] = useState<FormSettings>(() =>\n normalizeFormSettings(initialData?.settings)\n );\n const [notifications, setNotifications] = useState<FormNotification[]>(\n initialData?.notifications || []\n );\n\n // The form as last rendered, which a change records as what it replaced.\n // One object per render, so two writes in one event record it once.\n const formDocument = useMemo<FormDocument>(\n () => ({ fields, formData, settings, notifications, savedFieldNames }),\n [fields, formData, settings, notifications, savedFieldNames]\n );\n // Puts a form back for Undo. The setters are stable, so this is too.\n const restore = useCallback((previous: FormDocument) => {\n setFieldsState(previous.fields);\n setFormData(previous.formData);\n setSettings(previous.settings);\n setNotifications(previous.notifications);\n setSavedFieldNames(previous.savedFieldNames);\n setSelectedFieldId(selected => keptSelection(selected, previous.fields));\n }, []);\n // Each of these is stable; the handle that carries them is not.\n const {\n changeCount,\n restored,\n record,\n undo,\n current: currentDocument,\n markSaved: historyMarkSaved,\n beginSave: historyBeginSave,\n rebase: rebaseHistory,\n } = useChangeHistory(formDocument, restore);\n\n // Actions\n const setFields = useCallback(\n (newFields: AnyFormField[]) => {\n setFieldsState(newFields);\n record(null);\n },\n [record]\n );\n\n const addField = useCallback(\n (field: AnyFormField, index?: number) => {\n setFieldsState(prev => {\n if (index !== undefined && index >= 0 && index <= prev.length) {\n const newFields = [...prev];\n newFields.splice(index, 0, field);\n return newFields;\n }\n return [...prev, field];\n });\n setSelectedFieldId(field.name);\n record(null);\n },\n [record]\n );\n\n /**\n * What a field's rename moves with it. The selection follows it, so the\n * open settings stay open — including when the name passes through the\n * empty string, where a rename that clears the input first goes: skipping it\n * there would stop the renamed field matching its own selection and close\n * its settings mid-rename. And a saved field stays saved under its new name.\n */\n const followRename = useCallback((from: string, to: string | undefined) => {\n if (to === undefined || to === from) return;\n vacatedRef.current.add(from);\n setSelectedFieldId(to);\n setSavedFieldNames(prev => {\n if (!prev.has(from)) return prev;\n const next = new Set(prev);\n next.delete(from);\n next.add(to);\n return next;\n });\n }, []);\n\n const updateField = useCallback(\n (fieldName: string, updates: Partial<AnyFormField>) => {\n setFieldsState(prev =>\n prev.map(f => (f.name === fieldName ? { ...f, ...updates } : f))\n );\n record(fieldChangeKey(currentDocument().fields, fieldName));\n followRename(fieldName, updates.name);\n },\n [followRename, record, currentDocument]\n );\n\n const replaceField = useCallback(\n (fieldName: string, next: AnyFormField, referencesFrom?: string) => {\n const moves =\n referencesFrom !== undefined &&\n next.name !== \"\" &&\n next.name !== referencesFrom;\n setFieldsState(prev => {\n const replaced = prev.map(f => (f.name === fieldName ? next : f));\n return moves\n ? renameInFieldConditions(replaced, referencesFrom, next.name)\n : replaced;\n });\n if (moves) {\n setNotifications(prev =>\n renameInNotifications(prev, referencesFrom, next.name)\n );\n // The Email field one response per person compares, by name.\n setSettings(prev =>\n prev.oneResponseField === referencesFrom\n ? { ...prev, oneResponseField: next.name }\n : prev\n );\n }\n record(fieldChangeKey(currentDocument().fields, fieldName));\n followRename(fieldName, next.name);\n },\n [followRename, record, currentDocument]\n );\n\n const { fieldKey, editField } = useFieldIdentity(replaceField);\n\n const deleteField = useCallback(\n (fieldName: string) => {\n setFieldsState(prev => prev.filter(f => f.name !== fieldName));\n vacatedRef.current.add(fieldName);\n // The name no longer stands for a stored field: a new field given it\n // later is a new field, with its type and name still its own to choose.\n setSavedFieldNames(prev => {\n if (!prev.has(fieldName)) return prev;\n const next = new Set(prev);\n next.delete(fieldName);\n return next;\n });\n if (selectedFieldId === fieldName) {\n setSelectedFieldId(null);\n }\n record(null);\n },\n [selectedFieldId, record]\n );\n\n const moveField = useCallback(\n (fromIndex: number, toIndex: number) => {\n setFieldsState(prev => {\n if (\n fromIndex < 0 ||\n fromIndex >= prev.length ||\n toIndex < 0 ||\n toIndex >= prev.length\n ) {\n return prev;\n }\n const newFields = [...prev];\n const [removed] = newFields.splice(fromIndex, 1);\n newFields.splice(toIndex, 0, removed);\n return newFields;\n });\n record(null);\n },\n [record]\n );\n\n const duplicateField = useCallback(\n (fieldName: string) => {\n // Computed from the current fields (not inside the state updater):\n // updaters must stay pure, and the selection below needs the new name\n // deterministically.\n const fieldIndex = fields.findIndex(f => f.name === fieldName);\n if (fieldIndex === -1) return;\n\n const field = fields[fieldIndex];\n const newFieldName = generateFieldName(\n field.type,\n fields.map(f => f.name)\n );\n const newField: AnyFormField = {\n ...field,\n name: newFieldName,\n label: `${field.label} (Copy)`,\n };\n\n const newFields = [...fields];\n newFields.splice(fieldIndex + 1, 0, newField);\n setFieldsState(newFields);\n\n // Select the duplicated field so its card expands\n setSelectedFieldId(newFieldName);\n record(null);\n },\n [fields, record]\n );\n\n const selectField = useCallback((fieldName: string | null) => {\n setSelectedFieldId(fieldName);\n }, []);\n\n const updateFormData = useCallback(\n (updates: Partial<FormBuilderState[\"formData\"]>) => {\n setFormData(prev => ({ ...prev, ...updates }));\n record(formDataKey(updates));\n },\n [record]\n );\n\n const updateSettings = useCallback(\n (updates: Partial<FormSettings>) => {\n setSettings(prev => ({ ...prev, ...updates }));\n record(`settings:${Object.keys(updates).sort().join(\",\")}`);\n },\n [record]\n );\n\n const addNotification = useCallback(\n (notification: FormNotification) => {\n setNotifications(prev => [...prev, notification]);\n record(null);\n },\n [record]\n );\n\n const duplicateNotification = useCallback(\n (id: string) => {\n // The non-deterministic id is generated outside the updater (updaters\n // must stay pure and may re-run); the copy itself derives from `prev`\n // so rapid duplications never work from a stale snapshot.\n const copyId = `notif_${Date.now().toString(36)}_${Math.random().toString(36).substring(2, 6)}`;\n setNotifications(prev => {\n const index = prev.findIndex(n => n.id === id);\n if (index === -1) return prev;\n const source = prev[index];\n const copy: FormNotification = {\n ...source,\n id: copyId,\n name: `${source.name} (Copy)`,\n // Copies start disabled so duplicating never doubles the emails a\n // live rule sends until the copy is deliberately turned on.\n enabled: false,\n };\n const next = [...prev];\n next.splice(index + 1, 0, copy);\n return next;\n });\n record(null);\n },\n [record]\n );\n\n const seedNotifications = useCallback((rules: FormNotification[]) => {\n // Only fills an empty list, and does not mark the form dirty: the seed\n // is a starting point the user hasn't authored, not an unsaved edit.\n setNotifications(prev => (prev.length > 0 ? prev : rules));\n }, []);\n\n const updateNotification = useCallback(\n (id: string, updates: Partial<FormNotification>) => {\n setNotifications(prev =>\n prev.map(n => (n.id === id ? { ...n, ...updates } : n))\n );\n record(`notification:${id}`);\n },\n [record]\n );\n\n const deleteNotification = useCallback(\n (id: string) => {\n setNotifications(prev => prev.filter(n => n.id !== id));\n record(null);\n },\n [record]\n );\n\n // A save stores the form as it stood when it began, its status among it.\n const {\n idFixed: formIdFixed,\n beginSave: statusBeginSave,\n markSaved: markAsSaved,\n saveFailed,\n } = useBeenLive(initialData, currentDocument, {\n beginSave: historyBeginSave,\n markSaved: historyMarkSaved,\n });\n\n const beginSave = useCallback(() => {\n vacatedRef.current = new Set();\n statusBeginSave();\n }, [statusBeginSave]);\n\n const isDirty = changeCount > 0;\n\n const markFieldsSaved = useCallback(\n (names: readonly string[]) => {\n // What was saved is what the stored form now holds.\n const vacated = vacatedRef.current;\n const stored = new Set(names.filter(name => !vacated.has(name)));\n setSavedFieldNames(stored);\n // A change kept after the save (made while it ran) holds the stored\n // set from before it, so taking that change back would forget what the\n // save stored and unfix a stored field's type.\n rebaseHistory(record => withStoredFields(record, stored));\n },\n [rebaseHistory]\n );\n\n // Memoized context value\n const value = useMemo<FormBuilderInternalValue>(\n () => ({\n // State\n fields,\n selectedFieldId,\n activeTab,\n isDirty,\n savedFieldNames,\n formData,\n settings,\n notifications,\n // Actions\n setFields,\n addField,\n updateField,\n replaceField,\n deleteField,\n moveField,\n duplicateField,\n selectField,\n setActiveTab,\n updateFormData,\n updateSettings,\n addNotification,\n duplicateNotification,\n updateNotification,\n deleteNotification,\n seedNotifications,\n markAsSaved,\n beginSave,\n markFieldsSaved,\n readOnly,\n changeCount,\n undo,\n restored,\n formIdFixed,\n saveFailed,\n fieldKey,\n editField,\n }),\n [\n fields,\n selectedFieldId,\n activeTab,\n isDirty,\n savedFieldNames,\n formData,\n settings,\n notifications,\n setFields,\n addField,\n updateField,\n replaceField,\n deleteField,\n moveField,\n duplicateField,\n selectField,\n updateFormData,\n updateSettings,\n addNotification,\n duplicateNotification,\n updateNotification,\n deleteNotification,\n seedNotifications,\n markAsSaved,\n beginSave,\n markFieldsSaved,\n readOnly,\n changeCount,\n undo,\n restored,\n formIdFixed,\n saveFailed,\n fieldKey,\n editField,\n ]\n );\n\n /**\n * The same value with every document-changing action withheld.\n *\n * 🔴 Fail-closed, and deliberately so: it withholds every function EXCEPT the\n * ones named, rather than naming the ones to withhold. An action added here\n * later is inert under a colleague's claim without anyone remembering this\n * exists, which is the opposite of how a list of things-to-disable ages.\n *\n * What stays is navigation and the save's own bookkeeping. Neither changes\n * the document, and both are what \"you can read it\" has to mean.\n */\n const guarded = useMemo<FormBuilderInternalValue>(() => {\n if (!readOnly) return value;\n return Object.fromEntries(\n Object.entries(value).map(([name, member]) =>\n typeof member === \"function\" && !READS_ONLY.has(name)\n ? [name, () => undefined]\n : [name, member]\n )\n ) as FormBuilderInternalValue;\n }, [value, readOnly]);\n\n return (\n <FormBuilderContext.Provider value={guarded}>\n {children}\n </FormBuilderContext.Provider>\n );\n}\n\n// ============================================================================\n// Hooks\n// ============================================================================\n\n/**\n * useFormBuilder - Access form builder context\n *\n * Hook to access form builder state and actions from within the builder UI.\n * Must be used within a FormBuilderProvider.\n *\n * @returns Form builder context value\n * @throws Error if used outside of FormBuilderProvider\n *\n * @example\n * ```tsx\n * function FieldList() {\n * const { fields, selectField, deleteField } = useFormBuilder();\n *\n * return (\n * <ul>\n * {fields.map(field => (\n * <li key={field.name} onClick={() => selectField(field.name)}>\n * {field.label}\n * <button onClick={() => deleteField(field.name)}>Delete</button>\n * </li>\n * ))}\n * </ul>\n * );\n * }\n * ```\n */\nexport function useFormBuilder(): FormBuilderContextValue {\n const context = useContext(FormBuilderContext);\n\n if (!context) {\n throw new Error(\"useFormBuilder must be used within a FormBuilderProvider\");\n }\n\n return context;\n}\n\n/**\n * useOptionalFormBuilder - Access form builder context safely\n *\n * Like useFormBuilder but returns null if not within a provider.\n * Useful for components that may be rendered outside of the form builder.\n *\n * @returns Form builder context value or null\n */\nexport function useOptionalFormBuilder(): FormBuilderContextValue | null {\n return useContext(FormBuilderContext);\n}\n\n/**\n * The builder's state and actions with its field bookkeeping (see\n * `FieldBookkeeping`), for the builder's own components. Not exported from\n * the package.\n */\nexport function useFieldBookkeeping(): FormBuilderInternalValue {\n const context = useContext(FormBuilderContext);\n if (!context) {\n throw new Error(\n \"useFieldBookkeeping must be used within a FormBuilderProvider\"\n );\n }\n return context;\n}\n","/**\n * The badges a field's row in the outline shows: what a person would\n * otherwise open the field to find out. Required, a width other than full,\n * a condition that decides whether it is shown, and a default answer.\n *\n * @module admin/components/builder/field-badges\n */\nimport type { AnyFormField } from \"../../../types\";\n\n/** One badge: its words, and a longer description where the words are short. */\nexport interface FieldBadge {\n kind: \"required\" | \"width\" | \"condition\" | \"default\";\n label: string;\n /** The whole of what the badge stands for, for its tooltip. */\n title?: string;\n}\n\n/** The longest default shown in full; a longer one is cut, with its whole in the title. */\nconst DEFAULT_LENGTH = 24;\n\n/** A field's options, where it has any (select, radio). */\nfunction optionsOf(\n field: AnyFormField\n): readonly { label: string; value: string }[] {\n return \"options\" in field && Array.isArray(field.options)\n ? field.options\n : [];\n}\n\n/**\n * A default as a person reads it: a choice's label rather than its stored\n * value, a yes or no for a checkbox, and nothing for an empty one.\n */\nfunction defaultText(field: AnyFormField): string | undefined {\n const value: unknown =\n \"defaultValue\" in field ? field.defaultValue : undefined;\n if (value === undefined || value === null || value === \"\") return undefined;\n if (typeof value === \"boolean\") return value ? \"Checked\" : undefined;\n if (typeof value !== \"string\" && typeof value !== \"number\") return undefined;\n const text = String(value);\n return optionsOf(field).find(option => option.value === text)?.label ?? text;\n}\n\n/**\n * What a field's condition does, as its badge's tooltip says it, naming the\n * fields it reads by their labels.\n */\nfunction conditionTitle(\n field: AnyFormField,\n labelOf: (name: string) => string\n): string | undefined {\n const logic = field.conditionalLogic;\n if (logic === undefined || !logic.enabled || logic.conditions.length === 0) {\n return undefined;\n }\n const fields = logic.conditions.map(condition => labelOf(condition.field));\n const which = logic.operator === \"OR\" ? \"any\" : \"all\";\n const verb = logic.action === \"hide\" ? \"Hidden\" : \"Shown\";\n return logic.conditions.length === 1\n ? `${verb} depending on ${fields[0]}`\n : `${verb} depending on ${which} of ${fields.join(\", \")}`;\n}\n\n/**\n * The badges for `field`, in the order the row shows them. `labelOf` names\n * another field by its label, for a condition's tooltip.\n */\nexport function fieldBadges(\n field: AnyFormField,\n labelOf: (name: string) => string = name => name\n): FieldBadge[] {\n const badges: FieldBadge[] = [];\n if (field.required) badges.push({ kind: \"required\", label: \"Required\" });\n const width = field.admin?.width;\n if (width !== undefined && width !== \"100%\") {\n badges.push({ kind: \"width\", label: width, title: `${width} of the row` });\n }\n const condition = conditionTitle(field, labelOf);\n if (condition !== undefined) {\n badges.push({\n kind: \"condition\",\n label:\n field.conditionalLogic?.action === \"hide\" ? \"Hidden if…\" : \"Shown if…\",\n title: condition,\n });\n }\n const fallback = defaultText(field);\n if (fallback !== undefined) {\n const cut =\n fallback.length > DEFAULT_LENGTH\n ? `${fallback.slice(0, DEFAULT_LENGTH - 1)}…`\n : fallback;\n badges.push({\n kind: \"default\",\n label: `= ${cut}`,\n title: `Default: ${fallback}`,\n });\n }\n return badges;\n}\n","\"use client\";\n\n/**\n * Conditional Logic Editor\n *\n * Component for configuring conditional show/hide logic for form fields.\n * Allows users to define conditions based on other field values.\n *\n * @module admin/components/builder/ConditionalLogicEditor\n * @since 0.1.0\n */\n\n\"use client\";\n\nimport {\n ConditionRow,\n type ConditionOperatorName,\n type ConditionSource,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n FormLabelWithTooltip,\n Button,\n Checkbox,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { type JSX, useCallback, useMemo } from \"react\";\n\nimport type {\n AnyFormField,\n ConditionalLogic,\n ConditionalLogicCondition,\n} from \"../../../types\";\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface ConditionalLogicEditorProps {\n /** The field being edited (built-in or plugin-contributed). */\n field: AnyFormField;\n /** All fields in the form (for field references) */\n allFields: AnyFormField[];\n /** Callback when conditional logic is updated */\n onUpdate: (updates: Partial<AnyFormField>) => void;\n /**\n * Why the field may not be given a condition, when it may not: turning\n * conditions on is then refused, and the reason shown. One already on can\n * still be turned off.\n */\n lockedReason?: string;\n}\n\n// ============================================================================\n// Constants\n// ============================================================================\n\n/**\n * The comparisons `evaluateConditions` implements.\n *\n * The shared row offers seventeen operators, and this runtime understands\n * seven of them. Every operator offered has to be one this evaluator can\n * answer: a field configured with `isTrue` would store fine, render fine, and\n * then never match at form time, with nothing anywhere to say why. So the\n * vocabulary is pinned here rather than taken from the row's default.\n *\n * Typed as the shared row's operator names AND as this module's stored\n * comparisons, so the day either list moves the other stops compiling.\n */\nconst SUPPORTED_COMPARISONS = [\n \"equals\",\n \"notEquals\",\n \"contains\",\n \"isEmpty\",\n \"isNotEmpty\",\n \"greaterThan\",\n \"lessThan\",\n] as const satisfies readonly ConditionOperatorName[] &\n readonly ConditionalLogicCondition[\"comparison\"][];\n\nconst SUPPORTED_SET: ReadonlySet<string> = new Set(SUPPORTED_COMPARISONS);\n\n/**\n * Whether an operator the row reported is one this runtime can evaluate.\n *\n * `operatorsFor` already narrows what the row offers, so this should always\n * hold; it is a guard rather than an assertion so that if the two ever drift,\n * the condition is left as it was instead of stored in a shape the evaluator\n * will not understand.\n */\nfunction isSupportedComparison(\n operator: string\n): operator is ConditionalLogicCondition[\"comparison\"] {\n return SUPPORTED_SET.has(operator);\n}\n\n// The field types whose values are text, named as `FormFieldType` spells them.\n// `tel` and `password` were guesses and match nothing; the builder's telephone\n// field is `phone`, so it was falling through to the full comparison set and\n// offering \"greater than\" for a phone number.\nconst TEXT_TYPES = new Set([\n \"text\",\n \"textarea\",\n \"email\",\n \"url\",\n \"phone\",\n \"hidden\",\n]);\n\n/**\n * The comparisons worth offering for a source type, within what the evaluator\n * supports.\n *\n * Narrower than the full seven where the type makes some of them meaningless:\n * \"contains\" on a number compares substrings of a formatted value, which is\n * not what anyone building that condition means.\n */\nfunction comparisonsForType(\n sourceType: string | undefined\n): ConditionOperatorName[] {\n if (sourceType === \"checkbox\") return [\"equals\", \"notEquals\"];\n if (sourceType === \"number\") {\n return [\"equals\", \"notEquals\", \"greaterThan\", \"lessThan\"];\n }\n if (sourceType !== undefined && TEXT_TYPES.has(sourceType)) {\n return [\"equals\", \"notEquals\", \"contains\", \"isEmpty\", \"isNotEmpty\"];\n }\n return [...SUPPORTED_COMPARISONS];\n}\n\n// ============================================================================\n// Helpers\n// ============================================================================\n\n/**\n * A form field as the shared row needs to see it.\n *\n * Choice fields hand over their options so the value editor becomes a dropdown\n * of exactly those, rather than a text box in which an option can be misspelled\n * into a condition that never matches.\n */\nfunction toSource(field: AnyFormField): ConditionSource {\n // Only the choice field types carry an option list, and `AnyFormField` is a\n // union over every type, so the property has to be probed rather than read.\n // An option with an empty value is dropped: a Select cannot render one, and\n // it is a placeholder row in the editor rather than a value to compare with.\n const options =\n \"options\" in field && Array.isArray(field.options)\n ? field.options\n .filter(option => option.value !== \"\")\n .map(option => ({ value: option.value, label: option.label }))\n : undefined;\n return {\n name: field.name,\n label: field.label || field.name,\n type: field.type,\n options,\n };\n}\n\n/**\n * A stored condition's value as the row edits it.\n *\n * Storage types this `unknown`, and the row edits scalars. An object that got\n * in some other way is shown as nothing rather than as `[object Object]`,\n * which is not a value anyone typed and not one the evaluator can use.\n */\nfunction toRowValue(value: unknown): string | number | boolean | undefined {\n if (value === null || value === undefined) return undefined;\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n return value;\n }\n return undefined;\n}\n\n// ============================================================================\n// Component\n// ============================================================================\n\n/**\n * ConditionalLogicEditor - Configure conditional show/hide logic\n *\n * Allows users to define when a field should be shown or hidden\n * based on the values of other fields in the form.\n *\n * @example\n * ```tsx\n * <ConditionalLogicEditor\n * field={selectedField}\n * allFields={allFields}\n * onUpdate={handleUpdate}\n * />\n * ```\n */\nexport function ConditionalLogicEditor({\n field,\n allFields,\n onUpdate,\n lockedReason,\n}: ConditionalLogicEditorProps): JSX.Element {\n // Get current conditional logic or create default.\n // Wrapped in useMemo so dependent useCallbacks don't re-create on every render.\n const logic: ConditionalLogic = useMemo(\n () =>\n field.conditionalLogic || {\n enabled: false,\n action: \"show\",\n operator: \"AND\",\n conditions: [],\n },\n [field.conditionalLogic]\n );\n\n // Get other fields that can be referenced (exclude current field). A\n // consent is left out too: every sent form has it agreed, so a condition on\n // it could only ever answer one way.\n const availableFields = allFields.filter(\n f => f.name !== field.name && f.type !== \"consent\"\n );\n\n // Update the entire conditional logic object\n const updateLogic = useCallback(\n (updates: Partial<ConditionalLogic>) => {\n onUpdate({\n conditionalLogic: { ...logic, ...updates },\n });\n },\n [logic, onUpdate]\n );\n\n // Toggle enabled state\n const handleToggleEnabled = useCallback(() => {\n updateLogic({ enabled: !logic.enabled });\n }, [logic.enabled, updateLogic]);\n\n // Add a new condition\n const handleAddCondition = useCallback(() => {\n const firstField = availableFields[0];\n if (!firstField) return;\n\n const newCondition: ConditionalLogicCondition = {\n field: firstField.name,\n comparison: \"equals\",\n value: \"\",\n };\n\n updateLogic({\n conditions: [...logic.conditions, newCondition],\n });\n }, [availableFields, logic.conditions, updateLogic]);\n\n // Update a specific condition\n const handleUpdateCondition = useCallback(\n (index: number, updates: Partial<ConditionalLogicCondition>) => {\n const newConditions = logic.conditions.map((c, i) =>\n i === index ? { ...c, ...updates } : c\n );\n updateLogic({ conditions: newConditions });\n },\n [logic.conditions, updateLogic]\n );\n\n // Remove a condition\n const handleRemoveCondition = useCallback(\n (index: number) => {\n const newConditions = logic.conditions.filter((_, i) => i !== index);\n updateLogic({ conditions: newConditions });\n },\n [logic.conditions, updateLogic]\n );\n\n return (\n <div className=\"space-y-6 pt-2\">\n {/* Enable toggle */}\n <div className=\"space-y-2\">\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id=\"logic-enabled\"\n checked={logic.enabled}\n disabled={lockedReason !== undefined && !logic.enabled}\n aria-describedby={\n lockedReason !== undefined && !logic.enabled\n ? \"logic-enabled-locked\"\n : undefined\n }\n onCheckedChange={handleToggleEnabled}\n />\n <FormLabelWithTooltip\n label=\"Enable conditional logic\"\n htmlFor=\"logic-enabled\"\n description=\"Show or hide this field based on other field values.\"\n />\n </div>\n {lockedReason !== undefined && !logic.enabled ? (\n <p\n id=\"logic-enabled-locked\"\n className=\"text-xs text-muted-foreground\"\n >\n {lockedReason}\n </p>\n ) : null}\n </div>\n\n {logic.enabled && (\n // Semantic border token so this top divider is visible at the 3:1 UI minimum.\n <div className=\"space-y-4 pt-4 border-t border-border\">\n {/* Action and operator row */}\n <div className=\"flex flex-wrap items-center gap-2 text-sm text-muted-foreground\">\n <Select\n value={logic.action}\n onValueChange={value =>\n updateLogic({ action: value as \"show\" | \"hide\" })\n }\n >\n <SelectTrigger className=\"w-[100px] h-9 bg-transparent border-input dark:bg-muted/50\">\n <SelectValue placeholder=\"Show/Hide\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"show\">Show</SelectItem>\n <SelectItem value=\"hide\">Hide</SelectItem>\n </SelectContent>\n </Select>\n <span>this field when</span>\n <Select\n value={logic.operator}\n onValueChange={value =>\n updateLogic({ operator: value as \"AND\" | \"OR\" })\n }\n >\n <SelectTrigger className=\"w-[80px] h-9 bg-transparent border-input dark:bg-muted/50\">\n <SelectValue placeholder=\"All/Any\" />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"AND\">All</SelectItem>\n <SelectItem value=\"OR\">Any</SelectItem>\n </SelectContent>\n </Select>\n <span>of the following conditions are met:</span>\n </div>\n\n {/* Conditions list */}\n <div className=\"space-y-3 pt-4\">\n {logic.conditions.length === 0 ? (\n // Semantic border token so this empty-state boundary is visible at the 3:1 UI minimum.\n <div className=\"p-4 bg-muted rounded-none border border-dashed border-border text-center text-xs text-muted-foreground\">\n No conditions defined. Add a condition to get started.\n </div>\n ) : (\n logic.conditions.map((condition, index) => (\n // Semantic border token so this condition card boundary is visible at the 3:1 UI minimum.\n <div\n key={index}\n className=\"flex flex-col gap-2 p-3 rounded-none bg-muted border border-border relative group\"\n >\n {/* Source, comparison and value, shared with the schema\n builder. Stacked rather than in three columns: this card\n sits in a builder sidebar, where three columns leave each\n control too narrow to read its own selection. */}\n <ConditionRow\n className=\"grid grid-cols-1 gap-2\"\n condition={{\n field: condition.field,\n operator: condition.comparison,\n value: toRowValue(condition.value),\n }}\n sources={availableFields.map(toSource)}\n operatorsFor={comparisonsForType}\n onChange={next => {\n if (!next || !isSupportedComparison(next.operator))\n return;\n handleUpdateCondition(index, {\n field: next.field,\n comparison: next.operator,\n value: toRowValue(next.value),\n });\n }}\n />\n\n {/* Remove button */}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n onClick={() => handleRemoveCondition(index)}\n // Semantic border token so the remove button's boundary is visible at the 3:1 UI minimum.\n className=\"absolute -top-2 -right-2 h-6 w-6 rounded-full bg-background border border-border opacity-0 group-hover:opacity-100 transition-opacity\"\n title=\"Remove condition\"\n >\n <span className=\"text-xs\">×</span>\n </Button>\n </div>\n ))\n )}\n </div>\n\n {/* Add condition button */}\n {availableFields.length > 0 ? (\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={handleAddCondition}\n className=\"w-full mt-4 border-dashed\"\n >\n + Add Condition\n </Button>\n ) : (\n // Full-strength warning border so this notice boundary is perceivable over its tinted fill.\n // The 600 shade rather than the base token: the base measures 4.37:1\n // once its own 10% fill composites over the page container, short of\n // the 4.5:1 text needs. The 600 shade holds 5.13:1 at its worst\n // surface in either mode.\n <p className=\"mt-4 text-center text-xs text-warning-600 font-medium bg-warning/10 p-2 rounded-none border border-warning\">\n Add more fields to the form to create conditions.\n </p>\n )}\n </div>\n )}\n </div>\n );\n}\n","/**\n * A form field as the shared settings panel edits it, and back.\n *\n * The panel edits the admin's field definition, which spells some of a form\n * field's settings differently (help text is `admin.description`, the custom\n * message `validation.message`, a dropdown's multiple choice `hasMany`) and\n * has no place for the rest (a text area's rows, a file's accepted types, a\n * field's show-or-hide logic). The settings it has a place for are moved\n * across; everything else travels unchanged under a key of this module's\n * own (a symbol, so no setting a plugin declares can be mistaken for it),\n * where the forms surface's own controls read and write it. A plugin field\n * type's declared settings are `pluginOptions`, on the form field as in the\n * panel. So a field the panel did not change comes back exactly as it went\n * in, and nothing a form stores is lost by being edited.\n *\n * @module admin/components/builder/panel-field\n */\n\nimport type { FieldSettingsField } from \"@nextlyhq/plugin-sdk/admin\";\n\nimport type { AnyFormField } from \"../../../types\";\n\ntype Bag = Record<string, unknown>;\n\n/**\n * The form-only settings: the field's own properties the panel has no place\n * for, and the parts of its `validation` and `admin` that likewise have none.\n * `validation` and `admin` are present exactly when the form field had them,\n * so an empty object on the way in is an empty object on the way out.\n */\ninterface FormOnly extends Bag {\n validation?: Bag;\n admin?: Bag;\n}\n\n/**\n * Where the form-only settings travel on the panel's field. A symbol rather\n * than a named key: the panel copies a field by spreading it, which keeps a\n * symbol-keyed property, and no key a plugin declares can equal it.\n */\nconst FORM_ONLY: unique symbol = Symbol(\"form builder: form-only settings\");\n\n/** The panel's field, carrying the form-only settings. */\ntype PanelField = FieldSettingsField & { [FORM_ONLY]?: FormOnly };\n\ntype Width = NonNullable<FieldSettingsField[\"admin\"]>[\"width\"];\n/** A default the panel's default editor can show and set. */\ntype PanelDefault = string | number | boolean;\n\n/** The object with its undefined entries left out. */\nfunction defined<T extends Bag>(bag: T): Partial<T> {\n return Object.fromEntries(\n Object.entries(bag).filter(([, value]) => value !== undefined)\n ) as Partial<T>;\n}\n\n/** A value only when it has entries, or when the original had the object. */\nfunction present(bag: Bag, had: boolean): Bag | undefined {\n return had || Object.keys(bag).length > 0 ? bag : undefined;\n}\n\nfunction isPanelDefault(value: unknown): value is PanelDefault {\n return (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n );\n}\n\n/**\n * The field as the panel edits it. `id` tells it apart from the fields beside\n * it while it is edited; forms store no id, so the host mints one per field.\n */\nexport function toPanelField(\n field: AnyFormField,\n id: string\n): FieldSettingsField {\n const {\n type,\n name,\n label,\n placeholder,\n helpText,\n required,\n validation,\n admin,\n defaultValue,\n ...rest\n } = field;\n const { options, allowMultiple, pluginOptions, ...own } = rest as Bag & {\n options?: Array<{ label: string; value: string }>;\n allowMultiple?: boolean;\n pluginOptions?: Bag;\n };\n const {\n errorMessage,\n minLength,\n maxLength,\n min,\n max,\n pattern,\n ...validationRest\n } = (validation ?? {}) as Bag & {\n errorMessage?: string;\n minLength?: number;\n maxLength?: number;\n min?: number;\n max?: number;\n pattern?: string;\n };\n const { width, ...adminRest } = (admin ?? {}) as Bag & { width?: Width };\n\n const formOnly: FormOnly = defined({\n ...own,\n validation: validation === undefined ? undefined : validationRest,\n admin: admin === undefined ? undefined : adminRest,\n // A default the panel cannot hold (a dropdown's several choices, a\n // plugin field's null) stays with the form's own settings rather than\n // being bent into one, or read as no default.\n defaultValue:\n defaultValue === undefined || isPanelDefault(defaultValue)\n ? undefined\n : defaultValue,\n });\n\n const panel: PanelField = {\n id,\n type,\n name,\n label,\n ...defined({\n options,\n hasMany: allowMultiple,\n defaultValue: isPanelDefault(defaultValue) ? defaultValue : undefined,\n // A plugin field type's declared settings, where the panel's controls\n // for them read and write.\n pluginOptions,\n }),\n admin: defined({ placeholder, description: helpText, width }),\n validation: defined({\n required,\n minLength,\n maxLength,\n min,\n max,\n pattern,\n message: errorMessage,\n }),\n [FORM_ONLY]: formOnly,\n };\n return panel;\n}\n\n/** The form-only settings a panel field carries. */\nexport function formOnlyOf(field: FieldSettingsField): FormOnly {\n return (field as PanelField)[FORM_ONLY] ?? {};\n}\n\n/** The form field the panel's field describes. */\nexport function fromPanelField(field: FieldSettingsField): AnyFormField {\n const {\n validation: validationRest,\n admin: adminRest,\n defaultValue: formDefault,\n ...own\n } = formOnlyOf(field);\n const panelAdmin = field.admin ?? {};\n const panelValidation = field.validation ?? {};\n\n const validation = present(\n {\n ...validationRest,\n ...defined({\n minLength: panelValidation.minLength,\n maxLength: panelValidation.maxLength,\n min: panelValidation.min,\n max: panelValidation.max,\n pattern: panelValidation.pattern,\n errorMessage: panelValidation.message,\n }),\n },\n validationRest !== undefined\n );\n const admin = present(\n { ...adminRest, ...defined({ width: panelAdmin.width }) },\n adminRest !== undefined\n );\n\n return {\n ...own,\n type: field.type,\n name: field.name,\n label: field.label,\n ...defined({\n placeholder: panelAdmin.placeholder,\n helpText: panelAdmin.description,\n required: panelValidation.required,\n options: field.options?.map(({ label, value }) => ({ label, value })),\n allowMultiple: field.hasMany,\n defaultValue: formDefaultValue(field, formDefault),\n validation,\n admin,\n pluginOptions: field.pluginOptions,\n }),\n };\n}\n\n/**\n * The default the form stores. The panel's default editor picks one value, so\n * where a dropdown takes several, the value it picks becomes a list of one:\n * the form checks a several-choice answer, its default included, as a list.\n * A list the panel could not show is kept until the panel sets another.\n */\nfunction formDefaultValue(\n field: FieldSettingsField,\n formDefault: unknown\n): unknown {\n const picked = dateOnly(field);\n if (picked === undefined || picked === null) {\n // A list kept for a dropdown that now takes one choice: its one value, or\n // none, since the form checks a one-choice answer as a single value.\n if (\n field.type === \"select\" &&\n field.hasMany !== true &&\n Array.isArray(formDefault)\n ) {\n return formDefault.length === 1 ? formDefault[0] : undefined;\n }\n return formDefault;\n }\n return field.hasMany === true && typeof picked === \"string\"\n ? [picked]\n : picked;\n}\n\n/**\n * The panel's default, with a date's in the form's own shape. The panel's\n * date editor stores the chosen day as that day's UTC midnight\n * (`2026-01-01T00:00:00.000Z`); a form's date is the day alone, as its date\n * input shows it and a visitor's answer arrives, so the day is kept.\n */\nfunction dateOnly(\n field: FieldSettingsField\n): FieldSettingsField[\"defaultValue\"] {\n const value = field.defaultValue;\n return field.type === \"date\" &&\n typeof value === \"string\" &&\n /^\\d{4}-\\d{2}-\\d{2}T/.test(value)\n ? value.slice(0, 10)\n : value;\n}\n\n/**\n * The panel field with some of its form-only settings changed: how the forms\n * surface's own controls write what the panel has no place for.\n */\nexport function withFormOnly(\n field: FieldSettingsField,\n changes: Partial<AnyFormField>\n): FieldSettingsField {\n return toPanelField({ ...fromPanelField(field), ...changes }, field.id);\n}\n","\"use client\";\n\n/**\n * The forms surface's configuration of the shared settings panel.\n *\n * The panel draws what every field has (label, name, help text, required,\n * width, default value, the validation rules, placeholder); this module adds\n * what only a form has, and narrows what a form does not honour:\n * - a dropdown's and a radio group's choices, stored as plain label and value\n * pairs, and whether a dropdown takes several;\n * - a checkbox as one yes or no, with no list of choices;\n * - a text area's rows, a file's accepted types and several files, a date's\n * bounds, a number's step and a hidden field's value;\n * - a file's size limit, which a form stores on the field;\n * - the form's own show-or-hide logic on several conditions, as its own tab;\n * - only the validation rules the form's checks enforce for the type;\n * - no Read only or Hidden switches, which mean something in the entry editor\n * and nothing on a public form;\n * - a type fixed once the form has saved the field.\n *\n * Each control edits the form field through `panel-field`, so what the panel\n * has no place for is carried and written back unchanged.\n *\n * @module admin/components/builder/forms-field-surface\n */\n\nimport {\n FieldOptionsEditor,\n withOptionIds,\n type FieldOptionsEditorProps,\n type FieldSettingsConditionsProps,\n type FieldSettingsControlProps,\n type FieldSettingsSurface,\n type FieldSettingsSurfaceControlProps,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Checkbox,\n FormLabelWithTooltip,\n Input,\n Label,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { type JSX, type ReactNode, useCallback, useState } from \"react\";\n\nimport type { AnyFieldValidation, AnyFormField } from \"../../../types\";\nimport { enforcedValidationRules } from \"../../../utils/enforced-validation\";\nimport { oneResponseEmailField } from \"../../../utils/field-references\";\nimport {\n comparesByEmail,\n ONE_RESPONSE_FIELD_REASON,\n} from \"../../../utils/one-response-field\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { ConditionalLogicEditor } from \"./ConditionalLogicEditor\";\nimport { fromPanelField, toPanelField, withFormOnly } from \"./panel-field\";\n\n/**\n * The name a label gives a new field: a key for the submission data, which\n * must start with a letter and hold only letters, digits and underscores.\n * \"Full name\" becomes `full_name`. A label with none of those letters (one in\n * Japanese, say) still gives a name, `field`, which the host makes unique on\n * the form: the picker adds nothing without a name, so an empty one would\n * refuse the label outright.\n */\nexport function formFieldName(label: string): string {\n const name = label\n .normalize(\"NFKD\")\n // The accents NFKD splits off (\"é\" becomes \"e\" and a combining mark).\n .replace(/[\\u0300-\\u036f]/g, \"\")\n .toLowerCase()\n .replace(/[^a-z0-9]+/g, \"_\")\n .replace(/^[^a-z]+/, \"\")\n .replace(/_+$/, \"\");\n return name || (label.trim() ? \"field\" : \"\");\n}\n\n/** The form field a control edits, and how it writes a change back. */\nfunction useFormField({ field, onChange }: FieldSettingsControlProps): {\n form: AnyFormField;\n update: (changes: Partial<AnyFormField>) => void;\n} {\n return {\n form: fromPanelField(field),\n update: changes => onChange(withFormOnly(field, changes)),\n };\n}\n\n/** One labelled setting, as the panel draws its own. */\nfunction Setting({\n id,\n label,\n help,\n children,\n}: {\n id: string;\n label: string;\n help: string;\n children: ReactNode;\n}): JSX.Element {\n return (\n <div className=\"space-y-2\">\n <FormLabelWithTooltip label={label} htmlFor={id} description={help} />\n {children}\n </div>\n );\n}\n\n/** One yes-or-no setting. */\nfunction Toggle({\n id,\n label,\n help,\n checked,\n readOnly,\n onChange,\n}: {\n id: string;\n label: string;\n help: string;\n checked: boolean;\n readOnly: boolean;\n onChange: (checked: boolean) => void;\n}): JSX.Element {\n return (\n <div className=\"flex items-center gap-3\">\n <Checkbox\n id={id}\n checked={checked}\n disabled={readOnly}\n onCheckedChange={value => onChange(value === true)}\n />\n <FormLabelWithTooltip label={label} htmlFor={id} description={help} />\n </div>\n );\n}\n\ntype KitOption = FieldOptionsEditorProps[\"options\"][number];\n\n/**\n * A dropdown's or radio group's choices. The options editor works on rows\n * with ids for drag and drop, which a form does not store: they live here and\n * `panel-field` strips them on the way back.\n */\nfunction FormChoices(props: FieldSettingsControlProps): JSX.Element {\n const { field, readOnly, onChange } = props;\n const [rows, setRows] = useState<KitOption[]>(() =>\n withOptionIds(field.options ?? [])\n );\n const changeRows = useCallback(\n (next: KitOption[]) => {\n setRows(next);\n const options = next.map(({ label, value }) => ({ label, value }));\n const form = fromPanelField({ ...field, options });\n onChange(\n toPanelField(\n {\n ...form,\n defaultValue: defaultAmong(\n form.defaultValue,\n options.map(option => option.value)\n ),\n },\n field.id\n )\n );\n },\n [field, onChange]\n );\n return (\n <div className=\"space-y-4\">\n <fieldset disabled={readOnly} className=\"contents\">\n <FieldOptionsEditor options={rows} onOptionsChange={changeRows} />\n </fieldset>\n {field.type === \"select\" && (\n <Toggle\n id={`${field.id}-several`}\n label=\"Allow several choices\"\n help=\"A visitor may pick more than one option.\"\n checked={field.hasMany === true}\n readOnly={readOnly}\n onChange={hasMany => onChange({ ...field, hasMany })}\n />\n )}\n </div>\n );\n}\n\n/**\n * A choice field's default, kept only where its choices still hold it: a\n * default naming a choice that was removed or renamed is one the form's check\n * would refuse, so it goes rather than being saved.\n */\nfunction defaultAmong(value: unknown, choices: readonly string[]): unknown {\n if (Array.isArray(value)) {\n const kept = value.filter(each => choices.includes(each as string));\n return kept.length > 0 ? kept : undefined;\n }\n return typeof value === \"string\" && !choices.includes(value)\n ? undefined\n : value;\n}\n\n/** A number input whose empty value means \"not set\". */\nfunction numberOrUndefined(text: string): number | undefined {\n if (text.trim() === \"\") return undefined;\n const value = Number(text);\n return Number.isFinite(value) ? value : undefined;\n}\n\n/** What a text area, a file, a date, a number and a hidden field add. */\nfunction FormTypeSettings(\n props: FieldSettingsSurfaceControlProps\n): JSX.Element | null {\n switch (props.field.type) {\n case \"textarea\":\n return <TextareaRows {...props} />;\n case \"file\":\n return <FileSettings {...props} />;\n case \"date\":\n return <DateBounds {...props} />;\n case \"number\":\n return <NumberStep {...props} />;\n case \"hidden\":\n return <HiddenValue {...props} />;\n case \"email\":\n return <OneResponseUse {...props} />;\n default:\n return null;\n }\n}\n\n/**\n * Whether this Email field is the one one response per email compares: chosen\n * here or in Settings, offered only while Settings compares by email and the\n * field can be compared (required, never hidden by a condition). Another\n * field is chosen to change it, so the switch is not turned off here.\n */\nfunction OneResponseUse(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form } = useFormField(props);\n const { settings, updateSettings } = useFormBuilder();\n const id = `${props.field.id}-one-response`;\n const byEmail = settings.oneResponse === \"email\";\n const chosen = byEmail && settings.oneResponseField === form.name;\n const help = !byEmail\n ? 'Only when Settings › One response per person is \"One per email address\".'\n : chosen\n ? \"Compared for one response per person, so it stays required and always shown. Choose another Email field to change it.\"\n : comparesByEmail(form)\n ? \"Compare this field for one response per person instead.\"\n : \"Make it required, with no condition, to use it.\";\n return (\n <div className=\"flex items-start justify-between gap-4\">\n <div className=\"space-y-1\">\n <Label htmlFor={id}>Use for one response per person</Label>\n <p id={`${id}-help`} className=\"text-xs text-muted-foreground\">\n {help}\n </p>\n </div>\n <Switch\n id={id}\n aria-describedby={`${id}-help`}\n checked={chosen}\n disabled={\n props.readOnly || !byEmail || chosen || !comparesByEmail(form)\n }\n onCheckedChange={on => {\n if (on) updateSettings({ oneResponseField: form.name });\n }}\n />\n </div>\n );\n}\n\n/** A text area's height. */\nfunction TextareaRows(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const rows = (form as Extract<AnyFormField, { type: \"textarea\" }>).rows;\n const id = `${props.field.id}-rows`;\n return (\n <Setting\n id={id}\n label=\"Rows\"\n help=\"The text area's height, in lines of text.\"\n >\n <Input\n id={id}\n type=\"number\"\n min={2}\n max={20}\n disabled={props.readOnly}\n value={rows ?? \"\"}\n onChange={e => update({ rows: numberOrUndefined(e.target.value) })}\n className=\"w-24\"\n />\n </Setting>\n );\n}\n\n/** The first and last date a visitor may pick. */\nfunction DateBounds(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const date = form as Extract<AnyFormField, { type: \"date\" }>;\n const id = props.field.id;\n return (\n <div className=\"grid grid-cols-2 gap-4\">\n <Setting\n id={`${id}-min-date`}\n label=\"Earliest date\"\n help=\"The first date a visitor may pick.\"\n >\n <Input\n id={`${id}-min-date`}\n type=\"date\"\n disabled={props.readOnly}\n value={date.min ?? \"\"}\n onChange={e => update({ min: e.target.value || undefined })}\n />\n </Setting>\n <Setting\n id={`${id}-max-date`}\n label=\"Latest date\"\n help=\"The last date a visitor may pick.\"\n >\n <Input\n id={`${id}-max-date`}\n type=\"date\"\n disabled={props.readOnly}\n value={date.max ?? \"\"}\n onChange={e => update({ max: e.target.value || undefined })}\n />\n </Setting>\n </div>\n );\n}\n\n/** How far each press of a number's arrows moves it. */\nfunction NumberStep(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const validation: AnyFieldValidation = form.validation ?? {};\n const id = `${props.field.id}-step`;\n return (\n <Setting\n id={id}\n label=\"Step\"\n help=\"How far each press of the arrows moves the number, 0.01 for cents.\"\n >\n <Input\n id={id}\n type=\"number\"\n disabled={props.readOnly}\n placeholder=\"1\"\n value={validation.step ?? \"\"}\n onChange={e =>\n update({\n validation: {\n ...validation,\n step: numberOrUndefined(e.target.value),\n },\n })\n }\n className=\"w-24\"\n />\n </Setting>\n );\n}\n\n/** The value a hidden field sends. */\nfunction HiddenValue(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const id = `${props.field.id}-hidden-value`;\n return (\n <Setting\n id={id}\n label=\"Value\"\n help=\"Sent with every submission, without the visitor seeing it.\"\n >\n <Input\n id={id}\n type=\"text\"\n disabled={props.readOnly}\n value={typeof form.defaultValue === \"string\" ? form.defaultValue : \"\"}\n onChange={e => update({ defaultValue: e.target.value || undefined })}\n />\n </Setting>\n );\n}\n\n/**\n * What a consent's visitor agrees to. Each answer keeps the text it was given\n * under, so changing it here changes what later visitors agree to and leaves\n * earlier answers as they were.\n */\nfunction ConsentText(props: FieldSettingsControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const text = (form as Extract<AnyFormField, { type: \"consent\" }>)\n .agreementText;\n const id = `${props.field.id}-agreement-text`;\n return (\n <Setting\n id={id}\n label=\"Agreement text\"\n help=\"Shown beside the box. Each submission keeps the text it agreed to, so a change applies to later visitors only.\"\n >\n <Textarea\n id={id}\n rows={3}\n disabled={props.readOnly}\n value={text ?? \"\"}\n onChange={e => update({ agreementText: e.target.value })}\n />\n </Setting>\n );\n}\n\n/** A file's accepted types, and whether it takes several files. */\nfunction FileSettings(props: FieldSettingsSurfaceControlProps): JSX.Element {\n const { form, update } = useFormField(props);\n const { readOnly } = props;\n const id = props.field.id;\n const file = form as Extract<AnyFormField, { type: \"file\" }>;\n return (\n <div className=\"space-y-4\">\n <Setting\n id={`${id}-accept`}\n label=\"Accepted file types\"\n help=\"File types a visitor may upload, separated by commas, such as image/* or application/pdf.\"\n >\n <Input\n id={`${id}-accept`}\n type=\"text\"\n disabled={readOnly}\n placeholder=\"image/*,application/pdf\"\n value={file.accept ?? \"\"}\n onChange={e => update({ accept: e.target.value || undefined })}\n />\n </Setting>\n <Toggle\n id={`${id}-multiple`}\n label=\"Allow several files\"\n help=\"A visitor may upload more than one file.\"\n checked={file.multiple === true}\n readOnly={readOnly}\n onChange={multiple => update({ multiple })}\n />\n {/* Which notifications carry its files is chosen on each\n notification's page (Attach files from); a field's earlier switch\n stays stored as what a notification saved before attaches. */}\n </div>\n );\n}\n\n/** A file's size limit, which a form stores on the field. */\nfunction FileSizeSetting(\n props: FieldSettingsSurfaceControlProps\n): JSX.Element | null {\n const { form, update } = useFormField(props);\n if (form.type !== \"file\") return null;\n const file = form as Extract<AnyFormField, { type: \"file\" }>;\n const id = `${props.field.id}-max-size`;\n return (\n <Setting\n id={id}\n label=\"Largest file (bytes)\"\n help=\"The size a file may not exceed; 10485760 is 10 MB.\"\n >\n <Input\n id={id}\n type=\"number\"\n min={0}\n disabled={props.readOnly}\n placeholder=\"10485760\"\n value={file.maxFileSize ?? \"\"}\n onChange={e =>\n update({ maxFileSize: numberOrUndefined(e.target.value) })\n }\n />\n </Setting>\n );\n}\n\n/**\n * The form's own show-or-hide logic, on any number of conditions joined by\n * all or any: a shape the panel's single condition cannot hold.\n */\nfunction FormConditions(props: FieldSettingsConditionsProps): JSX.Element {\n const { form, update } = useFormField(props);\n const { settings } = useFormBuilder();\n const siblings = props.siblings.map(fromPanelField);\n return (\n <fieldset disabled={props.readOnly} className=\"contents\">\n <ConditionalLogicEditor\n field={form}\n allFields={siblings}\n onUpdate={update}\n // The Email field one response per email compares is always shown.\n lockedReason={\n oneResponseEmailField(settings) === form.name\n ? ONE_RESPONSE_FIELD_REASON\n : undefined\n }\n />\n </fieldset>\n );\n}\n\n/** Why a saved field's type cannot change. */\nexport const SAVED_TYPE_REASON =\n \"A saved field keeps its type, so its answers stay readable. For another type, add a new field.\";\n\n/**\n * Why a saved field's name cannot change: its answers are stored under it, so\n * a new name would leave every earlier answer behind under the old one.\n */\nexport const SAVED_NAME_REASON =\n \"Answers are stored under this name, so it stays as saved. For another name, add a new field.\";\n\n/** How the Form Builder configures the shared settings panel. */\nexport const FORMS_FIELD_SURFACE: FieldSettingsSurface = {\n surface: \"forms\",\n deriveName: formFieldName,\n fixed: [\n { setting: \"type\", when: \"saved\", reason: SAVED_TYPE_REASON },\n { setting: \"name\", when: \"saved\", reason: SAVED_NAME_REASON },\n ],\n extraSettings: { general: FormTypeSettings, validation: FileSizeSetting },\n conditions: FormConditions,\n // Offered only where the form's checks read the rule for that type: a rule\n // nothing enforces would let an author set a limit visitors never meet.\n validationRules: (type, allowed) => {\n const enforced = enforcedValidationRules(type);\n return allowed.filter(rule => enforced.includes(rule));\n },\n displaySwitches: false,\n typeSettings: {\n // One yes or no (a consent box, say), never a list of choices.\n checkbox: {},\n // What the visitor agrees to is what makes a consent, so it comes first.\n consent: { defining: ConsentText },\n select: { defining: FormChoices },\n radio: { defining: FormChoices },\n },\n};\n","\"use client\";\n\n/**\n * The selected field's settings: the shared settings panel, configured for\n * forms (`forms-field-surface`).\n *\n * Where the window is wide enough it docks at the window's edge as a column\n * of its own beside the whole editor (`FieldInspector`); where it is not, the\n * list opens it under the selected row (`useFieldPanel`). One panel either\n * way, so the two cannot come to differ.\n *\n * @module admin/components/builder/FieldInspector\n */\nimport { FieldSettingsPanel } from \"@nextlyhq/plugin-sdk/admin\";\nimport { toast } from \"@nextlyhq/ui\";\nimport { type JSX, type ReactNode, useMemo, useSyncExternalStore } from \"react\";\n\nimport { consentFieldEdit } from \"../../../utils/consent\";\nimport { oneResponseEditProblem } from \"../../../utils/one-response-field\";\nimport {\n useFieldBookkeeping,\n useFormBuilder,\n} from \"../../context/FormBuilderContext\";\n\nimport { FORMS_FIELD_SURFACE } from \"./forms-field-surface\";\nimport { fromPanelField, toPanelField } from \"./panel-field\";\n\n/** The id of the docked settings column, which a selected row controls. */\nexport const DOCKED_PANEL_ID = \"form-field-settings\";\n\n/**\n * The width from which the settings panel docks at the window's edge: the\n * Schema Builder's. The form editor folds the Forms panel away, so from\n * 1024px the outline keeps room to read a field's label beside it.\n */\nconst DOCKED = \"(min-width: 1024px)\";\n\nfunction subscribeDocked(onChange: () => void): () => void {\n if (typeof window.matchMedia !== \"function\") return () => undefined;\n const query = window.matchMedia(DOCKED);\n query.addEventListener(\"change\", onChange);\n return () => query.removeEventListener(\"change\", onChange);\n}\n\n/**\n * Whether the window is wide enough to dock the panel. A window without\n * `matchMedia` (a server render, a test) opens it under the row, which works\n * at any width.\n */\nexport function useDocked(): boolean {\n return useSyncExternalStore(\n subscribeDocked,\n () =>\n typeof window.matchMedia === \"function\" &&\n window.matchMedia(DOCKED).matches,\n () => false\n );\n}\n\n/**\n * The selected field's settings panel, or `null` with no field selected.\n * `closable` offers the panel's own Close, for the docked column; under a row,\n * the row itself closes it.\n */\nexport function useFieldPanel(closable: boolean): ReactNode {\n const { fields, settings, selectedFieldId, selectField } = useFormBuilder();\n // A colleague's claim makes every setting read-only, as the provider holds it.\n const { savedFieldNames, readOnly, fieldKey, editField, restored } =\n useFieldBookkeeping();\n\n // Every field as the panel sees it, so a condition can name any of them.\n const panelFields = useMemo(\n () => fields.map(field => toPanelField(field, fieldKey(field.name))),\n [fields, fieldKey]\n );\n\n const index = fields.findIndex(field => field.name === selectedFieldId);\n if (index === -1) return null;\n const selected = fields[index];\n return (\n <FieldSettingsPanel\n // Opened afresh on the restored field after an Undo, so nothing the\n // panel keeps of its own shows what was taken back.\n key={restored}\n surface={FORMS_FIELD_SURFACE}\n field={panelFields[index]}\n ancestors={[]}\n siblings={panelFields}\n saved={savedFieldNames.has(selected.name)}\n readOnly={readOnly}\n onChange={next => {\n // A consent stays required, and the Email field one response per\n // email compares stays required, shown and an Email field: such an\n // edit is refused, and why said.\n const kept = consentFieldEdit(selected, fromPanelField(next));\n if (\"refused\" in kept) {\n toast.error(kept.refused);\n return;\n }\n const refused = oneResponseEditProblem(selected, kept.field, settings);\n if (refused !== undefined) {\n toast.error(refused);\n return;\n }\n editField(selected, kept.field, fields);\n }}\n onClose={closable ? () => selectField(null) : undefined}\n />\n );\n}\n\n/**\n * The docked panel: a column at the window's edge, as tall as the window\n * below the bar, its own body scrolling. Draws nothing below the docking\n * width, where the list opens the panel under the row instead.\n */\nexport function FieldInspector(): JSX.Element | null {\n const docked = useDocked();\n const panel = useFieldPanel(true);\n if (!docked || panel === null) return null;\n return (\n <aside\n id={DOCKED_PANEL_ID}\n aria-label=\"Field settings\"\n className=\"sticky top-0 flex h-[var(--editor-scrollport,calc(100vh-var(--admin-bar-height)))] w-[23rem] shrink-0 flex-col overflow-hidden border-l border-border bg-card\"\n >\n {panel}\n </aside>\n );\n}\n","\"use client\";\n\n/**\n * Field Cards\n *\n * The form builder's fields as one outline: a row per field showing its\n * label, type and name, with badges for what it would otherwise take opening\n * it to see (required, width, a condition, a default). Selecting a row opens\n * the shared settings panel (`FieldInspector`): docked at the window's edge\n * where the window is wide enough, and under the row where it is not.\n *\n * A field is added through the shared field picker, from the toolbar, from\n * between two rows, or with \"/\" on a row, which adds below it.\n *\n * Reordering is triple-redundant on purpose: drag handles (pointer), the\n * same handle with Space + arrow keys (dnd-kit keyboard sensor), and\n * explicit Move up / Move down menu items. Deleting a field that another\n * field's condition, a notification or a form setting references is blocked\n * with the list of referrers instead of silently breaking them.\n *\n * @module admin/components/builder/FieldCards\n */\n\nimport {\n closestCenter,\n DndContext,\n KeyboardSensor,\n PointerSensor,\n useSensor,\n useSensors,\n type DragEndEvent,\n} from \"@dnd-kit/core\";\nimport {\n SortableContext,\n sortableKeyboardCoordinates,\n useSortable,\n verticalListSortingStrategy,\n} from \"@dnd-kit/sortable\";\nimport { CSS } from \"@dnd-kit/utilities\";\nimport {\n describeFieldTypes,\n FieldPicker,\n type FieldPickerChoice,\n type FieldPickerConfig,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Tooltip,\n TooltipContent,\n TooltipTrigger,\n} from \"@nextlyhq/ui\";\nimport * as Lucide from \"lucide-react\";\nimport type {\n FieldTypeCatalogEntry,\n FormFieldCatalogType,\n} from \"nextly/field-catalog\";\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport {\n type JSX,\n type KeyboardEvent,\n type ReactNode,\n useCallback,\n useMemo,\n useState,\n} from \"react\";\n\nimport type { AnyFormField, FormFieldTypeId } from \"../../../types\";\nimport { buildFieldReferenceMap } from \"../../../utils/field-references\";\nimport {\n useFieldBookkeeping,\n useFormBuilder,\n} from \"../../context/FormBuilderContext\";\n\nimport { fieldBadges, type FieldBadge } from \"./field-badges\";\nimport { DOCKED_PANEL_ID, useDocked, useFieldPanel } from \"./FieldInspector\";\nimport { formFieldName } from \"./forms-field-surface\";\n\n// ---------------------------------------------------------------------------\n// Catalog helpers\n// ---------------------------------------------------------------------------\n\nconst CATALOG_BY_TYPE = new Map<\n string,\n FieldTypeCatalogEntry<FormFieldCatalogType>\n>(FORM_FIELD_TYPE_CATALOG.map(entry => [entry.type, entry]));\n\n/** Resolve a catalog icon name against lucide; unknown names fall back. */\nfunction CatalogIcon({\n name,\n className,\n}: {\n name: string | undefined;\n className?: string;\n}) {\n const icons = Lucide as unknown as Record<string, React.ElementType>;\n const Icon = (name && icons[name]) || Lucide.Type;\n return <Icon className={className} aria-hidden=\"true\" />;\n}\n\n// ---------------------------------------------------------------------------\n// One row\n// ---------------------------------------------------------------------------\n\n/** A row's badges, quiet chips; Required reads in the foreground colour. */\nfunction Badges({ badges }: { badges: readonly FieldBadge[] }) {\n if (badges.length === 0) return null;\n return (\n <span className=\"hidden shrink-0 flex-wrap justify-end gap-1 sm:flex\">\n {badges.map(badge => (\n <span\n key={badge.kind}\n title={badge.title}\n className={`inline-flex h-5 items-center gap-1 whitespace-nowrap rounded border border-border bg-card px-1.5 text-[11px] font-medium ${\n badge.kind === \"required\"\n ? \"text-foreground\"\n : \"text-muted-foreground\"\n }`}\n >\n {badge.kind === \"condition\" ? (\n <Lucide.GitBranch className=\"h-3 w-3\" aria-hidden=\"true\" />\n ) : null}\n {badge.label}\n </span>\n ))}\n </span>\n );\n}\n\ninterface FieldActionsProps {\n label: string;\n index: number;\n total: number;\n deleteBlockers: readonly string[];\n onDuplicate: () => void;\n onDelete: () => void;\n onMove: (direction: -1 | 1) => void;\n}\n\n/** A row's menu: move, duplicate, and delete unless something refers to it. */\nfunction FieldActions({\n label,\n index,\n total,\n deleteBlockers,\n onDuplicate,\n onDelete,\n onMove,\n}: FieldActionsProps) {\n return (\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n className=\"h-8 w-8 shrink-0 text-muted-foreground hover:text-foreground\"\n aria-label={`Actions for ${label}`}\n >\n <Lucide.MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-56\">\n <DropdownMenuItem\n disabled={index === 0}\n onClick={() => onMove(-1)}\n className=\"gap-2\"\n >\n <Lucide.ArrowUp className=\"h-4 w-4 text-muted-foreground\" />\n Move up\n </DropdownMenuItem>\n <DropdownMenuItem\n disabled={index === total - 1}\n onClick={() => onMove(1)}\n className=\"gap-2\"\n >\n <Lucide.ArrowDown className=\"h-4 w-4 text-muted-foreground\" />\n Move down\n </DropdownMenuItem>\n <DropdownMenuItem onClick={onDuplicate} className=\"gap-2\">\n <Lucide.Copy className=\"h-4 w-4 text-muted-foreground\" />\n Duplicate\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n {deleteBlockers.length > 0 ? (\n <Tooltip delayDuration={0}>\n <TooltipTrigger asChild>\n <div>\n <DropdownMenuItem\n disabled\n className=\"gap-2 text-muted-foreground\"\n >\n <Lucide.Trash2 className=\"h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n </div>\n </TooltipTrigger>\n <TooltipContent side=\"left\" className=\"max-w-64\">\n Used by {deleteBlockers.join(\", \")}. Remove those references\n first.\n </TooltipContent>\n </Tooltip>\n ) : (\n <DropdownMenuItem\n onClick={onDelete}\n className=\"gap-2 text-destructive focus:text-destructive\"\n >\n <Lucide.Trash2 className=\"h-4 w-4\" />\n Delete\n </DropdownMenuItem>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\n/**\n * \"Add here\", on the edge above a row: a field goes where it belongs rather\n * than at the end and dragged up. Shown on hover of that row and on keyboard\n * focus, and absent above the first row.\n */\nfunction AddHere({\n label,\n onAdd,\n}: {\n label: string;\n onAdd: (() => void) | undefined;\n}) {\n if (onAdd === undefined) return null;\n return (\n <button\n type=\"button\"\n onClick={onAdd}\n aria-label={`Add a field above ${label}`}\n // 24px tall, the smallest target a pointer is asked to hit, and shown\n // only for the row under the pointer, so the list does not fill with\n // them at once.\n className=\"absolute left-1/2 top-0 z-10 inline-flex h-6 min-w-6 -translate-x-1/2 -translate-y-1/2 items-center gap-1 rounded-full border border-border bg-card px-2.5 text-[11px] text-muted-foreground opacity-0 transition-opacity hover:text-foreground focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring group-hover/row:opacity-100\"\n >\n <Lucide.Plus className=\"h-3 w-3\" aria-hidden=\"true\" />\n Add here\n </button>\n );\n}\n\ninterface OutlineRowProps extends Omit<FieldActionsProps, \"label\"> {\n field: AnyFormField;\n /** Its key while edited: React's, and the inline panel's id. */\n rowKey: string;\n badges: readonly FieldBadge[];\n selected: boolean;\n /** The panel under the row, where the window is too narrow to dock it. */\n settings: ReactNode;\n /** Where a selected row's settings open, for `aria-controls`. */\n settingsId: string | undefined;\n onSelect: () => void;\n /** \"/\" on the row: add a field below it. */\n onAddBelow: () => void;\n /** \"Add here\", above this row; absent on the first. */\n onAddAbove: (() => void) | undefined;\n readOnly: boolean;\n}\n\nfunction OutlineRow({\n field,\n rowKey,\n badges,\n selected,\n settings,\n settingsId,\n onSelect,\n onAddBelow,\n onAddAbove,\n readOnly,\n ...actions\n}: OutlineRowProps) {\n const {\n attributes,\n listeners,\n setNodeRef,\n setActivatorNodeRef,\n transform,\n transition,\n isDragging,\n } = useSortable({ id: field.name });\n\n const entry = CATALOG_BY_TYPE.get(field.type);\n const label = field.label || field.name;\n const onKeyDown = (event: KeyboardEvent<HTMLButtonElement>) => {\n if (event.key !== \"/\" || readOnly) return;\n event.preventDefault();\n onAddBelow();\n };\n\n return (\n <li\n ref={setNodeRef}\n style={{ transform: CSS.Transform.toString(transform), transition }}\n className={`group/row relative border-b border-border last:border-b-0 ${\n isDragging ? \"z-10 bg-card opacity-80\" : \"\"\n }`}\n >\n {readOnly ? null : <AddHere label={label} onAdd={onAddAbove} />}\n <div\n className={`relative flex min-h-[52px] items-center gap-2 pl-1.5 pr-2.5 ${\n selected\n ? \"bg-accent shadow-[inset_0_0_0_1.5px_var(--nx-foreground)]\"\n : \"hover:bg-muted\"\n }`}\n >\n <button\n type=\"button\"\n ref={setActivatorNodeRef}\n {...attributes}\n {...listeners}\n className=\"flex h-8 w-6 shrink-0 cursor-grab items-center justify-center text-muted-foreground active:cursor-grabbing focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={`Reorder ${label}. Press Space to lift, arrow keys to move, Space to drop.`}\n >\n <Lucide.GripVertical className=\"h-4 w-4\" aria-hidden=\"true\" />\n </button>\n <button\n type=\"button\"\n onClick={onSelect}\n onKeyDown={onKeyDown}\n aria-expanded={selected}\n aria-controls={selected ? settingsId : undefined}\n aria-keyshortcuts=\"/\"\n className=\"flex min-w-0 flex-1 items-center gap-2.5 py-2 text-left focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <span className=\"grid h-[30px] w-[30px] shrink-0 place-items-center rounded-md border border-border bg-background text-foreground\">\n <CatalogIcon name={entry?.icon} className=\"h-4 w-4\" />\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-foreground\">\n {label}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {entry?.label ?? field.type}\n <span className=\"mx-1.5\">·</span>\n <code className=\"font-mono text-[11.5px]\">{field.name}</code>\n </span>\n </span>\n <Badges badges={badges} />\n </button>\n <FieldActions label={label} {...actions} />\n </div>\n {selected && settings ? (\n <div\n id={`field-settings-${rowKey}`}\n role=\"region\"\n aria-label={`${label} settings`}\n className=\"border-t border-border\"\n >\n {settings}\n </div>\n ) : null}\n </li>\n );\n}\n\n// ---------------------------------------------------------------------------\n// The outline\n// ---------------------------------------------------------------------------\n\nexport interface FieldCardsProps {\n /**\n * Form-surface types the host has NOT excluded (the plugin's field\n * enable/disable option). `null` while the host configuration is still\n * loading: Add field waits rather than offering the unfiltered set. Types\n * outside this set stay editable on existing fields but cannot be added.\n */\n enabledTypes: readonly FormFieldCatalogType[] | null;\n /**\n * Type ids the host disabled (`config.fields[type] === false`). Applied to\n * plugin field types too, so the host exclude layer is not bypassed for\n * contributed types.\n */\n disabledFieldTypes?: ReadonlySet<string>;\n /**\n * Creates and selects a field of the chosen type. The picker's whole choice\n * comes second (the label the person typed and the name it gives), so a host\n * written for the type alone keeps working and one that wants the label\n * reads it there. The third is where in the list it goes, when it was added\n * between two rows or below one; absent for the end.\n */\n onAddField: (\n type: FormFieldTypeId,\n choice?: FieldPickerChoice,\n index?: number\n ) => void;\n /**\n * Where the selected field's settings dock from 1024px: `\"beside\"` the list\n * (the default, for a list drawn on its own), or by the `\"host\"` page,\n * which draws `FieldInspector` against the window's edge itself. Below\n * 1024px they open under the row either way.\n */\n dockedSettings?: \"beside\" | \"host\";\n}\n\n/**\n * The list with the selected field's settings docked beside it, for a list\n * drawn on its own; the column scrolls on its own body.\n */\nfunction BesideSettings({\n list,\n panel,\n}: {\n list: JSX.Element;\n panel: ReactNode;\n}): JSX.Element {\n if (panel === null) return list;\n return (\n <div className=\"grid grid-cols-[minmax(0,1fr)_minmax(20rem,26rem)] items-start gap-4\">\n {list}\n <aside\n id={DOCKED_PANEL_ID}\n aria-label=\"Field settings\"\n className=\"sticky top-4 flex max-h-[calc(100vh-8rem)] flex-col overflow-hidden rounded-md border border-border bg-card\"\n >\n {panel}\n </aside>\n </div>\n );\n}\n\n/**\n * What every submission stores beside a form's own fields: its IP address\n * and browser only while the form keeps them (Settings › Privacy).\n */\nfunction storedWithEverySubmission(storesAddress: boolean): string {\n return storesAddress\n ? \"Every submission also stores when it was submitted, its status, internal notes, and the IP address and browser it came from.\"\n : \"Every submission also stores when it was submitted, its status and internal notes. This form keeps no IP address or browser.\";\n}\n\nexport function FieldCards({\n enabledTypes,\n disabledFieldTypes,\n onAddField,\n dockedSettings = \"beside\",\n}: FieldCardsProps): JSX.Element {\n const {\n fields,\n notifications,\n settings,\n selectedFieldId,\n selectField,\n deleteField,\n duplicateField,\n moveField,\n } = useFormBuilder();\n const { readOnly, fieldKey } = useFieldBookkeeping();\n\n // Where the picker adds, while it is open: an index, or the end.\n const [addAt, setAddAt] = useState<number | \"end\" | null>(null);\n const docked = useDocked();\n const inlinePanel = useFieldPanel(false);\n const besidePanel = useFieldPanel(true);\n\n const sensors = useSensors(\n useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),\n useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })\n );\n\n // The picker offers the enabled built-ins, then the plugin types that opted\n // into the forms surface; a plugin reusing a built-in's id never shadows it,\n // and a type the host disabled is left out whoever declared it.\n const pickerConfig = useMemo<FieldPickerConfig | null>(() => {\n if (enabledTypes === null) return null;\n const allowed = new Set<string>(enabledTypes);\n return {\n surface: \"forms\",\n types: describeFieldTypes(\n FORM_FIELD_TYPE_CATALOG.filter(entry => allowed.has(entry.type))\n ),\n exclude: [\n ...FORM_FIELD_TYPE_CATALOG.filter(\n entry => !allowed.has(entry.type)\n ).map(entry => entry.type),\n ...(disabledFieldTypes ?? []),\n ],\n deriveName: formFieldName,\n };\n }, [enabledTypes, disabledFieldTypes]);\n\n // One O(N^2) pass per fields/notifications change; each row then looks\n // its blockers up in O(1) instead of re-walking everything per render.\n const referenceMap = useMemo(\n () => buildFieldReferenceMap(fields, notifications, settings),\n [fields, notifications, settings]\n );\n\n const labels = useMemo(\n () => new Map(fields.map(field => [field.name, field.label || field.name])),\n [fields]\n );\n\n const handleDragEnd = useCallback(\n (event: DragEndEvent) => {\n const { active, over } = event;\n if (!over || active.id === over.id) return;\n const from = fields.findIndex(f => f.name === active.id);\n const to = fields.findIndex(f => f.name === over.id);\n if (from !== -1 && to !== -1) moveField(from, to);\n },\n [fields, moveField]\n );\n\n const canAdd = pickerConfig !== null && !readOnly;\n const addButton = (\n <Button\n type=\"button\"\n variant={fields.length === 0 ? \"default\" : \"outline\"}\n size=\"sm\"\n disabled={!canAdd}\n onClick={() => setAddAt(\"end\")}\n >\n <Lucide.Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n {pickerConfig === null ? \"Loading field types…\" : \"Add field\"}\n </Button>\n );\n\n const list = (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <h2 className=\"text-sm font-semibold text-foreground\">Fields</h2>\n {fields.length > 0 ? (\n <span className=\"hidden text-xs text-muted-foreground md:inline\">\n Drag to reorder. Press / on a field to add one below it.\n </span>\n ) : null}\n <span className=\"flex-1\" />\n {fields.length > 0 ? addButton : null}\n </div>\n\n {fields.length === 0 ? (\n <div className=\"flex flex-col items-center gap-3 rounded-md border border-dashed border-border bg-card px-6 py-14 text-center\">\n <span className=\"flex h-10 w-10 items-center justify-center rounded-md bg-muted text-foreground\">\n <Lucide.ListPlus className=\"h-5 w-5\" aria-hidden=\"true\" />\n </span>\n <div>\n <p className=\"text-sm font-medium text-foreground\">No fields yet</p>\n <p className=\"mt-1 text-xs text-muted-foreground\">\n Add the first field to start building this form.\n </p>\n </div>\n {addButton}\n </div>\n ) : (\n <DndContext\n sensors={sensors}\n collisionDetection={closestCenter}\n onDragEnd={handleDragEnd}\n >\n <SortableContext\n items={fields.map(f => f.name)}\n strategy={verticalListSortingStrategy}\n >\n <ul\n aria-label=\"Form fields\"\n className=\"overflow-visible rounded-md border border-border bg-card\"\n >\n {fields.map((field, index) => {\n const key = fieldKey(field.name);\n const selected = selectedFieldId === field.name;\n return (\n <OutlineRow\n key={key}\n rowKey={key}\n field={field}\n index={index}\n total={fields.length}\n badges={fieldBadges(\n field,\n name => labels.get(name) ?? name\n )}\n selected={selected}\n settings={!docked && selected ? inlinePanel : null}\n settingsId={\n docked ? DOCKED_PANEL_ID : `field-settings-${key}`\n }\n onSelect={() => selectField(selected ? null : field.name)}\n onAddBelow={() => canAdd && setAddAt(index + 1)}\n onAddAbove={\n index === 0 || !canAdd ? undefined : () => setAddAt(index)\n }\n readOnly={readOnly}\n deleteBlockers={(referenceMap.get(field.name) ?? []).map(\n ref => ref.label\n )}\n onDuplicate={() => duplicateField(field.name)}\n onDelete={() => deleteField(field.name)}\n onMove={direction => moveField(index, index + direction)}\n />\n );\n })}\n </ul>\n </SortableContext>\n </DndContext>\n )}\n\n <p className=\"flex items-center gap-2 rounded-md border border-dashed border-border px-3 py-2.5 text-xs text-muted-foreground\">\n <Lucide.Lock className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n {storedWithEverySubmission(settings.storeIpAndBrowser !== false)}\n </p>\n\n {pickerConfig && (\n <FieldPicker\n open={addAt !== null}\n config={pickerConfig}\n onCancel={() => setAddAt(null)}\n onAdd={(choice, how) => {\n const at = addAt;\n onAddField(\n choice.type,\n choice,\n typeof at === \"number\" ? at : undefined\n );\n // A field added between rows moves the next one down, so another\n // goes below it.\n if (typeof at === \"number\")\n setAddAt(how.addAnother ? at + 1 : null);\n else if (!how.addAnother) setAddAt(null);\n }}\n />\n )}\n </div>\n );\n return docked && dockedSettings === \"beside\" ? (\n <BesideSettings list={list} panel={besidePanel} />\n ) : (\n list\n );\n}\n","\"use client\";\n\n/**\n * The rows Settings is drawn with: a setting's name and what it does on the\n * left with its control on the right, or stacked when the control needs the\n * width (a message, a list of choices). Every section uses these, so the page\n * reads as one list whatever the setting.\n *\n * @module admin/components/builder/settings-rows\n */\nimport { Label, RadioGroup, RadioGroupItem } from \"@nextlyhq/ui\";\nimport type { JSX, ReactNode } from \"react\";\n\n/** The id of a setting's description, for its control's `aria-describedby`. */\nexport function descriptionIdOf(htmlFor: string): string {\n return `${htmlFor}-description`;\n}\n\nexport interface SettingRowProps {\n label: ReactNode;\n /** What the setting does; read out with the control it describes. */\n description?: ReactNode;\n /** The control's id. Its description is `descriptionIdOf(htmlFor)`. */\n htmlFor?: string;\n /** Put the control under the text, at the row's full width. */\n stack?: boolean;\n children: ReactNode;\n}\n\nexport function SettingRow({\n label,\n description,\n htmlFor,\n stack = false,\n children,\n}: SettingRowProps): JSX.Element {\n return (\n // The section spaces its rows; a row that padded itself would double it.\n <div\n className={\n stack\n ? \"flex flex-col gap-3\"\n : \"flex flex-col gap-3 sm:flex-row sm:items-center sm:gap-6\"\n }\n >\n <div className=\"min-w-0 flex-1 space-y-1\">\n <Label htmlFor={htmlFor} className=\"text-sm font-semibold\">\n {label}\n </Label>\n {description ? (\n <p\n id={htmlFor ? descriptionIdOf(htmlFor) : undefined}\n className=\"max-w-prose text-xs leading-relaxed text-muted-foreground\"\n >\n {description}\n </p>\n ) : null}\n </div>\n <div\n className={\n stack ? undefined : \"flex shrink-0 items-center sm:justify-end\"\n }\n >\n {children}\n </div>\n </div>\n );\n}\n\n/** One choice of a `ChoiceCards` list. */\nexport interface Choice<Value extends string> {\n value: Value;\n title: string;\n description?: ReactNode;\n disabled?: boolean;\n /** Shown under the choice whether or not it is chosen. */\n note?: ReactNode;\n /** Shown under the choice once it is chosen: its message, address, page. */\n details?: ReactNode;\n}\n\nexport interface ChoiceCardsProps<Value extends string> {\n /** Names the group; each choice's id starts with it. */\n name: string;\n label: string;\n value: Value;\n onChange: (value: Value) => void;\n choices: readonly Choice<Value>[];\n}\n\n/**\n * A choice of one among a few, each a card with what it means, and the chosen\n * one opening to show what it needs. Cards rather than bare radio buttons,\n * because each choice carries a sentence and, once chosen, a control.\n */\nexport function ChoiceCards<Value extends string>({\n name,\n label,\n value,\n onChange,\n choices,\n}: ChoiceCardsProps<Value>): JSX.Element {\n return (\n <RadioGroup\n aria-label={label}\n value={value}\n onValueChange={next => {\n // Only a value one of the choices carries.\n const chosen = choices.find(choice => choice.value === next);\n if (chosen) onChange(chosen.value);\n }}\n className=\"flex flex-col gap-2\"\n >\n {choices.map(choice => {\n const id = `${name}-${choice.value}`;\n const chosen = choice.value === value;\n return (\n <div\n key={choice.value}\n className={`flex items-start gap-3 rounded-md border bg-background px-3 py-2.5 ${\n chosen ? \"border-foreground\" : \"border-border\"\n }`}\n >\n <RadioGroupItem\n value={choice.value}\n id={id}\n disabled={choice.disabled}\n aria-describedby={\n choice.description ? descriptionIdOf(id) : undefined\n }\n className=\"mt-0.5\"\n />\n <div className=\"min-w-0 flex-1 space-y-1\">\n <Label\n htmlFor={id}\n className={\n choice.disabled\n ? \"text-sm font-semibold text-muted-foreground\"\n : \"text-sm font-semibold\"\n }\n >\n {choice.title}\n </Label>\n {choice.description ? (\n <p\n id={descriptionIdOf(id)}\n className=\"text-xs leading-relaxed text-muted-foreground\"\n >\n {choice.description}\n </p>\n ) : null}\n {choice.note}\n {chosen && choice.details ? (\n <div className=\"pt-1\">{choice.details}</div>\n ) : null}\n </div>\n </div>\n );\n })}\n </RadioGroup>\n );\n}\n","\"use client\";\n\n/**\n * The first section of a form's Settings: its name, its ID (the slug the Form\n * block and the API read it by), and a note for people in the admin.\n *\n * @module admin/components/builder/FormIdentitySection\n */\nimport { FormSection, Input, Textarea } from \"@nextlyhq/ui\";\nimport type { JSX } from \"react\";\n\nimport {\n useFieldBookkeeping,\n useFormBuilder,\n} from \"../../context/FormBuilderContext\";\n\nimport { descriptionIdOf, SettingRow } from \"./settings-rows\";\n\n/** A name as an ID: lowercase words joined by dashes. */\nfunction slugify(text: string): string {\n return text\n .toLowerCase()\n .trim()\n .replace(/[^\\w\\s-]/g, \"\")\n .replace(/[\\s_-]+/g, \"-\")\n .replace(/^-+|-+$/g, \"\");\n}\n\n/**\n * The ID a renamed form should take: one that still follows the name (empty,\n * or what the old name gave) follows the new one; one a person typed stays.\n */\nfunction followingSlug(slug: string, oldName: string, newName: string): string {\n return slug === \"\" || slug === slugify(oldName) ? slugify(newName) : slug;\n}\n\n/**\n * Why a form's ID no longer changes once the form has been live: the site's\n * pages and any integration read the form by it.\n */\nexport const FIXED_FORM_ID_REASON =\n \"Fixed once the form has been live: pages and integrations read the form by this ID.\";\n\nexport function FormIdentitySection(): JSX.Element {\n const { formData, settings, updateFormData, updateSettings } =\n useFormBuilder();\n // Once live, the site may have read it by its ID, even if it has since\n // been closed or set back to draft.\n const { formIdFixed: idFixed } = useFieldBookkeeping();\n return (\n <FormSection label=\"Form\">\n <SettingRow\n label=\"Name\"\n htmlFor=\"form-name\"\n description=\"Shown in the admin, and as the heading if your site shows one.\"\n >\n <Input\n id=\"form-name\"\n type=\"text\"\n className=\"w-full sm:w-72\"\n aria-describedby={descriptionIdOf(\"form-name\")}\n value={formData.name}\n onChange={event =>\n updateFormData({\n name: event.target.value,\n ...(idFixed\n ? {}\n : {\n slug: followingSlug(\n formData.slug,\n formData.name,\n event.target.value\n ),\n }),\n })\n }\n placeholder=\"e.g., Contact form\"\n />\n </SettingRow>\n <SettingRow\n label=\"Form ID\"\n htmlFor=\"form-slug\"\n description={\n idFixed\n ? FIXED_FORM_ID_REASON\n : \"Used by the Form block and the API. Fixed once the form is live.\"\n }\n >\n <Input\n id=\"form-slug\"\n type=\"text\"\n className=\"w-full font-mono sm:w-72\"\n aria-describedby={descriptionIdOf(\"form-slug\")}\n value={formData.slug}\n // Disabled as the field panel draws a fixed name, with the reason\n // it cannot change as its description.\n disabled={idFixed}\n onChange={event => updateFormData({ slug: event.target.value })}\n placeholder=\"e.g., contact-form\"\n />\n </SettingRow>\n <SettingRow\n label=\"Internal note\"\n htmlFor=\"form-internal-note\"\n description=\"Only people in the admin see this.\"\n stack\n >\n <Textarea\n id=\"form-internal-note\"\n aria-describedby={descriptionIdOf(\"form-internal-note\")}\n value={settings.internalNote ?? \"\"}\n onChange={event =>\n updateSettings({ internalNote: event.target.value || undefined })\n }\n rows={3}\n />\n </SettingRow>\n </FormSection>\n );\n}\n","/**\n * Which notification a form's Notifications page shows: none, the list; or\n * one, by its id in the address's `section` query. A query rather than a\n * further path segment, because a form page is one segment of the form's\n * address, and the query gives each notification an address of its own that\n * survives a reload and can be shared.\n *\n * `section` is the key the admin gives a document view to name the part of\n * the view it opens at, and its unsaved-work guard treats a change of it as a\n * move within the document. Any other key would count as leaving the form, so\n * opening another notification, or going back to the list, with edits not yet\n * saved would ask to save or discard them.\n *\n * Read through the admin's own navigation: the admin announces each move with\n * a `locationchange` event (and the browser's Back with `popstate`), so the\n * page follows without a reload and keeps the unsaved form.\n *\n * @module admin/components/notifications/notification-address\n */\nimport { navigateTo } from \"@nextlyhq/plugin-sdk/admin\";\nimport { useSyncExternalStore } from \"react\";\n\n/** The query key naming the notification shown: the view's own section. */\nconst NOTIFICATION_PARAM = \"section\";\n\nfunction subscribe(onChange: () => void): () => void {\n window.addEventListener(\"locationchange\", onChange);\n window.addEventListener(\"popstate\", onChange);\n return () => {\n window.removeEventListener(\"locationchange\", onChange);\n window.removeEventListener(\"popstate\", onChange);\n };\n}\n\nfunction shownId(): string | null {\n return new URLSearchParams(window.location.search).get(NOTIFICATION_PARAM);\n}\n\n/** The id of the notification the address names, or null for the list. */\nexport function useShownNotification(): string | null {\n return useSyncExternalStore(subscribe, shownId, () => null);\n}\n\n/** The address of one notification, or of the list for null. */\nexport function notificationHref(id: string | null): string {\n const query = new URLSearchParams(window.location.search);\n if (id === null) query.delete(NOTIFICATION_PARAM);\n else query.set(NOTIFICATION_PARAM, id);\n const search = query.toString();\n return `${window.location.pathname}${search ? `?${search}` : \"\"}`;\n}\n\n/**\n * Show one notification, or the list for null. Inside the admin it moves\n * through the admin's navigation; a form not yet saved has no address of its\n * own to move under, so the change stays in the browser's history only.\n */\nexport function showNotification(id: string | null): void {\n const href = notificationHref(id);\n if (window.location.pathname.startsWith(\"/admin/\")) {\n navigateTo(href);\n return;\n }\n window.history.pushState(null, \"\", href);\n window.dispatchEvent(new Event(\"locationchange\"));\n}\n","/**\n * What a form's notification pages read from the admin: the email providers,\n * the Email hub's messages, and whether the signed-in person may open a\n * message in the Email hub. Each read answers an empty or refusing value when\n * it fails, so a page draws with less rather than not at all.\n *\n * @module admin/components/notifications/notification-sources\n */\nimport { useEffect, useState } from \"react\";\n\n/** Host-level notification defaults surfaced by the builder-config route. */\nexport interface NotificationDefaults {\n defaultFrom?: string;\n defaultToEmail?: string;\n}\n\n/** An email provider, as the pages name it. */\nexport interface ProviderOption {\n id: string;\n name: string;\n type: string;\n isDefault: boolean;\n}\n\n/** One of the Email hub's messages. */\nexport interface TemplateOption {\n id: string;\n name: string;\n slug: string;\n isActive: boolean;\n /** \"template\" or \"layout\": layouts wrap messages and cannot be sent. */\n kind?: string;\n /** The message's own sender, provider and reply-to, where it sets them. */\n fromOverride?: string | null;\n providerId?: string | null;\n replyTo?: string | null;\n subject?: string | null;\n}\n\n/**\n * Where a message opens in the admin's Email hub. Written out rather than\n * imported because a plugin builds against the SDK, not the admin's routes.\n */\nexport function emailHubEditHref(templateId: string): string {\n return `/admin/settings/email/messages/edit/${encodeURIComponent(templateId)}`;\n}\n\nasync function readJson<T>(url: string): Promise<T | undefined> {\n try {\n const res = await fetch(url, { credentials: \"include\" });\n return res.ok ? ((await res.json()) as T) : undefined;\n } catch {\n return undefined;\n }\n}\n\nasync function fetchProviders(): Promise<ProviderOption[]> {\n const json = await readJson<{ providers?: ProviderOption[] }>(\n \"/admin/api/email-providers?pageSize=100&page=1\"\n );\n return json?.providers ?? [];\n}\n\nasync function fetchTemplates(): Promise<TemplateOption[]> {\n const json = await readJson<{ templates?: TemplateOption[] }>(\n \"/admin/api/email-templates\"\n );\n return json?.templates ?? [];\n}\n\n/**\n * Whether the signed-in person may open a message in the Email hub's editor.\n * The editor's route needs the grant that saves a message, so a link shown\n * without it would open a page that refuses them. The retired\n * `manage-email-templates` opens nothing on the server, so it shows no link.\n */\nasync function fetchCanEditTemplates(): Promise<boolean> {\n const json = await readJson<{\n permissions?: string[];\n isSuperAdmin?: boolean;\n }>(\"/admin/api/me/permissions\");\n if (!json) return false;\n return (\n Boolean(json.isSuperAdmin) ||\n (json.permissions ?? []).includes(\"update-email-templates\")\n );\n}\n\n/** What the notification pages draw from, read once per page. */\nexport interface NotificationSources {\n providers: ProviderOption[];\n templates: TemplateOption[];\n canEditTemplates: boolean;\n}\n\n/** The providers, messages and Email hub access, as they arrive. */\nexport function useNotificationSources(): NotificationSources {\n const [sources, setSources] = useState<NotificationSources>({\n providers: [],\n templates: [],\n canEditTemplates: false,\n });\n useEffect(() => {\n let live = true;\n void Promise.all([\n fetchProviders(),\n fetchTemplates(),\n fetchCanEditTemplates(),\n ]).then(([providers, templates, canEditTemplates]) => {\n if (live) setSources({ providers, templates, canEditTemplates });\n });\n return () => {\n live = false;\n };\n }, []);\n return sources;\n}\n","\"use client\";\n\n/**\n * What the form editor reads from the plugin's resolved options: which field\n * types the picker offers, the notification and spam defaults, the redirect\n * targets, and the collections' resolved slugs.\n *\n * @module admin/use-builder-config\n */\nimport type { FormFieldCatalogType } from \"nextly/field-catalog\";\nimport { FORM_FIELD_TYPE_CATALOG } from \"nextly/field-catalog\";\nimport { useEffect, useState } from \"react\";\n\nimport type { NotificationDefaults } from \"./components/builder/FormNotificationsTab\";\nimport type { SpamDefaults } from \"./components/builder/FormSettingsTab\";\n\n/** The configuration as the route answers it. */\ninterface BuilderConfigResponse {\n fields?: Record<string, boolean>;\n notifications?: NotificationDefaults;\n spamProtection?: SpamDefaults;\n redirectCollections?: string[];\n slugs?: { forms?: string; submissions?: string };\n}\n\n/**\n * The configuration as the editor uses it. Each value is `null` until the\n * request settles, so a consumer can tell \"not known yet\" from \"none\".\n */\nexport interface BuilderConfig {\n /**\n * The field types the picker offers. If the request fails every type is\n * offered: the exclude list is an authoring preference (the server accepts\n * any type in a form's JSON), so hiding every type on a passing failure\n * would cost more than a picker briefly unfiltered.\n */\n enabledTypes: FormFieldCatalogType[] | null;\n /**\n * Type ids the host turned off (`fields[type] === false`), plugin types\n * included, so the host's exclusions apply to contributed types too.\n */\n disabledFieldTypes: ReadonlySet<string>;\n /** The host's notification defaults; `{}` when none or the request failed. */\n notificationDefaults: NotificationDefaults | null;\n /** The plugin's spam defaults, what \"inherit\" resolves to. */\n spamDefaults: SpamDefaults | null;\n /** Collections a form may redirect to. */\n redirectCollections: string[] | null;\n /**\n * Whether the request FAILED, kept apart from what it returned: a refusal\n * mapped to an empty list would read as \"this site configures no redirect\n * targets\", stated as fact with no way to tell or retry.\n */\n failed: boolean;\n /** The submissions collection's resolved slug, once known. */\n submissionsSlug: string | undefined;\n /** The forms collection's resolved slug, once known. */\n formsSlug: string | undefined;\n}\n\nconst LOADING: BuilderConfig = {\n enabledTypes: null,\n disabledFieldTypes: new Set(),\n notificationDefaults: null,\n spamDefaults: null,\n redirectCollections: null,\n failed: false,\n submissionsSlug: undefined,\n formsSlug: undefined,\n};\n\n/** The editor's configuration from the route's answer, `null` for a failure. */\nfunction builderConfigFrom(\n config: BuilderConfigResponse | null\n): BuilderConfig {\n const allTypes = FORM_FIELD_TYPE_CATALOG.map(entry => entry.type);\n const fields = config?.fields;\n return {\n enabledTypes: fields\n ? allTypes.filter(type => fields[type] !== false)\n : allTypes,\n disabledFieldTypes: new Set(\n Object.entries(fields ?? {})\n .filter(([, enabled]) => enabled === false)\n .map(([type]) => type)\n ),\n notificationDefaults: config?.notifications ?? {},\n spamDefaults: config?.spamProtection ?? {},\n redirectCollections: config?.redirectCollections ?? [],\n failed: config === null,\n submissionsSlug: config?.slugs?.submissions,\n formsSlug: config?.slugs?.forms,\n };\n}\n\n/** Reads the configuration once, as the editor mounts. */\nexport function useBuilderConfig(): BuilderConfig {\n const [config, setConfig] = useState<BuilderConfig>(LOADING);\n useEffect(() => {\n let cancelled = false;\n void fetch(\n \"/admin/api/plugins/@nextlyhq/plugin-form-builder/builder-config\",\n { credentials: \"include\" }\n )\n .then(response => (response.ok ? response.json() : null))\n .then((body: BuilderConfigResponse | null) => {\n if (!cancelled) setConfig(builderConfigFrom(body));\n })\n .catch(() => {\n if (!cancelled) setConfig(builderConfigFrom(null));\n });\n return () => {\n cancelled = true;\n };\n }, []);\n return config;\n}\n","/**\n * How a notification is described in a sentence: who it goes to, when, and\n * by which message. The list's rows and the page's header say the same thing\n * through these, so a notification never reads two ways.\n *\n * @module admin/components/notifications/notification-summary\n */\nimport type {\n AnyFormField,\n ConditionalLogicCondition,\n FormNotification,\n} from \"../../../types\";\nimport { optionLabels } from \"../../../utils/export-formats\";\nimport { parseFieldRef } from \"../../../utils/field-references\";\nimport {\n conditionsOf,\n teamAddressesOf,\n} from \"../../../utils/notification-model\";\n\nimport type { TemplateOption } from \"./notification-sources\";\n\n/** A comparison as a sentence reads it. */\nexport const COMPARISON_WORDS: Record<\n ConditionalLogicCondition[\"comparison\"],\n string\n> = {\n equals: \"is\",\n notEquals: \"is not\",\n contains: \"contains\",\n isEmpty: \"is empty\",\n isNotEmpty: \"is not empty\",\n greaterThan: \"is more than\",\n lessThan: \"is less than\",\n};\n\n/** Comparisons that take no value. */\nexport const VALUELESS: ReadonlySet<ConditionalLogicCondition[\"comparison\"]> =\n new Set([\"isEmpty\", \"isNotEmpty\"]);\n\nfunction fieldNamed(\n fields: readonly AnyFormField[],\n name: string\n): AnyFormField | undefined {\n return fields.find(each => each.name === name);\n}\n\nfunction labelOf(fields: readonly AnyFormField[], name: string): string {\n return fieldNamed(fields, name)?.label || name;\n}\n\n/** Who a notification goes to, in a few words. */\nexport function recipientSummary(\n notification: FormNotification,\n fields: readonly AnyFormField[]\n): string {\n if (notification.recipientType === \"field\") {\n const field = parseFieldRef(notification.to);\n return field === null\n ? \"The person who submitted\"\n : `The person who submitted (${labelOf(fields, field)})`;\n }\n const addresses = teamAddressesOf(notification);\n if (addresses.length === 0) return \"No address yet\";\n return addresses.join(\", \");\n}\n\n/**\n * A condition's value as a sentence ends with it, with a leading space: an\n * option by its label, read the way a submission's answers are, so a\n * condition on a choice reads \"is In person\" rather than the stored\n * `in_person`.\n */\nfunction valueText(value: unknown, field: AnyFormField | undefined): string {\n if (typeof value === \"string\" || typeof value === \"number\") {\n const options = field && \"options\" in field ? field.options : undefined;\n return options?.length\n ? ` ${optionLabels({ options }, String(value))}`\n : ` ${value}`;\n }\n if (typeof value === \"boolean\") return value ? \" checked\" : \" not checked\";\n return \"\";\n}\n\n/** When a notification sends, in a few words. */\nexport function whenSummary(\n notification: FormNotification,\n fields: readonly AnyFormField[]\n): string {\n const { match, list } = conditionsOf(notification);\n if (list.length === 0) return \"Every submission\";\n if (list.length === 1) {\n const [condition] = list;\n const value = VALUELESS.has(condition.comparison)\n ? \"\"\n : valueText(condition.value, fieldNamed(fields, condition.field));\n return `Only when ${labelOf(fields, condition.field)} ${COMPARISON_WORDS[condition.comparison]}${value}`;\n }\n return `Only when ${match === \"any\" ? \"any\" : \"all\"} of ${list.length} conditions hold`;\n}\n\n/** The message a notification sends, by name, or undefined for none. */\nexport function messageOf(\n notification: FormNotification,\n templates: readonly TemplateOption[]\n): TemplateOption | undefined {\n if (!notification.templateSlug) return undefined;\n return templates.find(each => each.slug === notification.templateSlug);\n}\n","\"use client\";\n\n/**\n * When a notification sends: on every submission, or only when all, or any,\n * of several conditions hold. Spam is never emailed either way.\n *\n * @module admin/components/notifications/NotificationConditions\n */\nimport {\n Button,\n FormSection,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { Plus, X } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\nimport type {\n AnyFormField,\n ConditionalLogicCondition,\n FormNotification,\n} from \"../../../types\";\nimport {\n type ConditionMatch,\n conditionsOf,\n} from \"../../../utils/notification-model\";\nimport { ChoiceCards } from \"../builder/settings-rows\";\n\nimport { COMPARISON_WORDS, VALUELESS } from \"./notification-summary\";\nimport type { NotificationUpdate } from \"./NotificationRecipients\";\n\nconst TRIGGER = \"w-full bg-transparent border-input dark:bg-muted/50\";\n\n/** A field a condition can test: any answer a visitor gives. */\nfunction testable(fields: readonly AnyFormField[]): AnyFormField[] {\n return fields.filter(field => field.type !== \"file\");\n}\n\n/** The choices a field offers, for a value picked rather than typed. */\nfunction choicesOf(\n field: AnyFormField | undefined\n): Array<{ label: string; value: string }> | undefined {\n if (field?.type === \"checkbox\") {\n return [\n { label: \"checked\", value: \"true\" },\n { label: \"not checked\", value: \"false\" },\n ];\n }\n if (field?.type === \"select\" || field?.type === \"radio\") {\n const { options } = field as { options?: unknown };\n return Array.isArray(options)\n ? (options as Array<{ label: string; value: string }>)\n : undefined;\n }\n return undefined;\n}\n\n/** A condition's value as its control shows it. */\nfunction valueText(value: unknown): string {\n if (typeof value === \"string\" || typeof value === \"number\") {\n return String(value);\n }\n return typeof value === \"boolean\" ? String(value) : \"\";\n}\n\n/**\n * What a condition compares against: one of the field's choices (a dropdown's\n * options, a box's checked or not) picked by its label, or a value typed.\n */\nfunction ConditionValue({\n n,\n condition,\n field,\n onChange,\n}: {\n n: number;\n condition: ConditionalLogicCondition;\n field: AnyFormField | undefined;\n onChange: (value: ConditionalLogicCondition[\"value\"]) => void;\n}): JSX.Element {\n const choices = choicesOf(field);\n if (choices) {\n return (\n <Select\n value={valueText(condition.value)}\n onValueChange={value =>\n // A box's answer is stored as true or false, not as text.\n onChange(field?.type === \"checkbox\" ? value === \"true\" : value)\n }\n >\n <SelectTrigger\n aria-label={`Condition ${n} value`}\n className={`${TRIGGER} text-left sm:flex-1 [&>span]:truncate`}\n >\n <SelectValue placeholder=\"Choose\" />\n </SelectTrigger>\n <SelectContent>\n {choices.map(choice => (\n <SelectItem key={choice.value} value={choice.value}>\n {choice.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n }\n return (\n <Input\n aria-label={`Condition ${n} value`}\n value={valueText(condition.value)}\n onChange={event => onChange(event.target.value)}\n className=\"sm:flex-1\"\n />\n );\n}\n\nfunction ConditionRow({\n index,\n condition,\n fields,\n onChange,\n onRemove,\n}: {\n index: number;\n condition: ConditionalLogicCondition;\n fields: readonly AnyFormField[];\n onChange: (condition: ConditionalLogicCondition) => void;\n onRemove: () => void;\n}): JSX.Element {\n const n = index + 1;\n return (\n <li className=\"flex flex-col gap-2 rounded-md border border-border bg-background p-2 sm:flex-row sm:items-center\">\n <Select\n value={condition.field}\n onValueChange={field => onChange({ ...condition, field })}\n >\n <SelectTrigger\n aria-label={`Condition ${n} field`}\n className={`${TRIGGER} text-left sm:w-52 [&>span]:truncate`}\n >\n <SelectValue placeholder=\"Choose a field\" />\n </SelectTrigger>\n <SelectContent>\n {fields.map(field => (\n <SelectItem key={field.name} value={field.name}>\n {field.label || field.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n <Select\n value={condition.comparison}\n onValueChange={comparison =>\n onChange({\n ...condition,\n comparison: comparison as ConditionalLogicCondition[\"comparison\"],\n })\n }\n >\n <SelectTrigger\n aria-label={`Condition ${n} comparison`}\n className={`${TRIGGER} sm:w-40`}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {Object.entries(COMPARISON_WORDS).map(([value, label]) => (\n <SelectItem key={value} value={value}>\n {label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n {!VALUELESS.has(condition.comparison) && (\n <ConditionValue\n n={n}\n condition={condition}\n field={fields.find(each => each.name === condition.field)}\n onChange={value => onChange({ ...condition, value })}\n />\n )}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n className=\"h-8 w-8 shrink-0 self-end sm:self-auto\"\n aria-label={`Remove condition ${n}`}\n onClick={onRemove}\n >\n <X className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </li>\n );\n}\n\nexport function NotificationConditions({\n notification,\n fields,\n update,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n update: NotificationUpdate;\n}): JSX.Element {\n const { match, list } = conditionsOf(notification);\n const choices = testable(fields);\n // Written as the list, the single legacy condition cleared, so the rule\n // reads one way from here on.\n const write = (next: {\n match: ConditionMatch;\n list: ConditionalLogicCondition[];\n }) => update({ conditions: next, condition: undefined });\n const blank = (): ConditionalLogicCondition => ({\n field: choices[0]?.name ?? \"\",\n comparison: \"equals\",\n value: \"\",\n });\n return (\n <FormSection label=\"When to send\">\n <ChoiceCards\n name=\"notification-when\"\n label=\"When to send\"\n value={list.length === 0 ? \"always\" : \"only\"}\n onChange={value =>\n write(\n value === \"always\"\n ? { match, list: [] }\n : { match, list: [blank()] }\n )\n }\n choices={[\n {\n value: \"always\",\n title: \"Every submission\",\n description: \"Spam is never emailed.\",\n },\n {\n value: \"only\",\n title: \"Only when…\",\n disabled: choices.length === 0,\n details: (\n <div className=\"flex flex-col gap-2\">\n <div className=\"flex flex-wrap items-center gap-2\">\n <Select\n value={match}\n onValueChange={value =>\n write({ match: value === \"any\" ? \"any\" : \"all\", list })\n }\n >\n <SelectTrigger\n aria-label=\"How conditions combine\"\n className={`${TRIGGER} w-24`}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"all\">All</SelectItem>\n <SelectItem value=\"any\">Any</SelectItem>\n </SelectContent>\n </Select>\n <span className=\"text-sm text-muted-foreground\">\n of these are true\n </span>\n </div>\n <ul aria-label=\"Conditions\" className=\"flex flex-col gap-2\">\n {list.map((condition, index) => (\n <ConditionRow\n key={index}\n index={index}\n condition={condition}\n fields={choices}\n onChange={next =>\n write({\n match,\n list: list.map((each, i) =>\n i === index ? next : each\n ),\n })\n }\n onRemove={() =>\n write({\n match,\n list: list.filter((_, i) => i !== index),\n })\n }\n />\n ))}\n </ul>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n className=\"w-fit\"\n onClick={() => write({ match, list: [...list, blank()] })}\n >\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n Add condition\n </Button>\n </div>\n ),\n },\n ]}\n />\n </FormSection>\n );\n}\n","import type { FormNotification } from \"../../../types\";\nimport { parseFieldRef } from \"../../../utils/field-references\";\nimport { teamAddressesOf } from \"../../../utils/notification-model\";\n\n/**\n * Deliberately loose email shape check (something@something.tld): the goal is\n * catching typos before they fail at delivery, not RFC 5322 conformance.\n */\nconst EMAIL_PATTERN = /^[^\\s@]+@[^\\s@]+\\.[^\\s@]+$/;\n\nexport function isValidEmail(value: string): boolean {\n return EMAIL_PATTERN.test(value.trim());\n}\n\n/** The address fields a notification can get wrong. */\nexport type AddressField = \"senderEmail\" | \"to\" | \"replyTo\";\n\nexport type AddressErrors = Partial<Record<AddressField, string>>;\n\nconst MESSAGE = \"Enter a valid email address.\";\n\n/**\n * Whether ONE address is unusable, by the single rule the whole feature uses.\n *\n * A blank address is not an error — every one of these is optional or has an\n * inherited default — and a `{{field}}` reference is not an address at all: it\n * resolves against each submission at send time, so it cannot be checked now.\n */\nexport function addressError(\n field: AddressField,\n value: string | undefined\n): string | undefined {\n if (field === \"replyTo\" && parseFieldRef(value) !== null) return undefined;\n if (!value?.trim()) return undefined;\n return isValidEmail(value) ? undefined : MESSAGE;\n}\n\n/**\n * Every unusable address in a notification.\n *\n * The editor asks this about one field as it is left; the page asks it about\n * every rule before it saves. The same function answers both, because the\n * alternative is a field that rejects an address and a save that accepts it —\n * which is what happened while the editor held the only copy of the answer.\n *\n * `to` is checked only when the recipient is a literal address: a `field`\n * recipient names a form field, which is not an address to validate.\n */\nexport function addressErrorsIn(notification: FormNotification): AddressErrors {\n const errors: AddressErrors = {};\n\n const sender = addressError(\"senderEmail\", notification.senderEmail);\n if (sender) errors.senderEmail = sender;\n\n // Every team address, the list or the single one a rule was saved with.\n if (notification.recipientType === \"static\") {\n const to = teamAddressesOf(notification)\n .map(address => addressError(\"to\", address))\n .find(Boolean);\n if (to) errors.to = to;\n }\n\n const replyTo = addressError(\"replyTo\", notification.replyTo);\n if (replyTo) errors.replyTo = replyTo;\n\n return errors;\n}\n\n/** The rules a save must refuse, named so the message can say which. */\nexport function notificationsWithBadAddresses(\n notifications: readonly FormNotification[]\n): string[] {\n return notifications\n .filter(n => Object.keys(addressErrorsIn(n)).length > 0)\n .map(n => n.name.trim() || \"Untitled notification\");\n}\n\n/**\n * What to tell an author who pressed Save with a malformed address, or `null`\n * when there is nothing to tell them.\n *\n * The wording lives beside the rule that produces it so the caller has one\n * question to ask and one branch to write — a save path that composed this\n * itself would be a second place deciding what counts as wrong.\n */\nexport function badAddressMessage(\n notifications: readonly FormNotification[]\n): string | null {\n const names = notificationsWithBadAddresses(notifications);\n if (names.length === 0) return null;\n return names.length === 1\n ? `${names[0]} has an invalid email address`\n : `${names.length} notifications have invalid email addresses`;\n}\n\n/**\n * The one reason a save must refuse the notifications, or `null`.\n *\n * A single question so the save has a single branch. Naming comes first: an\n * address complaint that says \"Untitled notification\" twice cannot be acted on,\n * whereas a named rule can then be pointed at.\n */\nexport function notificationsBlockingSave(\n notifications: readonly FormNotification[]\n): string | null {\n const unnamed = unnamedNotificationCount(notifications);\n if (unnamed > 0) {\n return unnamed === 1\n ? \"A notification needs a name\"\n : `${unnamed} notifications need a name`;\n }\n return badAddressMessage(notifications);\n}\n\n/**\n * Rules a save must refuse for having no name.\n *\n * The sheet this editor replaced disabled its own commit while the name was\n * blank. There is no commit here any more — the page's action bar is the only\n * one — so the precondition moves to it rather than disappearing with the\n * button that used to carry it. A nameless rule leaves its summary and its\n * menu labelled with nothing.\n */\nexport function unnamedNotificationCount(\n notifications: readonly FormNotification[]\n): number {\n return notifications.filter(n => !n.name.trim()).length;\n}\n","\"use client\";\n\n/**\n * The email a notification sends: the Email hub message it uses (its wording\n * and design live there), the form's file fields whose files go with it, and\n * the From and Provider it sends with, the message's own unless picked here.\n *\n * @module admin/components/notifications/NotificationEmail\n */\nimport {\n Button,\n Checkbox,\n FormSection,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { ExternalLink, Mail, TriangleAlert } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\nimport {\n FORM_CONFIRMATION_TEMPLATE,\n starterTemplateFor,\n} from \"../../../emails/starter-messages\";\nimport type { AnyFormField, FormNotification } from \"../../../types\";\nimport { attachFieldsOf } from \"../../../utils/notification-model\";\nimport { addressError } from \"../builder/notification-addresses\";\nimport { descriptionIdOf, SettingRow } from \"../builder/settings-rows\";\n\nimport {\n emailHubEditHref,\n type NotificationDefaults,\n type NotificationSources,\n type TemplateOption,\n} from \"./notification-sources\";\nimport { messageOf } from \"./notification-summary\";\nimport type { NotificationUpdate } from \"./NotificationRecipients\";\n\nconst TRIGGER = \"w-full bg-transparent border-input dark:bg-muted/50\";\n\n/** What the message is, or that there is none, with its starter to hand. */\nfunction MessageDetails({\n notification,\n message,\n update,\n}: {\n notification: FormNotification;\n message: TemplateOption | undefined;\n update: NotificationUpdate;\n}): JSX.Element {\n if (!notification.templateSlug) {\n const starter = starterTemplateFor(notification.recipientType);\n return (\n <div className=\"min-w-0 flex-1 space-y-2\">\n <p className=\"flex items-center gap-1.5 text-sm font-semibold text-foreground\">\n <TriangleAlert\n className=\"h-4 w-4 shrink-0 text-destructive\"\n aria-hidden=\"true\"\n />\n No message: this notification never sends\n </p>\n <Button\n type=\"button\"\n variant=\"outline\"\n size=\"sm\"\n onClick={() => update({ templateSlug: starter })}\n >\n Use{\" \"}\n {starter === FORM_CONFIRMATION_TEMPLATE\n ? \"Form confirmation\"\n : \"Form submission\"}\n </Button>\n </div>\n );\n }\n return (\n <div className=\"min-w-0 flex-1 space-y-1\">\n <p className=\"text-sm font-semibold\">\n {message?.name ?? notification.templateSlug}\n </p>\n {message?.subject && (\n <p className=\"truncate text-xs text-muted-foreground\">\n Subject: <code className=\"font-mono\">{message.subject}</code>\n </p>\n )}\n <p className=\"text-xs text-muted-foreground\">\n Its wording and design are edited in Settings › Email.\n </p>\n </div>\n );\n}\n\n/** Open the message in the Email hub, or choose another. */\nfunction MessageActions({\n notification,\n message,\n sources,\n update,\n}: {\n notification: FormNotification;\n message: TemplateOption | undefined;\n sources: NotificationSources;\n update: NotificationUpdate;\n}): JSX.Element {\n const sendable = sources.templates.filter(\n each => each.isActive && each.kind !== \"layout\"\n );\n return (\n <div className=\"flex flex-wrap items-center gap-2\">\n {sources.canEditTemplates && message && (\n // A new tab, so the unsaved form is not left to edit the email.\n <Button asChild variant=\"outline\" size=\"sm\">\n <a\n href={emailHubEditHref(message.id)}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n Edit in Email\n </a>\n </Button>\n )}\n <Select\n value={notification.templateSlug ?? \"\"}\n onValueChange={templateSlug => update({ templateSlug })}\n >\n <SelectTrigger aria-label=\"Message\" className={`${TRIGGER} sm:w-48`}>\n <SelectValue placeholder=\"Choose a message\" />\n </SelectTrigger>\n <SelectContent>\n {sendable.map(each => (\n <SelectItem key={each.id} value={each.slug}>\n {each.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n );\n}\n\n/** The Email hub message the notification sends. */\nfunction MessageCard({\n notification,\n sources,\n update,\n}: {\n notification: FormNotification;\n sources: NotificationSources;\n update: NotificationUpdate;\n}): JSX.Element {\n const message = messageOf(notification, sources.templates);\n return (\n <div className=\"flex flex-col gap-3 rounded-md border border-border bg-card p-4 sm:flex-row sm:items-start\">\n <span className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded-md border border-border bg-muted text-muted-foreground\">\n <Mail className=\"h-4 w-4\" aria-hidden=\"true\" />\n </span>\n <MessageDetails\n notification={notification}\n message={message}\n update={update}\n />\n <MessageActions\n notification={notification}\n message={message}\n sources={sources}\n update={update}\n />\n </div>\n );\n}\n\nfunction AttachFiles({\n notification,\n fields,\n update,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n update: NotificationUpdate;\n}): JSX.Element {\n const files = fields.filter(field => field.type === \"file\");\n const chosen = attachFieldsOf(notification, fields);\n return (\n <SettingRow\n label=\"Attach files from\"\n description=\"The form's file fields whose files go with this email.\"\n stack={files.length > 0}\n >\n {files.length === 0 ? (\n <span className=\"text-sm text-muted-foreground\">\n This form has no file fields\n </span>\n ) : (\n <ul aria-label=\"Attach files from\" className=\"flex flex-col gap-2\">\n {files.map(field => {\n const id = `attach-${field.name}`;\n return (\n <li key={field.name} className=\"flex items-center gap-2\">\n <Checkbox\n id={id}\n checked={chosen.includes(field.name)}\n onCheckedChange={checked =>\n update({\n attachFields:\n checked === true\n ? [...chosen, field.name]\n : chosen.filter(name => name !== field.name),\n })\n }\n />\n <Label htmlFor={id} className=\"text-sm font-normal\">\n {field.label || field.name}\n </Label>\n </li>\n );\n })}\n </ul>\n )}\n </SettingRow>\n );\n}\n\n/** The address a notification sends from when its From is blank. */\n/**\n * Where a blank From sends from, in the order sending decides it: the\n * plugin's default sender (`notifications.defaultFrom`), which the send path\n * passes as the sender, then the message's own, then the provider's.\n */\nfunction inheritedSender(\n message: TemplateOption | undefined,\n defaults: NotificationDefaults | null\n): { address: string; description: string } {\n if (defaults?.defaultFrom) {\n return {\n address: defaults.defaultFrom,\n description: `Leave blank to send from ${defaults.defaultFrom}, the site's default sender.`,\n };\n }\n if (message?.fromOverride) {\n return {\n address: message.fromOverride,\n description: `Leave blank to send from ${message.fromOverride}, the message's setting.`,\n };\n }\n return {\n address: \"The provider's address\",\n description: \"Leave blank to send from the provider's own address.\",\n };\n}\n\n/** The provider the message sends through, or the site's default. */\nfunction inheritedProvider(\n message: TemplateOption | undefined,\n providers: NotificationSources[\"providers\"]\n): string {\n const chosen =\n providers.find(each => each.id === message?.providerId) ??\n providers.find(each => each.isDefault);\n return chosen ? ` (${chosen.name})` : \"\";\n}\n\nfunction FromRow({\n notification,\n inherited,\n update,\n}: {\n notification: FormNotification;\n inherited: { address: string; description: string };\n update: NotificationUpdate;\n}): JSX.Element {\n const error = addressError(\"senderEmail\", notification.senderEmail);\n return (\n <SettingRow\n label=\"From\"\n htmlFor=\"notification-from\"\n description={inherited.description}\n >\n <div className=\"flex w-full flex-col gap-1 sm:w-72\">\n <Input\n id=\"notification-from\"\n aria-describedby={descriptionIdOf(\"notification-from\")}\n type=\"email\"\n value={notification.senderEmail ?? \"\"}\n placeholder={inherited.address}\n onChange={event =>\n update({ senderEmail: event.target.value || undefined })\n }\n aria-invalid={error ? true : undefined}\n />\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </div>\n </SettingRow>\n );\n}\n\nfunction ProviderRow({\n notification,\n providers,\n inherited,\n update,\n}: {\n notification: FormNotification;\n providers: NotificationSources[\"providers\"];\n inherited: string;\n update: NotificationUpdate;\n}): JSX.Element {\n return (\n <SettingRow\n label=\"Provider\"\n htmlFor=\"notification-provider\"\n description=\"The message's provider unless you pick another.\"\n >\n <Select\n value={notification.providerId ?? \"__message\"}\n onValueChange={value =>\n update({ providerId: value === \"__message\" ? undefined : value })\n }\n >\n <SelectTrigger\n id=\"notification-provider\"\n aria-describedby={descriptionIdOf(\"notification-provider\")}\n className={`${TRIGGER} sm:w-72`}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"__message\">\n The message's provider{inherited}\n </SelectItem>\n {providers.map(each => (\n <SelectItem key={each.id} value={each.id}>\n {each.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </SettingRow>\n );\n}\n\nexport function NotificationEmail({\n notification,\n fields,\n sources,\n defaults,\n update,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n sources: NotificationSources;\n defaults: NotificationDefaults | null;\n update: NotificationUpdate;\n}): JSX.Element {\n const message = messageOf(notification, sources.templates);\n return (\n <FormSection label=\"The email\">\n {/* A row of the section, padded by it, holding the bordered card. */}\n <div>\n <MessageCard\n notification={notification}\n sources={sources}\n update={update}\n />\n </div>\n <AttachFiles\n notification={notification}\n fields={fields}\n update={update}\n />\n <FromRow\n notification={notification}\n inherited={inheritedSender(message, defaults)}\n update={update}\n />\n <ProviderRow\n notification={notification}\n providers={sources.providers}\n inherited={inheritedProvider(message, sources.providers)}\n update={update}\n />\n </FormSection>\n );\n}\n","\"use client\";\n\n/**\n * A submission's emails, on its page: each notification it triggered, every\n * recipient with its latest result (Waiting, Retrying, Sending, Sent, Sent\n * after N retries, Failed, Cancelled and why), and when. A notification whose\n * recipients differ reads Partly sent.\n *\n * Resend is offered to a person who may both update submissions and manage\n * email deliveries, never while a send is in progress: the row says one is.\n * It sends to the recipients who have not received it, and to any the person\n * ticks among those the notification can address now.\n *\n * @module admin/components/submissions/SubmissionEmails\n */\nimport {\n Button,\n Checkbox,\n Dialog,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n DialogTitle,\n Label,\n} from \"@nextlyhq/ui\";\nimport { useMutation, useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport { RotateCcw, Send } from \"lucide-react\";\nimport { type JSX, useState } from \"react\";\n\nimport {\n type AddressableRecipient,\n cancelText,\n notificationLook,\n recipientLook,\n type RecipientStatus,\n type StatusLook,\n} from \"../../../utils/send-status\";\n\nimport { shortWhenText } from \"./submission-row\";\n\n/** Where the plugin's routes answer. */\nconst ROUTES = \"/admin/api/plugins/@nextlyhq/plugin-form-builder\";\n\n/** One notification's sends, as `GET /submissions/:id/sends` answers it. */\nexport interface NotificationSendsView {\n notification: string | null;\n name: string;\n recipients: RecipientStatus[];\n inProgress: boolean;\n sends: Array<{\n queuedId: string;\n state: string;\n attempts: number;\n at: string | null;\n requestedBy: string | null;\n retired: boolean;\n }>;\n addressable: AddressableRecipient[];\n}\n\n/** The dot each tone draws, from the admin's tokens. */\nconst TONE_DOT: Record<StatusLook[\"tone\"], string> = {\n ok: \"bg-success\",\n warn: \"bg-warning\",\n bad: \"bg-destructive\",\n quiet: \"bg-muted-foreground\",\n};\n\n/** A status as a quiet chip with a dot. */\nexport function SendChip({ look }: { look: StatusLook }): JSX.Element {\n return (\n <span className=\"inline-flex h-6 shrink-0 items-center gap-1.5 whitespace-nowrap rounded-md border border-border bg-card px-2 text-xs font-medium text-foreground\">\n <span\n aria-hidden=\"true\"\n className={`size-1.5 shrink-0 rounded-full ${TONE_DOT[look.tone]}`}\n />\n {look.label}\n </span>\n );\n}\n\n/** The line a recipient takes in words. */\nfunction lineOf(recipient: RecipientStatus): string {\n return recipient.kind === \"to\"\n ? \"To\"\n : recipient.kind === \"cc\"\n ? \"Cc\"\n : \"Bcc\";\n}\n\n/** Who a notification went to, by line: `To t•••@acme.test · Cc …`. */\nexport function recipientsText(recipients: readonly RecipientStatus[]): string {\n const lines = new Map<string, string[]>();\n for (const recipient of recipients) {\n const line = lineOf(recipient);\n lines.set(line, [...(lines.get(line) ?? []), recipient.hint]);\n }\n return [...lines]\n .map(([line, hints]) => `${line} ${hints.join(\", \")}`)\n .join(\" · \");\n}\n\n/**\n * What the row says under the notification's name: who it went to, and,\n * when its recipients differ, what became of each; a cancel's cause; a send\n * made again; a send in progress.\n */\nfunction detailOf(group: NotificationSendsView, look: StatusLook): string[] {\n const parts = [recipientsText(group.recipients)];\n if (look.label === \"Partly sent\") {\n parts.push(\n group.recipients\n .map(\n recipient =>\n `${recipient.hint} ${recipientLook(recipient).label.toLowerCase()}`\n )\n .join(\"; \")\n );\n }\n const cancelled = group.recipients.find(\n recipient => recipient.state === \"cancelled\"\n );\n if (cancelled) parts.push(cancelText(cancelled.cancel));\n if (group.sends.some(send => send.requestedBy !== null)) {\n parts.push(\"Sent again on request\");\n }\n return parts;\n}\n\n/** The latest time a notification's sends changed. */\nfunction latestAt(group: NotificationSendsView): string | null {\n return group.sends.reduce<string | null>(\n (latest, send) =>\n send.at !== null && (latest === null || send.at > latest)\n ? send.at\n : latest,\n null\n );\n}\n\n/** Asks which recipients to send a notification to again. */\nfunction ResendDialog({\n group,\n open,\n onOpenChange,\n onSend,\n pending,\n error,\n}: {\n group: NotificationSendsView;\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onSend: (tick: string[]) => void;\n pending: boolean;\n error: string | null;\n}): JSX.Element {\n const [ticked, setTicked] = useState<string[]>([]);\n const missed = group.addressable.filter(recipient => !recipient.reached);\n const reached = group.addressable.filter(recipient => recipient.reached);\n return (\n <Dialog open={open} onOpenChange={onOpenChange}>\n <DialogContent>\n <DialogHeader>\n <DialogTitle>Send “{group.name}” again</DialogTitle>\n <DialogDescription>\n It goes to everyone who has not received it. Tick anyone who has, to\n send it to them again.\n </DialogDescription>\n </DialogHeader>\n {/* Who missed it goes regardless, so they are listed, not offered as\n boxes that could not be unticked; only who received it is a\n choice. */}\n {missed.length > 0 ? (\n <section aria-label=\"Sends to\" className=\"flex flex-col gap-1.5\">\n <h3 className=\"text-xs font-medium text-muted-foreground\">\n Sends to\n </h3>\n <ul className=\"flex flex-col gap-1.5\">\n {missed.map(recipient => (\n <li\n key={recipient.digest}\n className=\"flex items-center gap-2 text-sm text-foreground\"\n >\n <Send\n className=\"h-3.5 w-3.5 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n {recipient.hint}\n <span className=\"text-muted-foreground\">\n · has not received it\n </span>\n </li>\n ))}\n </ul>\n </section>\n ) : null}\n {reached.length > 0 ? (\n <section aria-label=\"Send again to\" className=\"flex flex-col gap-1.5\">\n <h3 className=\"text-xs font-medium text-muted-foreground\">\n Send again to\n </h3>\n <ul className=\"flex flex-col gap-2\">\n {reached.map(recipient => {\n const id = `resend-${recipient.digest}`;\n return (\n <li\n key={recipient.digest}\n className=\"flex items-center gap-2\"\n >\n <Checkbox\n id={id}\n checked={ticked.includes(recipient.digest)}\n onCheckedChange={checked =>\n setTicked(\n checked === true\n ? [...ticked, recipient.digest]\n : ticked.filter(\n digest => digest !== recipient.digest\n )\n )\n }\n />\n <Label htmlFor={id} className=\"text-sm font-normal\">\n {recipient.hint}{\" \"}\n <span className=\"text-muted-foreground\">\n · received it\n </span>\n </Label>\n </li>\n );\n })}\n </ul>\n </section>\n ) : null}\n {error ? (\n <p role=\"alert\" className=\"text-sm text-destructive\">\n {error}\n </p>\n ) : null}\n <DialogFooter>\n <Button\n type=\"button\"\n variant=\"outline\"\n onClick={() => onOpenChange(false)}\n >\n Cancel\n </Button>\n <Button\n type=\"button\"\n disabled={pending || (missed.length === 0 && ticked.length === 0)}\n onClick={() => onSend(ticked)}\n >\n Send\n </Button>\n </DialogFooter>\n </DialogContent>\n </Dialog>\n );\n}\n\n/** What a refused resend says, from the route's error envelope. */\nasync function refusalOf(response: Response): Promise<string> {\n const body = (await response.json().catch(() => null)) as {\n error?: { message?: string };\n } | null;\n return body?.error?.message ?? \"It could not be sent again.\";\n}\n\n/** Resend for one notification, as its row offers it. */\nfunction useResend(submissionId: string, notification: string | null) {\n const client = useQueryClient();\n return useMutation({\n mutationFn: async (tick: string[]) => {\n const response = await fetch(\n `${ROUTES}/submissions/${encodeURIComponent(submissionId)}/resend`,\n {\n method: \"POST\",\n credentials: \"include\",\n headers: { \"content-type\": \"application/json\" },\n body: JSON.stringify({ notification, tick }),\n }\n );\n if (!response.ok) throw new Error(await refusalOf(response));\n },\n onSuccess: () =>\n client.invalidateQueries({\n queryKey: [\"form-builder\", \"submission-sends\", submissionId],\n }),\n });\n}\n\n/** The row's action: Resend, or why it is not offered now. */\nfunction ResendAction({\n submissionId,\n group,\n}: {\n submissionId: string;\n group: NotificationSendsView;\n}): JSX.Element | null {\n const [open, setOpen] = useState(false);\n const resend = useResend(submissionId, group.notification);\n if (group.inProgress) {\n return (\n <span className=\"block text-xs text-muted-foreground\" role=\"status\">\n A send is in progress.\n </span>\n );\n }\n if (group.addressable.length === 0) return null;\n return (\n <>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"sm\"\n // Pulled left by its own padding, so its icon lines up with the\n // notification's name above it.\n className=\"-ml-2 h-7 gap-1.5 px-2\"\n onClick={() => {\n resend.reset();\n setOpen(true);\n }}\n >\n <RotateCcw className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Resend\n </Button>\n <ResendDialog\n group={group}\n open={open}\n onOpenChange={setOpen}\n pending={resend.isPending}\n error={resend.error ? resend.error.message : null}\n onSend={tick =>\n resend.mutate(tick, { onSuccess: () => setOpen(false) })\n }\n />\n </>\n );\n}\n\n/** One notification's row. */\nfunction NotificationRow({\n submissionId,\n group,\n canResend,\n}: {\n submissionId: string;\n group: NotificationSendsView;\n canResend: boolean;\n}): JSX.Element {\n const look = notificationLook(group.recipients);\n const at = latestAt(group);\n return (\n <li className=\"grid grid-cols-[1.5rem_minmax(0,1fr)_auto] items-start gap-3 py-2.5\">\n <span\n aria-hidden=\"true\"\n className=\"mt-0.5 flex h-6 w-6 items-center justify-center rounded-full border border-border bg-card text-muted-foreground\"\n >\n <Send className=\"h-3 w-3\" />\n </span>\n <div className=\"min-w-0 text-sm\">\n <span className=\"font-medium text-foreground\">{group.name}</span>\n {detailOf(group, look).map(part => (\n <span\n key={part}\n className=\"block break-words text-xs text-muted-foreground\"\n >\n {part}\n </span>\n ))}\n {canResend ? (\n <div className=\"mt-1\">\n <ResendAction submissionId={submissionId} group={group} />\n </div>\n ) : null}\n </div>\n <div className=\"flex flex-col items-end gap-1\">\n <SendChip look={look} />\n {at ? (\n <span className=\"whitespace-nowrap text-xs text-muted-foreground tabular-nums\">\n {shortWhenText(at)}\n </span>\n ) : null}\n </div>\n </li>\n );\n}\n\n/** How often the emails are read again while a send is in progress. */\nconst IN_PROGRESS_REFRESH_MS = 5000;\n\n/** The submission's sends, as its route answers them. */\nfunction useSubmissionSends(submissionId: string, status: string | undefined) {\n return useQuery({\n // By status too: marking Spam cancels what is waiting.\n queryKey: [\"form-builder\", \"submission-sends\", submissionId, status],\n // A send in progress settles in the background, and the admin does not\n // refetch on focus: read again until every send has settled, so the page\n // stops saying \"in progress\" and offers Resend once it may.\n refetchInterval: query =>\n query.state.data?.some(group => group.inProgress)\n ? IN_PROGRESS_REFRESH_MS\n : false,\n queryFn: async (): Promise<NotificationSendsView[]> => {\n const response = await fetch(\n `${ROUTES}/submissions/${encodeURIComponent(submissionId)}/sends`,\n { credentials: \"include\" }\n );\n if (!response.ok) throw new Error(\"The emails could not be read.\");\n const body = (await response.json()) as {\n items?: NotificationSendsView[];\n };\n return body.items ?? [];\n },\n });\n}\n\n/** A submission's emails, inside its page's Emails box. */\nexport function SubmissionEmails({\n submissionId,\n status,\n canResend,\n}: {\n submissionId: string;\n status?: string;\n canResend: boolean;\n}): JSX.Element {\n const { data, error } = useSubmissionSends(submissionId, status);\n if (error) {\n return (\n <p className=\"px-4 py-3 text-sm text-muted-foreground sm:px-5\">\n {error.message}\n </p>\n );\n }\n if (data === undefined) {\n return (\n <p className=\"px-4 py-3 text-sm text-muted-foreground sm:px-5\">\n Reading emails…\n </p>\n );\n }\n if (data.length === 0) {\n return (\n <p className=\"px-4 py-3 text-sm text-muted-foreground sm:px-5\">\n This submission sent no emails.\n </p>\n );\n }\n // Marking Spam cancels what is waiting, and a resend of a Spam\n // submission is refused: say so once, and offer no Resend.\n const spam = status === \"spam\";\n return (\n <>\n {spam ? (\n <p className=\"border-b border-border px-4 py-2.5 text-xs text-muted-foreground sm:px-5\">\n Marked Spam: its emails are not sent. Mark it Not spam to send them\n again.\n </p>\n ) : null}\n <ol className=\"divide-y divide-border px-4 sm:px-5\">\n {data.map(group => (\n <NotificationRow\n key={group.notification ?? group.sends[0]?.queuedId ?? group.name}\n submissionId={submissionId}\n group={group}\n canResend={canResend && !spam}\n />\n ))}\n </ol>\n </>\n );\n}\n","\"use client\";\n\n/**\n * A notification's recent sends, on its page: the latest submissions it went\n * out for, each with what became of it (Sent, Sent after a retry, Partly\n * sent, Failed, and the rest), who it went to by masked hint, and when, each\n * opening its submission. Shown to a person who may read submissions; the\n * route refuses anyone else, and the section then says nothing.\n *\n * @module admin/components/notifications/NotificationRecentSends\n */\nimport { FormSection } from \"@nextlyhq/ui\";\nimport { useQuery } from \"@tanstack/react-query\";\nimport type { JSX } from \"react\";\n\nimport {\n notificationLook,\n type RecipientStatus,\n} from \"../../../utils/send-status\";\nimport { submissionPageAddress } from \"../submissions/submission-list-scope\";\nimport { shortWhenText } from \"../submissions/submission-row\";\nimport { recipientsText, SendChip } from \"../submissions/SubmissionEmails\";\n\n/** Where the plugin's routes answer. */\nconst ROUTES = \"/admin/api/plugins/@nextlyhq/plugin-form-builder\";\n\n/** How many recent sends the page lists. */\nconst RECENT = 10;\n\n/** One recent send, as `GET /notification-sends` answers it. */\nexport interface RecentSendView {\n submission: string;\n title: string;\n recipients: RecipientStatus[];\n at: string | null;\n}\n\n/** The notification's recent sends, or undefined while unknown or refused. */\nfunction useRecentSends(formId: string | undefined, notificationId: string) {\n return useQuery({\n enabled: formId !== undefined,\n queryKey: [\"form-builder\", \"notification-sends\", formId, notificationId],\n retry: false,\n queryFn: async (): Promise<RecentSendView[] | null> => {\n const response = await fetch(\n `${ROUTES}/notification-sends?${new URLSearchParams({\n form: formId ?? \"\",\n notification: notificationId,\n limit: String(RECENT),\n })}`,\n { credentials: \"include\" }\n );\n // A person who may not read submissions: nothing to show.\n if (response.status === 403) return null;\n if (!response.ok) throw new Error(\"Recent sends could not be read.\");\n return (\n ((await response.json()) as { items?: RecentSendView[] }).items ?? []\n );\n },\n });\n}\n\n/** One recent send's row. */\nfunction RecentRow({\n send,\n submissionsSlug,\n}: {\n send: RecentSendView;\n submissionsSlug: string | undefined;\n}): JSX.Element {\n const label = send.title || \"Submission\";\n return (\n <li className=\"grid grid-cols-[auto_minmax(0,1fr)] items-center gap-x-3 gap-y-1 py-2.5 text-sm sm:grid-cols-[auto_minmax(0,1fr)_auto]\">\n <SendChip look={notificationLook(send.recipients)} />\n <span className=\"min-w-0 truncate text-foreground\">\n {submissionsSlug ? (\n <a\n href={submissionPageAddress(submissionsSlug, send.submission, {\n status: \"all\",\n })}\n className=\"underline decoration-muted-foreground underline-offset-4 hover:decoration-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {label}\n </a>\n ) : (\n label\n )}\n <span className=\"text-muted-foreground\">\n {\" \"}\n · {recipientsText(send.recipients)}\n </span>\n </span>\n <span className=\"col-start-2 whitespace-nowrap text-xs text-muted-foreground tabular-nums sm:col-start-auto\">\n {send.at ? shortWhenText(send.at) : \"\"}\n </span>\n </li>\n );\n}\n\n/** The notification's Recent sends section. */\nexport function NotificationRecentSends({\n formId,\n notificationId,\n submissionsSlug,\n}: {\n formId: string | undefined;\n notificationId: string;\n submissionsSlug: string | undefined;\n}): JSX.Element | null {\n const { data, error } = useRecentSends(formId, notificationId);\n // Not saved yet, or not the person's to read: no section.\n if (formId === undefined || data === null) return null;\n return (\n <FormSection label=\"Recent sends\">\n {error ? (\n <p className=\"text-sm text-muted-foreground\">{error.message}</p>\n ) : data === undefined ? (\n <p className=\"text-sm text-muted-foreground\">Reading recent sends…</p>\n ) : data.length === 0 ? (\n <p className=\"text-sm text-muted-foreground\">\n Nothing sent yet. Each submission's email shows here once it goes\n out.\n </p>\n ) : (\n <ol className=\"divide-y divide-border\">\n {data.map(send => (\n <RecentRow\n key={`${send.submission}:${send.at ?? \"\"}`}\n send={send}\n submissionsSlug={submissionsSlug}\n />\n ))}\n </ol>\n )}\n </FormSection>\n );\n}\n","\"use client\";\n\n/**\n * A form's notifications as a list: each one's name and when it sends, who it\n * goes to and by which message, and whether it is active. A row opens the\n * notification's own page; \"Add notification\" starts one from a preset, to the\n * team or a confirmation to the person who submitted.\n *\n * @module admin/components/notifications/NotificationsList\n */\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuTrigger,\n Switch,\n} from \"@nextlyhq/ui\";\nimport {\n ChevronRight,\n Mail,\n Plus,\n Send,\n TriangleAlert,\n Users,\n} from \"lucide-react\";\nimport type { JSX, MouseEvent } from \"react\";\n\nimport type { AnyFormField, FormNotification } from \"../../../types\";\nimport {\n createNotification,\n useFormBuilder,\n} from \"../../context/FormBuilderContext\";\n\nimport { notificationHref, showNotification } from \"./notification-address\";\nimport type { TemplateOption } from \"./notification-sources\";\nimport {\n messageOf,\n recipientSummary,\n whenSummary,\n} from \"./notification-summary\";\n\n/** Why \"Confirm to the visitor\" cannot be chosen on a form with no Email field. */\nexport const NO_EMAIL_FIELD_REASON =\n \"Add an Email field to the form to confirm to the visitor.\";\n\n/** Follow a row's link through the admin, keeping the unsaved form. */\nfunction open(event: MouseEvent<HTMLAnchorElement>, id: string): void {\n if (event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey) {\n return;\n }\n event.preventDefault();\n showNotification(id);\n}\n\nfunction AddNotification({\n fields,\n onAdd,\n}: {\n fields: readonly AnyFormField[];\n onAdd: (kind: \"team\" | \"visitor\") => void;\n}): JSX.Element {\n const hasEmailField = fields.some(field => field.type === \"email\");\n return (\n // Not modal: the page around the menu stays as it is to assistive tech.\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\">\n <Plus className=\"h-4 w-4\" aria-hidden=\"true\" />\n Add notification\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-72\">\n <DropdownMenuItem\n onSelect={() => onAdd(\"team\")}\n className=\"items-start\"\n >\n <Users className=\"mt-0.5 h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n <span className=\"flex flex-col\">\n <span className=\"font-medium\">Notify your team</span>\n <span className=\"text-xs text-muted-foreground\">\n An email to addresses you choose\n </span>\n </span>\n </DropdownMenuItem>\n <DropdownMenuItem\n onSelect={() => onAdd(\"visitor\")}\n disabled={!hasEmailField}\n className=\"items-start\"\n >\n <Send className=\"mt-0.5 h-4 w-4 shrink-0\" aria-hidden=\"true\" />\n <span className=\"flex flex-col\">\n <span className=\"font-medium\">Confirm to the visitor</span>\n <span className=\"text-xs text-muted-foreground\">\n {hasEmailField\n ? \"An email to the address they typed\"\n : NO_EMAIL_FIELD_REASON}\n </span>\n </span>\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nfunction NotificationRow({\n notification,\n fields,\n templates,\n onActive,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n templates: readonly TemplateOption[];\n onActive: (enabled: boolean) => void;\n}): JSX.Element {\n const message = messageOf(notification, templates);\n const name = notification.name.trim() || \"Untitled notification\";\n return (\n <li\n className={`grid grid-cols-[1fr_auto] items-center gap-x-4 gap-y-1 px-4 py-3 sm:grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)_auto] ${\n notification.enabled ? \"\" : \"text-muted-foreground\"\n }`}\n >\n <div className=\"min-w-0\">\n <a\n href={notificationHref(notification.id)}\n onClick={event => open(event, notification.id)}\n className=\"block truncate text-sm font-semibold text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n {name}\n </a>\n <p className=\"truncate text-xs text-muted-foreground\">\n {whenSummary(notification, fields)}\n </p>\n </div>\n <div className=\"col-span-2 min-w-0 sm:col-span-1 sm:row-start-1 sm:col-start-2\">\n <p className=\"truncate text-sm\">\n {recipientSummary(notification, fields)}\n </p>\n <p className=\"truncate text-xs text-muted-foreground\">\n {notification.templateSlug ? (\n <>Message: {message?.name ?? notification.templateSlug}</>\n ) : (\n // The warning colour is on the icon; the words stay in the\n // foreground colour, readable at this size in both themes.\n <span className=\"inline-flex items-center gap-1 text-foreground\">\n <TriangleAlert\n className=\"h-3 w-3 shrink-0 text-destructive\"\n aria-hidden=\"true\"\n />\n No message: will not send\n </span>\n )}\n </p>\n </div>\n <div className=\"col-start-2 row-start-1 flex items-center gap-2 sm:col-start-3\">\n <Switch\n checked={notification.enabled}\n onCheckedChange={onActive}\n aria-label={`${name} active`}\n />\n <ChevronRight\n className=\"h-4 w-4 text-muted-foreground\"\n aria-hidden=\"true\"\n />\n </div>\n </li>\n );\n}\n\nexport function NotificationsList({\n templates,\n}: {\n templates: readonly TemplateOption[];\n}): JSX.Element {\n const { notifications, fields, addNotification, updateNotification } =\n useFormBuilder();\n\n // A new notification is added at once and opened on its own page.\n const add = (kind: \"team\" | \"visitor\") => {\n const created = createNotification(kind, fields);\n addNotification(created);\n showNotification(created.id);\n };\n\n return (\n <div className=\"flex flex-col gap-4\">\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-center sm:justify-between\">\n <p className=\"max-w-[60ch] text-sm text-muted-foreground\">\n Emails sent when someone submits this form. Each one uses a message\n from Settings › Email, where its wording and design live.\n </p>\n <div className=\"shrink-0\">\n <AddNotification fields={fields} onAdd={add} />\n </div>\n </div>\n {notifications.length === 0 ? (\n <div className=\"flex flex-col items-center gap-2 rounded-md border border-dashed border-border px-6 py-12 text-center\">\n <Mail className=\"h-6 w-6 text-muted-foreground\" aria-hidden=\"true\" />\n <p className=\"text-sm font-medium\">No notifications yet</p>\n <p className=\"max-w-sm text-sm text-muted-foreground\">\n Email your team about each submission, or send the visitor a\n confirmation.\n </p>\n </div>\n ) : (\n <div className=\"overflow-hidden rounded-md border border-border bg-card\">\n <div\n aria-hidden=\"true\"\n className=\"hidden grid-cols-[minmax(0,1.1fr)_minmax(0,1fr)_auto] gap-x-4 border-b border-border px-4 py-2 text-xs font-medium text-muted-foreground sm:grid\"\n >\n <span>Notification</span>\n <span>Sent to</span>\n <span>Active</span>\n </div>\n <ul aria-label=\"Notifications\" className=\"divide-y divide-border\">\n {notifications.map(notification => (\n <NotificationRow\n key={notification.id}\n notification={notification}\n fields={fields}\n templates={templates}\n onActive={enabled =>\n updateNotification(notification.id, { enabled })\n }\n />\n ))}\n </ul>\n </div>\n )}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * Who a notification goes to: the team, at addresses typed in, or the person\n * who submitted, at the address in one of the form's Email fields; where a\n * reply lands; and its Cc and Bcc.\n *\n * @module admin/components/notifications/NotificationRecipients\n */\nimport {\n Badge,\n Button,\n FormSection,\n Input,\n Label,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { ChevronDown, ChevronUp, X } from \"lucide-react\";\nimport { type JSX, useId, useState } from \"react\";\n\nimport { starterTemplateFor } from \"../../../emails/starter-messages\";\nimport type { AnyFormField, FormNotification } from \"../../../types\";\nimport { parseFieldRef } from \"../../../utils/field-references\";\nimport {\n parseAddressList,\n teamAddressesOf,\n} from \"../../../utils/notification-model\";\nimport { addressError, isValidEmail } from \"../builder/notification-addresses\";\nimport {\n ChoiceCards,\n descriptionIdOf,\n SettingRow,\n} from \"../builder/settings-rows\";\n\nimport { NO_EMAIL_FIELD_REASON } from \"./NotificationsList\";\n\n/** A change to the notification shown. */\nexport type NotificationUpdate = (changes: Partial<FormNotification>) => void;\n\nconst TRIGGER = \"w-full bg-transparent border-input dark:bg-muted/50\";\n\n/**\n * Addresses as chips, with a box to add more: several typed at once (a\n * pasted list) are split, and one that is not an address is refused, saying\n * so, before it can be saved and fail at delivery.\n */\nfunction AddressList({\n label,\n addresses,\n placeholder,\n onChange,\n}: {\n label: string;\n addresses: string[];\n placeholder: string;\n onChange: (addresses: string[]) => void;\n}): JSX.Element {\n const id = useId();\n const [draft, setDraft] = useState(\"\");\n const [error, setError] = useState<string | null>(null);\n const add = () => {\n const typed = parseAddressList(draft);\n if (typed.length === 0) return;\n const bad = typed.find(address => !isValidEmail(address));\n if (bad !== undefined) {\n setError(`${bad} is not an email address.`);\n return;\n }\n const known = new Set(addresses.map(address => address.toLowerCase()));\n onChange([\n ...addresses,\n ...typed.filter(address => !known.has(address.toLowerCase())),\n ]);\n setDraft(\"\");\n setError(null);\n };\n return (\n <div className=\"flex flex-col gap-2\">\n {addresses.length > 0 && (\n <ul aria-label={label} className=\"flex flex-wrap gap-1.5\">\n {addresses.map(address => (\n <li key={address}>\n <Badge\n variant=\"outline\"\n className=\"gap-1 py-0.5 pl-2 pr-1 text-xs font-medium\"\n >\n {address}\n <button\n type=\"button\"\n className=\"rounded-sm p-0.5 text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n aria-label={`Remove ${address}`}\n onClick={() =>\n onChange(addresses.filter(each => each !== address))\n }\n >\n <X className=\"h-3 w-3\" aria-hidden=\"true\" />\n </button>\n </Badge>\n </li>\n ))}\n </ul>\n )}\n <div className=\"flex gap-2\">\n <Input\n id={id}\n aria-label={`Add to ${label}`}\n value={draft}\n placeholder={placeholder}\n onChange={event => {\n setDraft(event.target.value);\n setError(null);\n }}\n onKeyDown={event => {\n if (event.key === \"Enter\" || event.key === \",\") {\n event.preventDefault();\n add();\n }\n }}\n onBlur={add}\n aria-invalid={error ? true : undefined}\n aria-describedby={error ? `${id}-error` : undefined}\n // No width of its own: the page sets the form's measure, and the\n // input fills the row beside its Add button within it.\n />\n <Button type=\"button\" variant=\"outline\" onClick={add}>\n Add\n </Button>\n </div>\n {error && (\n <p id={`${id}-error`} className=\"text-xs text-destructive\">\n {error}\n </p>\n )}\n </div>\n );\n}\n\n/** The form's Email fields, the ones a visitor's address can come from. */\nfunction emailFieldsOf(fields: readonly AnyFormField[]): AnyFormField[] {\n return fields.filter(field => field.type === \"email\");\n}\n\n/** Where a reply lands, as the Reply-To choice reads it. */\nfunction replyChoiceOf(notification: FormNotification): string {\n if (!notification.replyTo) return \"message\";\n const field = parseFieldRef(notification.replyTo);\n return field === null ? \"custom\" : `field:${field}`;\n}\n\nfunction ReplyTo({\n notification,\n fields,\n update,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n update: NotificationUpdate;\n}): JSX.Element {\n const choice = replyChoiceOf(notification);\n // Typing a new address is a choice of its own before anything is typed.\n const [typing, setTyping] = useState(choice === \"custom\");\n const shown = typing ? \"custom\" : choice;\n const team = notification.recipientType !== \"field\";\n const error = addressError(\"replyTo\", notification.replyTo);\n return (\n <SettingRow\n label=\"Reply-To\"\n htmlFor=\"notification-reply-to\"\n description={\n team\n ? \"Replying from your inbox answers the visitor.\"\n : \"Where the visitor's reply lands.\"\n }\n >\n <div className=\"flex w-full flex-col gap-2 sm:w-72\">\n <Select\n value={shown}\n onValueChange={value => {\n setTyping(value === \"custom\");\n if (value === \"message\") update({ replyTo: undefined });\n else if (value.startsWith(\"field:\")) {\n update({ replyTo: `{{${value.slice(\"field:\".length)}}}` });\n } else update({ replyTo: \"\" });\n }}\n >\n <SelectTrigger\n id=\"notification-reply-to\"\n aria-describedby={descriptionIdOf(\"notification-reply-to\")}\n className={TRIGGER}\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {emailFieldsOf(fields).map(field => (\n <SelectItem key={field.name} value={`field:${field.name}`}>\n The visitor, field: {field.label || field.name}\n </SelectItem>\n ))}\n <SelectItem value=\"message\">The message's Reply-To</SelectItem>\n <SelectItem value=\"custom\">Another address</SelectItem>\n </SelectContent>\n </Select>\n {shown === \"custom\" && (\n <>\n <Input\n aria-label=\"Reply-To address\"\n type=\"email\"\n value={notification.replyTo ?? \"\"}\n placeholder=\"replies@example.com\"\n onChange={event => update({ replyTo: event.target.value })}\n aria-invalid={error ? true : undefined}\n />\n {error && <p className=\"text-xs text-destructive\">{error}</p>}\n </>\n )}\n </div>\n </SettingRow>\n );\n}\n\n/** One copy line, Cc or Bcc, with its addresses. */\nfunction CopyList({\n label,\n addresses,\n onChange,\n}: {\n label: \"Cc\" | \"Bcc\";\n addresses: string[];\n onChange: (addresses: string[]) => void;\n}): JSX.Element {\n return (\n <div className=\"flex flex-col gap-2\">\n <Label className=\"text-xs font-semibold\">{label}</Label>\n <AddressList\n label={label}\n addresses={addresses}\n placeholder=\"Add an address\"\n onChange={onChange}\n />\n </div>\n );\n}\n\n/** Cc and Bcc, folded away until wanted or already set. */\nfunction CopiesTo({\n notification,\n update,\n}: {\n notification: FormNotification;\n update: NotificationUpdate;\n}): JSX.Element {\n const cc = notification.cc ?? [];\n const bcc = notification.bcc ?? [];\n const [open, setOpen] = useState(cc.length + bcc.length > 0);\n const Icon = open ? ChevronUp : ChevronDown;\n return (\n <div className=\"flex flex-col gap-3\">\n <button\n type=\"button\"\n aria-expanded={open}\n onClick={() => setOpen(!open)}\n className=\"inline-flex w-fit items-center gap-1.5 text-sm font-medium text-foreground hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Icon className=\"h-4 w-4\" aria-hidden=\"true\" />\n Cc and Bcc\n </button>\n {open && (\n <div className=\"grid gap-4 md:grid-cols-2\">\n <CopyList\n label=\"Cc\"\n addresses={cc}\n onChange={next => update({ cc: next })}\n />\n <CopyList\n label=\"Bcc\"\n addresses={bcc}\n onChange={next => update({ bcc: next })}\n />\n </div>\n )}\n </div>\n );\n}\n\n/**\n * Turn the notification to the team or to the visitor. Its message follows\n * to the other kind's starter while it is still on the starter for the old\n * one; a message the author chose stays.\n */\nfunction turnedTo(\n notification: FormNotification,\n kind: \"team\" | \"visitor\",\n fields: readonly AnyFormField[]\n): Partial<FormNotification> {\n const recipientType = kind === \"visitor\" ? \"field\" : \"static\";\n const templateSlug =\n notification.templateSlug === starterTemplateFor(notification.recipientType)\n ? starterTemplateFor(recipientType)\n : notification.templateSlug;\n const email = emailFieldsOf(fields)[0]?.name;\n return kind === \"visitor\"\n ? {\n kind,\n recipientType,\n to: email === undefined ? \"\" : `{{${email}}}`,\n recipients: undefined,\n templateSlug,\n }\n : { kind, recipientType, to: \"\", recipients: [], templateSlug };\n}\n\nexport function NotificationRecipients({\n notification,\n fields,\n update,\n}: {\n notification: FormNotification;\n fields: readonly AnyFormField[];\n update: NotificationUpdate;\n}): JSX.Element {\n const emailFields = emailFieldsOf(fields);\n const kind = notification.recipientType === \"field\" ? \"visitor\" : \"team\";\n const addresses = teamAddressesOf(notification);\n const visitorField = parseFieldRef(notification.to) ?? \"\";\n return (\n <FormSection label=\"Who gets it\">\n <ChoiceCards\n name=\"notification-recipient\"\n label=\"Who gets it\"\n value={kind}\n onChange={next => update(turnedTo(notification, next, fields))}\n choices={[\n {\n value: \"team\",\n title: \"Your team\",\n details: (\n <AddressList\n label=\"Team addresses\"\n addresses={addresses}\n placeholder=\"Add an address\"\n onChange={list =>\n update({ recipients: list, to: list[0] ?? \"\" })\n }\n />\n ),\n },\n {\n value: \"visitor\",\n title: \"The person who submitted\",\n disabled: emailFields.length === 0 && kind !== \"visitor\",\n note:\n emailFields.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n {NO_EMAIL_FIELD_REASON}\n </p>\n ) : undefined,\n details: (\n <div className=\"flex flex-wrap items-center gap-2\">\n <span className=\"text-sm text-muted-foreground\">\n To the address in\n </span>\n <Select\n value={visitorField}\n onValueChange={name => update({ to: `{{${name}}}` })}\n >\n <SelectTrigger\n aria-label=\"Email field\"\n className={`${TRIGGER} sm:w-56`}\n >\n <SelectValue placeholder=\"Choose an Email field\" />\n </SelectTrigger>\n <SelectContent>\n {emailFields.map(field => (\n <SelectItem key={field.name} value={field.name}>\n {field.label || field.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n ),\n },\n ]}\n />\n <ReplyTo notification={notification} fields={fields} update={update} />\n <CopiesTo notification={notification} update={update} />\n </FormSection>\n );\n}\n","\"use client\";\n\n/**\n * One notification on a page of its own: its name and whether it is active,\n * then who gets it, when it sends, and the email it sends. Every edit is\n * written to the form as it is made and saved with the form's own Save, so\n * moving between the list and a notification keeps unsaved work.\n *\n * @module admin/components/notifications/NotificationPage\n */\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Input,\n Label,\n Switch,\n} from \"@nextlyhq/ui\";\nimport { ChevronLeft, Copy, MoreHorizontal, Trash2 } from \"lucide-react\";\nimport type { JSX, MouseEvent } from \"react\";\n\nimport {\n useFieldBookkeeping,\n useFormBuilder,\n} from \"../../context/FormBuilderContext\";\nimport { useBuilderConfig } from \"../../use-builder-config\";\n\nimport { notificationHref, showNotification } from \"./notification-address\";\nimport type {\n NotificationDefaults,\n NotificationSources,\n} from \"./notification-sources\";\nimport { NotificationConditions } from \"./NotificationConditions\";\nimport { NotificationEmail } from \"./NotificationEmail\";\nimport { NotificationRecentSends } from \"./NotificationRecentSends\";\nimport { NotificationRecipients } from \"./NotificationRecipients\";\n\nfunction BackToList(): JSX.Element {\n return (\n <a\n href={notificationHref(null)}\n onClick={(event: MouseEvent<HTMLAnchorElement>) => {\n if (event.button !== 0 || event.metaKey || event.ctrlKey) return;\n event.preventDefault();\n showNotification(null);\n }}\n className=\"inline-flex w-fit items-center gap-1 text-sm text-muted-foreground hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <ChevronLeft className=\"h-4 w-4\" aria-hidden=\"true\" />\n All notifications\n </a>\n );\n}\n\nexport function NotificationPage({\n id,\n sources,\n defaults,\n}: {\n id: string;\n sources: NotificationSources;\n defaults: NotificationDefaults | null;\n}): JSX.Element {\n const {\n notifications,\n fields,\n formData,\n updateNotification,\n duplicateNotification,\n deleteNotification,\n } = useFormBuilder();\n const { submissionsSlug } = useBuilderConfig();\n const notification = notifications.find(each => each.id === id);\n // After an Undo the sections draw afresh from the restored rule, so a\n // choice they hold for themselves (typing another Reply-To) follows it.\n const { restored } = useFieldBookkeeping();\n\n if (!notification) {\n return (\n <div className=\"flex flex-col gap-3\">\n <BackToList />\n <p className=\"text-sm text-muted-foreground\">\n This notification is not on the form. It may have been deleted.\n </p>\n </div>\n );\n }\n\n const update = (changes: Parameters<typeof updateNotification>[1]) =>\n updateNotification(notification.id, changes);\n\n return (\n <div className=\"flex max-w-[56rem] flex-col gap-6\">\n <BackToList />\n <div className=\"flex flex-col gap-3 sm:flex-row sm:items-end\">\n <div className=\"min-w-0 flex-1 space-y-1.5\">\n <Label htmlFor=\"notification-name\" className=\"text-sm font-semibold\">\n Name\n </Label>\n <Input\n id=\"notification-name\"\n value={notification.name}\n onChange={event => update({ name: event.target.value })}\n placeholder=\"Notify your team\"\n className=\"sm:max-w-md\"\n />\n </div>\n <div className=\"flex items-center gap-3\">\n <Label htmlFor=\"notification-active\" className=\"text-sm\">\n Active\n </Label>\n <Switch\n id=\"notification-active\"\n checked={notification.enabled}\n onCheckedChange={enabled => update({ enabled })}\n />\n {/* Not modal: the page around it stays as it is to assistive tech. */}\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger asChild>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"More for this notification\"\n >\n <MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onSelect={() => duplicateNotification(notification.id)}\n >\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n Duplicate\n </DropdownMenuItem>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-destructive focus:text-destructive\"\n onSelect={() => {\n deleteNotification(notification.id);\n showNotification(null);\n }}\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n Delete\n </DropdownMenuItem>\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </div>\n <NotificationRecipients\n key={`recipients:${restored}`}\n notification={notification}\n fields={fields}\n update={update}\n />\n <NotificationConditions\n notification={notification}\n fields={fields}\n update={update}\n />\n <NotificationEmail\n notification={notification}\n fields={fields}\n sources={sources}\n defaults={defaults}\n update={update}\n />\n <NotificationRecentSends\n formId={formData.id}\n notificationId={notification.id}\n submissionsSlug={submissionsSlug}\n />\n </div>\n );\n}\n","\"use client\";\n\n/**\n * A form's Notifications page: the list of its notifications, or one of them\n * on a page of its own when the address names it (`?section=<id>`).\n * Both read and write the form held by the editor, so moving between them\n * keeps unsaved work, and the form's Save commits it.\n *\n * @module admin/components/builder/FormNotificationsTab\n */\nimport type { JSX } from \"react\";\n\nimport { useShownNotification } from \"../notifications/notification-address\";\nimport {\n type NotificationDefaults,\n useNotificationSources,\n} from \"../notifications/notification-sources\";\nimport { NotificationPage } from \"../notifications/NotificationPage\";\nimport { NotificationsList } from \"../notifications/NotificationsList\";\n\nexport type { NotificationDefaults } from \"../notifications/notification-sources\";\n\ninterface FormNotificationsTabProps {\n /** `null` while the builder-config request is still settling. */\n defaults: NotificationDefaults | null;\n}\n\nexport function FormNotificationsTab({\n defaults,\n}: FormNotificationsTabProps): JSX.Element {\n const shown = useShownNotification();\n const sources = useNotificationSources();\n return shown === null ? (\n <NotificationsList templates={sources.templates} />\n ) : (\n <NotificationPage id={shown} sources={sources} defaults={defaults} />\n );\n}\n\nexport default FormNotificationsTab;\n","\"use client\";\n\n/**\n * Spam protection's bot check and rate limit in Settings. The bot check is\n * the site default, off, reCAPTCHA or Turnstile; a provider whose keys are\n * not set in the plugin's options cannot be newly chosen, and a form whose\n * check has no secret says that its submissions are accepted unchecked,\n * naming the option to set. The rate limit is the plugin's, shown here so\n * people know it exists.\n *\n * @module admin/components/builder/BotCheckRows\n */\nimport {\n Alert,\n AlertDescription,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport type { JSX } from \"react\";\n\nimport { DEFAULT_FORM_SETTINGS } from \"../../../config/defaults\";\nimport type { BotCheckMode } from \"../../../types\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { descriptionIdOf, SettingRow } from \"./settings-rows\";\n\ntype BotProvider = \"recaptcha\" | \"turnstile\";\n\n/** What the plugin's options say about bot checks and the rate limit. */\nexport interface BotCheckSiteDefaults {\n /** The provider \"Site default\" resolves to, or null for none. */\n botCheckDefault?: BotProvider | null;\n /** Which providers have their keys set. */\n botChecks?: Record<BotProvider, { secret: boolean; siteKey: boolean }>;\n rateLimit?: { maxSubmissions: number; windowMs: number } | null;\n}\n\nconst NAMES: Record<BotProvider, string> = {\n recaptcha: \"reCAPTCHA\",\n turnstile: \"Turnstile\",\n};\n\n/** What each choice does, said under the control. */\nconst DESCRIPTIONS: Record<BotProvider | \"none\", string> = {\n recaptcha:\n \"Google reCAPTCHA scores each visitor in the background; there is no puzzle to solve.\",\n turnstile:\n \"Cloudflare Turnstile checks each visitor in the background and rarely shows a puzzle.\",\n none: \"No bot check. reCAPTCHA and Turnstile keys are set in the plugin's options.\",\n};\n\n/** The provider a choice resolves to, the site default followed. */\nfunction providerOf(\n choice: BotCheckMode,\n defaults: BotCheckSiteDefaults | null\n): BotProvider | undefined {\n if (choice === \"recaptcha\" || choice === \"turnstile\") return choice;\n if (choice === \"default\") return defaults?.botCheckDefault ?? undefined;\n return undefined;\n}\n\n/**\n * The keys `provider` lacks for this form, when the plugin's options are\n * known: the secret to verify with, and a site key for the visitor's widget,\n * where a form's own legacy reCAPTCHA site key counts. A check missing\n * either is not run, so it cannot be newly chosen.\n */\nfunction missingKeys(\n provider: BotProvider,\n defaults: BotCheckSiteDefaults | null,\n settings: { captchaSiteKey?: string }\n): (\"secretKey\" | \"siteKey\")[] {\n const keys = defaults?.botChecks?.[provider];\n if (!keys) return [];\n const siteKey =\n keys.siteKey ||\n (provider === \"recaptcha\" && Boolean(settings.captchaSiteKey));\n return [\n ...(keys.secret ? [] : [\"secretKey\" as const]),\n ...(siteKey ? [] : [\"siteKey\" as const]),\n ];\n}\n\nexport function BotCheckRow({\n defaults,\n}: {\n /** The plugin's options as Settings is told them; null while loading. */\n defaults: BotCheckSiteDefaults | null;\n}): JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n const choice = settings.botCheck ?? DEFAULT_FORM_SETTINGS.botCheck;\n const provider = providerOf(choice, defaults);\n const siteDefault = defaults?.botCheckDefault;\n return (\n <SettingRow\n label=\"Bot check\"\n htmlFor=\"settings-bot-check\"\n description={DESCRIPTIONS[provider ?? \"none\"]}\n >\n <div className=\"flex flex-col items-end gap-3\">\n <Select\n value={choice}\n onValueChange={value => {\n const botCheck = CHOICES.find(mode => mode === value);\n if (botCheck) updateSettings({ botCheck });\n }}\n >\n <SelectTrigger\n id=\"settings-bot-check\"\n aria-describedby={descriptionIdOf(\"settings-bot-check\")}\n className=\"w-56 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"default\">\n {siteDefault === undefined\n ? \"Site default\"\n : `Site default (${siteDefault ? NAMES[siteDefault] : \"off\"})`}\n </SelectItem>\n <SelectItem value=\"off\">Off</SelectItem>\n {([\"recaptcha\", \"turnstile\"] as const).map(option => (\n // A check with no keys cannot be newly chosen: it would accept\n // every submission unchecked. A form that stores it keeps it.\n <SelectItem\n key={option}\n value={option}\n disabled={\n missingKeys(option, defaults, settings).length > 0 &&\n choice !== option\n }\n >\n {missingKeys(option, defaults, settings).length > 0\n ? `${NAMES[option]} (needs keys)`\n : NAMES[option]}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </div>\n </SettingRow>\n );\n}\n\n/** The bot check's warnings: no secret, and a site key kept on the form. */\nexport function BotCheckNotices({\n defaults,\n}: {\n defaults: BotCheckSiteDefaults | null;\n}): JSX.Element | null {\n const { settings } = useFormBuilder();\n const provider = providerOf(\n settings.botCheck ?? DEFAULT_FORM_SETTINGS.botCheck,\n defaults\n );\n if (provider === undefined) return null;\n const missing = missingKeys(provider, defaults, settings);\n const unchecked = missing.length > 0;\n const ownSiteKey =\n provider === \"recaptcha\" &&\n Boolean(settings.captchaSiteKey) &&\n defaults?.botChecks?.recaptcha.siteKey === false;\n if (!unchecked && !ownSiteKey) return null;\n return (\n <div className=\"flex flex-col gap-3\">\n {unchecked ? (\n <Alert variant=\"warning\">\n <AlertDescription>\n This form’s bot check is {NAMES[provider]}, but the plugin’s options\n are missing its{\" \"}\n {missing.map((key, index) => (\n <span key={key}>\n {index > 0 ? \" and \" : \"\"}\n <code>{`spamProtection.${provider}.${key}`}</code>\n </span>\n ))}\n , so submissions are accepted unchecked.\n </AlertDescription>\n </Alert>\n ) : null}\n {ownSiteKey ? (\n <Alert variant=\"info\">\n <AlertDescription>\n This form keeps its own reCAPTCHA site key, which it goes on using.\n Move it into the plugin’s options as{\" \"}\n <code>spamProtection.recaptcha.siteKey</code> so every form uses one\n key.\n </AlertDescription>\n </Alert>\n ) : null}\n </div>\n );\n}\n\n/** \"10 a minute\", from a count and a window in milliseconds. */\nfunction rateOf(limit: { maxSubmissions: number; windowMs: number }): string {\n const { maxSubmissions, windowMs } = limit;\n if (windowMs === 60_000) return `${maxSubmissions} a minute`;\n if (windowMs === 3_600_000) return `${maxSubmissions} an hour`;\n const seconds = Math.round(windowMs / 1000);\n return `${maxSubmissions} every ${seconds} seconds`;\n}\n\nexport function RateLimitRow({\n defaults,\n}: {\n defaults: BotCheckSiteDefaults | null;\n}): JSX.Element | null {\n const limit = defaults?.rateLimit;\n if (!limit) return null;\n return (\n <SettingRow\n label=\"Rate limit\"\n description=\"Submissions from one network, set in the plugin's options (spamProtection.rateLimit).\"\n >\n <span className=\"text-sm text-muted-foreground\">{rateOf(limit)}</span>\n </SettingRow>\n );\n}\n\n/** Every bot check a form can choose. */\nconst CHOICES: readonly BotCheckMode[] = [\n \"default\",\n \"off\",\n \"recaptcha\",\n \"turnstile\",\n];\n","\"use client\";\n\n/**\n * The two ways a live form closes by itself, in Settings' Availability: on a\n * date, and once it has a number of submissions. Each is off until switched\n * on, which fills in a value to change, and switching it off clears it.\n *\n * @module admin/components/builder/ClosingRows\n */\nimport { Input, Switch } from \"@nextlyhq/ui\";\nimport { useState, type JSX } from \"react\";\n\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { descriptionIdOf, SettingRow } from \"./settings-rows\";\n\n/** What a new limit starts at, to be changed. */\nconst DEFAULT_LIMIT = 100;\n\n/** Two digits, as a date-and-time input writes them. */\nconst two = (n: number) => String(n).padStart(2, \"0\");\n\n/**\n * An instant as the author's date-and-time input shows it, in their own time\n * zone; empty when it is not a readable instant.\n */\nfunction toLocalInput(iso: string | undefined): string {\n const date = iso === undefined ? undefined : new Date(iso);\n if (date === undefined || Number.isNaN(date.getTime())) return \"\";\n return `${date.getFullYear()}-${two(date.getMonth() + 1)}-${two(date.getDate())}T${two(date.getHours())}:${two(date.getMinutes())}`;\n}\n\n/** What the author typed, read in their time zone, as the instant stored. */\nfunction fromLocalInput(value: string): string | undefined {\n const date = new Date(value);\n return value === \"\" || Number.isNaN(date.getTime())\n ? undefined\n : date.toISOString();\n}\n\n/** A week from now, on the hour: a closing time to change. */\nfunction aWeekFromNow(): string {\n const date = new Date(Date.now() + 7 * 24 * 60 * 60 * 1000);\n date.setMinutes(0, 0, 0);\n return date.toISOString();\n}\n\n/** A whole number above zero, or nothing. */\nfunction asLimit(value: string): number | undefined {\n const n = Number(value);\n return Number.isInteger(n) && n > 0 ? n : undefined;\n}\n\n/**\n * What a field shows while a person types: their own text, stored once it\n * reads as a value, so a field can be emptied and typed again. A change to\n * the stored value from elsewhere (Undo, a switch) replaces it, and leaving\n * the field puts back the stored value if the text never became one.\n */\nfunction useDraft(stored: string, commit: (text: string) => void) {\n const [draft, setDraft] = useState(stored);\n const [seen, setSeen] = useState(stored);\n if (stored !== seen) {\n setSeen(stored);\n setDraft(stored);\n }\n return {\n value: draft,\n onChange: (event: { target: { value: string } }) => {\n setDraft(event.target.value);\n commit(event.target.value);\n },\n onBlur: () => setDraft(stored),\n };\n}\n\nexport function ClosingTimeRow(): JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n const on = settings.closesAt !== undefined;\n const field = useDraft(toLocalInput(settings.closesAt), text => {\n const closesAt = fromLocalInput(text);\n if (closesAt !== undefined) updateSettings({ closesAt });\n });\n return (\n <SettingRow\n label=\"Close on a date\"\n htmlFor=\"settings-closes-on\"\n description=\"Closes by itself at this time, in your time zone. Visitors then see the closed message.\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n {on ? (\n <Input\n type=\"datetime-local\"\n aria-label=\"Closing date and time\"\n className=\"w-auto\"\n {...field}\n />\n ) : null}\n <Switch\n id=\"settings-closes-on\"\n aria-describedby={descriptionIdOf(\"settings-closes-on\")}\n checked={on}\n onCheckedChange={next =>\n updateSettings({ closesAt: next ? aWeekFromNow() : undefined })\n }\n />\n </div>\n </SettingRow>\n );\n}\n\nexport function SubmissionLimitRow(): JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n const on = settings.submissionLimit !== undefined;\n const field = useDraft(String(settings.submissionLimit ?? \"\"), text => {\n const submissionLimit = asLimit(text);\n if (submissionLimit !== undefined) updateSettings({ submissionLimit });\n });\n return (\n <SettingRow\n label=\"Limit the number of submissions\"\n htmlFor=\"settings-limit-on\"\n description=\"Closes once this many arrive. Spam does not count. Whichever comes first, the date or the limit, closes it.\"\n >\n <div className=\"flex flex-wrap items-center gap-3\">\n {on ? (\n <Input\n type=\"number\"\n min={1}\n step={1}\n inputMode=\"numeric\"\n aria-label=\"Submission limit\"\n className=\"w-24\"\n {...field}\n />\n ) : null}\n <Switch\n id=\"settings-limit-on\"\n aria-describedby={descriptionIdOf(\"settings-limit-on\")}\n checked={on}\n onCheckedChange={next =>\n updateSettings({\n submissionLimit: next ? DEFAULT_LIMIT : undefined,\n })\n }\n />\n </div>\n </SettingRow>\n );\n}\n","\"use client\";\n\n/**\n * Picks the document a form redirects to after submission.\n *\n * The plugin cannot reach the admin's own relationship input: `@nextlyhq/admin`\n * is not on the plugin import boundary, and `plugin-sdk/admin` does not\n * re-export it. This is the narrow version of that control — one value, chosen\n * from the collections the site configured as redirect targets.\n *\n * @module admin/components/builder/RedirectPagePicker\n */\n\nimport {\n Input,\n Select,\n SelectContent,\n SelectGroup,\n SelectItem,\n SelectLabel,\n SelectTrigger,\n SelectValue,\n} from \"@nextlyhq/ui\";\nimport { encodeSelectParam } from \"nextly/query\";\nimport { type JSX, useEffect, useState } from \"react\";\n\nimport {\n documentReachability,\n type Reachability,\n parseRedirectReference,\n} from \"../../../utils/redirect-reference\";\n\n/** Rows per request. Search, not paging, is how the rest are reached. */\nconst PAGE_SIZE = 50;\n\n/**\n * The conventional title fields, tried in order when a collection does not say\n * which of its fields names a document.\n */\nconst FALLBACK_TITLE_FIELDS = [\"title\", \"name\", \"label\", \"slug\"] as const;\n\n/**\n * The fields this control reads for one collection, in the order\n * `documentLabel` prefers them.\n *\n * A collection declares what names its documents through `admin.useAsTitle`,\n * and that declaration leads here: a collection titled by `headline` is listed\n * by its headlines rather than by opaque ids, which is the difference between\n * choosing a redirect target and guessing at one. The conventional names still\n * follow it, so a row whose configured title field happens to be empty reads\n * as something recognisable instead of falling straight through to an id.\n *\n * `id` is always first because the control stores it.\n *\n * One declaration, used to build the request AND to choose the label, so a\n * field added to one cannot go missing from the other.\n */\nexport function labelFieldsFor(useAsTitle?: string): readonly string[] {\n const configured = useAsTitle?.trim();\n if (!configured) return [\"id\", ...FALLBACK_TITLE_FIELDS];\n return [\n \"id\",\n configured,\n ...FALLBACK_TITLE_FIELDS.filter(field => field !== configured),\n ];\n}\n\n/**\n * The `select` query value that projects a read down to `fields`.\n *\n * The dispatcher reads this parameter with `JSON.parse` and keeps only the\n * keys whose values are booleans (`parseSelectParam`,\n * packages/nextly/src/dispatcher/helpers/validation.ts). A value it cannot\n * parse becomes `undefined` — no error, no warning, and a response carrying\n * every field of every row. A comma-separated list is precisely such a value,\n * so the encoding is the whole difference between projecting a read and\n * appearing to project it.\n */\n/**\n * The fields to REQUEST, which is the label fields plus what decides\n * reachability.\n *\n * Kept apart from `labelFieldsFor` on purpose. `status` decides whether a page\n * is marked as a draft; it must never become a document's name, and\n * `documentLabel` walks whatever list it is given — so a page with no title\n * would otherwise be offered to an author as \"draft\".\n *\n * Derived from the label list rather than written out again, so a field added\n * for labelling is still requested.\n */\nexport function selectFieldsFor(useAsTitle?: string): readonly string[] {\n // `firstPublishedAt` as well as `status`: it is what says the collection has\n // a publish lifecycle at all, and without it a page whose collection merely\n // has a field called `status` would be marked as a draft. See\n // `hasPublishLifecycle`.\n return [...labelFieldsFor(useAsTitle), \"status\", \"firstPublishedAt\"];\n}\n\n/**\n * The `select` parameter, ready to sit in a hand-built query string.\n *\n * The format itself comes from core rather than from here. This module once\n * spelled it out, having first shipped a comma list the server accepted and\n * discarded — every field of up to fifty documents per collection, to fill a\n * dropdown reading five.\n */\nexport function selectParam(fields: readonly string[]): string {\n return encodeURIComponent(encodeSelectParam(fields));\n}\n\n/** One selectable document, reduced to what the control shows and stores. */\ninterface Choice {\n collection: string;\n id: string;\n label: string;\n /**\n * Whether a visitor could reach this page today. Unpublished pages are\n * offered on purpose — a form is usually configured beside the page it\n * points at — but an author choosing one should be able to see that it is\n * not live yet.\n *\n * Kept as the three-way answer rather than a boolean: a localized page whose\n * translation may be public is not the same as one that is definitely a\n * draft, and only the definite case earns a marker.\n */\n reachability: Reachability;\n}\n\ninterface RedirectPagePickerProps {\n /** Collections configured as redirect targets, in configuration order. */\n collections: readonly string[];\n /** The stored relationship value, in whatever shape it was saved. */\n value: unknown;\n onChange: (next: { relationTo: string; value: string } | undefined) => void;\n}\n\n/**\n * The document a stored value points at, as `collection:id`.\n *\n * The reading is `parseRedirectReference`, not a second copy of it: the picker\n * and the submit handler have to agree about which document a stored value\n * names, and two readers that agree today would drift — silently, because a\n * control showing the wrong page still shows a page.\n */\nexport function selectionKey(\n value: unknown,\n collections: readonly string[]\n): string | undefined {\n const reference = parseRedirectReference(value, collections);\n return reference ? `${reference.collection}:${reference.id}` : undefined;\n}\n\n/** What to call a document, preferring the field an author would recognise. */\nexport function documentLabel(\n row: Record<string, unknown>,\n fields: readonly string[] = labelFieldsFor()\n): string {\n for (const field of fields.filter(name => name !== \"id\")) {\n // A collection only has to point `useAsTitle` at a field that EXISTS, so\n // the configured title can be a number or a boolean. Skipping those falls\n // through to a conventional field or to the id, which is the opaque label\n // reading the configuration was meant to replace. `0` and `false` are\n // titles like any other, so emptiness is decided after stringifying, not\n // by truthiness.\n const candidate = row[field];\n const text =\n typeof candidate === \"string\"\n ? candidate\n : typeof candidate === \"number\" && Number.isFinite(candidate)\n ? String(candidate)\n : typeof candidate === \"boolean\"\n ? String(candidate)\n : undefined;\n if (text !== undefined && text.trim()) return text;\n }\n return typeof row.id === \"string\" ? row.id : \"Untitled\";\n}\n\n/**\n * One collection's matching documents, or null when it could not be read.\n *\n * `limit`, not `pageSize` — the other name is accepted and ignored, leaving\n * the default of 10. `status=all` because a form is usually configured\n * alongside the page it points at, and filtering to published shows \"create\n * one first\" while the page sits in the next tab. `depth=0` because this reads\n * a handful of scalar fields and would otherwise inherit the collection API's\n * expansion, pulling every nested relation of every row to fill a dropdown.\n */\nasync function fetchChoices(\n collection: string,\n query: string,\n page: number,\n meta: CollectionMeta\n): Promise<{ rows: Choice[]; hasMore: boolean } | null> {\n const search = query ? `&search=${encodeURIComponent(query)}` : \"\";\n try {\n // `select` as well as `depth=0`. Without one the server sends every scalar\n // and JSON field of up to fifty documents per collection — for\n // page-builder documents that is the whole block tree — to fill a dropdown\n // that reads a handful of them.\n const response = await fetch(\n `/admin/api/collections/${collection}/entries` +\n `?limit=${PAGE_SIZE}&page=${page}&status=all&depth=0` +\n `&select=${selectParam(selectFieldsFor(meta.titleField))}${search}`,\n { credentials: \"include\" }\n );\n if (!response.ok) return null;\n const body = (await response.json()) as {\n items?: Record<string, unknown>[];\n meta?: { totalPages?: number };\n };\n return {\n rows: (body.items ?? [])\n .filter(row => typeof row.id === \"string\")\n .map(row => ({\n collection,\n id: row.id as string,\n label: documentLabel(row, labelFieldsFor(meta.titleField)),\n reachability: documentReachability(\n row as { id: string },\n meta.localized\n ),\n })),\n // Reported rather than assumed, so the control can offer the rest\n // instead of truncating where the author cannot see it.\n hasMore: page < (body.meta?.totalPages ?? 1),\n };\n } catch {\n return null;\n }\n}\n\n/**\n * Existing choices plus new ones, de-duplicated on the pair that identifies a\n * document. Order is preserved so a list does not reshuffle under the author.\n */\nfunction mergeChoices(\n existing: readonly Choice[],\n incoming: readonly Choice[]\n): Choice[] {\n const seen = new Set(existing.map(c => `${c.collection}:${c.id}`));\n const merged = [...existing];\n for (const choice of incoming) {\n const key = `${choice.collection}:${choice.id}`;\n if (seen.has(key)) continue;\n seen.add(key);\n merged.push(choice);\n }\n return merged;\n}\n\n/** One option, keyed and valued by the pair the caller stores. */\nfunction renderChoice(choice: Choice) {\n return (\n <SelectItem\n key={`${choice.collection}:${choice.id}`}\n value={`${choice.collection}:${choice.id}`}\n >\n <span className=\"inline-flex items-center gap-2\">\n <span>{choice.label}</span>\n {/* Unpublished pages are offered deliberately — a form is usually\n configured beside the page it points at — so the marker is what\n keeps \"offered\" from reading as \"live\". A published form saved\n against one is refused, and an author who cannot see which pages\n are drafts has no way to predict that. */}\n {choice.reachability === \"unreachable\" && (\n <span className=\"shrink-0 rounded-sm border px-1 py-px text-[10px] uppercase tracking-wide text-muted-foreground\">\n Draft\n </span>\n )}\n </span>\n </SelectItem>\n );\n}\n\n/**\n * Which collections the picker needs metadata for, as the key the lookup is\n * cached on.\n *\n * The configured ones, plus the collection the STORED target names when that\n * is no longer among them. A polymorphic value can point at a collection since\n * removed from `redirectRelationships`, and the picker recovers and displays\n * that target on purpose — looking up only the configured ones would label it\n * by the conventional names while the listing uses each collection's own title\n * field, showing one document under two rules.\n */\nexport function metadataCollections(\n collections: readonly string[],\n value: unknown\n): string {\n const stored = selectionKey(value, collections)?.split(\":\")[0];\n const wanted =\n stored && !collections.includes(stored)\n ? [...collections, stored]\n : collections;\n return wanted.join(\",\");\n}\n\n/**\n * The field a collection says names its documents, or undefined when it does\n * not say or cannot be asked.\n *\n * Undefined covers both \"this collection declares no title field\" and \"the\n * metadata could not be read\", because the control does the same thing in\n * either case: fall back to the conventional names. Nothing on screen depends\n * on telling them apart, and the listing itself still reports its own\n * failures.\n */\ninterface CollectionMeta {\n /** The field the collection says names its documents. */\n titleField: string | undefined;\n /**\n * The collection's `localized` setting, or undefined when it could not be\n * read. Undefined is NOT false: a localized collection read as plain marks\n * reachable translations as drafts.\n */\n localized: boolean | undefined;\n}\n\nasync function fetchCollectionMeta(\n collection: string\n): Promise<CollectionMeta> {\n try {\n // The bare document, not a `{ message, item }` envelope — that shape is\n // for mutations, and this is a read.\n const response = await fetch(`/admin/api/collections/${collection}`, {\n credentials: \"include\",\n });\n if (!response.ok) return { titleField: undefined, localized: undefined };\n const body = (await response.json()) as {\n admin?: { useAsTitle?: unknown };\n localized?: unknown;\n };\n const configured = body.admin?.useAsTitle;\n return {\n titleField:\n typeof configured === \"string\" && configured.trim()\n ? configured.trim()\n : undefined,\n localized:\n typeof body.localized === \"boolean\" ? body.localized : undefined,\n };\n } catch {\n return { titleField: undefined, localized: undefined };\n }\n}\n\n/**\n * The fields to read and to label from, per configured collection.\n *\n * Resolved once per collection list rather than per search: what a collection\n * calls its title does not change while an author types, and folding this into\n * the listing effect would spend a request per keystroke to re-learn a\n * constant.\n *\n * `null` means not resolved yet. The listing waits for it, because a first\n * page fetched under the fallback fields would have to be refetched or else\n * left labelled differently from every page after it.\n */\nfunction useTitleFields(key: string) {\n const [fields, setFields] = useState<Record<string, CollectionMeta> | null>(\n null\n );\n\n useEffect(() => {\n let cancelled = false;\n const wanted = key ? key.split(\",\") : [];\n setFields(null);\n\n void Promise.all(\n wanted.map(async collection => {\n return [collection, await fetchCollectionMeta(collection)] as const;\n })\n ).then(pairs => {\n if (!cancelled) setFields(Object.fromEntries(pairs));\n });\n\n return () => {\n cancelled = true;\n };\n }, [key]);\n\n return fields;\n}\n\n/**\n * The matching documents for every configured collection, and which of them\n * could not be read.\n */\nfunction useChoices(\n key: string,\n applied: string,\n titleFields: Record<string, CollectionMeta> | null\n) {\n const [choices, setChoices] = useState<Choice[] | null>(null);\n const [failed, setFailed] = useState<readonly string[]>([]);\n const [hasMore, setHasMore] = useState(false);\n const [loading, setLoading] = useState(false);\n // Per collection, not shared. A shared counter advances every collection\n // when any one of them has more, so a collection whose page N failed is\n // never asked for page N again — and if its N+1 succeeds the warning clears\n // too, leaving those documents absent with nothing on screen saying so.\n const [pages, setPages] = useState<Record<string, number>>({});\n\n // A new query is a new list, not more of the old one.\n //\n // The SAME reference is returned when there is nothing to clear. Writing a\n // fresh `{}` every time is a state change even when the map is already\n // empty, and the fetch effect below depends on `pages` — so each query\n // change ran the fetch once with the old map, cancelled it, and ran it\n // again. Every collection was requested twice per search and the first\n // answer discarded.\n useEffect(() => {\n setPages(current => (Object.keys(current).length === 0 ? current : {}));\n }, [key, applied]);\n\n useEffect(() => {\n // Nothing to request until each collection's title field is known —\n // see `useTitleFields`. `choices` stays null, which the control already\n // renders as loading.\n if (titleFields === null) return;\n\n let cancelled = false;\n const wanted = key ? key.split(\",\") : [];\n setLoading(true);\n\n void Promise.all(\n wanted.map(collection =>\n fetchChoices(\n collection,\n applied,\n pages[collection] ?? 1,\n titleFields[collection] ?? {\n titleField: undefined,\n localized: undefined,\n }\n )\n )\n ).then(results => {\n if (cancelled) return;\n setLoading(false);\n\n // WHICH collections failed, not merely whether any did: one unreadable\n // collection beside a readable-but-empty one otherwise reports \"nothing\n // here\" about a collection nobody could see.\n setFailed(wanted.filter((_, index) => results[index] === null));\n\n const loaded = results.filter(\n (result): result is { rows: Choice[]; hasMore: boolean } =>\n result !== null\n );\n setHasMore(loaded.some(result => result.hasMore));\n\n const rows = loaded.flatMap(result => result.rows);\n const first = wanted.every(collection => (pages[collection] ?? 1) === 1);\n // Merged by identity rather than appended. A retry re-requests a page\n // that already succeeded for its siblings, and appending would list\n // those documents twice — duplicate options, and duplicate React keys.\n // Keyed, a repeated page is a no-op whatever caused the repeat.\n setChoices(current => mergeChoices(first ? [] : (current ?? []), rows));\n });\n\n return () => {\n cancelled = true;\n };\n }, [key, applied, pages, titleFields]);\n\n return {\n choices,\n setChoices,\n failed,\n hasMore,\n loading,\n /**\n * Advances only the collections that have more to give, and RETRIES the\n * ones whose last page failed rather than stepping over it.\n *\n * Guarded on the in-flight flag: two clicks before the first resolves\n * either batch into a double increment or cancel the pending effect, and a\n * skipped page is then absent from a list that reports having loaded it.\n */\n loadMore: () => {\n if (loading) return;\n setPages(current => {\n const next: Record<string, number> = { ...current };\n for (const collection of key ? key.split(\",\") : []) {\n // A failed collection stays on its page so the retry asks for the\n // page it never got.\n if (failed.includes(collection)) continue;\n next[collection] = (current[collection] ?? 1) + 1;\n }\n return next;\n });\n },\n /** Ask the failed collections again, without advancing anyone. */\n retryFailed: () => {\n if (!loading) setPages(current => ({ ...current }));\n },\n };\n}\n\n/**\n * Adds the selected document when the listing did not reach it.\n *\n * Keyed on the selection rather than on the collection list, because the\n * stored value arrives with the form and often AFTER this mounts — a hook\n * watching only the collections would capture the moment when nothing was\n * selected and never look again.\n *\n * A control that silently drops its own value is worse than one that cannot\n * list everything: the author sees blank, re-picks, and saves a different\n * destination than the one that was stored.\n */\nfunction useSelectedChoice(\n selected: string | undefined,\n choices: Choice[] | null,\n setChoices: (update: (current: Choice[] | null) => Choice[]) => void,\n titleFields: Record<string, CollectionMeta> | null\n) {\n // Whether the stored selection could not be read. A controlled `Select`\n // holding a value with no matching option renders BLANK, which looks\n // identical to \"nothing chosen\" — so an author with a perfectly good\n // destination is invited to pick over it. Reported rather than swallowed.\n const [unreadable, setUnreadable] = useState(false);\n\n useEffect(() => {\n if (!selected || choices === null) return;\n const separator = selected.indexOf(\":\");\n const collection = selected.slice(0, separator);\n const id = selected.slice(separator + 1);\n // Collection AND id: two configured collections can hold the same id, and\n // matching on id alone skips the fetch while the option that is actually\n // selected is absent.\n if (\n !id ||\n choices.some(\n choice => choice.id === id && choice.collection === collection\n )\n ) {\n setUnreadable(false);\n return;\n }\n\n let cancelled = false;\n void (async () => {\n try {\n // `status=all` for the same reason the listing carries it: a stored\n // target may be a draft, and a published-only read answers 404.\n const response = await fetch(\n `/admin/api/collections/${collection}/entries/${id}?depth=0&status=all`,\n { credentials: \"include\" }\n );\n if (!response.ok) {\n if (!cancelled) setUnreadable(true);\n return;\n }\n // A by-id read answers with the document ITSELF. Only mutations carry\n // the `{ message, item }` envelope.\n const row = (await response.json()) as Record<string, unknown>;\n if (cancelled) return;\n if (!row || typeof row.id !== \"string\") {\n setUnreadable(true);\n return;\n }\n setUnreadable(false);\n setChoices(current => [\n {\n collection,\n id: row.id as string,\n // The same fields the listing labels by. Labelling the recovered\n // document differently would show one document under two names\n // depending on whether the listing happened to reach it.\n label: documentLabel(\n row,\n labelFieldsFor(titleFields?.[collection]?.titleField)\n ),\n reachability: documentReachability(\n row as { id: string },\n titleFields?.[collection]?.localized\n ),\n },\n ...(current ?? []),\n ]);\n } catch {\n if (!cancelled) setUnreadable(true);\n }\n })();\n\n return () => {\n cancelled = true;\n };\n }, [selected, choices, setChoices, titleFields]);\n\n return unreadable;\n}\n\n/**\n * The options, grouped by the collection each one came from.\n *\n * The groups are derived from the CHOICES, not from the configured list. A\n * stored target whose collection was removed since the form was saved is\n * recovered by id and belongs in the list — filtering by configuration\n * dropped it, leaving the control blank over a value that is still stored,\n * with the unreadable warning cleared because the recovery had succeeded.\n *\n * That divergence was visible in the code before it was visible on screen: the\n * single-collection branch rendered every choice and the grouped branch\n * filtered them, so the same state produced different pickers depending on how\n * many collections a site happened to configure. One path now.\n */\nexport interface ChoiceGroup {\n collection: string;\n label: string;\n /** True when this collection is no longer a configured redirect target. */\n removed: boolean;\n choices: Choice[];\n}\n\n/**\n * The choices arranged into groups, in configuration order, with collections\n * that are no longer configured last and marked.\n *\n * Derived from the CHOICES rather than from the configured list, which is the\n * whole point: a stored target whose collection was removed is recovered by id\n * and belongs in the list.\n */\nexport function groupChoices(\n choices: readonly Choice[],\n collections: readonly string[]\n): ChoiceGroup[] {\n const present = [...new Set(choices.map(choice => choice.collection))];\n const order = [\n ...collections.filter(name => present.includes(name)),\n ...present.filter(name => !collections.includes(name)),\n ];\n\n return order.map(collection => {\n const removed = !collections.includes(collection);\n return {\n collection,\n // Named as unavailable rather than hidden: the value is still stored and\n // the server refuses a save into a collection with no pattern, so an\n // author needs to see what is set in order to change it.\n label: removed ? `${collection} — no longer configured` : collection,\n removed,\n choices: choices.filter(choice => choice.collection === collection),\n };\n });\n}\n\nfunction Options({\n choices,\n collections,\n}: {\n choices: Choice[];\n collections: readonly string[];\n}) {\n const groups = groupChoices(choices, collections);\n if (groups.length <= 1 && !groups[0]?.removed) {\n return <>{choices.map(renderChoice)}</>;\n }\n return (\n <>\n {groups.map(group => (\n <SelectGroup key={group.collection}>\n <SelectLabel>{group.label}</SelectLabel>\n {group.choices.map(renderChoice)}\n </SelectGroup>\n ))}\n </>\n );\n}\n\n/**\n * What to say when there is nothing to choose, and why.\n *\n * `collections` here is the READABLE ones. Passing the full configured list\n * asserted that a collection which could not be read is empty — printed\n * directly beneath the notice saying it could not be read.\n */\nfunction Empty({\n applied,\n collections,\n}: {\n applied: string;\n collections: readonly string[];\n}) {\n return (\n <p className=\"text-[12px] text-muted-foreground\">\n {applied\n ? `No documents match “${applied}”.`\n : collections.length === 0\n ? \"No collection is configured as a redirect target.\"\n : `No documents to redirect to yet. Create one in ${collections.join(\" or \")} first.`}\n </p>\n );\n}\n\n/**\n * The things this control has to say that its options cannot.\n *\n * Three separate states, kept apart deliberately. A collection that could not\n * be read, a saved page that cannot be IDENTIFIED because no collection is\n * configured to look it up in, and a saved page whose lookup FAILED are three\n * different situations, and each one previously appeared as an empty control —\n * which reads as \"nothing is set\" and invites an author to overwrite a\n * destination they were never shown.\n */\nfunction PickerNotices({\n failed,\n loading,\n onRetry,\n unidentifiable,\n unreadableSelection,\n}: {\n failed: readonly string[];\n loading: boolean;\n onRetry: () => void;\n unidentifiable: boolean;\n unreadableSelection: boolean;\n}) {\n return (\n <>\n {failed.length > 0 && (\n <p className=\"text-[12px] text-destructive\">\n Could not read {failed.join(\" or \")}. You may not have permission to\n list {failed.length > 1 ? \"them\" : \"it\"}, or the request failed.{\" \"}\n <button\n type=\"button\"\n onClick={onRetry}\n disabled={loading}\n className=\"underline underline-offset-2 disabled:no-underline\"\n >\n Try again\n </button>\n </p>\n )}\n\n {unidentifiable && (\n <p className=\"text-[12px] text-muted-foreground\">\n A page is saved on this form, but it cannot be identified without a\n configured redirect collection. Choosing a different confirmation\n replaces it.\n </p>\n )}\n\n {unreadableSelection && (\n <p className=\"text-[12px] text-destructive\">\n This form has a page selected that could not be read. Leave it as it\n is unless you mean to change it — saving a new choice replaces the\n stored one.\n </p>\n )}\n </>\n );\n}\n\nexport function RedirectPagePicker({\n collections,\n value,\n onChange,\n}: RedirectPagePickerProps): JSX.Element {\n const [query, setQuery] = useState(\"\");\n const [applied, setApplied] = useState(\"\");\n\n // Typing should not issue a request per keystroke.\n useEffect(() => {\n const timer = setTimeout(() => setApplied(query), 250);\n return () => clearTimeout(timer);\n }, [query]);\n\n // `collections` is an array prop, so a new identity every render would\n // refetch forever; the join is the value that actually decides the request.\n const titleFields = useTitleFields(metadataCollections(collections, value));\n const {\n choices,\n setChoices,\n failed,\n hasMore,\n loading,\n loadMore,\n retryFailed,\n } = useChoices(collections.join(\",\"), applied, titleFields);\n const selected = selectionKey(value, collections);\n // A value is stored and this control cannot say WHICH document it is: a bare\n // id names no collection, and with none configured there is nowhere to look\n // it up. Distinct from an unreadable selection, which is a failed request\n // rather than missing information.\n const unidentifiable = value != null && selected === undefined;\n const selectionUnreadable = useSelectedChoice(\n selected,\n choices,\n setChoices,\n titleFields\n );\n\n if (choices === null) {\n return <p className=\"text-[12px] text-muted-foreground\">Loading pages…</p>;\n }\n\n return (\n <div className=\"space-y-2\">\n {/* Search rather than paging: a dropdown cannot usefully list a large\n collection, and a ceiling on how many pages to walk would leave the\n documents past it unreachable however high it were set. */}\n {/* Nothing to search when no collection is listed — the only entry in\n the control then is the stored target, recovered by id. */}\n {collections.length > 0 && (\n <Input\n aria-label=\"Search pages\"\n value={query}\n onChange={event => setQuery(event.target.value)}\n placeholder=\"Search…\"\n />\n )}\n\n {/* Reported BESIDE the choices rather than instead of them: a collection\n the author cannot read is a different problem from one that is empty,\n and the collections that did load are still choosable. */}\n <PickerNotices\n failed={failed}\n loading={loading}\n onRetry={retryFailed}\n unidentifiable={unidentifiable}\n unreadableSelection={selectionUnreadable}\n />\n\n {choices.length === 0 ? (\n failed.length < collections.length && (\n <Empty\n applied={applied}\n collections={collections.filter(name => !failed.includes(name))}\n />\n )\n ) : (\n <Select\n value={selected ?? \"\"}\n onValueChange={next => {\n const separator = next.indexOf(\":\");\n if (separator < 0) return onChange(undefined);\n onChange({\n relationTo: next.slice(0, separator),\n value: next.slice(separator + 1),\n });\n }}\n >\n <SelectTrigger aria-label=\"Redirect page\" className=\"w-full\">\n <SelectValue placeholder=\"Choose a page\" />\n </SelectTrigger>\n <SelectContent>\n <Options choices={choices} collections={collections} />\n </SelectContent>\n </Select>\n )}\n\n {/* The rest of the matches, reachable rather than silently absent. A cap\n with no way past it makes a document beyond it unselectable however\n high the cap is set. */}\n {hasMore && (\n <button\n type=\"button\"\n onClick={loadMore}\n disabled={loading}\n className=\"text-[12px] text-muted-foreground underline underline-offset-2 hover:text-foreground disabled:no-underline disabled:opacity-60\"\n >\n {loading ? \"Loading…\" : \"Load more\"}\n </button>\n )}\n </div>\n );\n}\n","\"use client\";\n\n/**\n * A form's Settings after its Form section: what happens after someone\n * submits, whether it is taking submissions, how many responses one person\n * may send, and its spam protection.\n *\n * Every control here is consumed somewhere (the submit handler or the\n * confirmation flow): a setting that does nothing does not get a control.\n * Spam controls are per-form OVERRIDES of the plugin's options: \"Site\n * default\" follows them, and the value it follows is shown.\n *\n * @module admin/components/builder/FormSettingsTab\n */\n\nimport {\n FormSection,\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport type React from \"react\";\n\nimport { DEFAULT_FORM_SETTINGS } from \"../../../config/defaults\";\nimport type {\n AnyFormField,\n FormSettings,\n OneResponseMode,\n} from \"../../../types\";\nimport { emailFieldsToCompare } from \"../../../utils/one-response-field\";\nimport { parseRedirectReference } from \"../../../utils/redirect-reference\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport {\n type BotCheckSiteDefaults,\n BotCheckNotices,\n BotCheckRow,\n RateLimitRow,\n} from \"./BotCheckRows\";\nimport { ClosingTimeRow, SubmissionLimitRow } from \"./ClosingRows\";\nimport { RedirectPagePicker } from \"./RedirectPagePicker\";\nimport {\n type Choice,\n ChoiceCards,\n descriptionIdOf,\n SettingRow,\n} from \"./settings-rows\";\n\n/**\n * Tri-state override select: inherit from the plugin config (showing the\n * effective value), or force on/off for this form.\n */\nfunction InheritToggle({\n id,\n value,\n inherited,\n onChange,\n}: {\n /** The trigger's id; its row's description is read with it. */\n id: string;\n value: boolean | undefined;\n inherited: boolean | undefined;\n onChange: (value: boolean | undefined) => void;\n}) {\n const inheritedLabel =\n inherited === undefined\n ? \"Site default\"\n : `Site default (${inherited ? \"on\" : \"off\"})`;\n return (\n <Select\n value={value === undefined ? \"inherit\" : value ? \"on\" : \"off\"}\n onValueChange={selected =>\n onChange(selected === \"inherit\" ? undefined : selected === \"on\")\n }\n >\n <SelectTrigger\n id={id}\n aria-describedby={descriptionIdOf(id)}\n className=\"w-48 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n <SelectItem value=\"inherit\">{inheritedLabel}</SelectItem>\n <SelectItem value=\"on\">On</SelectItem>\n <SelectItem value=\"off\">Off</SelectItem>\n </SelectContent>\n </Select>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport interface SpamDefaults extends BotCheckSiteDefaults {\n honeypot?: boolean;\n recaptchaEnabled?: boolean;\n}\n\ninterface FormSettingsTabProps {\n /** Plugin-level spam defaults (from builder-config); null while loading. */\n spamDefaults: SpamDefaults | null;\n /**\n * Collections a form may redirect to (from builder-config); null while\n * loading. Empty means the site configured none, and the option is not\n * offered — choosing it could only ever produce a form with no destination.\n */\n redirectCollections: string[] | null;\n /**\n * The configuration request failed, as distinct from returning nothing. An\n * empty list is a valid answer; a failure is not an answer at all, and\n * saying \"no collection is configured\" on a 403 states something false.\n */\n redirectConfigFailed: boolean;\n}\n\n/**\n * The \"Redirect to a page\" choice and its picker.\n *\n * Shown when the site configures a redirect collection, AND whenever this form\n * ALREADY redirects to a page. Hiding a stored confirmation leaves the radio\n * group holding a value none of its items carry, so nothing appears selected —\n * while that stored value is still posted on the next save. The author would\n * be looking at a form that says one thing and saves another.\n *\n * Configuration can be removed after a form was saved, and the builder-config\n * request can fail; both arrive here as an empty list, which is why the stored\n * value rather than the configuration decides whether this renders.\n */\n/**\n * What the \"Redirect to a page\" option should show.\n *\n * Four outcomes, named because they were four nested conditions and the\n * difference between two of them is not visual — an empty configuration is an\n * ANSWER, a failed configuration read is not, and stating the first when the\n * second happened tells an author something false about their site.\n */\n/**\n * Whether these settings actually NAME a page — not whether the page-redirect\n * option happens to be selected.\n *\n * Conflating the two let an author choose the option while the configuration\n * was unknown, which classified the state as \"a page is stored\" with nothing\n * stored: a picker with no collections to offer, and a save that could only be\n * refused. Selecting a confirmation is not a destination.\n *\n * A BARE id counts. `parseRedirectReference` resolves one only when exactly\n * one collection is configured, which is the right rule for \"which document is\n * this\" — but the question here is \"is anything stored at all\", and the answer\n * must not depend on configuration that may have failed to load or been\n * removed. Those are precisely the states where an author needs to be shown\n * what is already saved.\n */\nexport function hasStoredRedirectPage(settings: {\n redirectPage?: unknown;\n}): boolean {\n const value = settings.redirectPage;\n if (typeof value === \"string\") return value.trim().length > 0;\n return parseRedirectReference(value, []) !== null;\n}\n\nexport type RedirectOptionState =\n /** Not offered: nothing stored, nothing configured, nothing unknown. */\n | \"hidden\"\n /** Offered but not selectable: the choices could not be loaded. */\n | \"unknown\"\n /** Offered because a page IS stored, though none can be chosen now. */\n | \"stored-only\"\n /** Offered with choices. */\n | \"ready\";\n\nexport function redirectOptionState(input: {\n /** A page is actually stored — not merely that this option is selected. */\n hasStoredPage: boolean;\n collections: string[] | null;\n configFailed: boolean;\n}): RedirectOptionState {\n const configured = input.collections !== null && input.collections.length > 0;\n if (configured) return \"ready\";\n // A STORED page outranks a failed request: it is a fact about this form,\n // and the author needs to see it to change it.\n if (input.hasStoredPage) return \"stored-only\";\n return input.configFailed ? \"unknown\" : \"hidden\";\n}\n\ntype ConfirmationType = NonNullable<FormSettings[\"confirmationType\"]>;\n\n/**\n * The \"Go to a page on this site\" choice, or none when it is not offered.\n *\n * Selectable only when there are collections to pick from. In every other\n * state choosing it leads to a save the server must refuse: the rule requires\n * a page, and a page can only come from a configured collection.\n * `stored-only` shows what is already saved; it is not an invitation to\n * switch a different form into that state.\n */\nfunction pageChoice({\n redirectCollections,\n configFailed,\n settings,\n updateSettings,\n}: {\n redirectCollections: string[] | null;\n configFailed: boolean;\n settings: FormSettings;\n updateSettings: (patch: Partial<FormSettings>) => void;\n}): Choice<ConfirmationType> | null {\n const state = redirectOptionState({\n hasStoredPage: hasStoredRedirectPage(settings),\n collections: redirectCollections,\n configFailed,\n });\n if (state === \"hidden\") return null;\n return {\n value: \"relationship\",\n title: \"Go to a page on this site\",\n description: \"Picks a page; the link follows if the page moves.\",\n disabled: state !== \"ready\",\n note:\n state === \"ready\" ? undefined : (\n <p className=\"text-xs text-muted-foreground\">\n {configFailed\n ? \"The redirect configuration could not be loaded, so the pages you can choose from are not listed. Reload to try again.\"\n : \"No collection is configured as a redirect target right now. The page below is still saved, and stays until you choose a different confirmation.\"}\n </p>\n ),\n // Mounted whenever a page is stored, including with NO configured\n // collections: the picker is what recovers the stored target by id and\n // labels it, so leaving it out there tells an author a page is saved\n // without telling them which.\n details: (\n <RedirectPagePicker\n collections={redirectCollections ?? []}\n value={settings.redirectPage}\n onChange={next => updateSettings({ redirectPage: next })}\n />\n ),\n };\n}\n\nfunction AfterSubmitSection({\n redirectCollections,\n redirectConfigFailed,\n}: Pick<\n FormSettingsTabProps,\n \"redirectCollections\" | \"redirectConfigFailed\"\n>): React.JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n const page = pageChoice({\n redirectCollections,\n configFailed: redirectConfigFailed,\n settings,\n updateSettings,\n });\n const choices: Choice<ConfirmationType>[] = [\n {\n value: \"message\",\n title: \"Show a message\",\n description: \"Replaces the form on the page.\",\n details: (\n <Textarea\n aria-label=\"Message after submitting\"\n value={settings.successMessage ?? \"\"}\n onChange={event =>\n updateSettings({ successMessage: event.target.value })\n }\n rows={3}\n placeholder=\"Thank you for your submission!\"\n />\n ),\n },\n ...(page ? [page] : []),\n {\n value: \"redirect\",\n title: \"Go to a web address\",\n details: (\n <Input\n aria-label=\"Web address\"\n type=\"url\"\n className=\"font-mono\"\n value={settings.redirectUrl ?? \"\"}\n onChange={event =>\n updateSettings({ redirectUrl: event.target.value || undefined })\n }\n placeholder=\"https://example.com/thanks\"\n />\n ),\n },\n ];\n return (\n <FormSection label=\"After someone submits\">\n <SettingRow label=\"Button text\" htmlFor=\"settings-submit-text\">\n <Input\n id=\"settings-submit-text\"\n type=\"text\"\n className=\"w-full sm:w-72\"\n value={settings.submitButtonText ?? \"\"}\n onChange={event =>\n updateSettings({ submitButtonText: event.target.value })\n }\n />\n </SettingRow>\n <SettingRow label=\"Then\" stack>\n <ChoiceCards\n name=\"settings-confirm\"\n label=\"What happens after someone submits\"\n value={settings.confirmationType ?? \"message\"}\n onChange={confirmationType => updateSettings({ confirmationType })}\n choices={choices}\n />\n </SettingRow>\n </FormSection>\n );\n}\n\n/** What \"Accepting submissions\" says, by the form's status as edited. */\nconst ACCEPTING_DESCRIPTION = {\n draft:\n \"This form is a draft. Make it live from the status in the bar to start taking submissions.\",\n published: \"Turn off to close the form now. Visitors see the closed message.\",\n closed:\n \"The form is closed: visitors see the closed message. Turn on to open it.\",\n} as const satisfies Record<\"draft\" | \"published\" | \"closed\", string>;\n\nfunction AvailabilitySection(): React.JSX.Element {\n const { formData, updateFormData } = useFormBuilder();\n const status = formData.status;\n return (\n <FormSection label=\"Availability\">\n <SettingRow\n label=\"Accepting submissions\"\n htmlFor=\"settings-accepting\"\n description={ACCEPTING_DESCRIPTION[status]}\n >\n <Switch\n id=\"settings-accepting\"\n aria-describedby={descriptionIdOf(\"settings-accepting\")}\n checked={status === \"published\"}\n // A draft is opened from the status menu, where Live is chosen on\n // purpose; this switch only closes and reopens a live form.\n disabled={status === \"draft\"}\n onCheckedChange={accepting =>\n updateFormData({ status: accepting ? \"published\" : \"closed\" })\n }\n />\n </SettingRow>\n <ClosingTimeRow />\n <SubmissionLimitRow />\n <SettingRow\n label=\"Message when closed\"\n htmlFor=\"settings-closed-message\"\n description=\"Shown to anyone who reaches this form while it is closed. Left empty, they are told only that it is not taking submissions.\"\n stack\n >\n <Textarea\n id=\"settings-closed-message\"\n aria-describedby={descriptionIdOf(\"settings-closed-message\")}\n value={formData.closedMessage ?? \"\"}\n onChange={event =>\n updateFormData({ closedMessage: event.target.value })\n }\n rows={3}\n placeholder=\"e.g., Applications closed on 31 March. Thank you to everyone who applied.\"\n />\n </SettingRow>\n </FormSection>\n );\n}\n\n/** Every way a form can limit responses, by how Settings names it. */\nconst ONE_RESPONSE_TITLES = {\n off: \"Allow several responses\",\n email: \"One per email address\",\n browser: \"One per browser\",\n ip: \"One per network (IP address)\",\n} as const satisfies Record<OneResponseMode, string>;\n\n/** How each way is explained, beside its title. */\nconst ONE_RESPONSE_DESCRIPTIONS = {\n email: \"Uses one of the form's Email fields. Best for sign-ups.\",\n // Earlier submissions kept no browser token, so they cannot be counted.\n browser:\n \"Counted from when you choose it. Someone who clears their browser's data, or uses another browser, can respond again.\",\n ip: \"People in one office or home count as one person, so only one of them can respond. It needs your site to see each visitor's real address (the trusted proxy setting); until it can, every response is accepted.\",\n} as const;\n\n/**\n * One per email address: the Email field it compares, picked among those it\n * can compare (required, never hidden by a condition). A form with none\n * cannot take it yet, and the note says what to add.\n */\nfunction emailChoice(\n fields: readonly AnyFormField[],\n settings: FormSettings,\n updateSettings: (patch: Partial<FormSettings>) => void\n): Choice<OneResponseMode> {\n const candidates = emailFieldsToCompare(fields);\n const chosen = candidates.find(\n field => field.name === settings.oneResponseField\n );\n return {\n value: \"email\",\n title: ONE_RESPONSE_TITLES.email,\n description: ONE_RESPONSE_DESCRIPTIONS.email,\n // Kept choosable while it is what the form has, so it is never shown\n // chosen and disabled at once; Save then says what to fix.\n disabled: candidates.length === 0 && settings.oneResponse !== \"email\",\n note:\n candidates.length === 0 ? (\n <p className=\"text-xs text-muted-foreground\">\n Add an Email field that is required and not hidden by a condition to\n use this.\n </p>\n ) : undefined,\n details:\n candidates.length === 0 ? undefined : (\n <Select\n value={chosen?.name ?? \"\"}\n onValueChange={oneResponseField =>\n updateSettings({ oneResponseField })\n }\n >\n <SelectTrigger\n aria-label=\"Email field to compare\"\n className=\"w-full sm:w-72 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue placeholder=\"Choose an Email field\" />\n </SelectTrigger>\n <SelectContent>\n {candidates.map(field => (\n <SelectItem key={field.name} value={field.name}>\n {field.label || field.name}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n ),\n };\n}\n\nfunction OneResponseSection(): React.JSX.Element {\n const { fields, settings, updateSettings } = useFormBuilder();\n // Always set once the settings are read; the default covers the type.\n const current = settings.oneResponse ?? DEFAULT_FORM_SETTINGS.oneResponse;\n const choices: Choice<OneResponseMode>[] = [\n { value: \"off\", title: ONE_RESPONSE_TITLES.off },\n emailChoice(fields, settings, updateSettings),\n {\n value: \"browser\",\n title: ONE_RESPONSE_TITLES.browser,\n description: ONE_RESPONSE_DESCRIPTIONS.browser,\n },\n {\n value: \"ip\",\n title: ONE_RESPONSE_TITLES.ip,\n description: ONE_RESPONSE_DESCRIPTIONS.ip,\n },\n ];\n return (\n <FormSection label=\"One response per person\">\n <SettingRow label=\"Who may respond again\" stack>\n <ChoiceCards\n name=\"settings-one-response\"\n label=\"One response per person\"\n value={current}\n onChange={oneResponse =>\n updateSettings({\n oneResponse,\n // One per email starts on the first field it can compare, so\n // the choice is complete when it is made.\n ...(oneResponse === \"email\" &&\n !emailFieldsToCompare(fields).some(\n field => field.name === settings.oneResponseField\n )\n ? {\n oneResponseField: emailFieldsToCompare(fields)[0]?.name,\n }\n : {}),\n })\n }\n choices={choices}\n />\n </SettingRow>\n </FormSection>\n );\n}\n\nfunction SpamSection({\n spamDefaults,\n}: Pick<FormSettingsTabProps, \"spamDefaults\">): React.JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n return (\n <FormSection label=\"Spam protection\">\n <SettingRow\n label=\"Hidden trap field\"\n htmlFor=\"settings-honeypot\"\n description=\"A field people never see; bots fill it in.\"\n >\n <InheritToggle\n id=\"settings-honeypot\"\n value={settings.honeypotEnabled}\n inherited={spamDefaults?.honeypot}\n onChange={honeypotEnabled => updateSettings({ honeypotEnabled })}\n />\n </SettingRow>\n <BotCheckRow defaults={spamDefaults} />\n <BotCheckNotices defaults={spamDefaults} />\n <RateLimitRow defaults={spamDefaults} />\n </FormSection>\n );\n}\n\n/**\n * What a form keeps about who sent a submission. Off, new submissions store\n * neither their IP address nor their browser; one per network still compares\n * the address, by the keyed digest made before it is dropped. And how long it\n * keeps submissions: the nightly pass deletes older ones with the files they\n * hold and their email records.\n */\nfunction PrivacySection(): React.JSX.Element {\n const { settings, updateSettings } = useFormBuilder();\n return (\n <FormSection label=\"Privacy\">\n <SettingRow\n label=\"Store IP address and browser\"\n htmlFor=\"settings-store-ip\"\n description=\"Helps spot spam and shows where a response came from. Off, new submissions keep neither; one per network still works, from a scrambled copy.\"\n >\n <Switch\n id=\"settings-store-ip\"\n aria-describedby={descriptionIdOf(\"settings-store-ip\")}\n checked={settings.storeIpAndBrowser !== false}\n onCheckedChange={storeIpAndBrowser =>\n updateSettings({ storeIpAndBrowser })\n }\n />\n </SettingRow>\n <SettingRow\n label=\"Delete submissions after\"\n htmlFor=\"settings-keep-days\"\n description=\"Older submissions, their files and their email records are deleted each night. Spam goes after 30 days either way.\"\n >\n <Select\n value={keepChoiceOf(settings.keepDays)}\n onValueChange={choice =>\n updateSettings({\n keepDays: choice === \"never\" ? undefined : Number(choice),\n })\n }\n >\n <SelectTrigger\n id=\"settings-keep-days\"\n aria-describedby={descriptionIdOf(\"settings-keep-days\")}\n className=\"w-full sm:w-40 bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {keepChoicesFor(settings.keepDays).map(choice => (\n <SelectItem key={choice.value} value={choice.value}>\n {choice.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n </SettingRow>\n </FormSection>\n );\n}\n\n/** How long a form keeps its submissions: never deleted, or after a while. */\nconst KEEP_CHOICES = [\n { value: \"never\", label: \"Never\" },\n { value: \"30\", label: \"30 days\" },\n { value: \"90\", label: \"90 days\" },\n { value: \"365\", label: \"1 year\" },\n] as const;\n\n/** The choice a stored keep period reads as. */\nfunction keepChoiceOf(keepDays: number | undefined): string {\n return keepDays === undefined ? \"never\" : String(keepDays);\n}\n\n/**\n * The choices offered. A period set some other way (the API, code) is offered\n * as itself, so opening Settings shows it and never changes it.\n */\nfunction keepChoicesFor(\n keepDays: number | undefined\n): ReadonlyArray<{ value: string; label: string }> {\n const current = keepChoiceOf(keepDays);\n return KEEP_CHOICES.some(choice => choice.value === current)\n ? KEEP_CHOICES\n : [...KEEP_CHOICES, { value: current, label: `${current} days` }];\n}\n\nexport function FormSettingsTab({\n spamDefaults,\n redirectCollections,\n redirectConfigFailed,\n}: FormSettingsTabProps): React.JSX.Element {\n return (\n // The page's shell spaces the sections, and the Form section above.\n <>\n <AfterSubmitSection\n redirectCollections={redirectCollections}\n redirectConfigFailed={redirectConfigFailed}\n />\n <AvailabilitySection />\n <OneResponseSection />\n <SpamSection spamDefaults={spamDefaults} />\n <PrivacySection />\n </>\n );\n}\n\nexport default FormSettingsTab;\n","/**\n * What the form's preview shows and checks, worked out the way a visitor\n * meets it: which fields are on the page as the answers stand, and what a\n * submission of those answers would be refused for.\n *\n * The checks are the submission's own (`generateZodSchema` over the answers\n * `transformFormData` shapes, reported by `getValidationIssues`), so the\n * preview cannot come to accept what the site refuses, or the reverse.\n *\n * @module admin/components/builder/preview-checks\n */\nimport { isKnownFormField, type AnyFormField } from \"../../../types\";\nimport { fieldsHiddenByCondition } from \"../../../utils/condition-visibility\";\nimport {\n generateZodSchema,\n getValidationIssues,\n transformFormData,\n} from \"../../../utils/generate-schema\";\n\n/** What a blank answer to a required field is told. */\nconst REQUIRED_MESSAGE = \"This field is required.\";\n\n/** Why a field is not on the visitor's page, when it is not. */\nexport type HiddenBecause = \"hidden-field\" | \"condition\";\n\n/** One field of the preview: on the page, or why not. */\nexport interface PreviewField {\n field: AnyFormField;\n hidden?: HiddenBecause;\n}\n\n/**\n * Every field with whether a visitor would see it now. Visibility is worked\n * out in field order against the answers of fields that are themselves shown,\n * so a hidden field's leftover answer never keeps a later condition true.\n */\nexport function previewFields(\n fields: readonly AnyFormField[],\n values: Record<string, unknown>\n): PreviewField[] {\n const hiddenByCondition = fieldsHiddenByCondition(fields, values);\n return fields.map(field => {\n if (field.type === \"hidden\") return { field, hidden: \"hidden-field\" };\n return hiddenByCondition.has(field.name)\n ? { field, hidden: \"condition\" }\n : { field };\n });\n}\n\n/**\n * Whether the preview can check a field: a file cannot be attached here, and\n * a plugin's field draws a placeholder with no answer to give, so asking for\n * either would refuse every submission with no way to satisfy it.\n */\nfunction checkable(field: AnyFormField): boolean {\n return isKnownFormField(field) && field.type !== \"file\";\n}\n\n/**\n * What submitting `values` would be refused for, by field name: the first\n * message per field, as the site would report it. Only the fields the\n * visitor sees are checked, since only they are on the page to answer.\n */\nexport function previewIssues(\n shown: readonly AnyFormField[],\n values: Record<string, unknown>\n): Map<string, string> {\n const fields = shown.filter(checkable);\n const result = generateZodSchema(fields).safeParse(\n transformFormData(values, fields)\n );\n return new Map(\n getValidationIssues(result, values).map(issue => [\n issue.path,\n // A blank answer is said in words a person reads; the schema's own text\n // for it names types. Any other refusal is the site's own message.\n issue.code === \"REQUIRED\" ? REQUIRED_MESSAGE : issue.message,\n ])\n );\n}\n","\"use client\";\n\n/**\n * Form Preview\n *\n * An interactive simulation of the form: real enabled inputs, conditional\n * logic evaluated live against what you type (the same evaluator the\n * runtime uses), the form's actual settings (button text, confirmation\n * behavior), and a device-width toggle. It is honest about what it is —\n * a faithful simulation inside the admin, not the published form; nothing\n * here submits anywhere.\n *\n * @module admin/components/builder/FormPreview\n */\n\nimport {\n Button,\n Checkbox,\n Input,\n Label,\n RadioGroup,\n RadioGroupItem,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { Eye, Monitor, Paperclip, RotateCcw, Smartphone } from \"lucide-react\";\nimport { type JSX, useMemo, useState } from \"react\";\n\nimport type { AnyFormField, CustomFormField, FormField } from \"../../../types\";\nimport { isKnownFormField } from \"../../../types\";\nimport { useFormBuilder } from \"../../context/FormBuilderContext\";\n\nimport { hasStoredRedirectPage } from \"./FormSettingsTab\";\nimport {\n previewFields,\n previewIssues,\n type HiddenBecause,\n} from \"./preview-checks\";\n\n// ---------------------------------------------------------------------------\n// Field rendering\n// ---------------------------------------------------------------------------\n\nfunction PreviewFieldInput({\n field,\n value,\n onChange,\n}: {\n field: FormField;\n value: unknown;\n onChange: (value: unknown) => void;\n}) {\n const id = `preview-${field.name}`;\n switch (field.type) {\n case \"textarea\":\n return (\n <Textarea\n id={id}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n rows={\"rows\" in field ? (field.rows ?? 4) : 4}\n placeholder={field.placeholder}\n />\n );\n case \"checkbox\":\n case \"consent\":\n // A real checkbox, not a toggle switch — the preview should look like\n // what a frontend form renders for this type.\n return (\n <Checkbox\n id={id}\n checked={Boolean(value)}\n onCheckedChange={checked => onChange(checked === true)}\n />\n );\n case \"select\": {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <Select\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={onChange}\n >\n <SelectTrigger\n id={id}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue placeholder={field.placeholder ?? \"Select…\"} />\n </SelectTrigger>\n <SelectContent>\n {options.map(option => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n }\n case \"radio\": {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <RadioGroup\n value={typeof value === \"string\" ? value : \"\"}\n onValueChange={onChange}\n className=\"flex flex-col gap-2\"\n >\n {options.map(option => (\n <div key={option.value} className=\"flex items-center gap-2\">\n <RadioGroupItem\n value={option.value}\n id={`${id}-${option.value}`}\n />\n <Label htmlFor={`${id}-${option.value}`} className=\"font-normal\">\n {option.label}\n </Label>\n </div>\n ))}\n </RadioGroup>\n );\n }\n case \"number\":\n return (\n <Input\n id={id}\n type=\"number\"\n value={\n typeof value === \"number\" || typeof value === \"string\"\n ? String(value)\n : \"\"\n }\n onChange={e =>\n onChange(e.target.value === \"\" ? \"\" : Number(e.target.value))\n }\n placeholder={field.placeholder}\n />\n );\n case \"date\":\n case \"time\":\n return (\n <Input\n id={id}\n type={field.type}\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n />\n );\n case \"file\":\n // Nothing uploads from a preview; the affordance is shown, disabled.\n return (\n <div className=\"flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n <Paperclip className=\"h-4 w-4\" aria-hidden=\"true\" />\n File upload (disabled in preview)\n </div>\n );\n default:\n return (\n <Input\n id={id}\n type={\n field.type === \"email\"\n ? \"email\"\n : field.type === \"url\"\n ? \"url\"\n : field.type === \"phone\"\n ? \"tel\"\n : \"text\"\n }\n value={typeof value === \"string\" ? value : \"\"}\n onChange={e => onChange(e.target.value)}\n placeholder={field.placeholder}\n />\n );\n }\n}\n\n/**\n * Preview for a plugin-contributed field. The plugin owns the real input, which\n * renders from its own component in the live form; the builder preview shows a\n * labeled placeholder so the field's presence and order stay visible.\n */\nfunction PluginFieldPreview({ field }: { field: CustomFormField }) {\n return (\n <div className=\"space-y-1.5\">\n <Label htmlFor={`preview-${field.name}`}>\n {field.label || field.name}\n {field.required && <span className=\"ml-1 text-destructive\">*</span>}\n </Label>\n <div className=\"flex items-center gap-2 border border-dashed border-border bg-muted/40 px-3 py-2 text-sm text-muted-foreground\">\n Custom field ({field.type}) — renders from its plugin component in the\n live form.\n </div>\n {field.helpText && (\n <p className=\"text-xs text-muted-foreground\">{field.helpText}</p>\n )}\n </div>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Component\n// ---------------------------------------------------------------------------\n\nexport interface FormPreviewProps {\n /** Array of form fields to preview (built-in or plugin-contributed). */\n fields: AnyFormField[];\n /** Form metadata */\n formData?: {\n name?: string;\n slug?: string;\n description?: string;\n };\n}\n\n/**\n * What the visitor is told after a successful submission.\n *\n * Its own component because the fall-through belongs to the MESSAGE branch: a\n * confirmation this does not name is previewed as a thank-you note, which is\n * how the page redirect came to be previewed as something it is not. Each\n * confirmation that is not a message has to be named here.\n */\nfunction Confirmation({\n settings,\n}: {\n settings: {\n confirmationType?: string;\n redirectUrl?: string;\n successMessage?: string;\n redirectPage?: unknown;\n };\n}) {\n const panel = \"border border-border bg-muted/40 p-4 text-sm text-foreground\";\n\n if (settings.confirmationType === \"redirect\") {\n return (\n <p className={panel}>\n The visitor would now be redirected to{\" \"}\n <span className=\"font-mono\">\n {settings.redirectUrl || \"(no redirect URL set)\"}\n </span>\n .\n </p>\n );\n }\n\n if (settings.confirmationType === \"relationship\") {\n // Whether a page is actually named, not merely that this confirmation is\n // chosen. Claiming a redirect with no page saved describes something that\n // cannot happen: the save is refused, and a form that reached this state\n // another way resolves to no redirect at all.\n const named = hasStoredRedirectPage(settings);\n return (\n <p className={panel}>\n {named\n ? \"The visitor would now be redirected to the linked page. Its URL is built when the form is submitted, from the pattern the site configured for that collection.\"\n : \"No page is selected yet, so this form has no destination. Choose one under Settings, or pick a different confirmation.\"}\n </p>\n );\n }\n\n return (\n <p className={panel}>\n {settings.successMessage || \"Thank you for your submission!\"}\n </p>\n );\n}\n\n/** The widths the preview draws the form at. */\nconst DEVICES = [\n { value: \"desktop\", label: \"Desktop\", icon: Monitor },\n { value: \"phone\", label: \"Phone\", icon: Smartphone },\n] as const;\n\ntype Device = (typeof DEVICES)[number][\"value\"];\n\n/** Desktop or Phone, as a pair of pressed buttons rather than tabs. */\nfunction DeviceSwitch({\n device,\n onChange,\n}: {\n device: Device;\n onChange: (device: Device) => void;\n}): JSX.Element {\n return (\n <div\n role=\"group\"\n aria-label=\"Preview width\"\n className=\"inline-flex rounded-md border border-border bg-card p-0.5\"\n >\n {DEVICES.map(each => (\n <button\n key={each.value}\n type=\"button\"\n aria-pressed={device === each.value}\n onClick={() => onChange(each.value)}\n className=\"inline-flex h-7 items-center gap-1.5 rounded-[5px] px-2.5 text-xs font-medium text-muted-foreground transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring aria-pressed:bg-muted aria-pressed:text-foreground\"\n >\n <each.icon className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n {each.label}\n </button>\n ))}\n </div>\n );\n}\n\n/** Why a shown-anyway field is not on the visitor's page. */\nconst HIDDEN_NOTE: Record<HiddenBecause, string> = {\n \"hidden-field\": \"Hidden field: sent with every submission, never shown.\",\n condition: \"Hidden by a condition: shown once its condition is met.\",\n};\n\n/** One field on the preview's page, with what the checks said about it. */\nfunction PreviewFieldRow({\n field,\n hidden,\n value,\n issue,\n onChange,\n}: {\n field: AnyFormField;\n hidden: HiddenBecause | undefined;\n value: unknown;\n issue: string | undefined;\n onChange: (value: unknown) => void;\n}): JSX.Element {\n if (!isKnownFormField(field)) return <PluginFieldPreview field={field} />;\n const label = (\n <Label htmlFor={`preview-${field.name}`}>\n {field.label || field.name}\n {field.required && <span className=\"ml-1 text-destructive\">*</span>}\n </Label>\n );\n return (\n <div\n className={`space-y-1.5 ${hidden === undefined ? \"\" : \"rounded-md border border-dashed border-border p-3 opacity-70\"}`}\n >\n {field.type === \"consent\" ? (\n // The box is labelled by what the visitor agrees to, as a site shows\n // it; the field's own label names the question above it.\n <>\n <p className=\"text-sm font-medium text-foreground\">\n {field.label || field.name}\n </p>\n <div className=\"flex items-start gap-2\">\n <PreviewFieldInput\n field={field}\n value={value}\n onChange={onChange}\n />\n <Label\n htmlFor={`preview-${field.name}`}\n className=\"block font-normal leading-snug\"\n >\n {field.agreementText}\n <span className=\"ml-1 text-destructive\">*</span>\n </Label>\n </div>\n </>\n ) : field.type === \"checkbox\" ? (\n <div className=\"flex items-center gap-2\">\n <PreviewFieldInput field={field} value={value} onChange={onChange} />\n {label}\n </div>\n ) : (\n <>\n {label}\n <PreviewFieldInput field={field} value={value} onChange={onChange} />\n </>\n )}\n {field.helpText && (\n <p className=\"text-xs text-muted-foreground\">{field.helpText}</p>\n )}\n {issue === undefined ? null : (\n <p className=\"text-xs text-destructive\">{issue}</p>\n )}\n {hidden === undefined ? null : (\n <p className=\"text-xs text-muted-foreground\">{HIDDEN_NOTE[hidden]}</p>\n )}\n </div>\n );\n}\n\nexport function FormPreview({\n fields,\n formData,\n}: FormPreviewProps): JSX.Element {\n const { settings } = useFormBuilder();\n const [values, setValues] = useState<Record<string, unknown>>({});\n const [device, setDevice] = useState<Device>(\"desktop\");\n const [showHidden, setShowHidden] = useState(false);\n const [confirmed, setConfirmed] = useState(false);\n const [issues, setIssues] = useState<ReadonlyMap<string, string>>(\n () => new Map()\n );\n\n // The fields as the visitor meets them, with their conditions run against\n // what has been typed (the same evaluator the site runs).\n const all = useMemo(() => previewFields(fields, values), [fields, values]);\n const onPage = all.filter(each => each.hidden === undefined);\n const drawn = showHidden ? all : onPage;\n\n const reset = () => {\n setValues({});\n setConfirmed(false);\n setIssues(new Map());\n };\n\n // The site's own checks over the fields on the page: what it would refuse,\n // the preview refuses, field by field. Nothing is sent anywhere.\n const simulateSubmit = () => {\n const refused = previewIssues(\n onPage.map(each => each.field),\n values\n );\n setIssues(refused);\n if (refused.size === 0) setConfirmed(true);\n };\n\n return (\n <div className=\"flex flex-col gap-3\">\n <div className=\"flex flex-wrap items-center gap-x-4 gap-y-2\">\n <DeviceSwitch device={device} onChange={setDevice} />\n <label className=\"inline-flex items-center gap-2 text-sm text-foreground\">\n <Switch\n checked={showHidden}\n onCheckedChange={setShowHidden}\n aria-label=\"Show hidden fields\"\n />\n Show hidden fields\n </label>\n <span className=\"flex-1\" />\n <Button type=\"button\" variant=\"outline\" size=\"sm\" onClick={reset}>\n <RotateCcw className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Reset\n </Button>\n </div>\n\n {/* The form on a tinted stage, so the sheet inside reads as the\n visitor's page rather than as more of the admin. */}\n <div className=\"flex justify-center rounded-md border border-border bg-muted/30 p-4 sm:p-6\">\n <div\n data-preview-device={device}\n className={`w-full space-y-5 rounded-md border border-border bg-background p-6 ${\n device === \"phone\" ? \"max-w-95\" : \"max-w-2xl\"\n }`}\n >\n <div>\n <h3 className=\"text-lg font-semibold text-foreground\">\n {formData?.name || \"Untitled form\"}\n </h3>\n {formData?.description && (\n <p className=\"mt-1 text-sm text-muted-foreground\">\n {formData.description}\n </p>\n )}\n </div>\n\n {confirmed ? (\n <div className=\"space-y-4\">\n <Confirmation settings={settings} />\n <Button type=\"button\" variant=\"outline\" onClick={reset}>\n Fill again\n </Button>\n </div>\n ) : (\n <>\n {onPage.length === 0 && (\n <p className=\"text-sm text-muted-foreground\">\n No fields to show yet. Add them on Builder.\n </p>\n )}\n {drawn.map(({ field, hidden }) => (\n <PreviewFieldRow\n key={field.name}\n field={field}\n hidden={hidden}\n value={values[field.name]}\n issue={issues.get(field.name)}\n onChange={value =>\n setValues(prev => ({ ...prev, [field.name]: value }))\n }\n />\n ))}\n <Button\n type=\"button\"\n onClick={simulateSubmit}\n disabled={onPage.length === 0}\n >\n {settings.submitButtonText || \"Submit\"}\n </Button>\n </>\n )}\n </div>\n </div>\n\n <p className=\"flex items-center gap-2 text-xs text-muted-foreground\">\n <Eye className=\"h-3.5 w-3.5 shrink-0\" aria-hidden=\"true\" />\n {\n \"Answers here run the form's conditions and checks, but are never saved and send no email.\"\n }\n </p>\n </div>\n );\n}\n\nexport default FormPreview;\n","/**\n * Where each page of the form editor lives, and how a click on a link to one\n * moves there without reloading the admin.\n *\n * @module admin/components/editor/form-editor-address\n */\nimport { navigateTo } from \"@nextlyhq/plugin-sdk/admin\";\nimport type { MouseEvent } from \"react\";\n\nimport { FORM_PAGES, type FormPageId } from \"../../../form-pages\";\n\n/** A saved form's edit address: Builder, and the base of every other page. */\nfunction formEditAddress(collectionSlug: string, entryId: string): string {\n return `/admin/collections/${encodeURIComponent(collectionSlug)}/${encodeURIComponent(entryId)}`;\n}\n\n/** The address of one page of a saved form. */\nexport function formPageAddress(\n collectionSlug: string,\n entryId: string,\n page: FormPageId\n): string {\n const base = formEditAddress(collectionSlug, entryId);\n const declared = FORM_PAGES.find(each => (each.path ?? \"builder\") === page);\n return declared?.path === undefined ? base : `${base}/${declared.path}`;\n}\n\n/**\n * Follow a plain click on a link inside the admin through the admin's own\n * navigation, so the page is not reloaded and the editor keeps what it holds.\n * A click that asks for a new tab or window (a modifier, a middle click) is\n * left to the browser, which opens the link's own address.\n */\nexport function followWithinAdmin(\n event: MouseEvent<HTMLAnchorElement>,\n href: string\n): void {\n if (\n event.defaultPrevented ||\n event.button !== 0 ||\n event.metaKey ||\n event.ctrlKey ||\n event.shiftKey ||\n event.altKey\n ) {\n return;\n }\n event.preventDefault();\n navigateTo(href);\n}\n","\"use client\";\n\n/**\n * The form's status in the editor's bar: a chip that opens a menu of the three\n * states. Picking one is an unsaved change of the form, saved with the rest.\n *\n * @module admin/components/editor/FormStatusMenu\n */\nimport {\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuRadioGroup,\n DropdownMenuRadioItem,\n DropdownMenuTrigger,\n} from \"@nextlyhq/ui\";\nimport { ChevronDown } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\n/** A form's stored status. */\nexport type FormStatus = \"draft\" | \"published\" | \"closed\";\n\n/**\n * Each state as the editor names it, what it means for a visitor, and the\n * colour of its dot. \"Live\" for `published`: the word a person uses for a form\n * that is taking answers. Draft has no dot, as nothing is happening yet.\n */\nconst STATES: readonly {\n value: FormStatus;\n label: string;\n means: string;\n dot: string | null;\n}[] = [\n {\n value: \"published\",\n label: \"Live\",\n means: \"Accepting submissions\",\n dot: \"bg-success-solid\",\n },\n {\n value: \"closed\",\n label: \"Closed\",\n means: \"Shows the closed message\",\n dot: \"bg-muted-foreground\",\n },\n { value: \"draft\", label: \"Draft\", means: \"Not on the site yet\", dot: null },\n];\n\n/** The state a stored value names; an unknown one reads as Draft. */\nfunction stateOf(status: FormStatus): (typeof STATES)[number] {\n return STATES.find(state => state.value === status) ?? STATES[2];\n}\n\n/** A stored value a menu pick names, or `undefined` for anything else. */\nfunction statusOf(value: string): FormStatus | undefined {\n return STATES.find(state => state.value === value)?.value;\n}\n\nexport interface FormStatusMenuProps {\n /** The form's status, as edited. */\n status: FormStatus;\n /** A state was picked. */\n onChange: (status: FormStatus) => void;\n /** Nothing may change: a colleague holds the form, or a save is running. */\n disabled?: boolean;\n}\n\nexport function FormStatusMenu({\n status,\n onChange,\n disabled = false,\n}: FormStatusMenuProps): JSX.Element {\n const current = stateOf(status);\n return (\n // Not modal: a menu in the bar has no reason to hide the page from\n // assistive technology while it is open. Focus still moves into it, and\n // Escape or a click outside closes it.\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger\n // Named by its state, so a screen reader hears \"Status: Live, menu\"\n // rather than a bare word with no hint of what it is.\n aria-label={`Status: ${current.label}`}\n disabled={disabled}\n className=\"inline-flex h-7 shrink-0 items-center gap-1.5 rounded-full border border-border bg-card px-2.5 text-xs font-medium text-foreground transition-colors hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50\"\n >\n {current.dot === null ? null : (\n <span\n aria-hidden=\"true\"\n className={`size-1.5 shrink-0 rounded-full ${current.dot}`}\n />\n )}\n {current.label}\n <ChevronDown\n aria-hidden=\"true\"\n className=\"size-3.5 text-muted-foreground\"\n />\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-60\">\n <DropdownMenuRadioGroup\n value={status}\n onValueChange={value => {\n const next = statusOf(value);\n if (next !== undefined && next !== status) onChange(next);\n }}\n >\n {STATES.map(state => (\n <DropdownMenuRadioItem\n key={state.value}\n value={state.value}\n className=\"items-start py-2\"\n >\n <span className=\"flex flex-col gap-0.5\">\n <span className=\"font-medium\">{state.label}</span>\n <span className=\"text-xs text-muted-foreground\">\n {state.means}\n </span>\n </span>\n </DropdownMenuRadioItem>\n ))}\n </DropdownMenuRadioGroup>\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n","\"use client\";\n\n/**\n * The form editor's controls in the admin's one top bar, after the\n * breadcrumb: the status, the save state with Undo, the submissions count,\n * More, and Save, in the order an entry's bar puts its own.\n *\n * Drawn through `TopBarPlace`, so the controls stay in the editor's tree, with\n * its state, while they show in the bar.\n *\n * @module admin/components/editor/FormEditorBar\n */\nimport { TopBarPlace } from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n} from \"@nextlyhq/ui\";\nimport { Copy, Inbox, MoreHorizontal, Trash2, Undo2 } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\nimport { followWithinAdmin } from \"./form-editor-address\";\nimport { FormStatusMenu, type FormStatus } from \"./FormStatusMenu\";\n\n/** What the save state says: saving, how much is unsaved, or all saved. */\nfunction saveStateText(changeCount: number, saving: boolean): string {\n if (saving) return \"Saving…\";\n if (changeCount === 0) return \"All changes saved\";\n return `${changeCount} unsaved change${changeCount === 1 ? \"\" : \"s\"}`;\n}\n\nexport interface FormEditorBarProps {\n status: FormStatus;\n onStatusChange: (status: FormStatus) => void;\n /** Unsaved changes across every tab. */\n changeCount: number;\n saving: boolean;\n onUndo: () => void;\n /**\n * The form's submissions, and where its Submissions tab is; absent until\n * the form is saved and its count is known.\n */\n submissions?: { count: number; href: string };\n /** The form's ID (its slug), for Copy form ID. */\n formId: string;\n onCopyFormId: () => void;\n /** Delete the form, after asking; absent where it cannot be deleted. */\n onDelete?: () => void;\n onSave: () => void;\n /** Save cannot run now: nothing to save, a save is running, or it is held. */\n saveDisabled: boolean;\n /** Nothing in the form may change: a colleague holds it. */\n readOnly: boolean;\n}\n\n/**\n * The save state, as a live region so the count and \"Saving…\" are heard as\n * they change, and Undo while there is something to take back. The words\n * are hidden on a phone, where the Save button carries the state.\n */\nfunction SaveState({\n changeCount,\n saving,\n canUndo,\n onUndo,\n}: {\n changeCount: number;\n saving: boolean;\n canUndo: boolean;\n onUndo: () => void;\n}): JSX.Element {\n return (\n <>\n <span\n role=\"status\"\n className=\"hidden whitespace-nowrap text-sm text-muted-foreground sm:inline\"\n >\n {saveStateText(changeCount, saving)}\n </span>\n {canUndo ? (\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon-sm\"\n aria-label=\"Undo\"\n title=\"Undo the last change\"\n onClick={onUndo}\n >\n <Undo2 aria-hidden=\"true\" className=\"size-4\" />\n </Button>\n ) : null}\n </>\n );\n}\n\n/** The form's submission count, a link to its Submissions tab. */\nfunction SubmissionsLink({\n count,\n href,\n}: {\n count: number;\n href: string;\n}): JSX.Element {\n return (\n <Button\n asChild\n variant=\"ghost\"\n size=\"sm\"\n className=\"hidden gap-1.5 px-2 md:inline-flex\"\n >\n <a\n href={href}\n onClick={event => followWithinAdmin(event, href)}\n aria-label={`${count} submission${count === 1 ? \"\" : \"s\"}`}\n title=\"Open this form's submissions\"\n >\n <Inbox aria-hidden=\"true\" className=\"size-4\" />\n <span className=\"tabular-nums\">{count}</span>\n </a>\n </Button>\n );\n}\n\n/** More: Copy form ID, and Delete form where it can be deleted. */\nfunction MoreMenu({\n formId,\n onCopyFormId,\n onDelete,\n deleteDisabled,\n}: {\n formId: string;\n onCopyFormId: () => void;\n onDelete: (() => void) | undefined;\n deleteDisabled: boolean;\n}): JSX.Element {\n return (\n // Not modal, as the status menu: see `FormStatusMenu`.\n <DropdownMenu modal={false}>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"icon-sm\" aria-label=\"More\">\n <MoreHorizontal aria-hidden=\"true\" className=\"size-4\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\" className=\"w-52\">\n <DropdownMenuItem onSelect={onCopyFormId} disabled={formId === \"\"}>\n <Copy aria-hidden=\"true\" className=\"size-4\" />\n Copy form ID\n </DropdownMenuItem>\n {onDelete === undefined ? null : (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n onSelect={onDelete}\n disabled={deleteDisabled}\n className=\"text-destructive focus:text-destructive\"\n >\n <Trash2 aria-hidden=\"true\" className=\"size-4\" />\n Delete form…\n </DropdownMenuItem>\n </>\n )}\n </DropdownMenuContent>\n </DropdownMenu>\n );\n}\n\nexport function FormEditorBar({\n status,\n onStatusChange,\n changeCount,\n saving,\n onUndo,\n submissions,\n formId,\n onCopyFormId,\n onDelete,\n onSave,\n saveDisabled,\n readOnly,\n}: FormEditorBarProps): JSX.Element {\n const busy = readOnly || saving;\n return (\n <TopBarPlace name=\"end\">\n <div\n data-slot=\"form-editor-bar\"\n className=\"ml-auto flex min-w-0 items-center gap-2\"\n >\n <FormStatusMenu\n status={status}\n onChange={onStatusChange}\n disabled={busy}\n />\n <SaveState\n changeCount={changeCount}\n saving={saving}\n canUndo={changeCount > 0 && !busy}\n onUndo={onUndo}\n />\n <span\n aria-hidden=\"true\"\n className=\"mx-1 hidden h-5 w-px bg-border sm:block\"\n />\n {submissions === undefined ? null : (\n <SubmissionsLink count={submissions.count} href={submissions.href} />\n )}\n <MoreMenu\n formId={formId}\n onCopyFormId={onCopyFormId}\n onDelete={onDelete}\n deleteDisabled={busy}\n />\n <Button\n type=\"button\"\n size=\"sm\"\n onClick={onSave}\n disabled={saveDisabled}\n >\n {saving ? \"Saving…\" : \"Save\"}\n </Button>\n </div>\n </TopBarPlace>\n );\n}\n","\"use client\";\n\n/**\n * The form editor's page header: the form's name as the title, its field\n * count and API path as the line under it, and a tab for each page of the\n * editor, drawn as links.\n *\n * @module admin/components/editor/FormEditorHeader\n */\nimport { PageHeader, PageTab, PageTabs } from \"@nextlyhq/ui\";\nimport type { JSX, MouseEvent } from \"react\";\n\nimport { FORM_PAGES, formPageId, type FormPageId } from \"../../../form-pages\";\n\n/** One tab: its page, where it leads, and the count it shows, if any. */\nexport interface FormEditorTab {\n page: FormPageId;\n label: string;\n href: string;\n count?: number;\n onClick: (event: MouseEvent<HTMLAnchorElement>) => void;\n}\n\nexport interface FormEditorHeaderProps {\n /** The form's name as edited; a new form with none is \"New form\". */\n name: string;\n fieldCount: number;\n /** The form's ID (its slug), which its API path ends in. */\n formId: string;\n tabs: readonly FormEditorTab[];\n current: FormPageId;\n}\n\n/** How many fields the form has, as the header's line says it. */\nfunction fieldCountText(fieldCount: number): string {\n return `${fieldCount} field${fieldCount === 1 ? \"\" : \"s\"}`;\n}\n\n/** Where a site reads the form, or `undefined` before it has an ID. */\nfunction formApiPath(formId: string): string | undefined {\n return formId === \"\" ? undefined : `/api/forms/${formId}`;\n}\n\n/**\n * The pages a form's tabs show: every page, less Submissions where it is not\n * offered (a new form has none; a role may not read them).\n */\nexport function formEditorPages(withSubmissions: boolean): FormPageId[] {\n return FORM_PAGES.map(formPageId).filter(\n page => withSubmissions || page !== \"submissions\"\n );\n}\n\nexport function FormEditorHeader({\n name,\n fieldCount,\n formId,\n tabs,\n current,\n}: FormEditorHeaderProps): JSX.Element {\n const apiPath = formApiPath(formId);\n return (\n <PageHeader\n title={name.trim() === \"\" ? \"New form\" : name}\n description={\n <>\n {fieldCountText(fieldCount)}\n {apiPath === undefined ? null : (\n <>\n {\" · \"}\n <code className=\"font-mono text-xs\">{apiPath}</code>\n </>\n )}\n </>\n }\n tabs={\n <PageTabs label=\"Form pages\">\n {tabs.map(tab => (\n <PageTab key={tab.page} asChild current={tab.page === current}>\n <a href={tab.href} onClick={tab.onClick}>\n {tab.label}\n {tab.count === undefined ? null : (\n <span className=\"ml-2 inline-flex h-[18px] min-w-[18px] items-center justify-center rounded-sm bg-muted px-1 text-[10px] font-semibold tabular-nums text-muted-foreground\">\n {tab.count}\n </span>\n )}\n </a>\n </PageTab>\n ))}\n </PageTabs>\n }\n />\n );\n}\n","\"use client\";\n\n/**\n * The form editor's pieces that are not drawing: what the person may do from\n * it, the starting notification of a new form, and deleting the form.\n *\n * @module admin/form-editor-hooks\n */\nimport { toast } from \"@nextlyhq/ui\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport { useCallback, useEffect, useRef } from \"react\";\n\nimport type { NotificationDefaults } from \"./components/builder/FormNotificationsTab\";\nimport {\n createNotification,\n type FormNotification,\n} from \"./context/FormBuilderContext\";\nimport { useAdminPermissions } from \"./use-admin-permissions\";\n\n/** What the person may do from the form editor. */\nexport interface EditorAccess {\n /** Whether the permissions are known; until then nothing gated is said. */\n known: boolean;\n canDelete: boolean;\n /**\n * Whether submissions may be read. They carry what visitors sent, and their\n * collection is guarded on its own: reading a form does not allow it.\n */\n canReadSubmissions: boolean;\n}\n\n/**\n * The person's access, as the admin's permissions answer it. An action a\n * permission gates is offered once the answer says it may be, so nothing is\n * offered and then refused.\n */\nexport function useEditorAccess(\n formsSlug: string,\n submissionsSlug: string | undefined\n): EditorAccess {\n const can = useAdminPermissions();\n return {\n known: can !== undefined,\n canDelete: can?.(`delete-${formsSlug}`) === true,\n canReadSubmissions:\n submissionsSlug !== undefined &&\n can?.(`read-${submissionsSlug}`) === true,\n };\n}\n\n/**\n * Seeds a new form with the default admin notification once the host's\n * defaults are known. Once only: deleting the seeded rule is final, so a list\n * the person emptied is never seeded again.\n */\nexport function useSeededNotification(\n isCreating: boolean,\n defaults: NotificationDefaults | null,\n seed: (rules: FormNotification[]) => void\n): void {\n const seeded = useRef(false);\n useEffect(() => {\n if (!isCreating || defaults === null || seeded.current) return;\n seeded.current = true;\n seed([\n {\n ...createNotification(),\n name: \"Admin notification\",\n to: defaults.defaultToEmail ?? \"\",\n },\n ]);\n }, [isCreating, defaults, seed]);\n}\n\n/** What a refused delete tells the person: the server's words, or the status. */\nfunction deleteFailureMessage(body: unknown, status: string): string {\n const error =\n typeof body === \"object\" && body !== null && \"error\" in body\n ? body.error\n : undefined;\n const message =\n typeof error === \"object\" && error !== null && \"message\" in error\n ? error.message\n : undefined;\n return typeof message === \"string\" && message !== \"\"\n ? message\n : `Couldn't delete the form${status === \"\" ? \".\" : `: ${status}`}`;\n}\n\n/** Deletes the form, as the admin's own Delete does, then reports it. */\nexport function useFormDelete(target: {\n collectionSlug: string;\n entryId: string | undefined;\n /** Writes held by a colleague's claim, read when the delete is confirmed. */\n writesHeld: boolean;\n /** Clears the unsaved changes, so leaving is not asked about. */\n markAsSaved: () => void;\n onDelete: (() => void) | undefined;\n}): () => Promise<void> {\n const queryClient = useQueryClient();\n const { collectionSlug, entryId, writesHeld, markAsSaved, onDelete } = target;\n return useCallback(async () => {\n // The commit is where the refusal lives, as for Save: a colleague who\n // took the form while the question was open holds it now, and the claim\n // is advisory on the server, so nothing else would stop the delete.\n if (entryId === undefined || writesHeld) return;\n const response = await fetch(\n `/admin/api/collections/${collectionSlug}/entries/${entryId}`,\n { method: \"DELETE\", credentials: \"include\" }\n ).catch(() => undefined);\n if (response === undefined || !response.ok) {\n const body: unknown = await response?.json().catch(() => ({}));\n toast.error(deleteFailureMessage(body, response?.statusText ?? \"\"));\n return;\n }\n // Nothing is left to save, so leaving is not asked about.\n markAsSaved();\n void queryClient.invalidateQueries({\n queryKey: [\"entries\", \"list\", collectionSlug],\n });\n toast.success(\"Form deleted\");\n onDelete?.();\n }, [entryId, writesHeld, collectionSlug, markAsSaved, queryClient, onDelete]);\n}\n","/**\n * Where a refused save is fixed: the page of the form editor that holds what\n * the refusal names, so the editor can say what to fix and take a person\n * there.\n *\n * @module admin/save-refusal\n */\nimport type { FormPageId } from \"../form-pages\";\n\n/** What the editor says about a refused save, and where it is fixed. */\nexport interface SaveRefusal {\n message: string;\n /** The page holding what to fix, when the refusal names one. */\n page?: FormPageId;\n}\n\n/**\n * The page a stored path lives on: a field's on Builder, a notification's on\n * Notifications, and the form's own values (its name, ID, status, closed\n * message and settings) on Settings. `undefined` for a path none holds.\n */\nfunction pageOfPath(path: string): FormPageId | undefined {\n const head = path.split(/[.[]/, 1)[0];\n if (head === \"fields\") return \"builder\";\n if (head === \"notifications\") return \"notifications\";\n if (\n head === \"settings\" ||\n head === \"name\" ||\n head === \"slug\" ||\n head === \"status\" ||\n head === \"closedMessage\"\n ) {\n return \"settings\";\n }\n return undefined;\n}\n\n/**\n * The page a refusal's body points at: the first of its issues whose path a\n * page holds. A refusal with no such issue (a version conflict, a server\n * failure) points nowhere, and the editor says what happened without a link.\n */\nexport function refusedPage(body: unknown): FormPageId | undefined {\n if (typeof body !== \"object\" || body === null || !(\"error\" in body)) {\n return undefined;\n }\n const error = body.error;\n if (typeof error !== \"object\" || error === null || !(\"data\" in error)) {\n return undefined;\n }\n const data = error.data;\n if (typeof data !== \"object\" || data === null || !(\"errors\" in data)) {\n return undefined;\n }\n const issues = data.errors;\n if (!Array.isArray(issues)) return undefined;\n for (const issue of issues) {\n const path: unknown =\n typeof issue === \"object\" && issue !== null && \"path\" in issue\n ? issue.path\n : undefined;\n const page = typeof path === \"string\" ? pageOfPath(path) : undefined;\n if (page !== undefined) return page;\n }\n return undefined;\n}\n","/**\n * A save refused only because the plugin wrote the form since it was read.\n *\n * Changing how a form tells people apart can leave the plugin to finish\n * giving earlier submissions their keys in the background, and when it does\n * it records so on the form (`settings.oneResponseKeyedVersion`), which moves\n * the form's revision on. An editor still open on the revision it read would\n * then be refused its next save as if someone else had changed the form.\n *\n * So a save refused as out of date reads the stored form and compares only\n * what the save sends, as it stood when the editor read it: when none of it\n * changed but the plugin's own one-response versions, the save is sent again\n * on the stored revision. A save sends only those keys, so sending it again\n * cannot overwrite anyone's change; any other difference keeps the refusal.\n *\n * @module admin/stale-save\n */\n\n/** Settings the plugin writes for itself, never the editor. */\nconst PLUGIN_SETTINGS = [\"oneResponseVersion\", \"oneResponseKeyedVersion\"];\n\n/** JSON with every object's keys in one order, so equal values read equal. */\nfunction stableJson(value: unknown): string {\n return JSON.stringify(value, (_key, inner: unknown) =>\n inner !== null && typeof inner === \"object\" && !Array.isArray(inner)\n ? Object.fromEntries(\n Object.entries(inner as Record<string, unknown>).sort(([a], [b]) =>\n a < b ? -1 : a > b ? 1 : 0\n )\n )\n : inner\n );\n}\n\n/** What of `doc` a save of `keys` writes, without the plugin's own settings. */\nfunction sentPartOf(doc: Record<string, unknown>, keys: string[]): string {\n return stableJson(\n Object.fromEntries(\n keys.map(key => {\n const value = doc[key];\n if (key !== \"settings\" || value === null || typeof value !== \"object\") {\n return [key, value ?? null];\n }\n const settings = { ...(value as Record<string, unknown>) };\n for (const own of PLUGIN_SETTINGS) delete settings[own];\n return [key, settings];\n })\n )\n );\n}\n\n/** The error code a refused save answered with. */\nasync function refusalCode(refused: Response): Promise<unknown> {\n const body: unknown = await refused\n .clone()\n .json()\n .catch(() => undefined);\n return (body as { error?: { code?: unknown } } | undefined)?.error?.code;\n}\n\n/**\n * The stored revision to send a refused save again on, or undefined to keep\n * the refusal: anything but a refusal as out of date, an editor with no copy\n * of what it read, a form that cannot be read, or one whose stored values of\n * what the save sends differ from what the editor read.\n */\nexport async function revisionToRetryOn(input: {\n refused: Response;\n /** The form's own address, read with GET. */\n url: string;\n /** What the save sent. */\n sent: Record<string, unknown>;\n /** The form as the editor read it, or as its last save answered. */\n readAs: Record<string, unknown> | undefined;\n}): Promise<string | undefined> {\n const { refused, readAs } = input;\n if (refused.status !== 409 || readAs === undefined) return undefined;\n if ((await refusalCode(refused)) !== \"STALE_VERSION\") return undefined;\n const response = await fetch(input.url, { credentials: \"include\" });\n if (!response.ok) return undefined;\n // The admin answers a read with the document itself; `{ item }` too.\n const body = (await response.json().catch(() => undefined)) as\n | (Record<string, unknown> & { item?: Record<string, unknown> })\n | undefined;\n const stored = body?.item ?? body;\n if (!stored) return undefined;\n const keys = Object.keys(input.sent);\n if (sentPartOf(stored, keys) !== sentPartOf(readAs, keys)) return undefined;\n return typeof stored._rev === \"string\" ? stored._rev : undefined;\n}\n","\"use client\";\n\n/**\n * Form Builder View\n *\n * The form editor: an Editor page in the admin's frame, with the form's\n * controls in the admin's one top bar, the form's name and its pages as the\n * page header's tabs, and one set of unsaved changes across every page, saved\n * once.\n *\n * @module admin/FormBuilderView\n * @since 0.1.0\n */\n\nimport type {\n DocumentLockGates,\n FieldPickerChoice,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n useFoldedPanel,\n useReportUnsavedWork,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Alert,\n AlertDescription,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n toast,\n} from \"@nextlyhq/ui\";\nimport { useQueryClient } from \"@tanstack/react-query\";\nimport {\n type JSX,\n type MouseEvent,\n type ReactNode,\n useCallback,\n useRef,\n useState,\n} from \"react\";\n\nimport { FORM_PAGES, formPageOf, type FormPageId } from \"../form-pages\";\nimport type { AnyFormField, FormField, FormFieldTypeId } from \"../types\";\nimport { blankConsentFields, CONSENT_TEXT_REQUIRED } from \"../utils/consent\";\n\nimport { FieldCards } from \"./components/builder/FieldCards\";\nimport { FieldInspector } from \"./components/builder/FieldInspector\";\nimport { FormIdentitySection } from \"./components/builder/FormIdentitySection\";\nimport { FormNotificationsTab } from \"./components/builder/FormNotificationsTab\";\nimport { FormPreview } from \"./components/builder/FormPreview\";\nimport { FormSettingsTab } from \"./components/builder/FormSettingsTab\";\nimport { notificationsBlockingSave } from \"./components/builder/notification-addresses\";\nimport {\n followWithinAdmin,\n formPageAddress,\n} from \"./components/editor/form-editor-address\";\nimport { FormEditorBar } from \"./components/editor/FormEditorBar\";\nimport {\n formEditorPages,\n FormEditorHeader,\n type FormEditorTab,\n} from \"./components/editor/FormEditorHeader\";\nimport { useSubmissionCount } from \"./components/editor/useSubmissionCount\";\nimport { SubmissionsView } from \"./components/submissions/SubmissionsView\";\nimport {\n FormBuilderProvider,\n createFieldFromType,\n generateFieldName,\n useFieldBookkeeping,\n type FormNotification,\n} from \"./context/FormBuilderContext\";\nimport {\n useEditorAccess,\n useFormDelete,\n useSeededNotification,\n} from \"./form-editor-hooks\";\nimport { refusedPage, type SaveRefusal } from \"./save-refusal\";\nimport { revisionToRetryOn } from \"./stale-save\";\nimport { useBuilderConfig } from \"./use-builder-config\";\n\n/**\n * The key a form's version travels under, as the admin reads and sends it.\n * Spelled here because the plugin reaches the admin only through the SDK,\n * which does not publish it.\n */\nconst DOCUMENT_VERSION_KEY = \"_rev\";\n\n/**\n * The name the editor reports its unsaved work under, to the admin page that\n * guards leaving it.\n */\nconst UNSAVED_WORK_KEY = \"forms.editor\";\n\n/**\n * What a save sends: an existing form's save names the version the editor's\n * copy started from, where it has one, so a save from an older copy is refused.\n */\nfunction saveBody(\n saveData: Record<string, unknown>,\n isCreating: boolean,\n startedFrom: string | undefined\n): Record<string, unknown> {\n return isCreating || startedFrom === undefined\n ? saveData\n : { ...saveData, [DOCUMENT_VERSION_KEY]: startedFrom };\n}\n\n/** The version a save answered, or the copy's own where it names none. */\nfunction versionAfterSave(\n saved: Record<string, unknown>,\n previous: string | undefined\n): string | undefined {\n const answered = saved[DOCUMENT_VERSION_KEY];\n return typeof answered === \"string\" ? answered : previous;\n}\n\n// ============================================================================\n// Types\n// ============================================================================\n\nexport interface FormBuilderViewProps {\n id?: string;\n entryId?: string;\n collection?: string;\n collectionSlug?: string;\n isCreating?: boolean;\n initialData?: {\n id?: string;\n name?: string;\n slug?: string;\n description?: string;\n status?: \"draft\" | \"published\" | \"closed\";\n /** When the form first went live: what fixes its ID. */\n wentLiveAt?: string | null;\n closedMessage?: string;\n fields?: unknown[];\n settings?: Record<string, unknown>;\n notifications?: unknown[];\n /**\n * The form's version as read, which a save sends back so one made from an\n * older version is refused rather than replacing what someone saved since.\n */\n _rev?: string;\n };\n /**\n * Saves the form in the host's own way, in place of the builder's request.\n * The builder counts the form saved once this returns, or once the promise\n * it may return settles without failing.\n */\n onSave?: (data: unknown) => void | Promise<void>;\n onSuccess?: (entry?: Record<string, unknown>) => void;\n /** Called once the form has been deleted from the editor's More menu. */\n onDelete?: () => void;\n onCancel?: () => void;\n /**\n * What a colleague's claim on this form withholds, handed over by the admin\n * page that renders this view.\n *\n * The strip above this builder says in words that unsaved changes cannot be\n * saved while someone else holds the document. Committing anyway would make\n * that sentence false and would overwrite the holder's row, so the save reads\n * it too.\n */\n documentLock?: DocumentLockGates;\n /**\n * The page of the editor the address names (`\"settings\"` at\n * `/admin/collections/forms/<id>/settings`); absent on the form's edit\n * address, which opens Builder.\n */\n view?: string;\n}\n\n/** The props the editor's inner tree reads. */\ninterface FormEditorProps {\n isCreating: boolean;\n collectionSlug: string;\n entryId: string | undefined;\n onSave: FormBuilderViewProps[\"onSave\"];\n onSuccess: FormBuilderViewProps[\"onSuccess\"];\n onDelete: FormBuilderViewProps[\"onDelete\"];\n documentLock: DocumentLockGates | undefined;\n /** The form's version as read; see `initialData._rev`. */\n version: string | undefined;\n /** The form as read, which a save refused as out of date is compared with. */\n readAs: Record<string, unknown> | undefined;\n view: string | undefined;\n}\n\n// ============================================================================\n// Inner Component (uses context)\n// ============================================================================\n\n/** A page's label, as its tab names it. */\nfunction pageLabel(page: FormPageId): string {\n return (\n FORM_PAGES.find(each => (each.path ?? \"builder\") === page)?.label ??\n \"Builder\"\n );\n}\n\n/**\n * The page a saved form's address names, or the one a new form's tabs chose:\n * a form with no address yet keeps its page in the editor.\n */\nfunction useFormPage(\n isCreating: boolean,\n view: string | undefined\n): [FormPageId, (page: FormPageId) => void] {\n const [chosen, setChosen] = useState<FormPageId>(\"builder\");\n return [isCreating ? chosen : formPageOf(view), setChosen];\n}\n\n/** The page's own width: a Form page is a column, the rest take the width. */\nfunction PageBody({\n page,\n children,\n}: {\n page: FormPageId;\n children: ReactNode;\n}): JSX.Element {\n const column = page === \"settings\" || page === \"notifications\";\n return (\n <div\n data-form-page={page}\n className=\"min-w-0\"\n style={column ? { maxWidth: \"var(--nx-measure-form)\" } : undefined}\n >\n {children}\n </div>\n );\n}\n\n/** What `useFormSave` saves, and for whom. */\ninterface FormSaveTarget {\n isCreating: boolean;\n collectionSlug: string;\n entryId: string | undefined;\n onSave: FormBuilderViewProps[\"onSave\"];\n onSuccess: FormBuilderViewProps[\"onSuccess\"];\n /** Writes held by a colleague's claim, read when Save is pressed. */\n writesHeld: boolean;\n /** The form's version as read; see `initialData._rev`. */\n version: string | undefined;\n /** The form as read; see `revisionToRetryOn`. */\n readAs: Record<string, unknown> | undefined;\n}\n\n/**\n * The form's one save: the whole form (fields, settings, notifications and\n * status) sent once, through the host's own save where it gives one. A\n * refused save keeps every change and says what to fix, and where.\n */\nfunction useFormSave({\n isCreating,\n collectionSlug,\n entryId,\n onSave,\n onSuccess,\n writesHeld,\n version,\n readAs,\n}: FormSaveTarget): {\n handleSave: () => Promise<void>;\n isSaving: boolean;\n refusal: SaveRefusal | null;\n} {\n // The version the next save sends: the one read, then the one each save\n // answered, so a second save is not refused for this editor's first.\n const startedFrom = useRef(version);\n // The form as this editor last knew it stored: as read, then as each save\n // answered.\n const knownAs = useRef(readAs);\n const {\n fields,\n formData,\n settings,\n notifications,\n markAsSaved,\n beginSave,\n saveFailed,\n markFieldsSaved,\n } = useFieldBookkeeping();\n const queryClient = useQueryClient();\n const [isSaving, setIsSaving] = useState(false);\n const [refusal, setRefusal] = useState<SaveRefusal | null>(null);\n\n // ── Save ──────────────────────────────────────────────────────────────────\n const handleSave = useCallback(async () => {\n // A refused save keeps every change and says what to fix, and where, in\n // the page and as a toast.\n const refuse = (next: SaveRefusal) => {\n // Nothing was stored, so a save that would have made the form live\n // leaves its ID as it was.\n saveFailed();\n setRefusal(next);\n toast.error(next.message);\n };\n\n // The commit is where the refusal lives, following the rule the entry\n // editor sets: a write passes one gate rather than each control being\n // disabled separately. The Save button reads the same fact, so the\n // interface does not offer what this would refuse.\n if (writesHeld) return;\n\n const blocked = blockedBeforeSave(fields, notifications);\n if (blocked) {\n refuse(blocked);\n return;\n }\n\n const { id: _id, ...formDataWithoutId } = formData;\n const saveData = {\n ...formDataWithoutId,\n fields,\n settings,\n notifications,\n };\n\n beginSave();\n setIsSaving(true);\n setRefusal(null);\n if (onSave) {\n // The host's save. A field it stored is stored like one the builder\n // saved itself, so its type is fixed; the form's unsaved state is the\n // host's to clear, as before.\n await saveThroughHost(onSave, saveData, {\n saved: markFieldsSaved,\n failed: message => refuse({ message }),\n });\n setIsSaving(false);\n return;\n }\n\n try {\n const url = isCreating\n ? `/admin/api/collections/${collectionSlug}/entries`\n : `/admin/api/collections/${collectionSlug}/entries/${entryId}`;\n const send = (from: string | undefined) =>\n fetch(url, {\n method: isCreating ? \"POST\" : \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(saveBody(saveData, isCreating, from)),\n });\n let response = await send(startedFrom.current);\n // Refused only because the plugin wrote the form since: once more, on\n // the stored revision.\n const retryOn = await revisionToRetryOn({\n refused: response,\n url,\n sent: saveData,\n readAs: knownAs.current,\n });\n if (retryOn !== undefined) response = await send(retryOn);\n if (!response.ok) {\n const body: unknown = await response.json().catch(() => ({}));\n const where = refusedPage(body);\n refuse({\n message: saveErrorMessage(body, response.statusText),\n ...(where === undefined ? {} : { page: where }),\n });\n return;\n }\n\n const result = (await response.json()) as {\n message?: string;\n item?: Record<string, unknown>;\n } & Record<string, unknown>;\n const savedEntry: Record<string, unknown> = result.item ?? result;\n startedFrom.current = versionAfterSave(savedEntry, startedFrom.current);\n knownAs.current = savedEntry;\n void queryClient.invalidateQueries({\n queryKey: [\"entries\", \"list\", collectionSlug],\n });\n void queryClient.invalidateQueries({ queryKey: [\"entries\", \"count\"] });\n if (!isCreating && entryId) {\n void queryClient.invalidateQueries({\n queryKey: [\"entries\", \"detail\", collectionSlug, entryId],\n });\n }\n markAsSaved();\n // What the stored form now holds, whatever was edited meanwhile.\n markFieldsSaved(namesOf(saveData.fields));\n toast.success(isCreating ? \"Form created\" : \"Form saved\");\n if (onSuccess) onSuccess(savedEntry);\n } catch (error) {\n refuse({ message: saveFailureMessage(error) });\n } finally {\n setIsSaving(false);\n }\n }, [\n formData,\n fields,\n settings,\n notifications,\n onSave,\n onSuccess,\n isCreating,\n collectionSlug,\n entryId,\n queryClient,\n markAsSaved,\n beginSave,\n saveFailed,\n markFieldsSaved,\n // Read inside, so it belongs here. Without it the callback closes over the\n // claim as it stood when the form mounted, and a colleague taking the\n // document over mid-session would leave this still committing.\n writesHeld,\n ]);\n\n return { handleSave, isSaving, refusal };\n}\n\nfunction FormEditor({\n isCreating,\n collectionSlug,\n entryId,\n onSave,\n onSuccess,\n onDelete,\n documentLock,\n version,\n readAs,\n view,\n}: FormEditorProps): JSX.Element {\n const {\n fields,\n isDirty,\n formData,\n notifications,\n addField,\n selectField,\n seedNotifications,\n updateFormData,\n markAsSaved,\n changeCount,\n undo,\n readOnly,\n } = useFieldBookkeeping();\n\n const config = useBuilderConfig();\n const access = useEditorAccess(collectionSlug, config.submissionsSlug);\n const { canDelete, canReadSubmissions } = access;\n const [page, choosePage] = useFormPage(isCreating, view);\n const { handleSave, isSaving, refusal } = useFormSave({\n isCreating,\n collectionSlug,\n entryId,\n onSave,\n onSuccess,\n writesHeld: documentLock?.actionsDisabled === true,\n version,\n readAs,\n });\n const [confirmingDelete, setConfirmingDelete] = useState(false);\n\n // The form editor needs the width: the Forms panel starts folded on it, and\n // the bar's toggle brings it back. The admin remembers the reader's choice.\n useFoldedPanel(\"forms.builder\");\n // Leaving the form with unsaved changes asks first; moving between its tabs\n // stays on the form and does not.\n useReportUnsavedWork(UNSAVED_WORK_KEY, isDirty);\n\n const submissionCount = useSubmissionCount(\n isCreating || !canReadSubmissions ? undefined : entryId,\n config.submissionsSlug\n );\n\n useSeededNotification(\n isCreating,\n config.notificationDefaults,\n seedNotifications\n );\n\n // A field from the picker, added and opened (`fieldFromChoice`).\n const handleAddField = useCallback(\n (type: FormFieldTypeId, choice?: FieldPickerChoice, index?: number) => {\n const newField = fieldFromChoice(\n type,\n choice,\n fields.map(f => f.name)\n );\n // Where the outline asked for it (between two rows, or below one),\n // else at the end.\n addField(newField, index);\n selectField(newField.name);\n },\n [fields, addField, selectField]\n );\n\n // ── Delete ────────────────────────────────────────────────────────────────\n const handleDelete = useFormDelete({\n collectionSlug,\n entryId,\n writesHeld: documentLock?.actionsDisabled === true,\n markAsSaved,\n onDelete,\n });\n\n // ── Tabs ──────────────────────────────────────────────────────────────────\n const saved = !isCreating && entryId !== undefined;\n const tabs = formEditorPages(saved && canReadSubmissions).map(\n (each): FormEditorTab =>\n editorTab(each, {\n saved: saved ? { collectionSlug, entryId } : undefined,\n choose: choosePage,\n counts: {\n builder: fields.length,\n notifications: notifications.length,\n submissions: submissionCount,\n },\n })\n );\n const pageHref = (target: FormPageId): string =>\n saved ? formPageAddress(collectionSlug, entryId, target) : `#${target}`;\n\n const holdsWrites = documentLock?.actionsDisabled === true;\n\n return (\n <>\n <FormEditorBar\n status={formData.status}\n onStatusChange={status => updateFormData({ status })}\n changeCount={changeCount}\n saving={isSaving}\n onUndo={undo}\n {...(saved && submissionCount !== undefined\n ? {\n submissions: {\n count: submissionCount,\n href: pageHref(\"submissions\"),\n },\n }\n : {})}\n formId={formData.slug}\n onCopyFormId={() => {\n void copyText(formData.slug).then(copied =>\n copied\n ? toast.success(\"Form ID copied\")\n : toast.error(\"Couldn't copy the form ID\")\n );\n }}\n {...(saved && onDelete !== undefined && canDelete\n ? { onDelete: () => setConfirmingDelete(true) }\n : {})}\n onSave={() => {\n void handleSave();\n }}\n saveDisabled={\n isSaving || holdsWrites || readOnly || (!isCreating && !isDirty)\n }\n readOnly={readOnly}\n />\n\n {/* The editor's column, and on Builder the selected field's settings\n docked at the window's edge beside it, as tall as the window. */}\n <div\n data-slot=\"form-editor\"\n className=\"flex min-h-[var(--editor-scrollport,calc(100vh-var(--admin-bar-height)))] items-stretch\"\n >\n <div className=\"min-w-0 flex-1\">\n {/* The editor's column. The Editor frame draws no gutter, so the column\n carries the editor's own inset, in the steps the entry editor's\n field column uses, and its 72rem measure. */}\n <div\n data-slot=\"form-editor-column\"\n className=\"w-full max-w-[72rem] px-4 pb-16 pt-6 @3xl/content:px-6 @4xl/content:px-8 lg:pt-8\"\n >\n <FormEditorHeader\n name={formData.name}\n fieldCount={fields.length}\n formId={formData.slug}\n tabs={tabs}\n current={page}\n />\n\n <RefusalAlert\n refusal={refusal}\n current={page}\n hrefOf={pageHref}\n follow={(event, target) =>\n followTab(event, pageHref(target), {\n saved,\n choose: () => choosePage(target),\n })\n }\n />\n\n <PageBody page={page}>\n {pageContent(page, {\n builder: () => (\n <FieldCards\n // The editor docks the settings against the window's edge.\n dockedSettings=\"host\"\n enabledTypes={config.enabledTypes}\n disabledFieldTypes={config.disabledFieldTypes}\n onAddField={handleAddField}\n />\n ),\n preview: () => (\n <FormPreview fields={fields} formData={formData} />\n ),\n settings: () => (\n // One page in sections, one gap between each of them.\n <div className=\"flex flex-col gap-8\">\n <FormIdentitySection />\n <FormSettingsTab\n spamDefaults={config.spamDefaults}\n redirectCollections={config.redirectCollections}\n redirectConfigFailed={config.failed}\n />\n </div>\n ),\n notifications: () => (\n <FormNotificationsTab\n defaults={config.notificationDefaults}\n />\n ),\n submissions: () => (\n <SubmissionsPage\n formId={entryId}\n submissionsSlug={config.submissionsSlug}\n allowed={canReadSubmissions}\n known={access.known}\n />\n ),\n })}\n </PageBody>\n </div>\n </div>\n {page === \"builder\" ? <FieldInspector /> : null}\n </div>\n\n <DeleteFormDialog\n open={confirmingDelete}\n onOpenChange={setConfirmingDelete}\n name={formData.name}\n disabled={holdsWrites || readOnly}\n onConfirm={() => {\n void handleDelete();\n }}\n />\n </>\n );\n}\n\n/** A refused save: what to fix, and a link to the page that holds it. */\nfunction RefusalAlert({\n refusal,\n current,\n hrefOf,\n follow,\n}: {\n refusal: SaveRefusal | null;\n current: FormPageId;\n hrefOf: (page: FormPageId) => string;\n follow: (event: MouseEvent<HTMLAnchorElement>, page: FormPageId) => void;\n}): JSX.Element | null {\n if (refusal === null) return null;\n const elsewhere =\n refusal.page !== undefined && refusal.page !== current\n ? refusal.page\n : undefined;\n return (\n <Alert variant=\"destructive\" className=\"mb-6\">\n <AlertDescription>\n <span>{refusal.message}</span>\n {elsewhere === undefined ? null : (\n <>\n {\" \"}\n <a\n href={hrefOf(elsewhere)}\n className=\"font-medium underline underline-offset-2\"\n onClick={event => follow(event, elsewhere)}\n >\n Go to {pageLabel(elsewhere)}\n </a>\n </>\n )}\n </AlertDescription>\n </Alert>\n );\n}\n\n/** The Submissions page: this form's list, for a role that may read it. */\nfunction SubmissionsPage({\n formId,\n submissionsSlug,\n allowed,\n known,\n}: {\n formId: string | undefined;\n submissionsSlug: string | undefined;\n allowed: boolean;\n known: boolean;\n}): JSX.Element | null {\n if (formId === undefined) return null;\n if (!allowed) return <SubmissionsWithheld known={known} />;\n return (\n <SubmissionsView\n formId={formId}\n {...(submissionsSlug === undefined\n ? {}\n : { collectionSlug: submissionsSlug })}\n />\n );\n}\n\n/** Delete's question: the form is gone for good once answered. */\nfunction DeleteFormDialog({\n open,\n onOpenChange,\n name,\n disabled,\n onConfirm,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n name: string;\n disabled: boolean;\n onConfirm: () => void;\n}): JSX.Element {\n const which = name.trim() === \"\" ? \"The form\" : `\"${name}\"`;\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete this form?</AlertDialogTitle>\n <AlertDialogDescription>\n {which} is deleted for good, and the site can no longer show it.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep the form</AlertDialogCancel>\n <AlertDialogAction\n variant=\"destructive\"\n disabled={disabled}\n onClick={onConfirm}\n >\n Delete form\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n/**\n * The Submissions page for a person who may not read submissions: said once\n * the permissions are known, rather than a list every read of which is refused.\n */\nfunction SubmissionsWithheld({\n known,\n}: {\n known: boolean;\n}): JSX.Element | null {\n if (!known) return null;\n return (\n <p className=\"rounded-md border border-border bg-card px-4 py-6 text-sm text-muted-foreground\">\n {\n \"Your role can't see this form's submissions. Ask an admin for access to Submissions.\"\n }\n </p>\n );\n}\n\n/** The content of each page, drawn only for the page on screen. */\nfunction pageContent(\n page: FormPageId,\n pages: Record<FormPageId, () => ReactNode>\n): ReactNode {\n return pages[page]();\n}\n\n/**\n * Follow a tab: a saved form moves to the page's address through the admin;\n * a new form, which has no address yet, switches the page in place.\n */\nfunction followTab(\n event: MouseEvent<HTMLAnchorElement>,\n href: string,\n target: { saved: boolean; choose: () => void }\n): void {\n if (target.saved) {\n followWithinAdmin(event, href);\n return;\n }\n event.preventDefault();\n target.choose();\n}\n\n/** One tab of the header: its label, address, count, and how it is followed. */\nfunction editorTab(\n page: FormPageId,\n context: {\n saved: { collectionSlug: string; entryId: string } | undefined;\n choose: (page: FormPageId) => void;\n counts: Partial<Record<FormPageId, number | undefined>>;\n }\n): FormEditorTab {\n const href =\n context.saved === undefined\n ? `#${page}`\n : formPageAddress(\n context.saved.collectionSlug,\n context.saved.entryId,\n page\n );\n const count = context.counts[page];\n return {\n page,\n label: pageLabel(page),\n href,\n ...(count === undefined || count === 0 ? {} : { count }),\n onClick: event =>\n followTab(event, href, {\n saved: context.saved !== undefined,\n choose: () => context.choose(page),\n }),\n };\n}\n\n/**\n * What stops a save before it is sent, and where it is fixed: a form needs a\n * field, a consent needs its text, and a notification needs a name and good\n * addresses. A notification is\n * written to the form as it is typed, so nothing between the editor and here\n * refuses one; both preconditions are answered by the module the editor asks.\n */\nfunction blockedBeforeSave(\n fields: readonly AnyFormField[],\n notifications: readonly FormNotification[]\n): SaveRefusal | undefined {\n if (fields.length === 0) {\n return { message: \"Add at least one field to the form.\", page: \"builder\" };\n }\n const blank = blankConsentFields(fields)[0];\n if (blank !== undefined) {\n return {\n message: `${blank.label || blank.name}: ${CONSENT_TEXT_REQUIRED}`,\n page: \"builder\",\n };\n }\n const blocked = notificationsBlockingSave(notifications);\n return blocked ? { message: blocked, page: \"notifications\" } : undefined;\n}\n\n/** Copy text to the clipboard, answering whether it was copied. */\nasync function copyText(text: string): Promise<boolean> {\n try {\n await navigator.clipboard.writeText(text);\n return true;\n } catch {\n return false;\n }\n}\n\n// ============================================================================\n// Main Component (provides context)\n// ============================================================================\n\n/**\n * What to tell an author when a save is refused.\n *\n * A validation refusal carries the generic `\"Validation failed.\"` at the top\n * level and puts the actionable part in `data.errors`. Reporting only the top\n * level tells an author their form was rejected and nothing about what to\n * change — every rule in the collection produces that same sentence.\n */\nexport function saveErrorMessage(body: unknown, statusText: string): string {\n const error = (\n body as {\n error?: {\n code?: string;\n message?: string;\n data?: {\n errors?: { message?: string }[];\n changedBy?: { name?: unknown };\n };\n };\n }\n )?.error;\n\n // Refused because someone saved the form since: who, where the refusal\n // names them, and what to do. The edits stay in the editor.\n if (error?.code === \"STALE_VERSION\") {\n const name = error.data?.changedBy?.name;\n const who =\n typeof name === \"string\" && name.trim() !== \"\" ? name.trim() : \"Someone\";\n return `${who} changed this form since you opened it. Reload to see their change; your edits are still here, not saved.`;\n }\n\n const fieldIssues = (error?.data?.errors ?? [])\n .map(issue => issue.message)\n .filter((message): message is string => Boolean(message));\n\n return (\n fieldIssues.join(\" \") ||\n error?.message ||\n `Failed to save form: ${statusText}`\n );\n}\n\n/**\n * A new field from the picker: the type's defaults, the label the person\n * typed, and the name it gives, made unique among `names` (a second \"Email\"\n * becomes `email_2`). Without a choice, or a label that gives no name, the\n * type's own label and name.\n */\nfunction fieldFromChoice(\n type: FormFieldTypeId,\n choice: FieldPickerChoice | undefined,\n names: readonly string[]\n): AnyFormField {\n const fromType = createFieldFromType(type, names);\n const label = choice?.label.trim();\n return {\n ...fromType,\n label: label || fromType.label,\n name: choice?.name ? generateFieldName(choice.name, names) : fromType.name,\n };\n}\n\n/** What a failed save tells the person. */\nfunction saveFailureMessage(error: unknown): string {\n return error instanceof Error ? error.message : \"Failed to save form\";\n}\n\n/** The names fields are stored under. */\nfunction namesOf(fields: readonly AnyFormField[]): string[] {\n return fields.map(field => field.name);\n}\n\n/**\n * Saves through the host's own callback, waiting for the promise it may\n * return, then reports which way it went: on success, the names of the fields\n * it was handed, which the stored form now holds whatever was edited meanwhile.\n */\nasync function saveThroughHost(\n onSave: (data: unknown) => void | Promise<void>,\n data: { fields: readonly AnyFormField[] },\n report: {\n saved: (names: readonly string[]) => void;\n failed: (message: string) => void;\n }\n): Promise<void> {\n try {\n await onSave(data);\n } catch (error) {\n report.failed(saveFailureMessage(error));\n return;\n }\n report.saved(namesOf(data.fields));\n}\n\nexport function FormBuilderView({\n id,\n entryId,\n collection,\n collectionSlug,\n isCreating,\n initialData,\n onSave,\n onSuccess,\n onDelete,\n documentLock,\n view,\n}: FormBuilderViewProps): JSX.Element {\n const resolvedEntryId = entryId || id || initialData?.id;\n const resolvedCollectionSlug = collectionSlug || collection || \"forms\";\n const resolvedIsCreating = isCreating ?? !resolvedEntryId;\n const providerKey = resolvedEntryId || \"new\";\n\n return (\n <FormBuilderProvider\n key={providerKey}\n readOnly={documentLock?.readOnly === true}\n initialData={{\n id: resolvedEntryId,\n name: initialData?.name,\n slug: initialData?.slug,\n description: initialData?.description,\n status: initialData?.status,\n wentLiveAt: initialData?.wentLiveAt,\n closedMessage: initialData?.closedMessage,\n fields: initialData?.fields as FormField[],\n settings: initialData?.settings as Record<string, unknown>,\n notifications: initialData?.notifications as FormNotification[],\n }}\n >\n <FormEditor\n isCreating={resolvedIsCreating}\n collectionSlug={resolvedCollectionSlug}\n entryId={resolvedEntryId}\n onSave={onSave}\n onSuccess={onSuccess}\n onDelete={onDelete}\n documentLock={documentLock}\n version={initialData?._rev}\n readAs={resolvedIsCreating ? undefined : initialData}\n view={view}\n />\n </FormBuilderProvider>\n );\n}\n\nexport default FormBuilderView;\n","/**\n * A browser's User-Agent as a person reads it: \"Safari 18 on macOS\". Only\n * the common browsers and systems are named; anything else reads as the\n * browser or system it could tell, or \"Unknown browser\".\n *\n * @module admin/components/submissions/browser-label\n */\n\nconst BROWSERS: ReadonlyArray<[name: string, pattern: RegExp]> = [\n // Order matters: each of these also says \"Chrome\" or \"Safari\".\n [\"Edge\", /Edg(?:e|A|iOS)?\\/(\\d+)/],\n [\"Opera\", /OPR\\/(\\d+)/],\n [\"Samsung Internet\", /SamsungBrowser\\/(\\d+)/],\n [\"Firefox\", /(?:Firefox|FxiOS)\\/(\\d+)/],\n [\"Chrome\", /(?:Chrome|CriOS)\\/(\\d+)/],\n [\"Safari\", /Version\\/(\\d+)(?:\\.\\d+)*(?: Mobile\\/\\S+)? Safari\\//],\n];\n\nconst SYSTEMS: ReadonlyArray<[name: string, pattern: RegExp]> = [\n [\"iOS\", /iPhone|iPad|iPod/],\n [\"Android\", /Android/],\n [\"Windows\", /Windows/],\n [\"macOS\", /Mac OS X|Macintosh/],\n [\"ChromeOS\", /CrOS/],\n [\"Linux\", /Linux/],\n];\n\nexport function browserLabel(userAgent: string): string {\n const browser = BROWSERS.find(([, pattern]) => pattern.test(userAgent));\n const system = SYSTEMS.find(([, pattern]) => pattern.test(userAgent))?.[0];\n const named = browser\n ? `${browser[0]} ${userAgent.match(browser[1])?.[1] ?? \"\"}`.trim()\n : undefined;\n if (named && system) return `${named} on ${system}`;\n return named ?? (system ? `A browser on ${system}` : \"Unknown browser\");\n}\n","\"use client\";\n\n/**\n * A submission's answers as staff edit them: one input per field type, and a\n * consent shown as given, never offered for editing.\n *\n * @module admin/components/submissions/submission-fields\n */\n\nimport {\n Input,\n Select,\n SelectContent,\n SelectItem,\n SelectTrigger,\n SelectValue,\n Switch,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { CircleCheck, CircleX, Lock } from \"lucide-react\";\nimport type { JSX } from \"react\";\n\nimport type { FormField } from \"../../../types\";\nimport { isConsentAnswer } from \"../../../utils/consent\";\nimport { formatExportValue } from \"../../../utils/export-formats\";\n\n/**\n * A consent answer: the choice, and the text the visitor was shown, quoted as\n * stored. Read-only while editing as well, with the reason beside it, since a\n * consent keeps what the visitor agreed to and no edit may change it.\n */\nexport function ConsentValue({\n value,\n editing,\n}: {\n value: unknown;\n editing: boolean;\n}): JSX.Element {\n if (!isConsentAnswer(value)) {\n return <span className=\"text-sm text-muted-foreground\">—</span>;\n }\n const Icon = value.agreed ? CircleCheck : CircleX;\n return (\n <div className=\"space-y-1\">\n <p className=\"flex items-start gap-2 text-sm text-foreground\">\n <Icon\n className={\n value.agreed\n ? \"mt-0.5 h-4 w-4 shrink-0 text-success\"\n : \"mt-0.5 h-4 w-4 shrink-0 text-destructive\"\n }\n aria-hidden=\"true\"\n />\n <span className=\"min-w-0 wrap-break-word\">\n <span className=\"font-medium\">\n {value.agreed ? \"Agreed\" : \"Not agreed\"}\n </span>\n <span className=\"text-muted-foreground\">\n {\" \"}\n to “{value.text}”\n </span>\n </span>\n </p>\n {editing && (\n <p className=\"flex items-center gap-1.5 text-xs text-muted-foreground\">\n <Lock className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />A consent\n cannot be edited\n </p>\n )}\n </div>\n );\n}\n\n/** What each input is given: its id, the field, the answer, and the change. */\ninterface InputProps {\n id: string;\n field: FormField;\n value: unknown;\n onChange: (value: unknown) => void;\n}\n\nconst textValue = (value: unknown): string =>\n typeof value === \"string\" ? value : \"\";\n\nfunction TextareaInput({ id, value, onChange }: InputProps): JSX.Element {\n return (\n <Textarea\n id={id}\n value={textValue(value)}\n onChange={e => onChange(e.target.value)}\n rows={3}\n />\n );\n}\n\nfunction CheckboxInput({ id, value, onChange }: InputProps): JSX.Element {\n return (\n <Switch\n id={id}\n checked={Boolean(value)}\n onCheckedChange={checked => onChange(checked)}\n />\n );\n}\n\nfunction ChoiceInput({ id, field, value, onChange }: InputProps): JSX.Element {\n const options = \"options\" in field ? (field.options ?? []) : [];\n return (\n <Select value={textValue(value)} onValueChange={onChange}>\n <SelectTrigger\n id={id}\n className=\"w-full bg-transparent border-input dark:bg-muted/50\"\n >\n <SelectValue />\n </SelectTrigger>\n <SelectContent>\n {options.map(option => (\n <SelectItem key={option.value} value={option.value}>\n {option.label}\n </SelectItem>\n ))}\n </SelectContent>\n </Select>\n );\n}\n\nfunction NumberInput({ id, value, onChange }: InputProps): JSX.Element {\n const shown =\n typeof value === \"number\" || typeof value === \"string\" ? String(value) : \"\";\n return (\n <Input\n id={id}\n type=\"number\"\n value={shown}\n onChange={e =>\n onChange(e.target.value === \"\" ? \"\" : Number(e.target.value))\n }\n />\n );\n}\n\n/** A plain input of `type`: text, an email address, a date or a time. */\nfunction plainInput(type: string) {\n return function PlainInput({ id, value, onChange }: InputProps): JSX.Element {\n return (\n <Input\n id={id}\n type={type}\n value={textValue(value)}\n onChange={e => onChange(e.target.value)}\n />\n );\n };\n}\n\n/**\n * The input for each field type staff may edit. Files are media references,\n * hidden fields machine values, and other types may hold structured data:\n * coercing any of them through a text input would corrupt them, so a type\n * not here stays read-only.\n */\nconst INPUTS: Readonly<Record<string, (props: InputProps) => JSX.Element>> = {\n textarea: TextareaInput,\n checkbox: CheckboxInput,\n select: ChoiceInput,\n radio: ChoiceInput,\n number: NumberInput,\n date: plainInput(\"date\"),\n time: plainInput(\"time\"),\n text: plainInput(\"text\"),\n email: plainInput(\"email\"),\n url: plainInput(\"text\"),\n phone: plainInput(\"text\"),\n};\n\n/** Whether staff may edit an answer of this field type here. */\nexport function canEditAnswer(type: string): boolean {\n return type in INPUTS;\n}\n\n/** One editable input per field type; unsupported types render read-only. */\nexport function FieldValueInput({\n field,\n value,\n onChange,\n}: {\n field: FormField;\n value: unknown;\n onChange: (value: unknown) => void;\n}): JSX.Element {\n const Editor = INPUTS[field.type];\n if (Editor) {\n return (\n <Editor\n id={`submission-field-${field.name}`}\n field={field}\n value={value}\n onChange={onChange}\n />\n );\n }\n return (\n <p className=\"text-sm text-muted-foreground\">\n {formatExportValue(value, field) || \"—\"}\n </p>\n );\n}\n","\"use client\";\n\n/**\n * A submission's answers as its page reads them: each by the label it was\n * asked under, an option by its label, a date in words, an address or a\n * phone number as a link, a file as a file (name, size, download), and a\n * field its conditions hid as not asked.\n *\n * @module admin/components/submissions/SubmissionAnswers\n */\n\nimport { useQuery } from \"@tanstack/react-query\";\nimport { Download, ExternalLink, FileText } from \"lucide-react\";\nimport type { JSX, ReactNode } from \"react\";\n\nimport type { AskedField, AskedSnapshot } from \"../../../utils/asked-fields\";\nimport { isConsentAnswer } from \"../../../utils/consent\";\nimport { formatExportValue, optionLabels } from \"../../../utils/export-formats\";\n\nimport { ConsentValue } from \"./submission-fields\";\n\n/** A file in the Media library, as the page shows it. */\ninterface MediaItem {\n id: string;\n filename?: string;\n originalFilename?: string;\n mimeType?: string;\n size?: number;\n url?: string;\n}\n\nconst muted = (text: string) => (\n <span className=\"text-muted-foreground\">{text}</span>\n);\n\n/** A file's size in the units a person reads. */\nfunction fileSize(bytes: number | undefined): string {\n if (bytes === undefined || !Number.isFinite(bytes)) return \"\";\n if (bytes < 1024) return `${bytes} B`;\n if (bytes < 1024 * 1024) return `${Math.round(bytes / 1024)} KB`;\n return `${(bytes / (1024 * 1024)).toFixed(1)} MB`;\n}\n\n/** A date answer in words, \"Tue 24 Nov 2026\"; the stored text otherwise. */\nfunction dateInWords(value: string): string {\n const day = /^\\d{4}-\\d{2}-\\d{2}$/.test(value)\n ? new Date(`${value}T00:00:00`)\n : new Date(value);\n if (Number.isNaN(day.getTime())) return value;\n return day.toLocaleDateString(undefined, {\n weekday: \"short\",\n day: \"numeric\",\n month: \"short\",\n year: \"numeric\",\n });\n}\n\n/** One media item, read from the library; nothing while it loads or fails. */\nfunction useMediaItem(id: string) {\n return useQuery({\n queryKey: [\"form-builder\", \"submission-file\", id],\n queryFn: async (): Promise<MediaItem | null> => {\n const response = await fetch(\n `/admin/api/media/${encodeURIComponent(id)}`,\n { credentials: \"include\" }\n );\n if (!response.ok) return null;\n const body = (await response.json()) as MediaItem & { item?: MediaItem };\n return body.item ?? body;\n },\n staleTime: 60_000,\n });\n}\n\n/** What a file answer's card says, while it loads, once read, or if gone. */\nfunction fileLines(\n item: MediaItem | null | undefined,\n isLoading: boolean,\n name: string\n): { title: string; note: string } {\n if (isLoading) return { title: \"Loading file…\", note: \"\" };\n if (!item) {\n return {\n title: \"File not found\",\n note: \"It may have been deleted from the Media library.\",\n };\n }\n return {\n title: name,\n note: [fileSize(item.size), \"in the Media library\"]\n .filter(Boolean)\n .join(\" · \"),\n };\n}\n\n/** A file's type as its card's badge: its extension, at most four letters. */\nfunction extensionOf(name: string): string {\n const extension = name.includes(\".\") ? name.split(\".\").pop() : undefined;\n return extension ? extension.slice(0, 4).toUpperCase() : \"FILE\";\n}\n\n/** A file answer: its name, type, size, and a download link. */\nfunction FileAnswer({ id }: { id: string }): JSX.Element {\n const { data: item, isLoading } = useMediaItem(id);\n const name = item?.originalFilename || item?.filename || id;\n const lines = fileLines(item, isLoading, name);\n return (\n <div className=\"flex max-w-md items-center gap-3 rounded-md border border-border bg-card px-3 py-2\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded bg-muted text-[10px] font-semibold text-muted-foreground\"\n >\n {item ? extensionOf(name) : <FileText className=\"h-4 w-4\" />}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-foreground\">\n {lines.title}\n </span>\n <span className=\"block text-xs text-muted-foreground\">\n {lines.note}\n </span>\n </span>\n {item?.url ? (\n <a\n href={item.url}\n download={name}\n aria-label={`Download ${name}`}\n className=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <Download className=\"h-4 w-4\" aria-hidden=\"true\" />\n </a>\n ) : null}\n </div>\n );\n}\n\n/**\n * Whether a file reference is an address rather than a Media library id. A\n * file answer may hold either; an address is web (`https://…`, `//…`) or the\n * site's own (`/…`), never another scheme, so it is safe to link to.\n */\nfunction isFileAddress(reference: string): boolean {\n return /^(?:https?:)?\\/\\//i.test(reference) || reference.startsWith(\"/\");\n}\n\n/** The file name an address ends in, or the address itself. */\nfunction nameFromAddress(address: string): string {\n const last = address.split(/[?#]/)[0].split(\"/\").filter(Boolean).pop();\n if (!last) return address;\n try {\n return decodeURIComponent(last);\n } catch {\n return last;\n }\n}\n\n/** A file answer stored as an address: its name, and a link that opens it. */\nfunction LinkedFile({ address }: { address: string }): JSX.Element {\n const name = nameFromAddress(address);\n return (\n <div className=\"flex max-w-md items-center gap-3 rounded-md border border-border bg-card px-3 py-2\">\n <span\n aria-hidden=\"true\"\n className=\"flex h-9 w-9 shrink-0 items-center justify-center rounded bg-muted text-[10px] font-semibold text-muted-foreground\"\n >\n {extensionOf(name)}\n </span>\n <span className=\"min-w-0 flex-1\">\n <span className=\"block truncate text-sm font-medium text-foreground\">\n {name}\n </span>\n <span className=\"block truncate text-xs text-muted-foreground\">\n {address}\n </span>\n </span>\n <a\n href={address}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n aria-label={`Open ${name}`}\n className=\"inline-flex h-8 w-8 shrink-0 items-center justify-center rounded-md text-muted-foreground hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring\"\n >\n <ExternalLink className=\"h-4 w-4\" aria-hidden=\"true\" />\n </a>\n </div>\n );\n}\n\n/** The ids a file answer holds: one, or several. */\nfunction fileIds(value: unknown): string[] {\n const values = Array.isArray(value) ? value : [value];\n return values.filter(\n (each): each is string => typeof each === \"string\" && each !== \"\"\n );\n}\n\n// The underline is drawn in an ink token, not the border's: it is what marks\n// the text as a link, so it must read on every surface the page lands on, and\n// the border token falls far below that in dark.\nconst linkClass =\n \"text-foreground underline decoration-muted-foreground underline-offset-4 hover:decoration-foreground\";\n\n/** An answer as text: the text itself, or read the way exports read it. */\nfunction textOf(value: unknown): string {\n return typeof value === \"string\" ? value : formatExportValue(value);\n}\n\n/** A link answer, or the value plain when it is not text. */\nfunction linkAnswer(\n href: (text: string) => string,\n shown?: (text: string) => ReactNode\n) {\n return (value: unknown): JSX.Element => {\n if (typeof value !== \"string\") return muted(textOf(value));\n return (\n <a className={linkClass} href={href(value)}>\n {shown ? shown(value) : value}\n </a>\n );\n };\n}\n\n/** A web address: opened in a new tab, shown without its scheme. */\nfunction urlAnswer(value: unknown): JSX.Element {\n if (typeof value !== \"string\") return muted(textOf(value));\n return (\n <a\n className={`${linkClass} inline-flex items-center gap-1`}\n href={value}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n >\n {value.replace(/^https?:\\/\\//, \"\")}\n <ExternalLink className=\"h-3 w-3 shrink-0\" aria-hidden=\"true\" />\n </a>\n );\n}\n\n/** File answers: each a file with its name, size and download. */\nfunction filesAnswer(value: unknown): JSX.Element {\n const ids = fileIds(value);\n if (ids.length === 0) return muted(\"—\");\n return (\n <span className=\"flex flex-col gap-2\">\n {ids.map(reference =>\n isFileAddress(reference) ? (\n <LinkedFile key={reference} address={reference} />\n ) : (\n <FileAnswer key={reference} id={reference} />\n )\n )}\n </span>\n );\n}\n\n/** How each kind of field's answer reads. A kind not here reads as text. */\nconst ANSWER_KINDS: Readonly<\n Record<string, (value: unknown, field: AskedField) => JSX.Element>\n> = {\n email: linkAnswer(text => `mailto:${text}`),\n phone: linkAnswer(text => `tel:${text.replace(/\\s+/g, \"\")}`),\n url: urlAnswer,\n date: value => (\n <span>\n {typeof value === \"string\" ? dateInWords(value) : textOf(value)}\n </span>\n ),\n select: (value, field) => <span>{optionLabels(field, value)}</span>,\n radio: (value, field) => <span>{optionLabels(field, value)}</span>,\n checkbox: value => (\n <span>{value === true || value === \"true\" ? \"Yes\" : \"No\"}</span>\n ),\n textarea: value => (\n <span className=\"whitespace-pre-wrap\">{textOf(value)}</span>\n ),\n file: filesAnswer,\n};\n\n/** Whether an answer is empty: nothing, empty text, or an empty list. */\nfunction isBlank(value: unknown): boolean {\n return (\n value === undefined ||\n value === null ||\n value === \"\" ||\n (Array.isArray(value) && value.length === 0)\n );\n}\n\n/** One answer's value, by the kind of field it answers. */\nexport function AnswerValue({\n field,\n value,\n}: {\n field: AskedField;\n value: unknown;\n}): JSX.Element {\n if (field.type === \"consent\") {\n return <ConsentValue value={value} editing={false} />;\n }\n if (isBlank(value)) return muted(\"—\");\n const kind = ANSWER_KINDS[field.type];\n if (kind) return kind(value, field);\n return <span className=\"wrap-break-word\">{textOf(value) || \"—\"}</span>;\n}\n\n/** One row: the label as asked, and the answer or why there is none. */\nfunction AnswerRow({\n label,\n children,\n}: {\n label: ReactNode;\n children: ReactNode;\n}) {\n return (\n <div className=\"grid gap-1 border-b border-border py-3 last:border-b-0 sm:grid-cols-[minmax(0,14rem)_minmax(0,1fr)] sm:gap-6\">\n <dt className=\"text-sm text-muted-foreground\">{label}</dt>\n <dd className=\"min-w-0 text-sm text-foreground\">{children}</dd>\n </div>\n );\n}\n\n/** Every answer, in the order the submission was asked them. */\nexport function SubmissionAnswers({\n asked,\n data,\n}: {\n asked: AskedSnapshot;\n data: Record<string, unknown>;\n}): JSX.Element {\n const hidden = new Set(asked.hidden);\n const shown = asked.fields.filter(field => field.type !== \"hidden\");\n const orphans = orphanedAnswers(asked, data);\n return (\n <dl className=\"px-4 sm:px-5\">\n {shown.map(field => (\n <AnswerRow key={field.name} label={field.label}>\n {hidden.has(field.name) && data[field.name] === undefined ? (\n <span className=\"text-muted-foreground italic\">\n Not asked: a condition hid it\n </span>\n ) : (\n <AnswerValue field={field} value={data[field.name]} />\n )}\n </AnswerRow>\n ))}\n {orphans.map(field => (\n <AnswerRow\n key={field.name}\n label={\n <>\n <span className=\"font-mono\">{field.name}</span>\n <span className=\"block text-xs\">No longer on the form</span>\n </>\n }\n >\n <AnswerValue field={field} value={data[field.name]} />\n </AnswerRow>\n ))}\n </dl>\n );\n}\n\n/**\n * The answers stored under a name the snapshot does not describe. A\n * submission sent before snapshots existed is described by the form as it is\n * now, so an answer to a field since removed or renamed has no descriptor:\n * it is still what the visitor sent, so it shows under its stored name rather\n * than vanishing. A field the snapshot names, hidden ones among them, never\n * lands here.\n */\nfunction orphanedAnswers(\n asked: AskedSnapshot,\n data: Record<string, unknown>\n): AskedField[] {\n const described = new Set(asked.fields.map(field => field.name));\n return Object.keys(data)\n .filter(name => !described.has(name))\n .map(name => ({\n name,\n label: name,\n type: isConsentAnswer(data[name]) ? \"consent\" : \"text\",\n }));\n}\n","\"use client\";\n\n/**\n * The fields a site added to its submissions (`formSubmissionOverrides.fields`),\n * as the plugin's `/submission-fields` route answers them: what a submission's\n * page shows in its details beside its own. Read by anyone who may read\n * submissions, so a person without rights to forms still sees them.\n *\n * @module admin/use-submission-fields\n */\nimport { useQuery } from \"@tanstack/react-query\";\n\n/** A field a site added to the submissions collection. */\nexport interface SubmissionExtensionField {\n name: string;\n label: string;\n type: string;\n}\n\nconst NONE: SubmissionExtensionField[] = [];\n\n/** The site's own submission fields; none until known or if unreadable. */\nexport function useSubmissionFields(): SubmissionExtensionField[] {\n const { data } = useQuery({\n queryKey: [\"form-builder\", \"submission-fields\"],\n queryFn: async (): Promise<SubmissionExtensionField[]> => {\n const response = await fetch(\n \"/admin/api/plugins/@nextlyhq/plugin-form-builder/submission-fields\",\n { credentials: \"include\" }\n );\n if (!response.ok) return NONE;\n const body = (await response.json()) as {\n fields?: SubmissionExtensionField[];\n };\n return body.fields ?? NONE;\n },\n staleTime: Infinity,\n });\n return data ?? NONE;\n}\n","\"use client\";\n\n/**\n * A submission's own page: an Editor page with its own address, the answers\n * as the visitor was asked them, its activity, and a side panel with its\n * status, internal note and details. The bar moves to the newer or older\n * submission of the list it was opened from, keeping that list's filter.\n * Opening a New submission marks it Read for a person who may update it; one\n * who may only read leaves it New.\n *\n * @module admin/SubmissionPage\n */\n\nimport {\n type DocumentLockGates,\n navigateTo,\n PageFrame,\n PageHeader,\n TopBarPlace,\n useReportUnsavedWork,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport {\n Alert,\n AlertDescription,\n AlertDialog,\n AlertDialogAction,\n AlertDialogCancel,\n AlertDialogContent,\n AlertDialogDescription,\n AlertDialogFooter,\n AlertDialogHeader,\n AlertDialogTitle,\n Button,\n DropdownMenu,\n DropdownMenuContent,\n DropdownMenuItem,\n DropdownMenuSeparator,\n DropdownMenuTrigger,\n Textarea,\n} from \"@nextlyhq/ui\";\nimport { useQuery, useQueryClient } from \"@tanstack/react-query\";\nimport {\n Archive,\n Ban,\n ChevronDown,\n ChevronUp,\n Copy,\n Eye,\n Inbox,\n MoreHorizontal,\n Pencil,\n Trash2,\n} from \"lucide-react\";\nimport {\n createContext,\n type JSX,\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useRef,\n useState,\n} from \"react\";\n\nimport type { FormField } from \"../types\";\nimport { type AskedSnapshot, askedFieldsOf } from \"../utils/asked-fields\";\nimport { formatExportValue } from \"../utils/export-formats\";\nimport { pageFromReferer } from \"../utils/sent-from\";\nimport { senderOf } from \"../utils/submission-title\";\n\nimport { SUBMISSION_COUNT_KEY } from \"./components/editor/useSubmissionCount\";\nimport { browserLabel } from \"./components/submissions/browser-label\";\nimport {\n canEditAnswer,\n ConsentValue,\n FieldValueInput,\n} from \"./components/submissions/submission-fields\";\nimport {\n LIST_SORT,\n listWithWhere,\n neighbourQueries,\n scopeFromSearch,\n type SubmissionListScope,\n submissionPageAddress,\n} from \"./components/submissions/submission-list-scope\";\nimport {\n formIdOf,\n type SubmissionRow,\n whenText,\n} from \"./components/submissions/submission-row\";\nimport {\n AnswerValue,\n SubmissionAnswers,\n} from \"./components/submissions/SubmissionAnswers\";\nimport { SubmissionEmails } from \"./components/submissions/SubmissionEmails\";\nimport { readPermissions } from \"./use-admin-permissions\";\nimport { useBuilderConfig } from \"./use-builder-config\";\nimport {\n type SubmissionExtensionField,\n useSubmissionFields,\n} from \"./use-submission-fields\";\n\n/** A submission as the entries API answers it. */\nexport type SubmissionDocument = SubmissionRow;\n\ninterface FormDocument {\n id: string;\n name?: string;\n fields?: FormField[];\n}\n\nexport interface SubmissionPageProps {\n entryId?: string;\n collectionSlug?: string;\n initialData?: Partial<SubmissionDocument>;\n /**\n * What a colleague's claim on this submission withholds, as the entry page\n * hands it over. While either is set the page offers no change and sends\n * none, the automatic Read among them.\n */\n documentLock?: DocumentLockGates;\n /**\n * Tells the entry page the submission was deleted: it stands its leave\n * guard down and returns to the list. Used when unsaved work is on the\n * page; otherwise the page moves to the next submission itself.\n */\n onDelete?: () => void;\n}\n\nconst STATUSES = [\n { value: \"new\", label: \"New\", dot: \"bg-primary\" },\n { value: \"read\", label: \"Read\", dot: \"bg-muted-foreground\" },\n { value: \"archived\", label: \"Archived\", dot: \"bg-muted-foreground/50\" },\n { value: \"spam\", label: \"Spam\", dot: \"bg-destructive\" },\n] as const;\n\n/** The initials a sender shows as, at most two. */\nfunction initialsOf(sender: string): string {\n if (sender === \"Submission\") return \"S\";\n const parts = sender\n .replace(/@.*/, \"\")\n .split(/[\\s._-]+/)\n .filter(Boolean);\n return (\n parts\n .slice(0, 2)\n .map(part => part.charAt(0).toUpperCase())\n .join(\"\") || \"S\"\n );\n}\n\nasync function readJson<T>(url: string): Promise<T | null> {\n const response = await fetch(url, { credentials: \"include\" });\n if (!response.ok) return null;\n return (await response.json()) as T;\n}\n\n/** The submission as stored now. */\nfunction useSubmission(\n slug: string,\n id: string,\n initial: Partial<SubmissionDocument> | undefined\n) {\n return useQuery({\n queryKey: [\"form-builder\", \"submission\", slug, id],\n queryFn: async () => {\n const body = await readJson<\n SubmissionDocument & { item?: SubmissionDocument }\n >(\n `/admin/api/collections/${encodeURIComponent(slug)}/entries/${encodeURIComponent(id)}`\n );\n if (!body) throw new Error(\"This submission could not be read.\");\n return body.item ?? body;\n },\n ...(initial?.id === id && initial.data\n ? { initialData: initial as SubmissionDocument }\n : {}),\n });\n}\n\n/** Where this submission sits in its list, and its neighbours. */\ninterface ListPlace {\n index: number;\n total: number;\n newer?: string;\n older?: string;\n}\n\n/**\n * This submission's place in the list it was opened from, by the list's own\n * filter and order: how many are newer, how many in all, and the one just\n * newer and just older.\n */\nfunction useListPlace(\n slug: string,\n submission: SubmissionDocument | undefined,\n scope: SubmissionListScope\n) {\n return useQuery({\n enabled: submission?.submittedAt != null,\n queryKey: [\"form-builder\", \"submission-place\", slug, submission?.id, scope],\n queryFn: async (): Promise<ListPlace> => {\n const around = neighbourQueries(\n scope,\n submission!.submittedAt!,\n submission!.id\n );\n const page = async (where: object, sort: string) =>\n readJson<{ items?: Array<{ id: string }>; meta?: { total?: number } }>(\n `/admin/api/collections/${encodeURIComponent(slug)}/entries?${new URLSearchParams(\n { limit: \"1\", sort, where: JSON.stringify(where) }\n )}`\n );\n const [all, newer, older] = await Promise.all([\n page(listWithWhere(scope, submission!.id), LIST_SORT),\n page(around.newer.where, around.newer.sort),\n page(around.older.where, around.older.sort),\n ]);\n return {\n index: newer?.meta?.total ?? 0,\n total: all?.meta?.total ?? 0,\n newer: newer?.items?.[0]?.id,\n older: older?.items?.[0]?.id,\n };\n },\n });\n}\n\n/**\n * What a change says when someone else changed the submission since: the\n * server refused its version, or the values it was made from are gone.\n */\nconst CHANGED_SINCE_MESSAGE =\n \"Someone changed this submission since you opened it. It now shows their change; make yours again.\";\n\n/**\n * What a refused change says: someone saved it since, the server's own\n * reason for an answer it refused, or that it could not be saved.\n */\nfunction refusalMessage(body: unknown): string {\n const error = (\n body as {\n error?: {\n code?: string;\n message?: string;\n data?: { errors?: Array<{ message?: string }> };\n };\n } | null\n )?.error;\n if (error?.code === \"STALE_VERSION\") return CHANGED_SINCE_MESSAGE;\n return (\n error?.data?.errors?.[0]?.message ??\n error?.message ??\n \"The change could not be saved.\"\n );\n}\n\n/** One section of the main column, as the prototype draws a box. */\nfunction Box({\n title,\n action,\n children,\n}: {\n title: string;\n action?: ReactNode;\n children: ReactNode;\n}): JSX.Element {\n return (\n <section\n aria-label={title}\n className=\"rounded-lg border border-border bg-card\"\n >\n <div className=\"flex items-center justify-between gap-2 border-b border-border px-4 py-3 sm:px-5\">\n <h2 className=\"text-sm font-semibold text-foreground\">{title}</h2>\n {action}\n </div>\n {children}\n </section>\n );\n}\n\n/** One line of the activity trail. */\nfunction ActivityItem({\n icon,\n children,\n when,\n}: {\n icon: ReactNode;\n children: ReactNode;\n when?: string;\n}): JSX.Element {\n return (\n <li className=\"flex items-start gap-3 py-2\">\n <span\n aria-hidden=\"true\"\n className=\"mt-0.5 flex h-6 w-6 shrink-0 items-center justify-center rounded-full bg-muted text-muted-foreground\"\n >\n {icon}\n </span>\n <span className=\"min-w-0 flex-1 text-sm text-foreground\">{children}</span>\n {when ? (\n <span className=\"shrink-0 text-xs text-muted-foreground tabular-nums\">\n {when}\n </span>\n ) : null}\n </li>\n );\n}\n\n/** One row of the details list. */\nfunction Detail({ label, children }: { label: string; children: ReactNode }) {\n return (\n <div className=\"flex items-baseline justify-between gap-3 py-1.5 text-sm\">\n <dt className=\"shrink-0 text-muted-foreground\">{label}</dt>\n <dd className=\"min-w-0 truncate text-right font-medium text-foreground\">\n {children}\n </dd>\n </div>\n );\n}\n\n/**\n * Save a change. `basis` names the stored values the change was made from,\n * such as the answers as they were when editing began: if the submission no\n * longer holds them when the change would be sent, someone else changed them\n * since, and the change is refused rather than written over theirs.\n */\ntype Save = (\n changes: Record<string, unknown>,\n basis?: Record<string, unknown>\n) => Promise<SubmissionDocument | undefined>;\n\n/** The bar: where it sits in its list, newer and older, and its actions. */\nfunction SubmissionBar({\n place,\n go,\n canUpdate,\n canDelete,\n status,\n save,\n submission,\n setConfirmDelete,\n}: {\n place: ListPlace | undefined;\n go: (target: string | undefined) => void;\n canUpdate: boolean;\n canDelete: boolean;\n status: string;\n save: Save;\n submission: SubmissionDocument;\n setConfirmDelete: (open: boolean) => void;\n}): JSX.Element {\n return (\n <TopBarPlace name=\"end\">\n <div className=\"ml-auto flex min-w-0 items-center gap-1 sm:gap-2\">\n {place && place.total > 0 ? (\n <span className=\"hidden whitespace-nowrap text-xs text-muted-foreground tabular-nums lg:inline\">\n {place.index + 1} of {place.total}\n </span>\n ) : null}\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Newer submission\"\n disabled={!place?.newer}\n onClick={() => go(place?.newer)}\n >\n <ChevronUp className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n <Button\n type=\"button\"\n variant=\"ghost\"\n size=\"icon\"\n aria-label=\"Older submission\"\n disabled={!place?.older}\n onClick={() => go(place?.older)}\n >\n <ChevronDown className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n {canUpdate ? (\n <>\n <span\n aria-hidden=\"true\"\n className=\"mx-1 hidden h-5 w-px bg-border sm:block\"\n />\n <Button\n type=\"button\"\n variant=\"outline\"\n className=\"hidden md:inline-flex\"\n onClick={() =>\n void save(\n status === \"spam\"\n ? { status: \"new\", spamReason: null }\n : { status: \"spam\" }\n ).catch(() => undefined)\n }\n >\n <Ban className=\"h-4 w-4\" aria-hidden=\"true\" />\n {status === \"spam\" ? \"Not spam\" : \"Spam\"}\n </Button>\n <Button\n type=\"button\"\n variant=\"outline\"\n className=\"hidden md:inline-flex\"\n disabled={status === \"archived\"}\n onClick={() =>\n void save({ status: \"archived\" }).catch(() => undefined)\n }\n >\n <Archive className=\"h-4 w-4\" aria-hidden=\"true\" />\n Archive\n </Button>\n </>\n ) : null}\n <DropdownMenu>\n <DropdownMenuTrigger asChild>\n <Button type=\"button\" variant=\"ghost\" size=\"icon\" aria-label=\"More\">\n <MoreHorizontal className=\"h-4 w-4\" aria-hidden=\"true\" />\n </Button>\n </DropdownMenuTrigger>\n <DropdownMenuContent align=\"end\">\n <DropdownMenuItem\n onSelect={() =>\n void navigator.clipboard?.writeText(submission.id)\n }\n >\n <Copy className=\"h-4 w-4\" aria-hidden=\"true\" />\n Copy submission ID\n </DropdownMenuItem>\n {canDelete ? (\n <>\n <DropdownMenuSeparator />\n <DropdownMenuItem\n className=\"text-destructive focus:text-destructive\"\n onSelect={() => setConfirmDelete(true)}\n >\n <Trash2 className=\"h-4 w-4\" aria-hidden=\"true\" />\n Delete submission\n </DropdownMenuItem>\n </>\n ) : null}\n </DropdownMenuContent>\n </DropdownMenu>\n </div>\n </TopBarPlace>\n );\n}\n\n/**\n * One answer while editing: an input where staff may change it, and the\n * answer as the page reads it where they may not (a file, a hidden value,\n * a consent, which keeps what the visitor agreed to).\n */\nfunction EditableAnswer({\n field,\n asked,\n value,\n onChange,\n}: {\n field: FormField;\n asked: AskedSnapshot;\n value: unknown;\n onChange: (value: unknown) => void;\n}): JSX.Element {\n if (field.type === \"consent\") return <ConsentValue value={value} editing />;\n if (canEditAnswer(field.type)) {\n return <FieldValueInput field={field} value={value} onChange={onChange} />;\n }\n const shown = asked.fields.find(each => each.name === field.name) ?? {\n name: field.name,\n label: field.label || field.name,\n type: field.type,\n };\n return (\n <div className=\"text-sm\">\n <AnswerValue field={shown} value={value} />\n </div>\n );\n}\n\n/**\n * Whether a draft changes any of the form's answers. Compared by value, so an\n * answer only re-typed to what it was is no change.\n */\nfunction answersChanged(\n fields: readonly FormField[],\n stored: Record<string, unknown> | undefined,\n draft: Record<string, unknown>\n): boolean {\n const before = stored ?? {};\n return fields.some(\n field =>\n JSON.stringify(before[field.name] ?? null) !==\n JSON.stringify(draft[field.name] ?? null)\n );\n}\n\n/**\n * What on the page holds unsaved work, by key: each part reports to the\n * admin's leave guard and to the page, which asks before handing off a\n * deleted submission.\n */\nconst UnsavedOnPage = createContext<(key: string, unsaved: boolean) => void>(\n () => undefined\n);\n\n/** The page's record of which of its parts hold unsaved work. */\nfunction useUnsavedOnPage() {\n const held = useRef(new Map<string, boolean>());\n const report = useCallback((key: string, unsaved: boolean) => {\n held.current.set(key, unsaved);\n }, []);\n const any = useCallback(() => [...held.current.values()].some(Boolean), []);\n return { report, any };\n}\n\n/** Report unsaved work to the admin's leave guard and to the page. */\nfunction useHoldPage(key: string, unsaved: boolean): void {\n useReportUnsavedWork(key, unsaved);\n const report = useContext(UnsavedOnPage);\n useEffect(() => {\n report(key, unsaved);\n return () => report(key, false);\n }, [report, key, unsaved]);\n}\n\n/** The keys this page reports its unsaved answers and note under. */\nconst UNSAVED_ANSWERS_KEY = \"forms.submission-answers\";\nconst UNSAVED_NOTE_KEY = \"forms.submission-note\";\n\n/**\n * Editing a submission's answers: the draft, the answers it is made from, and\n * saving it. An answer changed and not yet saved holds the page until it is\n * saved or the person cancels. Edits made while a save is on its way stay\n * open, now made from the answers just saved.\n */\nfunction useAnswerDraft(\n submission: SubmissionDocument,\n fields: readonly FormField[],\n save: Save\n) {\n const [editing, setEditing] = useState(false);\n const [draft, setDraft] = useState<Record<string, unknown>>({});\n const draftNow = useRef(draft);\n draftNow.current = draft;\n // The answers as they were when editing began, which the draft is made from.\n const [basis, setBasis] = useState<Record<string, unknown> | null>(null);\n const changed = editing && answersChanged(fields, basis ?? undefined, draft);\n useHoldPage(UNSAVED_ANSWERS_KEY, changed);\n // Another submission opened: not editing it.\n useEffect(() => setEditing(false), [submission.id]);\n const begin = () => {\n setDraft(submission.data ?? {});\n setBasis(submission.data ?? null);\n setEditing(true);\n };\n const change = (name: string, value: unknown) =>\n setDraft(current => ({ ...current, [name]: value }));\n const afterSave = (\n sent: Record<string, unknown>,\n stored: SubmissionDocument | undefined\n ) => {\n if (JSON.stringify(draftNow.current) === JSON.stringify(sent)) {\n setEditing(false);\n } else {\n // Later edits are now made from the answers as the server stored them.\n setBasis(stored?.data ?? sent);\n }\n };\n const saveDraft = () => {\n // Saving untouched answers writes nothing: a `data` write stamps the\n // submission as edited in the admin.\n if (!answersChanged(fields, submission.data, draft)) {\n setEditing(false);\n return;\n }\n const sent = draft;\n void save({ data: sent }, { data: basis })\n .then(stored => afterSave(sent, stored))\n .catch(() => undefined);\n };\n return {\n editing,\n draft,\n begin,\n change,\n saveDraft,\n cancel: () => setEditing(false),\n };\n}\n\n/** The answers as inputs, and saving or leaving them. */\nfunction AnswerEditor({\n fields,\n asked,\n draft,\n}: {\n fields: readonly FormField[];\n asked: AskedSnapshot;\n draft: ReturnType<typeof useAnswerDraft>;\n}): JSX.Element {\n return (\n <div className=\"space-y-4 px-4 py-4 sm:px-5\">\n {fields.map(field => (\n <div key={field.name} className=\"space-y-1.5\">\n <label\n htmlFor={`submission-field-${field.name}`}\n className=\"text-sm font-medium text-foreground\"\n >\n {field.label || field.name}\n </label>\n <EditableAnswer\n field={field}\n asked={asked}\n value={draft.draft[field.name]}\n onChange={value => draft.change(field.name, value)}\n />\n </div>\n ))}\n <div className=\"flex justify-end gap-2 pt-2\">\n <Button type=\"button\" variant=\"outline\" onClick={draft.cancel}>\n Cancel\n </Button>\n <Button type=\"button\" onClick={draft.saveDraft}>\n Save answers\n </Button>\n </div>\n </div>\n );\n}\n\n/** The answers, as asked, and editing them for a person who may. */\nfunction AnswersBox({\n submission,\n form,\n asked,\n canUpdate,\n save,\n}: {\n submission: SubmissionDocument;\n form: FormDocument | null | undefined;\n asked: AskedSnapshot;\n canUpdate: boolean;\n save: Save;\n}): JSX.Element {\n const fields = form?.fields ?? [];\n const draft = useAnswerDraft(submission, fields, save);\n return (\n <Box\n title=\"Answers\"\n action={\n canUpdate && !draft.editing ? (\n <Button type=\"button\" variant=\"ghost\" size=\"sm\" onClick={draft.begin}>\n <Pencil className=\"h-3.5 w-3.5\" aria-hidden=\"true\" />\n Edit answers\n </Button>\n ) : null\n }\n >\n {draft.editing ? (\n <AnswerEditor fields={fields} asked={asked} draft={draft} />\n ) : (\n <SubmissionAnswers asked={asked} data={submission.data ?? {}} />\n )}\n </Box>\n );\n}\n\n/** What has happened to it: submitted, flagged, edited, its status. */\nfunction ActivityBox({\n submission,\n status,\n}: {\n submission: SubmissionDocument;\n status: string;\n}): JSX.Element {\n return (\n <Box title=\"Activity\">\n <ol className=\"px-4 py-2 sm:px-5\">\n <ActivityItem\n icon={<Inbox className=\"h-3 w-3\" />}\n when={whenText(submission.submittedAt ?? submission.createdAt)}\n >\n Submitted\n {submission.spamReason ? (\n <span className=\"block text-xs text-muted-foreground\">\n Flagged as spam ({submission.spamReason})\n </span>\n ) : null}\n </ActivityItem>\n {submission.editedAt ? (\n <ActivityItem\n icon={<Pencil className=\"h-3 w-3\" />}\n when={whenText(submission.editedAt)}\n >\n Answers edited in the admin\n </ActivityItem>\n ) : null}\n {status !== \"new\" ? (\n <ActivityItem icon={<Eye className=\"h-3 w-3\" />}>\n Now {STATUSES.find(each => each.value === status)?.label ?? status}\n </ActivityItem>\n ) : null}\n </ol>\n </Box>\n );\n}\n\n/**\n * The internal note as the person types it, saved when they leave it.\n *\n * Each save names the note it replaces: the stored note, or, while one of the\n * page's own note saves is on its way, the text that save will store. A newer\n * stored note replaces the textarea only while it holds nothing typed since\n * the stored note it last showed, so text typed while a save is on its way is\n * never wiped by that save's answer. \"Saved\" is said only of the text saved.\n */\nfunction useNoteDraft(submission: SubmissionDocument, save: Save) {\n const stored = submission.notes ?? \"\";\n const [note, setNote] = useState(stored);\n const [savedText, setSavedText] = useState<string | null>(null);\n const noteNow = useRef(note);\n noteNow.current = note;\n const storedNow = useRef(stored);\n storedNow.current = stored;\n // The stored note the textarea's text was last brought up to.\n const shown = useRef(stored);\n // The stored note once the page's own saves have landed, and how many are on their way.\n const expected = useRef<string>(stored);\n const pending = useRef(0);\n // The note as stored before the page's current run of note saves: what a\n // retry replaces if the whole run fails, since a failed save drops the\n // saves queued behind it.\n const runBasis = useRef<string>(stored);\n const openedId = useRef(submission.id);\n useEffect(() => {\n // Another submission opened: its note, nothing typed, nothing saved.\n if (openedId.current !== submission.id) {\n openedId.current = submission.id;\n setNote(stored);\n shown.current = stored;\n expected.current = stored;\n setSavedText(null);\n return;\n }\n // Text typed since the stored note last shown keeps its basis: a newer\n // stored note, someone else's, must not become what the draft replaces.\n const typed = noteNow.current !== shown.current;\n if (!typed) setNote(stored);\n shown.current = stored;\n // \"Saved\" names text the server holds; a newer stored note ends that.\n setSavedText(current => (current === stored ? current : null));\n if (pending.current === 0 && !typed) expected.current = stored;\n }, [submission.id, stored]);\n const saveNote = () => {\n const text = noteNow.current;\n if (text === expected.current) return;\n const basis = expected.current;\n if (pending.current === 0) runBasis.current = basis;\n expected.current = text;\n pending.current += 1;\n void save({ notes: text }, { notes: basis })\n .then(() => {\n setSavedText(text);\n runBasis.current = text;\n })\n .catch(() => {\n // Not stored: once no other note save is on its way, the next one\n // replaces the note as stored before this run of saves, so leaving\n // the note retries it, and is refused again if someone else changed\n // the note meanwhile.\n if (pending.current === 1) expected.current = runBasis.current;\n })\n .finally(() => {\n pending.current -= 1;\n });\n };\n // A note typed and not yet stored holds the page: leaving it asks first.\n useHoldPage(UNSAVED_NOTE_KEY, note !== stored && note !== savedText);\n return {\n note,\n edit: (text: string) => {\n setNote(text);\n // Taking the note as stored now, after someone else changed it, makes\n // it the note a further edit replaces; a draft made from the older\n // note is still refused until then.\n if (pending.current === 0 && text === storedNow.current) {\n expected.current = text;\n runBasis.current = text;\n shown.current = text;\n }\n },\n saveNote,\n saved: savedText !== null && savedText === note,\n };\n}\n\n/**\n * The fields a host added to submissions, with their values. Read-only: the\n * page draws no editor for a field it does not know, so they are changed\n * through the API, as the documentation says.\n */\nfunction ExtensionDetails({\n submission,\n fields,\n}: {\n submission: SubmissionDocument;\n fields: readonly SubmissionExtensionField[];\n}): JSX.Element | null {\n const values = submission as unknown as Record<string, unknown>;\n return (\n <>\n {fields.map(field => (\n <Detail key={field.name} label={field.label}>\n {formatExportValue(values[field.name]) || \"—\"}\n </Detail>\n ))}\n </>\n );\n}\n\n/**\n * The page a submission was sent from, by its path, linking to it. Read again\n * through the rule that kept it, so a value written some other way (the API)\n * is linked only when it is a web page.\n */\nfunction SentFrom({\n page,\n}: {\n page: string | null | undefined;\n}): JSX.Element | null {\n const address = pageFromReferer(page);\n if (!address) return null;\n return (\n <Detail label=\"Page\">\n <a\n href={address}\n target=\"_blank\"\n rel=\"noopener noreferrer\"\n title={address}\n className=\"underline decoration-muted-foreground underline-offset-4 hover:decoration-foreground\"\n >\n {new URL(address).pathname}\n </a>\n </Detail>\n );\n}\n\n/** The side panel: its status, the internal note, and its details. */\nfunction SidePanel({\n submission,\n status,\n canUpdate,\n save,\n formId,\n formName,\n formsSlug,\n extensionFields,\n}: {\n submission: SubmissionDocument;\n status: string;\n canUpdate: boolean;\n save: Save;\n formId: string | undefined;\n formName: string;\n formsSlug: string;\n extensionFields: readonly SubmissionExtensionField[];\n}): JSX.Element {\n const { note, edit, saveNote, saved } = useNoteDraft(submission, save);\n return (\n <aside\n aria-label=\"Submission details\"\n className=\"w-full shrink-0 border-t border-border bg-card @4xl/content:w-80 @4xl/content:border-t-0 @4xl/content:border-l\"\n >\n <div className=\"space-y-6 px-4 py-6 sm:px-6\">\n <section\n aria-labelledby=\"submission-status-label\"\n className=\"space-y-2\"\n >\n <h2\n id=\"submission-status-label\"\n className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\"\n >\n Status\n </h2>\n <div\n role=\"group\"\n aria-labelledby=\"submission-status-label\"\n className=\"grid grid-cols-2 gap-1.5\"\n >\n {STATUSES.map(each => (\n <button\n key={each.value}\n type=\"button\"\n aria-pressed={status === each.value}\n disabled={!canUpdate}\n onClick={() =>\n void save(\n each.value === \"spam\" || status !== \"spam\"\n ? { status: each.value }\n : { status: each.value, spamReason: null }\n ).catch(() => undefined)\n }\n className=\"flex items-center gap-2 rounded-md border border-border px-2.5 py-1.5 text-sm text-foreground hover:bg-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring disabled:cursor-not-allowed disabled:opacity-60 aria-pressed:border-foreground aria-pressed:bg-accent aria-pressed:font-medium\"\n >\n <span\n aria-hidden=\"true\"\n className={`h-2 w-2 rounded-full ${each.dot}`}\n />\n {each.label}\n </button>\n ))}\n </div>\n </section>\n\n <section className=\"space-y-2\">\n <h2 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n <label htmlFor=\"submission-note\">Internal note</label>\n </h2>\n <Textarea\n id=\"submission-note\"\n rows={4}\n value={note}\n disabled={!canUpdate}\n placeholder=\"Only people in the admin see this.\"\n onChange={event => edit(event.target.value)}\n onBlur={saveNote}\n />\n <p className=\"text-xs text-muted-foreground\" role=\"status\">\n Only people in the admin see notes.\n {saved ? \" Saved.\" : \"\"}\n </p>\n </section>\n\n <section className=\"space-y-1\">\n <h2 className=\"text-xs font-semibold uppercase tracking-wide text-muted-foreground\">\n Details\n </h2>\n <dl>\n <Detail label=\"Form\">\n {formId ? (\n <a\n className=\"underline decoration-muted-foreground underline-offset-4 hover:decoration-foreground\"\n href={`/admin/collections/${encodeURIComponent(formsSlug)}/${encodeURIComponent(formId)}`}\n >\n {formName}\n </a>\n ) : (\n formName\n )}\n </Detail>\n <Detail label=\"Submitted\">\n {whenText(submission.submittedAt ?? submission.createdAt)}\n </Detail>\n <SentFrom page={submission.page} />\n {submission.userAgent ? (\n <Detail label=\"Browser\">\n {browserLabel(submission.userAgent)}\n </Detail>\n ) : null}\n {submission.ipAddress ? (\n <Detail label=\"IP address\">\n <span className=\"font-mono\">{submission.ipAddress}</span>\n </Detail>\n ) : null}\n <Detail label=\"ID\">\n <span className=\"font-mono\">{submission.id.slice(0, 8)}</span>\n </Detail>\n <ExtensionDetails\n submission={submission}\n fields={extensionFields}\n />\n </dl>\n </section>\n </div>\n </aside>\n );\n}\n\n/** The form's name: as read, as the submission carries it, or \"Form\". */\nfunction formNameOf(\n form: FormDocument | null | undefined,\n submission: SubmissionDocument\n): string {\n if (form?.name) return form.name;\n return typeof submission.form === \"object\" && submission.form.name\n ? submission.form.name\n : \"Form\";\n}\n\n/** Who sent it, the form and when, under the sender's initials. */\nfunction SubmissionHeader({\n sender,\n formName,\n when,\n}: {\n sender: string;\n formName: string;\n when: string;\n}): JSX.Element {\n return (\n <PageHeader\n title={sender}\n media={\n <span\n aria-hidden=\"true\"\n className=\"flex h-11 w-11 shrink-0 items-center justify-center rounded-full border border-border bg-card text-sm font-semibold text-foreground\"\n >\n {initialsOf(sender)}\n </span>\n }\n description={\n <span className=\"text-sm text-muted-foreground\">\n {formName} · {when}\n </span>\n }\n />\n );\n}\n\n/** What a refused change said, above the answers. */\nfunction ChangeError({ error }: { error: string | null }): JSX.Element | null {\n if (!error) return null;\n return (\n <Alert variant=\"destructive\">\n <AlertDescription>{error}</AlertDescription>\n </Alert>\n );\n}\n\n/** Asks before a submission is deleted for good. */\nfunction DeleteDialog({\n open,\n onOpenChange,\n onConfirm,\n}: {\n open: boolean;\n onOpenChange: (open: boolean) => void;\n onConfirm: () => void;\n}): JSX.Element {\n return (\n <AlertDialog open={open} onOpenChange={onOpenChange}>\n <AlertDialogContent>\n <AlertDialogHeader>\n <AlertDialogTitle>Delete this submission?</AlertDialogTitle>\n <AlertDialogDescription>\n Its answers are deleted, and the emails it sent are forgotten, for\n good.\n </AlertDialogDescription>\n </AlertDialogHeader>\n <AlertDialogFooter>\n <AlertDialogCancel>Keep it</AlertDialogCancel>\n <AlertDialogAction variant=\"destructive\" onClick={onConfirm}>\n Delete submission\n </AlertDialogAction>\n </AlertDialogFooter>\n </AlertDialogContent>\n </AlertDialog>\n );\n}\n\n/** The list the page was opened from, as its address carries it. */\nfunction initialScope(): SubmissionListScope {\n return typeof window === \"undefined\"\n ? { status: \"all\" }\n : scopeFromSearch(window.location.search);\n}\n\n/**\n * Moving from this page: to a newer or older submission of its list, or,\n * once it is deleted, to the next one there or the list itself.\n */\nfunction usePageMoves(\n slug: string,\n scope: SubmissionListScope,\n place: ListPlace | undefined,\n remove: () => Promise<boolean>,\n leaveDeleted: { onDelete?: () => void; holdsUnsaved: () => boolean }\n) {\n const [confirmDelete, setConfirmDelete] = useState(false);\n const go = (target: string | undefined) => {\n if (target) navigateTo(submissionPageAddress(slug, target, scope));\n };\n const deleteSubmission = async () => {\n setConfirmDelete(false);\n if (!(await remove())) return;\n // With unsaved work on the page the admin's leave guard would ask about a\n // submission that no longer exists, so the page hands the move to the\n // host, which stands the guard down and returns to the list.\n if (leaveDeleted.holdsUnsaved() && leaveDeleted.onDelete) {\n leaveDeleted.onDelete();\n return;\n }\n const next = place?.older ?? place?.newer;\n navigateTo(\n next\n ? submissionPageAddress(slug, next, scope)\n : `/admin/collections/${encodeURIComponent(slug)}`\n );\n };\n return { go, confirmDelete, setConfirmDelete, deleteSubmission };\n}\n\n/**\n * What this person may do with this submission: nothing until it is known,\n * and nothing while a colleague holds it.\n */\nfunction useSubmissionRights(slug: string, held: boolean) {\n const { data: has } = useQuery({\n queryKey: [\"plugin-form-builder\", \"me-permissions\"],\n queryFn: readPermissions,\n retry: false,\n });\n return {\n known: has !== undefined,\n canUpdate: !held && has?.(`update-${slug}`) === true,\n canDelete: !held && has?.(`delete-${slug}`) === true,\n // Resend asks both: updating submissions and managing email deliveries.\n canResend:\n !held &&\n has?.(`update-${slug}`) === true &&\n has?.(\"manage-email-deliveries\") === true,\n };\n}\n\n/** What the page shows of a submission: its answers as asked, who, when. */\nfunction describe(\n submission: SubmissionDocument,\n form: FormDocument | null | undefined\n) {\n const data = submission.data ?? {};\n const asked = askedFieldsOf(submission.asked, form?.fields ?? [], data);\n return {\n asked,\n sender: senderOf(asked.fields, data) ?? \"Submission\",\n formName: formNameOf(form, submission),\n when: whenText(submission.submittedAt ?? submission.createdAt),\n };\n}\n\n/** The form a submission belongs to, read from its collection. */\nfunction useSubmissionForm(submission: SubmissionDocument | undefined) {\n const formId = submission ? formIdOf(submission) : undefined;\n // The forms collection as this install names it; a host may rename it.\n const formsSlug = useBuilderConfig().formsSlug ?? \"forms\";\n const { data: form } = useQuery({\n enabled: formId !== undefined,\n queryKey: [\"form-builder\", \"submission-form\", formsSlug, formId],\n queryFn: async () => {\n const body = await readJson<FormDocument & { item?: FormDocument }>(\n `/admin/api/collections/${encodeURIComponent(formsSlug)}/entries/${encodeURIComponent(formId!)}`\n );\n return body ? (body.item ?? body) : null;\n },\n });\n return { formId, form, formsSlug };\n}\n\n/** What the page says when reading the submission again failed. */\nconst REFRESH_FAILED_MESSAGE =\n \"This submission could not be read again, so it may have changed since. Your edits are kept.\";\n\n/** What a change the server never received says; the page keeps the draft. */\nconst UNSENT_MESSAGE =\n \"The change could not be sent. Check your connection and try again.\";\n\n/** What a change held by a colleague's claim says. */\nconst HELD_MESSAGE =\n \"Someone else is editing this submission. Your change was not saved.\";\n\n/** Whether the stored submission still holds the values a change was made from. */\nfunction holdsBasis(\n stored: SubmissionDocument | undefined,\n basis: Record<string, unknown> | undefined\n): boolean {\n if (basis === undefined) return true;\n const now = (stored ?? {}) as Record<string, unknown>;\n // Nothing, null and empty text are one value: a cleared note is stored as\n // \"\" and read back as either.\n const plain = (value: unknown) =>\n JSON.stringify(value === undefined || value === \"\" ? null : value);\n return Object.entries(basis).every(\n ([name, value]) => plain(now[name]) === plain(value)\n );\n}\n\n/**\n * Saving and deleting this submission, and what a refusal said. Saves run one\n * at a time, each with the version the one before it left: a note saved on\n * blur and the status button clicked straight after would otherwise both send\n * the version the page was read at, and the second be refused as stale.\n *\n * Taking the newest version is only safe for the page's own saves, so a\n * change made from stored values (an answer draft, a note) names them, and is\n * refused when someone else has changed them since. A refused save also drops\n * every save queued behind it: those were asked for on a page that was\n * already behind, and the refusal's message stays until the person acts\n * again. While a colleague holds the submission every write is refused here,\n * before it is sent, judged when it would run rather than when it was asked\n * for.\n */\nfunction useSubmissionWrites(slug: string, id: string, held: boolean) {\n const queryClient = useQueryClient();\n const [error, setError] = useState<string | null>(null);\n const heldNow = useRef(held);\n heldNow.current = held;\n const queue = useRef<Promise<unknown>>(Promise.resolve());\n // Moves on at each refusal; a save asked for in an earlier one is dropped.\n const generation = useRef(0);\n const address = `/admin/api/collections/${encodeURIComponent(slug)}/entries/${encodeURIComponent(id)}`;\n const key = [\"form-builder\", \"submission\", slug, id];\n /** Refuse with `message`, dropping every save queued behind this one. */\n const refuse = (message: string): never => {\n generation.current += 1;\n setError(message);\n throw new Error(message);\n };\n const refresh = async () => {\n await queryClient.invalidateQueries({ queryKey: key });\n void queryClient.invalidateQueries({\n queryKey: [\"form-builder\", \"submission-place\", slug],\n });\n void queryClient.invalidateQueries({ queryKey: SUBMISSION_COUNT_KEY });\n };\n /** Send a change, with the version the page holds now. */\n const send: Save = async (changes, basis) => {\n if (heldNow.current) refuse(HELD_MESSAGE);\n const stored = queryClient.getQueryData<SubmissionDocument>(key);\n if (!holdsBasis(stored, basis)) refuse(CHANGED_SINCE_MESSAGE);\n setError(null);\n const rev = stored?._rev;\n const response = await fetch(address, {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n body: JSON.stringify(\n typeof rev === \"string\" ? { ...changes, _rev: rev } : changes\n ),\n }).catch(() => refuse(UNSENT_MESSAGE));\n if (!response.ok) {\n const message = refusalMessage(await response.json().catch(() => null));\n await refresh();\n refuse(message);\n }\n // The saved submission, as the update answered it, is what the page\n // holds now, before reading it again: a read that then fails must not\n // leave the page holding the version this save replaced.\n const saved = (await response.json().catch(() => null)) as {\n item?: SubmissionDocument;\n } | null;\n if (saved?.item?.id === id) queryClient.setQueryData(key, saved.item);\n await refresh();\n // What the server stored, which may differ from what was sent: answers\n // are trimmed and cleaned as they are saved.\n return saved?.item?.id === id ? saved.item : undefined;\n };\n /**\n * Save a change once every change asked for before it has settled, unless\n * one of those was refused.\n */\n const save: Save = (changes, basis) => {\n const asked = generation.current;\n const run = queue.current.then(() =>\n asked === generation.current\n ? send(changes, basis)\n : Promise.reject(new Error(\"Dropped behind a refused change.\"))\n );\n queue.current = run.catch(() => undefined);\n return run;\n };\n /** Delete it, answering whether it went. */\n const remove = async (): Promise<boolean> => {\n if (heldNow.current) {\n setError(HELD_MESSAGE);\n return false;\n }\n const response = await fetch(address, {\n method: \"DELETE\",\n credentials: \"include\",\n }).catch(() => null);\n if (!response?.ok) {\n setError(\"The submission could not be deleted.\");\n return false;\n }\n void queryClient.invalidateQueries({ queryKey: SUBMISSION_COUNT_KEY });\n return true;\n };\n return { save, remove, error };\n}\n\n/**\n * Opening a New submission reads it: it is marked Read for a person who may\n * update it. Judged once per submission opened, as it was when opened, so a\n * person who then sets it back to New keeps it New. The save is read through\n * a ref, so the effect runs on what changes, not on each render's function.\n */\nfunction useReadOnOpen(\n submission: SubmissionDocument | undefined,\n permissionsKnown: boolean,\n canUpdate: boolean,\n save: Save\n): void {\n const saveNow = useRef(save);\n saveNow.current = save;\n const openedOnce = useRef<string | null>(null);\n const openedId = submission?.id;\n const statusWhenOpened = useRef<string | undefined>(undefined);\n if (openedId && openedOnce.current !== openedId) {\n statusWhenOpened.current = submission?.status;\n }\n useEffect(() => {\n if (!openedId || !permissionsKnown) return;\n if (openedOnce.current === openedId) return;\n openedOnce.current = openedId;\n if (canUpdate && statusWhenOpened.current === \"new\") {\n void saveNow.current({ status: \"read\" }).catch(() => undefined);\n }\n }, [openedId, permissionsKnown, canUpdate]);\n}\n\n/** The page while the submission is read, or why it could not be. */\nfunction PageMessage({ error }: { error?: Error | null }): JSX.Element {\n return (\n <PageFrame type=\"editor\">\n {error ? (\n <div className=\"p-6\">\n <Alert variant=\"destructive\">\n <AlertDescription>{error.message}</AlertDescription>\n </Alert>\n </div>\n ) : (\n <p role=\"status\" className=\"p-6 text-sm text-muted-foreground\">\n Loading submission…\n </p>\n )}\n </PageFrame>\n );\n}\n\n/** The submission the page is of, and its collection, as the host names them. */\nfunction pageTarget(\n collectionSlug: string | undefined,\n entryId: string | undefined,\n initialData: Partial<SubmissionDocument> | undefined\n): { slug: string; id: string } {\n return {\n slug: collectionSlug ?? \"form-submissions\",\n id: entryId ?? initialData?.id ?? \"\",\n };\n}\n\n/** Whether a colleague's claim withholds every change. */\nfunction heldBy(lock: DocumentLockGates | undefined): boolean {\n return lock?.readOnly === true || lock?.actionsDisabled === true;\n}\n\n/** What the page says above the answers: a refused change, or a failed refresh. */\nfunction pageNotice(error: string | null, readError: unknown): string | null {\n if (error) return error;\n return readError ? REFRESH_FAILED_MESSAGE : null;\n}\n\nexport function SubmissionPage({\n entryId,\n collectionSlug,\n initialData,\n documentLock,\n onDelete,\n}: SubmissionPageProps): JSX.Element {\n const { slug, id } = pageTarget(collectionSlug, entryId, initialData);\n const [scope] = useState<SubmissionListScope>(initialScope);\n const { data: submission, error: readError } = useSubmission(\n slug,\n id,\n initialData\n );\n const { formId, form, formsSlug } = useSubmissionForm(submission);\n const extensionFields = useSubmissionFields();\n const held = heldBy(documentLock);\n const { known, canUpdate, canDelete, canResend } = useSubmissionRights(\n slug,\n held\n );\n const { data: place } = useListPlace(slug, submission, scope);\n const { save, remove, error } = useSubmissionWrites(slug, id, held);\n useReadOnOpen(submission, known, canUpdate, save);\n const unsaved = useUnsavedOnPage();\n const { go, confirmDelete, setConfirmDelete, deleteSubmission } =\n usePageMoves(slug, scope, place, remove, {\n onDelete,\n holdsUnsaved: unsaved.any,\n });\n\n // Only a submission never read replaces the page. A refresh that fails\n // after it was read keeps the page, and every draft on it, and says so.\n if (!submission) return <PageMessage error={readError} />;\n\n const { asked, sender, formName, when } = describe(submission, form);\n const status = submission.status;\n\n return (\n <UnsavedOnPage.Provider value={unsaved.report}>\n <SubmissionBar\n place={place}\n go={go}\n canUpdate={canUpdate}\n canDelete={canDelete}\n status={status}\n save={save}\n submission={submission}\n setConfirmDelete={setConfirmDelete}\n />\n <DeleteDialog\n open={confirmDelete}\n onOpenChange={setConfirmDelete}\n onConfirm={() => void deleteSubmission()}\n />\n\n <PageFrame type=\"editor\">\n {/* Beside the answers once the admin's content area, not the window,\n is wide enough: its sidebars take a varying share of the window,\n and a viewport breakpoint at 1024px left the answers a column\n narrower than the details. */}\n <div className=\"flex min-h-full flex-col @4xl/content:flex-row\">\n <div className=\"min-w-0 flex-1 space-y-5 px-4 py-6 sm:px-6 @4xl/content:px-8\">\n <SubmissionHeader sender={sender} formName={formName} when={when} />\n <ChangeError error={pageNotice(error, readError)} />\n\n <AnswersBox\n submission={submission}\n form={form}\n asked={asked}\n canUpdate={canUpdate}\n save={save}\n />\n\n <Box title=\"Emails\">\n <SubmissionEmails\n submissionId={submission.id}\n status={status}\n canResend={canResend}\n />\n </Box>\n\n <ActivityBox submission={submission} status={status} />\n </div>\n\n <SidePanel\n submission={submission}\n status={status}\n canUpdate={canUpdate}\n save={save}\n formId={formId}\n formName={formName}\n formsSlug={formsSlug}\n extensionFields={extensionFields}\n />\n </div>\n </PageFrame>\n </UnsavedOnPage.Provider>\n );\n}\n","/**\n * Marking a submission read when it is opened from the New form submissions\n * card: the status the submissions list sets, through the same collection\n * update the list's own status changes are sent through, as the reader.\n *\n * Sent as an update of the submissions that match the id AND are still new,\n * so a submission someone archived, flagged or read since the card loaded is\n * matched by nothing and keeps its status. The update is judged as every\n * update of the collection is (`update-<slug>` and its rule): a reader who\n * may not update submissions is refused, and the submission stays new.\n *\n * @module dashboard/mark-read\n */\n\nimport { NEW_STATUS } from \"./new-submissions\";\n\n/** The status an opened submission moves to, as the submissions list names it. */\nconst READ_STATUS = \"read\";\n\n/** The admin's API root, which the submissions list sends its updates to. */\nconst ADMIN_API = \"/admin/api\";\n\n/** The request that marks submission `id` of `collection` read while it is still new. */\nexport function markReadRequest(\n collection: string,\n id: string\n): { url: string; init: RequestInit } {\n return {\n url: `${ADMIN_API}/collections/${encodeURIComponent(collection)}/entries`,\n init: {\n method: \"PATCH\",\n headers: { \"Content-Type\": \"application/json\" },\n credentials: \"include\",\n // The row's link navigates away at once; kept alive, the update is\n // still sent when that navigation leaves the page.\n keepalive: true,\n // An update by query writes one value to whatever matches, with no\n // version of its own for each document, so it says it sets the status\n // whatever is stored: the `where` above is what keeps it to a\n // submission still new.\n body: JSON.stringify({\n where: { id: { equals: id }, status: { equals: NEW_STATUS } },\n data: { status: READ_STATUS, _overwrite: true },\n }),\n },\n };\n}\n\n/**\n * Marks submission `id` read, answering whether the update was taken. A\n * refusal or a failed request answers `false` and leaves it new, which the\n * card and the list then still say; it never rejects.\n */\nexport async function markRead(\n collection: string,\n id: string\n): Promise<boolean> {\n const { url, init } = markReadRequest(collection, id);\n try {\n return (await fetch(url, init)).ok;\n } catch {\n return false;\n }\n}\n","\"use client\";\n\n/**\n * The New form submissions card's body: the newest new submissions as core's\n * list rows, each opening its submission and marking it read, with the\n * card's footer leading to every submission, and how many are new beside its\n * title. While nothing is new it says so, with a link to every submission,\n * and its title carries no count.\n *\n * Drawn only with data: the frame draws loading and every failure itself.\n * The empty state is drawn here rather than by the frame, because its link\n * is the submissions collection the site installed, which only the card's\n * data knows; the frame's would name the slug the plugin declared. Built from\n * the widget kit alone, so it reads like core's list cards beside it.\n *\n * @module dashboard/NewSubmissionsCard\n */\n\nimport {\n formatWidgetTime,\n FrameState,\n useRefreshDashboard,\n useWidgetClock,\n Widget,\n} from \"@nextlyhq/plugin-sdk/admin\";\nimport type { JSX, MouseEvent } from \"react\";\n\nimport { markRead } from \"./mark-read\";\nimport type { NewSubmissionRow, NewSubmissionsData } from \"./new-submissions\";\n\n/** The mouse button that opens a link in a new tab. */\nconst MIDDLE_BUTTON = 1;\n\n/** What a row says under its title: its form, and the first line of its first text answer. */\nfunction metaOf(row: NewSubmissionRow): readonly string[] {\n return [row.form, row.excerpt].filter(\n (part): part is string => part !== undefined\n );\n}\n\n/** The row whose link a click landed on, if it landed on one. */\nfunction openedRow(\n event: MouseEvent,\n rows: readonly NewSubmissionRow[]\n): NewSubmissionRow | undefined {\n if (!(event.target instanceof Element)) return undefined;\n const href = event.target.closest(\"a[href]\")?.getAttribute(\"href\");\n return href === null || href === undefined\n ? undefined\n : rows.find(row => row.href === href);\n}\n\n/**\n * Nothing new, with a way to every submission. Offered to whoever sees the\n * card: the card is shown only to a reader who may read the submissions\n * collection the site installed, which is where the link leads.\n */\nfunction NothingNew({ allHref }: { allHref: string }): JSX.Element {\n return (\n <FrameState\n state={{\n kind: \"empty\",\n empty: {\n title: \"No new submissions\",\n action: { label: \"All submissions\", href: allHref },\n },\n }}\n />\n );\n}\n\n/** The card's rows: its reader's \"Number of entries\", the rows its `load` read. */\nexport function NewSubmissionsCard({\n data,\n settings,\n}: {\n data: NewSubmissionsData;\n /** The card's settings as its `load` read them; `limit` is its \"Number of entries\". */\n settings?: Record<string, unknown>;\n}): JSX.Element {\n // One instant for every row, moved on each minute while the card is\n // mounted, so \"just now\" becomes \"1m ago\" without the card being reloaded.\n const now = useWidgetClock();\n const refresh = useRefreshDashboard();\n // The title carries how many are new, the total the header's part counts\n // from the same read (\"1000+\" where that is a floor); nothing while none\n // are, as the empty state says so.\n Widget.useTitleCount(\n data.total > 0 ? data.total : undefined,\n data.atLeast === true\n );\n if (data.total === 0) return <NothingNew allHref={data.allHref} />;\n // Opening a row, by a click or a middle click into a new tab, marks its\n // submission read; a click anywhere else in the card does nothing. Once it\n // is taken, the cards and the header's count are asked again: a dashboard\n // left open behind a new tab would otherwise go on listing it.\n const opened = (event: MouseEvent) => {\n const row = openedRow(event, data.items);\n if (row === undefined) return;\n void markRead(data.collection, row.id).then(taken => {\n if (taken) refresh();\n });\n };\n return (\n // Laid out as if absent (`contents`), so the list sits in the card's body\n // exactly as it would alone; it is here to hear a row being opened.\n <div\n className=\"contents\"\n onClickCapture={opened}\n onAuxClickCapture={event => {\n if (event.button === MIDDLE_BUTTON) opened(event);\n }}\n >\n <Widget.List\n rows={data.items}\n title={row => row.title}\n href={row => row.href}\n meta={metaOf}\n trailing={row =>\n row.at === undefined ? undefined : (\n <time dateTime={row.at}>{formatWidgetTime(row.at, now)}</time>\n )\n }\n viewAllHref={data.allHref}\n // How many there are in all, so the footer reads \"View all (N)\" once the\n // card shows fewer than there are.\n total={{\n count: data.total,\n ...(data.atLeast === true ? { atLeast: true } : {}),\n }}\n // Its reader's \"Number of entries\": the list draws the rows the load\n // read, by the rule the load read them by.\n limit={typeof settings?.limit === \"number\" ? settings.limit : undefined}\n />\n </div>\n );\n}\n","/**\n * Form Builder Admin Components\n *\n * Entry point for admin UI components used by the Nextly admin panel.\n * These components are referenced via component paths in collection configs.\n *\n * Component paths use the format: \"@nextlyhq/plugin-form-builder/admin#ExportName\"\n *\n * ## Auto-Registration\n *\n * This module automatically registers its components with the admin panel\n * when imported. No manual registration is required - just add the plugin\n * to your Nextly config and the admin will detect and load the components.\n *\n * @module admin\n * @since 0.1.0\n */\n\n// ============================================================================\n// Main View Components\n// ============================================================================\n\n// ============================================================================\n// Component Registration\n// ============================================================================\n\nimport {\n registerComponents,\n type ComponentPath,\n} from \"@nextlyhq/plugin-sdk/admin\";\n\nimport { FORM_EDITOR_VIEW } from \"../form-pages\";\n\nimport { SubmissionsView } from \"./components/submissions/SubmissionsView\";\nimport { FormBuilderView } from \"./FormBuilderView\";\nimport { SubmissionPage } from \"./SubmissionPage\";\n\nexport { FormBuilderView, type FormBuilderViewProps } from \"./FormBuilderView\";\nexport { SubmissionsView } from \"./components/submissions/SubmissionsView\";\nexport { SubmissionPage, type SubmissionPageProps } from \"./SubmissionPage\";\n\n// ============================================================================\n// Dashboard Widgets\n// ============================================================================\n\n// The New form submissions card's body, at the path its widget declares\n// (\"@nextlyhq/plugin-form-builder/admin#NewSubmissionsCard\"). A site's\n// generated component map imports it from here.\nexport { NewSubmissionsCard } from \"../dashboard/NewSubmissionsCard\";\n\n// ============================================================================\n// Builder Sub-Components\n// ============================================================================\n\nexport {\n FieldCards,\n FormPreview,\n type FieldCardsProps,\n type FormPreviewProps,\n} from \"./components/builder\";\n\n// ============================================================================\n// Context & Hooks\n// ============================================================================\n\nexport {\n FormBuilderProvider,\n useFormBuilder,\n useOptionalFormBuilder,\n createFieldFromType,\n createNotification,\n generateFieldName,\n generateFieldLabel,\n DEFAULT_SETTINGS,\n type FormBuilderState,\n type FormBuilderActions,\n type FormBuilderContextValue,\n type FormBuilderProviderProps,\n type FormSettings,\n type FormNotification,\n} from \"./context/FormBuilderContext\";\n\n// ============================================================================\n// Tab Components\n// ============================================================================\n\nexport { FormSettingsTab } from \"./components/builder/FormSettingsTab\";\nexport { FormNotificationsTab } from \"./components/builder/FormNotificationsTab\";\n\n/** Component path for FormBuilderView */\nexport const FORM_BUILDER_VIEW_PATH: ComponentPath = FORM_EDITOR_VIEW;\n\n/** Component path for SubmissionsView */\nexport const SUBMISSIONS_VIEW_PATH: ComponentPath =\n \"@nextlyhq/plugin-form-builder/admin#SubmissionsView\";\n\n/** Component path for SubmissionPage, a submission's own page */\nexport const SUBMISSION_PAGE_PATH: ComponentPath =\n \"@nextlyhq/plugin-form-builder/admin#SubmissionPage\";\n\n// ============================================================================\n// Auto-Registration with Admin\n// ============================================================================\n\n/**\n * Eagerly register components at module load time.\n *\n * This ensures components are available immediately when the admin\n * tries to resolve them, without needing to wait for async registration.\n */\nregisterComponents({\n [FORM_BUILDER_VIEW_PATH]: FormBuilderView,\n [SUBMISSIONS_VIEW_PATH]: SubmissionsView,\n [SUBMISSION_PAGE_PATH]: SubmissionPage,\n});\n"],"mappings":"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"}
|