@aglyn/plugins-forms 1.0.0-beta.143
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/LICENSE +201 -0
- package/README.md +35 -0
- package/package.json +52 -0
- package/src/index.d.ts +18 -0
- package/src/index.js +19 -0
- package/src/index.js.map +1 -0
- package/src/lib/components/form-design-preview.component.d.ts +68 -0
- package/src/lib/components/form-design-preview.component.js +238 -0
- package/src/lib/components/form-design-preview.component.js.map +1 -0
- package/src/lib/components/form-detail-card.d.ts +53 -0
- package/src/lib/components/form-detail-card.js +827 -0
- package/src/lib/components/form-detail-card.js.map +1 -0
- package/src/lib/components/form-metrics-card.component.d.ts +67 -0
- package/src/lib/components/form-metrics-card.component.js +298 -0
- package/src/lib/components/form-metrics-card.component.js.map +1 -0
- package/src/lib/components/form-submissions-card.component.d.ts +40 -0
- package/src/lib/components/form-submissions-card.component.js +104 -0
- package/src/lib/components/form-submissions-card.component.js.map +1 -0
- package/src/lib/components/form-zones.d.ts +52 -0
- package/src/lib/components/form-zones.js +20 -0
- package/src/lib/components/form-zones.js.map +1 -0
- package/src/lib/components/form.d.ts +186 -0
- package/src/lib/components/form.js +1014 -0
- package/src/lib/components/form.js.map +1 -0
- package/src/lib/components/forms-console-page.d.ts +22 -0
- package/src/lib/components/forms-console-page.js +55 -0
- package/src/lib/components/forms-console-page.js.map +1 -0
- package/src/lib/components/host-forms-card.component.d.ts +56 -0
- package/src/lib/components/host-forms-card.component.js +533 -0
- package/src/lib/components/host-forms-card.component.js.map +1 -0
- package/src/lib/components/use-form-promote-api.d.ts +48 -0
- package/src/lib/components/use-form-promote-api.js +63 -0
- package/src/lib/components/use-form-promote-api.js.map +1 -0
- package/src/lib/constants/bundle-common.d.ts +31 -0
- package/src/lib/constants/bundle-common.js +31 -0
- package/src/lib/constants/bundle-common.js.map +1 -0
- package/src/lib/plugin.d.ts +25 -0
- package/src/lib/plugin.js +76 -0
- package/src/lib/plugin.js.map +1 -0
- package/src/lib/site.d.ts +53 -0
- package/src/lib/site.js +83 -0
- package/src/lib/site.js.map +1 -0
- package/src/lib/utils/generate-preset-id.d.ts +25 -0
- package/src/lib/utils/generate-preset-id.js +30 -0
- package/src/lib/utils/generate-preset-id.js.map +1 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/form.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport { sendAnalyticsBeacon } from '@aglyn/aglyn/app-utils/analytics-beacon'\nimport { trackEventBeforeNavigation } from '@aglyn/aglyn/app-utils/analytics-events'\nimport { campaignTouchField } from '@aglyn/aglyn/app-utils/campaign-touch'\nimport * as Aglyn from '@aglyn/aglyn'\nimport {\n mdiEmailFastOutline,\n mdiEmailOutline,\n mdiFormTextbox,\n} from '@aglyn/shared-data-mdi'\nimport { isSameOriginPath } from '@aglyn/shared-util-http/safe-redirect'\nimport Alert from '@mui/material/Alert'\nimport Button from '@mui/material/Button'\nimport Checkbox from '@mui/material/Checkbox'\nimport FormControl from '@mui/material/FormControl'\nimport FormControlLabel from '@mui/material/FormControlLabel'\nimport FormGroup from '@mui/material/FormGroup'\nimport FormLabel from '@mui/material/FormLabel'\nimport Link from '@mui/material/Link'\nimport MenuItem from '@mui/material/MenuItem'\nimport Radio from '@mui/material/Radio'\nimport RadioGroup from '@mui/material/RadioGroup'\nimport Rating from '@mui/material/Rating'\nimport Stack from '@mui/material/Stack'\nimport TextField from '@mui/material/TextField'\nimport {\n type FormEvent,\n forwardRef,\n useCallback,\n useEffect,\n useRef,\n useState,\n} from 'react'\nimport { BUNDLE_ID } from '../constants/bundle-common'\nimport { generatePresetId } from '../utils/generate-preset-id'\n\n/*\n * The heading and the stack this block's form sits inside are mui elements,\n * so the nodes that carry them say so. A node's `pluginId` names the bundle\n * that REGISTERS its component, not the bundle whose preset placed it.\n *\n * The id comes from the core's constant, as it does for every plugin that\n * composes mui elements: importing the mui plugin for one string would make\n * this plugin unusable without it on the import graph.\n */\nconst { MUI_BUNDLE_ID } = Aglyn\n\n// Component ids are persisted in screen documents; never rename.\nexport const FORM_ID: Aglyn.ComponentId = 'form'\nexport const FORM_FIELD_ID: Aglyn.ComponentId = 'formField'\n\n/** After-submit outcomes (AGL-557); message is the historical default. */\nexport type FormAfterSubmit = 'message' | 'redirect' | 'reveal'\n\nexport interface FormProps {\n /**\n * The form entity this node submits as (`docs/specs/reusable-forms.md` §2c).\n *\n * THE identity. `formName` below is a caption and always was: it is copied\n * onto each submission at write time and reconciled with nothing, so\n * renaming a form split its history in two and two pages sharing a label\n * were one list. An id survives both.\n *\n * This is the whole binding. Reuse across pages needs nothing further —\n * promote the bound subtree once and the id travels inside the definition,\n * so every instance writes the same form's submissions without the author\n * retyping a label. Two instances of one definition are ONE form, which is\n * the point; an author who wants two forms makes two forms.\n */\n formId?: string\n /** Identifies the form in the submissions inbox. */\n formName?: string\n /**\n * Dataset (by id) submissions also append into (AGL-556); the inbox\n * copy is always written. Renaming the dataset never breaks the binding.\n */\n datasetId?: string\n /**\n * Legacy dataset-by-name binding (AGL-141), honored when no `datasetId`\n * is set; kept so persisted nodes keep working.\n */\n datasetName?: string\n /**\n * The dataset binding above, signed by the compose that rendered this form\n * (AGL-2773). The submit route writes a record only where this says, so the\n * page, not the request, decides which dataset a submission reaches. Present\n * on the published site only; the canvas and Preview never submit.\n */\n datasetBindingToken?: string\n submitLabel?: string\n successMessage?: string\n /**\n * What a successful submit does (AGL-557): show the success message\n * (default), redirect the visitor, or reveal a hidden element.\n */\n afterSubmit?: FormAfterSubmit\n /** Redirect target screen — rename-safe id, resolved like ScreenLink. */\n redirectScreenId?: string\n /**\n * Manual redirect target used when no screen is picked: a same-site\n * `/path` or an absolute https URL (everything else is dropped).\n */\n redirectUrl?: string\n /** Node revealed on submit; hidden until then (afterSubmit=reveal). */\n revealNodeId?: string\n children?: React.ReactNode\n}\n\n/**\n * Hidden-input prefix a FormField uses to publish its field → dataset\n * schema-field mapping to the enclosing Form (AGL-556). Rides the DOM so\n * arbitrary nesting between Form and field needs no React context; the\n * `__` prefix keeps it out of the submitted fields.\n */\nexport const FIELD_MAP_INPUT_PREFIX = '__map__'\n\n/**\n * Redirect targets are restricted to same-origin paths (`/thanks`) and\n * absolute https URLs (AGL-557): stored props reach every visitor's\n * browser, so `javascript:`/`data:` URLs — and protocol-relative\n * `//host` forms, which keep the scheme but swap the host — never pass.\n *\n * The relative branch asks {@link isSameOriginPath} rather than reading the\n * string's first two characters (AGL-1881). This site never got the AGL-2486\n * backslash fix, and the character list would not have been enough anyway:\n * the URL parser deletes tab/LF/CR before parsing, so `/<TAB>/evil.com`\n * carries neither `//` nor `\\` and `window.location.assign` — which\n * `formNavigation` below calls — still lands the visitor on `evil.com`. The\n * absolute branch is unchanged: an explicit `https://` target is a documented\n * feature, and for absolute input the parser's verdict was never in doubt.\n */\nexport const sanitizeRedirectUrl = (url?: string): string | undefined => {\n const trimmed = (url ?? '').trim()\n if (!trimmed) return undefined\n if (/^https:\\/\\//i.test(trimmed)) return trimmed\n if (isSameOriginPath(trimmed)) return trimmed\n return undefined\n}\n\n/**\n * Where a redirect outcome sends the visitor: the picked screen's\n * resolved href wins; the manual URL is the fallback, sanitized above.\n * Undefined (deleted screen, bad URL) degrades to the message outcome.\n */\nexport const resolveRedirectTarget = (\n screenHref: string | undefined,\n redirectUrl: string | undefined,\n): string | undefined => screenHref ?? sanitizeRedirectUrl(redirectUrl)\n\n// Reveal ids are interpolated into a CSS selector — restrict to the id\n// alphabet so a stored prop can't smuggle selector syntax (AGL-557).\nconst REVEAL_NODE_ID_PATTERN = /^[A-Za-z0-9_-]+$/\n\n/**\n * Navigation seam for the redirect outcome: jsdom's `window.location`\n * is not patchable, so tests stub this indirection instead.\n */\nexport const formNavigation = {\n assign: (url: string) => window.location.assign(url),\n}\n\n/** DOM event dispatched on every successful submit (AGL-557). */\nexport const FORM_SUBMITTED_EVENT = 'aglyn:form-submitted'\n\n/**\n * Report that a bound form was seen, or typed into.\n *\n * Fire-and-forget, and deliberately the same instrument the overlay counters\n * use: `sendBeacon` hands the request to the browser, which delivers it\n * outside the page's lifetime, so nothing here can delay a render or survive\n * long enough to fail visibly.\n *\n * Requires a bound `formId`. An unbound form has no document to count on, and\n * an id is never invented: the collector's `update` would find nothing, which\n * is the right outcome but a pointless request.\n */\nexport function sendFormBeacon(\n hostId: string | undefined,\n formId: string | undefined,\n event: 'view' | 'start',\n): void {\n if (!hostId || !formId) return\n // Best-effort by construction: `sendAnalyticsBeacon` swallows everything and\n // refuses outright from a non-production build or a browser marked ours. A\n // view that does not report is a view this form's completion rate is\n // measured without, which the console says.\n sendAnalyticsBeacon({ hostId, formId, form: event })\n}\n\n/**\n * Lead-capture form (AGL-76): collects its field children's values and\n * posts them to the tenant's `/api/forms/submit` with the site's host id\n * from SiteContext. Without a site context (besigner canvas, preview) the\n * submit is inert, so editing never creates submissions. Includes a\n * honeypot input for naive bots.\n */\nconst Form = forwardRef<HTMLFormElement, FormProps>((props, ref) => {\n const {\n formId,\n formName,\n // Read by compose, which signs them into `datasetBindingToken`; taken out\n // here only so they never reach the DOM through `rest`.\n datasetId: _datasetId,\n datasetName: _datasetName,\n datasetBindingToken,\n submitLabel,\n successMessage,\n afterSubmit,\n redirectScreenId,\n redirectUrl,\n revealNodeId,\n children,\n ...rest\n } = props\n const { hostId } = Aglyn.useSite()\n const siteFetch = Aglyn.useSiteFetch()\n // Resolves the redirect screen like ScreenLink does (rename-safe) and\n // flags editing surfaces, where outcomes must never fire (AGL-557).\n const { href: redirectScreenHref, suppressNavigation } = Aglyn.useScreenLink(\n afterSubmit === 'redirect' ? redirectScreenId : undefined,\n )\n // Reveal target (AGL-557): hidden through a form-owned <style> tag so\n // SSR paints it hidden from the first frame; the style unmounts with\n // the form after a successful submit — which IS the reveal. Editing\n // surfaces keep the target visible so it stays editable.\n const revealSelector =\n afterSubmit === 'reveal' &&\n revealNodeId &&\n REVEAL_NODE_ID_PATTERN.test(revealNodeId) &&\n !suppressNavigation\n ? `[data-aglyn=\"leaf:${revealNodeId}\"]`\n : undefined\n const [status, setStatus] = useState<\n 'idle' | 'sending' | 'sent' | 'error' | 'preview' | 'paused' | 'unavailable'\n >(\n 'idle',\n )\n /*\n * THE VIEW AND THE START — the two facts a completion rate is taken over.\n *\n * Both are reported ONCE per mounted form, from a ref rather than from\n * state: a counter driven by a re-render would be a Firestore write per\n * keystroke, which is the per-render write this must not become. The ref\n * also survives the re-renders `status` causes, so a visitor who edits a\n * field, submits, and is shown an error is one view and one start.\n *\n * Gated on `!suppressNavigation`, the editing-surface flag: the besigner\n * canvas and the console's preview render this component for its author,\n * and counting an author looking at their own draft as a visitor view would\n * put the merchant into their own denominator.\n */\n const reported = useRef({ view: false, start: false })\n useEffect(() => {\n if (suppressNavigation || reported.current.view) return\n reported.current.view = true\n sendFormBeacon(hostId, formId, 'view')\n }, [hostId, formId, suppressNavigation])\n\n /**\n * A START is the first edit anyone makes to this form.\n *\n * On the form's own `input`, which bubbles from every field — no listener\n * registration, no per-field wiring, and nothing to keep in sync when a\n * field type is added. Focus would be the looser reading and the wrong one:\n * a form that is tabbed through and abandoned was never started, and\n * counting it would inflate the denominator abandonment is measured over.\n */\n const handleFirstInput = useCallback(() => {\n if (suppressNavigation || reported.current.start) return\n reported.current.start = true\n sendFormBeacon(hostId, formId, 'start')\n }, [hostId, formId, suppressNavigation])\n /** The read-only lockdown's own words (AGL-1511); never a hardcoded line. */\n const [pausedMessage, setPausedMessage] = useState('')\n /** The abuse ceiling's visitor notice (AGL-1666); see `unavailable` below. */\n const [unavailable, setUnavailable] =\n useState<Aglyn.FormUnavailableNotice | null>(null)\n const [alerts, setAlerts] = useState<\n Array<{ message: string; severity?: string }>\n >([])\n\n const handleSubmit = useCallback(\n async (event: FormEvent<HTMLFormElement>) => {\n event.preventDefault()\n if (!hostId || status === 'sending') return\n const data = new FormData(event.currentTarget)\n const fields: Record<string, string> = {}\n let website = ''\n for (const [key, value] of data.entries()) {\n if (typeof value !== 'string') continue\n if (key === 'website') {\n website = value\n continue\n }\n // `__`-prefixed inputs are internal controls — the rating field's\n // star radios (AGL-544) and each field's dataset-mapping input — and\n // never submit.\n if (key.startsWith('__')) continue\n // Checkbox groups emit one entry per ticked box; join them under\n // the field name instead of letting the last one win (AGL-544).\n fields[key] = (\n key in fields ? `${fields[key]}, ${value}` : value\n ).slice(0, 2000)\n }\n setStatus('sending')\n try {\n const response = await siteFetch('/api/forms/submit', {\n method: 'POST',\n headers: { 'Content-Type': 'application/json' },\n body: JSON.stringify({\n hostId,\n // The bound form entity, when there is one. Sent ALONGSIDE the\n // caption rather than instead of it: the caption keeps the\n // pre-entity `?form=` filter working for every form that has not\n // been adopted yet, and no phase of the migration removes it.\n ...(formId ? { formId } : {}),\n formName: formName || 'Form',\n // The page's signed dataset binding (AGL-2773). The server writes\n // a record only where it says, so nothing this request names\n // chooses a dataset or a field.\n ...(datasetBindingToken\n ? { datasetBinding: datasetBindingToken }\n : {}),\n path: window.location.pathname,\n fields,\n website,\n // The campaign this visitor came from, when they came from one.\n // Absent entirely for direct traffic — never a placeholder, so\n // the server can tell \"arrived from nowhere\" from \"this door does\n // not report\".\n ...campaignTouchField(),\n }),\n })\n if (response.ok) {\n // Site alerts from the actions builder (AGL-148).\n const payload = await response.json().catch(() => ({}))\n if (Array.isArray(payload?.alerts)) setAlerts(payload.alerts)\n // Announced for reveal-style listeners/analytics (AGL-557).\n window.dispatchEvent(\n new CustomEvent(FORM_SUBMITTED_EVENT, {\n detail: { formName: formName || 'Form' },\n }),\n )\n // GA4 lead conversion (AGL-1561). Inside `response.ok`, so it\n // measures a SUBMISSION, not a click and not a validation failure —\n // the distinction that makes a conversion rate mean anything.\n //\n // AWAITED, because the branch below navigates. Ordering the call\n // ahead of the `assign` is not on its own enough, and the obvious\n // reasoning about beacons is the wrong one: once a hit REACHES gtag\n // a navigation cannot destroy it, so `transport_type: 'beacon'`\n // fixes nothing. What is lost is a hit that never reaches gtag,\n // which is what happens when the surface's transport is async and\n // its initialization promise is still pending — the continuation is\n // scheduled behind the navigation task and never runs. The tenant\n // runtime registers no transport, so this resolves immediately and\n // costs the redirect nothing; it is written this way because the\n // property that makes a bare call safe is invisible from here.\n //\n // `formName` is author-written site content, never a field VALUE:\n // no submitted field ever reaches GA from here.\n //\n // This runs on every tenant site, not only aglyn.com, and that is\n // deliberate — gtag is loaded with whatever measurement id the HOST\n // configured, so a customer's form reports into the customer's own\n // property and aglyn.com's reports into ours. It is consent-gated\n // either way: without a grant the script never loaded (AGL-1498)\n // and the delivery path finds no `window.gtag` to call.\n await trackEventBeforeNavigation('generate_lead', {\n form_name: formName || 'Form',\n form_location: window.location.pathname,\n })\n if (afterSubmit === 'redirect' && !suppressNavigation) {\n const target = resolveRedirectTarget(\n redirectScreenHref,\n redirectUrl,\n )\n // No valid target (deleted screen, rejected URL) degrades to\n // the success message below.\n if (target) formNavigation.assign(target)\n }\n }\n // A refusal is not a failure (AGL-1249). Preview declines writes on\n // purpose, and \"something went wrong — please try again\" invites the\n // author to retry something that is working exactly as intended.\n //\n // Since AGL-1511 there are TWO deliberate 423s a submit can meet, so\n // the status alone no longer identifies one: Preview's client-side\n // refusal, and a read-only lockdown refusing a real visitor's write\n // on a live site. Preview's carries `preview: true` and is settled\n // first; anything else 423 is the lockdown pause, whose own copy is\n // rendered rather than the Preview sentence — telling a customer's\n // visitor \"this works on your published site\" would be nonsense on\n // the published site they are standing on.\n if (!response.ok) {\n const body = await response.json().catch(() => ({}))\n if (Aglyn.isPreviewRefusal(response, body)) {\n setStatus('preview')\n return\n }\n const paused = Aglyn.parseLockdownRefusal(response.status, body)\n if (paused) {\n setPausedMessage(Aglyn.lockdownRefusalText(paused))\n setStatus('paused')\n return\n }\n // The site's form-submission abuse ceiling (AGL-1655/1666). This\n // is a THIRD deliberate refusal, and the only one whose status is\n // already spoken for: the Free plan's monthly wall answers 429\n // too. So it is matched on the body's `code` and never on the\n // status — `parseFormUnavailableRefusal` takes no status for\n // exactly that reason. A plan-wall 429 keeps falling through to\n // the generic branch below, which is right: it is the owner's\n // billing problem, and nothing about waiting fixes it.\n const notice = Aglyn.parseFormUnavailableRefusal(body)\n if (notice) {\n setUnavailable(notice)\n setStatus('unavailable')\n return\n }\n }\n setStatus(response.ok ? 'sent' : 'error')\n } catch {\n setStatus('error')\n }\n },\n [\n hostId,\n status,\n formId,\n formName,\n datasetBindingToken,\n afterSubmit,\n redirectScreenHref,\n redirectUrl,\n suppressNavigation,\n siteFetch,\n ],\n )\n\n if (status === 'sent') {\n // Reveal outcome: the form (with its hide-style below) unmounts and\n // the revealed element is the confirmation. Redirect keeps the\n // message visible as a fallback while the browser navigates.\n if (revealSelector) return null\n return (\n <Alert severity=\"success\">\n {successMessage || 'Thanks — your message has been sent.'}\n </Alert>\n )\n }\n\n return (\n <Stack\n ref={ref}\n component=\"form\"\n spacing={2}\n onSubmit={handleSubmit}\n onInput={handleFirstInput}\n {...rest}\n >\n {/* Hides the reveal target until submit (AGL-557); rendered by the\n form so SSR ships it hidden — no flash, no target-side props.\n MUI Stack spacing already skips <style> children. */}\n {revealSelector ? (\n <style>{`${revealSelector}{display:none !important}`}</style>\n ) : null}\n {children}\n {/* Honeypot: humans never see or fill this. */}\n <input\n type=\"text\"\n name=\"website\"\n tabIndex={-1}\n autoComplete=\"off\"\n aria-hidden=\"true\"\n style={{ position: 'absolute', left: '-5000px', height: 0, width: 0 }}\n />\n {status === 'error' ? (\n <Alert severity=\"error\">\n {'Something went wrong — please try again.'}\n </Alert>\n ) : null}\n {status === 'paused' ? (\n // `info`, like the Preview notice and for the same reason: this is a\n // deliberate, temporary pause on a site that is otherwise working,\n // and nothing the visitor typed was lost.\n <Alert severity=\"info\">{pausedMessage}</Alert>\n ) : null}\n {status === 'unavailable' && unavailable ? (\n // `warning`, not `info` and not `error` (AGL-1666). The two `info`\n // notices above are both cases where nothing was lost — Preview\n // never intended to send, and a lockdown pause is minutes long with\n // the visitor's text still in the fields. This one lost a real\n // message on a live site, so it must not read as reassurance. It is\n // not `error` either: nothing is broken and \"please try again\" would\n // walk the visitor into the same refusal.\n <Alert severity=\"warning\">\n {unavailable.message}\n {unavailable.contact ? (\n <>\n {' In the meantime you can reach us at '}\n <Link href={`mailto:${unavailable.contact}`}>\n {unavailable.contact}\n </Link>\n {'.'}\n </>\n ) : null}\n </Alert>\n ) : null}\n {status === 'preview' ? (\n // Deliberately `info`, not `error`, and it does not say \"try again\":\n // nothing failed, and the same submit works on the published site.\n <Alert severity=\"info\">\n {'Preview does not send form submissions. This form works on your ' +\n 'published site.'}\n </Alert>\n ) : null}\n {alerts.map((alert, index) => (\n <Alert key={index} severity={(alert.severity as any) || 'info'}>\n {alert.message}\n </Alert>\n ))}\n <Button\n type=\"submit\"\n variant=\"contained\"\n disabled={status === 'sending'}\n sx={{ alignSelf: 'flex-start' }}\n >\n {status === 'sending' ? 'Sending…' : submitLabel || 'Send'}\n </Button>\n </Stack>\n )\n})\nForm.displayName = 'AglynForm'\n\nexport interface FormFieldProps {\n /** Submission key; also the input's name attribute. */\n fieldName?: string\n /**\n * Stable model fieldId of the parent form's dataset this value is\n * stored under (AGL-556). When unset, values match dataset fields by\n * name — the legacy behavior.\n */\n datasetFieldId?: string\n label?: string\n /**\n * Grey hint shown inside the empty input (AGL-1330) — the frames pair a\n * label above the control with a placeholder inside it. A placeholder is\n * NOT a label: it disappears the moment the visitor types, so the label\n * stays the field's accessible name and is never replaced by this.\n * Ignored by radio, checkbox, and rating fields, which have no text input\n * to hint inside of.\n */\n placeholder?: string\n fieldType?:\n | 'text'\n | 'email'\n | 'textarea'\n | 'select'\n | 'radio'\n | 'checkbox'\n | 'rating'\n /**\n * Choice list for select/radio/checkbox fields (AGL-544): newline- or\n * comma-separated. Ignored by the other types.\n */\n options?: string\n required?: boolean\n}\n\n/**\n * Splits a newline- or comma-separated choice list into trimmed,\n * non-empty entries (AGL-544).\n */\nexport const parseFieldOptions = (options?: string): string[] =>\n (options || '')\n .split(/[\\n,]/)\n .map((entry) => entry.trim())\n .filter(Boolean)\n\n/**\n * Single input inside a Form (AGL-76). Survey field types — select,\n * radio, checkbox group, and star rating — are AGL-544.\n */\nconst FormField = forwardRef<HTMLDivElement, FormFieldProps>((props, ref) => {\n const {\n fieldName,\n datasetFieldId,\n label,\n placeholder,\n fieldType,\n options,\n required,\n ...rest\n } = props\n const name = fieldName || 'field'\n const fieldLabel = label || fieldName || 'Field'\n const choices = parseFieldOptions(options)\n // A CLEARED placeholder must render as no placeholder (AGL-1330). The\n // attributes form persists a cleared text field by dropping the key\n // (ddf maps an emptied field to its clearedValue and final-form's parse\n // turns `''` into `undefined`), and `updateNodeProps` REPLACES the props\n // object — so cleared reaches us as absent. Older documents can still\n // carry `''`/`null`, and a blank string would otherwise reach the DOM as\n // an empty `placeholder` attribute, so blank is normalized to absent too.\n const hint = typeof placeholder === 'string' && placeholder.trim()\n ? placeholder\n : undefined\n // MUI hides a placeholder for as long as the label still sits inside the\n // box, so the hint is only visible once the label is shrunk — and an\n // outlined input with a shrunk label must notch its outline or the label\n // crosses the border. Both are applied ONLY when there is a placeholder,\n // so fields without one keep the historical floating-label behavior.\n const hintSlotProps = hint\n ? { inputLabel: { shrink: true }, input: { notched: true } }\n : undefined\n // Checkbox groups count ticked boxes so \"required\" can mean \"at least\n // one\": native `required` is only asserted while none are ticked.\n const [checkedCount, setCheckedCount] = useState(0)\n // Rating is controlled so a hidden input can serialize the number.\n const [rating, setRating] = useState<number | null>(null)\n // Publishes this field's dataset schema-field mapping to the enclosing\n // Form via the DOM (AGL-556); `__`-prefixed, so never a submitted field.\n const mapInput = datasetFieldId ? (\n <input\n type=\"hidden\"\n name={`${FIELD_MAP_INPUT_PREFIX}${name}`}\n value={datasetFieldId}\n readOnly\n />\n ) : null\n\n if (fieldType === 'select') {\n return (\n <>\n <TextField\n ref={ref}\n select\n name={name}\n label={fieldLabel}\n required={Boolean(required)}\n defaultValue=\"\"\n fullWidth\n size=\"small\"\n // A Select has no native placeholder: the hint is what the closed\n // control displays while nothing is chosen, which needs\n // `displayEmpty` (MUI renders nothing for `''` without it).\n slotProps={\n hint\n ? {\n ...hintSlotProps,\n select: {\n displayEmpty: true,\n renderValue: (value: unknown) =>\n value ? (\n String(value)\n ) : (\n <span style={{ opacity: 0.6 }}>{hint}</span>\n ),\n },\n }\n : undefined\n }\n {...rest}\n >\n {choices.map((choice, index) => (\n <MenuItem key={index} value={choice}>\n {choice}\n </MenuItem>\n ))}\n </TextField>\n {mapInput}\n </>\n )\n }\n\n if (fieldType === 'radio') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <RadioGroup name={name}>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n value={choice}\n label={choice}\n control={<Radio size=\"small\" required={Boolean(required)} />}\n />\n ))}\n </RadioGroup>\n {mapInput}\n </FormControl>\n )\n }\n\n if (fieldType === 'checkbox') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <FormGroup>\n {choices.map((choice, index) => (\n <FormControlLabel\n key={index}\n label={choice}\n control={\n <Checkbox\n name={name}\n value={choice}\n size=\"small\"\n required={Boolean(required) && checkedCount === 0}\n onChange={(event) =>\n setCheckedCount(\n (count) => count + (event.target.checked ? 1 : -1),\n )\n }\n />\n }\n />\n ))}\n </FormGroup>\n {mapInput}\n </FormControl>\n )\n }\n\n if (fieldType === 'rating') {\n return (\n <FormControl ref={ref} required={Boolean(required)} {...rest}>\n <FormLabel>{fieldLabel}</FormLabel>\n <Rating\n // `__`-prefixed so the star radios themselves are skipped by\n // Form.handleSubmit; the hidden input carries the value.\n name={`__${name}`}\n value={rating}\n onChange={(_event, value) => setRating(value)}\n />\n <input type=\"hidden\" name={name} value={rating ?? ''} />\n {mapInput}\n </FormControl>\n )\n }\n\n return (\n <>\n <TextField\n ref={ref}\n name={name}\n label={fieldLabel}\n placeholder={hint}\n type={fieldType === 'email' ? 'email' : 'text'}\n required={Boolean(required)}\n multiline={fieldType === 'textarea'}\n minRows={fieldType === 'textarea' ? 3 : undefined}\n fullWidth\n size=\"small\"\n slotProps={hintSlotProps}\n {...rest}\n />\n {mapInput}\n </>\n )\n})\nFormField.displayName = 'AglynFormField'\n\nexport const formSchema: Aglyn.ComponentSchema<FormProps> = {\n $id: FORM_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form',\n description:\n 'Collects its Form Fields and sends the answers to you, or into a dataset.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n attributes: [\n {\n name: 'formId',\n description:\n 'The form this collects for, from the Forms page. Stored by id, so ' +\n 'renaming the form never splits its submission history and two ' +\n 'pages placing the same form share one list. Leave unset to keep ' +\n 'filing submissions under the name below.',\n component: Aglyn.FieldComponentType.FORM_SELECT,\n label: 'Form',\n },\n {\n name: 'formName',\n description:\n 'Caption shown in the submissions inbox, used when no form above ' +\n 'is picked. A name is not an identity: renaming it splits the ' +\n 'history of everything already submitted under the old one.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Form name',\n },\n {\n name: 'datasetId',\n description:\n 'Optional dataset (from the Data page) submissions are appended ' +\n 'to. Stored by id, so renaming the dataset never breaks the ' +\n 'binding (AGL-556). The inbox always gets a copy.',\n component: Aglyn.FieldComponentType.DATASET_SELECT,\n label: 'Write to dataset',\n },\n {\n name: 'datasetName',\n description:\n 'Legacy name-based dataset binding — matched against the ' +\n \"dataset's display name at submit time. Pick the dataset above \" +\n 'and clear this; it only shows while a name is still set.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Write to dataset (legacy name)',\n condition: { when: 'datasetName', isNotEmpty: true },\n },\n {\n name: 'submitLabel',\n description: 'Label of the submit button.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Submit label',\n },\n {\n name: 'successMessage',\n description: 'Shown after a successful submission.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Success message',\n },\n // After-submit outcomes (AGL-557).\n {\n name: 'afterSubmit',\n description:\n 'What a successful submit does: show the success message, send ' +\n 'the visitor to another page, or reveal a hidden element.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'After submit',\n // `message` is the first member of the declared `FormAfterSubmit`\n // union, so the sentinel already existed in the type and only the\n // option list was spelling it `''` (AGL-1451) — a value the\n // attributes form strips on change (AGL-1191), which meant a form\n // switched to Redirect could not be switched back to its message.\n options: [\n { value: 'message', label: 'Show the success message' },\n { value: 'redirect', label: 'Redirect the visitor' },\n { value: 'reveal', label: 'Reveal a hidden element' },\n ],\n },\n {\n name: 'redirectScreenId',\n description:\n 'Screen the visitor lands on after submitting. The address is ' +\n \"resolved at render time, so renaming the screen's slug never \" +\n 'breaks the redirect.',\n component: Aglyn.FieldComponentType.SCREEN_SELECT,\n label: 'Redirect to screen',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'redirectUrl',\n description:\n 'Used only when no screen is selected above: a same-site path ' +\n '(/thanks) or an https URL. Anything else is ignored.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Redirect URL',\n condition: { when: 'afterSubmit', is: 'redirect' },\n },\n {\n name: 'revealNodeId',\n description:\n 'Element shown after a successful submit — it stays hidden on ' +\n 'the published page until then.',\n component: Aglyn.FieldComponentType.NODE_SELECT,\n label: 'Element to reveal',\n condition: { when: 'afterSubmit', is: 'reveal' },\n },\n ],\n}\n\nexport const formFieldSchema: Aglyn.ComponentSchema<FormFieldProps> = {\n $id: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n displayName: 'Form Field',\n description:\n 'One input inside a form — text, select, radio, checkbox or rating.',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n flags: {\n selfClosing: Aglyn.FEATURE_FLAG.ENABLED,\n },\n attributes: [\n {\n name: 'fieldName',\n description: 'Key this value is stored under in submissions.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Field name',\n },\n {\n name: 'datasetFieldId',\n description:\n \"Schema field of the form's dataset this value is stored under. \" +\n 'Stored by id, so renaming the field never breaks the mapping ' +\n '(AGL-556). When unset, values match dataset fields by name.',\n component: Aglyn.FieldComponentType.DATASET_FIELD_SELECT,\n label: 'Maps to schema field',\n },\n {\n name: 'label',\n description: 'Visible input label.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Label',\n },\n {\n name: 'placeholder',\n description:\n 'Grey hint inside the empty input — \"you@company.com\" (AGL-1330). ' +\n 'It is not a label: it disappears as soon as the visitor types, so ' +\n 'always keep Label set. Ignored by radio, checkbox, and rating ' +\n 'fields. Clearing it removes the hint.',\n component: Aglyn.FieldComponentType.TEXT_FIELD,\n label: 'Placeholder',\n },\n {\n name: 'fieldType',\n description: 'Input type.',\n component: Aglyn.FieldComponentType.SELECT,\n label: 'Type',\n // `text` is the first member of this prop's own union, and the value\n // the render falls back to. As `''` it was the one field type an\n // author could not choose and keep (AGL-1191/AGL-1451): a field\n // switched to Email or Multiline could never be made plain text\n // again without deleting it.\n options: [\n { value: 'text', label: 'Text' },\n { value: 'email', label: 'Email' },\n { value: 'textarea', label: 'Multiline' },\n { value: 'select', label: 'Dropdown' },\n { value: 'radio', label: 'Radio choice' },\n { value: 'checkbox', label: 'Checkboxes' },\n { value: 'rating', label: 'Star rating' },\n ],\n },\n {\n name: 'options',\n description:\n 'Choices for dropdown, radio, and checkbox fields — one per line ' +\n '(or comma-separated). Ignored by the other types.',\n component: Aglyn.FieldComponentType.TEXTAREA,\n label: 'Options',\n },\n {\n name: 'required',\n description: 'Whether the field must be filled.',\n component: Aglyn.FieldComponentType.SWITCH,\n label: 'Required?',\n },\n ],\n}\n\nexport const formPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Form',\n pluginId: BUNDLE_ID,\n description: 'Name, email, and message with a send button',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiEmailFastOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: { formName: 'Contact' },\n nodes: [\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName: 'name', label: 'Name', required: true },\n },\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: {\n fieldName: 'email',\n label: 'Email',\n fieldType: 'email',\n required: true,\n },\n },\n {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: {\n fieldName: 'message',\n label: 'Message',\n fieldType: 'textarea',\n required: true,\n },\n },\n ],\n },\n },\n {\n $id: generatePresetId(FORM_FIELD_ID),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Form Field',\n pluginId: BUNDLE_ID,\n description: 'Single input inside a form',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName: 'field', label: 'Field' },\n },\n },\n /*\n * The marketing consent field as one preset rather than a recipe. Its props\n * are the platform's `MARKETING_CONSENT_FORM_FIELD`, which every place that adds\n * this field reads, so each adds the same unticked one-option checkbox. It\n * records consent once the form names it as its Marketing consent field.\n */\n {\n $id: generatePresetId(FORM_FIELD_ID, 'marketing-consent'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Marketing consent',\n pluginId: BUNDLE_ID,\n description: 'An unticked checkbox asking permission to send marketing emails',\n category: Aglyn.ComponentCategory.FORMS,\n icon: { path: mdiFormTextbox.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { ...Aglyn.MARKETING_CONSENT_FORM_FIELD },\n },\n },\n]\n\n/**\n * The composed section a form usually arrives inside.\n *\n * It lives here rather than with the generic block library because it PLACES\n * form nodes, and a preset's `data` carries the `pluginId` those nodes are\n * saved with — so authoring it anywhere else stamps every contact form on\n * every new site with the wrong bundle. The heading and the stack around it\n * name `mui`, which is correct: they ARE mui elements, and a page holding this\n * section needs both bundles registered before it paints.\n */\nexport const formBlockPresets: Aglyn.PresetSchema[] = [\n {\n $id: generatePresetId(FORM_ID, 'contact-section'),\n type: Aglyn.NodeType.PRESET,\n displayName: 'Contact Section',\n pluginId: BUNDLE_ID,\n description: 'Heading with a name/email/message form',\n category: Aglyn.ComponentCategory.BLOCKS,\n icon: { path: mdiEmailOutline.path, sx: { color: 'info.main' } },\n data: {\n $id: null,\n componentId: 'muiStack',\n pluginId: MUI_BUNDLE_ID,\n props: { spacing: 2 },\n sx: {\n paddingLeft: 4,\n paddingRight: 4,\n paddingTop: 6,\n paddingBottom: 6,\n maxWidth: 560,\n },\n nodes: [\n {\n $id: null,\n componentId: 'muiTypography',\n pluginId: MUI_BUNDLE_ID,\n props: { variant: 'h4', children: 'Get in touch' },\n },\n {\n $id: null,\n componentId: FORM_ID,\n pluginId: BUNDLE_ID,\n props: {\n formName: 'Contact',\n submitLabel: 'Send message',\n successMessage: 'Thanks — we will get back to you soon.',\n },\n nodes: [\n contactField('name', 'Name'),\n contactField('email', 'Email', { fieldType: 'email' }),\n contactField('message', 'Message', { fieldType: 'textarea' }),\n ],\n },\n ],\n },\n },\n]\n\nfunction contactField(fieldName: string, label: string, extra?: object) {\n return {\n $id: null,\n componentId: FORM_FIELD_ID,\n pluginId: BUNDLE_ID,\n props: { fieldName, label, required: true, ...extra },\n }\n}\n\nexport { Form, FormField }\nexport default Form\n"],"names":["sendAnalyticsBeacon","trackEventBeforeNavigation","campaignTouchField","Aglyn","mdiEmailFastOutline","mdiEmailOutline","mdiFormTextbox","isSameOriginPath","Alert","Button","Checkbox","FormControl","FormControlLabel","FormGroup","FormLabel","Link","MenuItem","Radio","RadioGroup","Rating","Stack","TextField","forwardRef","useCallback","useEffect","useRef","useState","BUNDLE_ID","generatePresetId","MUI_BUNDLE_ID","FORM_ID","FORM_FIELD_ID","FIELD_MAP_INPUT_PREFIX","sanitizeRedirectUrl","url","trimmed","trim","undefined","test","resolveRedirectTarget","screenHref","redirectUrl","REVEAL_NODE_ID_PATTERN","formNavigation","assign","window","location","FORM_SUBMITTED_EVENT","sendFormBeacon","hostId","formId","event","form","Form","props","ref","formName","datasetId","_datasetId","datasetName","_datasetName","datasetBindingToken","submitLabel","successMessage","afterSubmit","redirectScreenId","revealNodeId","children","rest","useSite","siteFetch","useSiteFetch","href","redirectScreenHref","suppressNavigation","useScreenLink","revealSelector","status","setStatus","reported","view","start","current","handleFirstInput","pausedMessage","setPausedMessage","unavailable","setUnavailable","alerts","setAlerts","handleSubmit","preventDefault","data","FormData","currentTarget","fields","website","key","value","entries","startsWith","slice","response","method","headers","body","JSON","stringify","datasetBinding","path","pathname","ok","payload","json","catch","Array","isArray","dispatchEvent","CustomEvent","detail","form_name","form_location","target","isPreviewRefusal","paused","parseLockdownRefusal","lockdownRefusalText","notice","parseFormUnavailableRefusal","severity","component","spacing","onSubmit","onInput","style","input","type","name","tabIndex","autoComplete","aria-hidden","position","left","height","width","message","contact","map","alert","index","variant","disabled","sx","alignSelf","displayName","parseFieldOptions","options","split","entry","filter","Boolean","FormField","fieldName","datasetFieldId","label","placeholder","fieldType","required","fieldLabel","choices","hint","hintSlotProps","inputLabel","shrink","notched","checkedCount","setCheckedCount","rating","setRating","mapInput","readOnly","select","defaultValue","fullWidth","size","slotProps","displayEmpty","renderValue","String","span","opacity","choice","control","onChange","count","checked","_event","multiline","minRows","formSchema","$id","pluginId","description","category","ComponentCategory","FORMS","icon","color","attributes","FieldComponentType","FORM_SELECT","TEXT_FIELD","DATASET_SELECT","condition","when","isNotEmpty","SELECT","SCREEN_SELECT","is","NODE_SELECT","formFieldSchema","flags","selfClosing","FEATURE_FLAG","ENABLED","DATASET_FIELD_SELECT","TEXTAREA","SWITCH","formPresets","NodeType","PRESET","componentId","nodes","MARKETING_CONSENT_FORM_FIELD","formBlockPresets","BLOCKS","paddingLeft","paddingRight","paddingTop","paddingBottom","maxWidth","contactField","extra"],"mappings":"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"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ConsolePluginPageProps } from '@aglyn/aglyn';
|
|
2
|
+
/**
|
|
3
|
+
* The Forms console surface: the catalog, and one form beneath it.
|
|
4
|
+
*
|
|
5
|
+
* A LIST WITH ENTITY ROUTES, not a hub with a rail. `/forms/{formId}` is one
|
|
6
|
+
* of the rows, and the set of ids is a property of the site's data, so there
|
|
7
|
+
* is no static section list that could name them — which is what the nav
|
|
8
|
+
* item's `ownsSubtree` asks for. The alternative, an invented single section,
|
|
9
|
+
* would put a one-item rail above every form and a meaningless segment in
|
|
10
|
+
* every URL.
|
|
11
|
+
*
|
|
12
|
+
* The detail surface is a ROUTE rather than an expanded row for the same two
|
|
13
|
+
* reasons a campaign's is: it is linkable, which is what an author wants to
|
|
14
|
+
* paste into a message about a form that stopped collecting; and the version
|
|
15
|
+
* history and the design preview are this surface's expensive reads, so a
|
|
16
|
+
* reader who came for the list must not pay for them.
|
|
17
|
+
*/
|
|
18
|
+
export declare function FormsConsolePage(props: ConsolePluginPageProps): import("react").JSX.Element;
|
|
19
|
+
export declare namespace FormsConsolePage {
|
|
20
|
+
var displayName: string;
|
|
21
|
+
}
|
|
22
|
+
export default FormsConsolePage;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @license
|
|
3
|
+
* Copyright 2026 Aglyn LLC
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/ 'use client';
|
|
17
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
18
|
+
import FormDetailCard from "./form-detail-card.js";
|
|
19
|
+
import HostFormsCard from "./host-forms-card.component.js";
|
|
20
|
+
/**
|
|
21
|
+
* The Forms console surface: the catalog, and one form beneath it.
|
|
22
|
+
*
|
|
23
|
+
* A LIST WITH ENTITY ROUTES, not a hub with a rail. `/forms/{formId}` is one
|
|
24
|
+
* of the rows, and the set of ids is a property of the site's data, so there
|
|
25
|
+
* is no static section list that could name them — which is what the nav
|
|
26
|
+
* item's `ownsSubtree` asks for. The alternative, an invented single section,
|
|
27
|
+
* would put a one-item rail above every form and a meaningless segment in
|
|
28
|
+
* every URL.
|
|
29
|
+
*
|
|
30
|
+
* The detail surface is a ROUTE rather than an expanded row for the same two
|
|
31
|
+
* reasons a campaign's is: it is linkable, which is what an author wants to
|
|
32
|
+
* paste into a message about a form that stopped collecting; and the version
|
|
33
|
+
* history and the design preview are this surface's expensive reads, so a
|
|
34
|
+
* reader who came for the list must not pay for them.
|
|
35
|
+
*/ export function FormsConsolePage(props) {
|
|
36
|
+
var _ref, _ref1;
|
|
37
|
+
const { hostId, basePath, segments, hostRole, org } = props;
|
|
38
|
+
// Everything under the surface's own href is a form id — see `ownsSubtree`.
|
|
39
|
+
const formId = (segments != null ? segments : [])[0];
|
|
40
|
+
return formId ? /*#__PURE__*/ _jsx(FormDetailCard, {
|
|
41
|
+
hostId: hostId,
|
|
42
|
+
formId: formId,
|
|
43
|
+
basePath: basePath,
|
|
44
|
+
canPublish: (_ref = hostRole == null ? void 0 : hostRole.canPublish) != null ? _ref : false,
|
|
45
|
+
hostRoleLoaded: (_ref1 = hostRole == null ? void 0 : hostRole.loaded) != null ? _ref1 : false
|
|
46
|
+
}) : /*#__PURE__*/ _jsx(HostFormsCard, {
|
|
47
|
+
hostId: hostId,
|
|
48
|
+
basePath: basePath,
|
|
49
|
+
org: org
|
|
50
|
+
});
|
|
51
|
+
}
|
|
52
|
+
FormsConsolePage.displayName = 'FormsConsolePage';
|
|
53
|
+
export default FormsConsolePage;
|
|
54
|
+
|
|
55
|
+
//# sourceMappingURL=forms-console-page.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../../../../../../../libs/plugins/forms/src/lib/components/forms-console-page.tsx"],"sourcesContent":["/**\n * @license\n * Copyright 2026 Aglyn LLC\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n'use client'\n\nimport type { ConsolePluginPageProps } from '@aglyn/aglyn'\nimport FormDetailCard from './form-detail-card'\nimport HostFormsCard from './host-forms-card.component'\n\n/**\n * The Forms console surface: the catalog, and one form beneath it.\n *\n * A LIST WITH ENTITY ROUTES, not a hub with a rail. `/forms/{formId}` is one\n * of the rows, and the set of ids is a property of the site's data, so there\n * is no static section list that could name them — which is what the nav\n * item's `ownsSubtree` asks for. The alternative, an invented single section,\n * would put a one-item rail above every form and a meaningless segment in\n * every URL.\n *\n * The detail surface is a ROUTE rather than an expanded row for the same two\n * reasons a campaign's is: it is linkable, which is what an author wants to\n * paste into a message about a form that stopped collecting; and the version\n * history and the design preview are this surface's expensive reads, so a\n * reader who came for the list must not pay for them.\n */\nexport function FormsConsolePage(props: ConsolePluginPageProps) {\n const { hostId, basePath, segments, hostRole, org } = props\n // Everything under the surface's own href is a form id — see `ownsSubtree`.\n const formId = (segments ?? [])[0]\n\n return formId ? (\n <FormDetailCard\n hostId={hostId}\n formId={formId}\n basePath={basePath}\n canPublish={hostRole?.canPublish ?? false}\n hostRoleLoaded={hostRole?.loaded ?? false}\n />\n ) : (\n <HostFormsCard hostId={hostId} basePath={basePath} org={org} />\n )\n}\nFormsConsolePage.displayName = 'FormsConsolePage'\n\nexport default FormsConsolePage\n"],"names":["FormDetailCard","HostFormsCard","FormsConsolePage","props","hostId","basePath","segments","hostRole","org","formId","canPublish","hostRoleLoaded","loaded","displayName"],"mappings":"AAAA;;;;;;;;;;;;;;;CAeC,GACD;;AAGA,OAAOA,oBAAoB,wBAAoB;AAC/C,OAAOC,mBAAmB,iCAA6B;AAEvD;;;;;;;;;;;;;;;CAeC,GACD,OAAO,SAASC,iBAAiBC,KAA6B;;IAC5D,MAAM,EAAEC,MAAM,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,QAAQ,EAAEC,GAAG,EAAE,GAAGL;IACtD,4EAA4E;IAC5E,MAAMM,SAAS,CAACH,mBAAAA,WAAY,EAAE,CAAC,CAAC,EAAE;IAElC,OAAOG,uBACL,KAACT;QACCI,QAAQA;QACRK,QAAQA;QACRJ,UAAUA;QACVK,UAAU,UAAEH,4BAAAA,SAAUG,UAAU,mBAAI;QACpCC,cAAc,WAAEJ,4BAAAA,SAAUK,MAAM,oBAAI;uBAGtC,KAACX;QAAcG,QAAQA;QAAQC,UAAUA;QAAUG,KAAKA;;AAE5D;AACAN,iBAAiBW,WAAW,GAAG;AAE/B,eAAeX,iBAAgB"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import type { AglynOrgBilling } from '@aglyn/aglyn';
|
|
2
|
+
export interface HostFormsCardProps {
|
|
3
|
+
hostId: string;
|
|
4
|
+
/**
|
|
5
|
+
* The Forms surface's own absolute console path, from the shell.
|
|
6
|
+
*
|
|
7
|
+
* A row's link is this plus the form's id, resolved synchronously. The
|
|
8
|
+
* alternative is `useConsoleHostRoute`, which answers `null` for a paint
|
|
9
|
+
* while it reads two documents — and a table whose every row links to
|
|
10
|
+
* `/null/...` on first render is worse than one that pays nothing.
|
|
11
|
+
*/
|
|
12
|
+
basePath?: string;
|
|
13
|
+
/**
|
|
14
|
+
* The org billing document, from the shell.
|
|
15
|
+
*
|
|
16
|
+
* The quota readout's denominator resolves from it. `undefined` means the
|
|
17
|
+
* plan is not known rather than that it is Free — the shell holds this
|
|
18
|
+
* surface behind a spinner until the org read settles, so it reads that way
|
|
19
|
+
* only when the read failed — and the readout says so instead of naming a
|
|
20
|
+
* cap it cannot support.
|
|
21
|
+
*/
|
|
22
|
+
org?: Partial<AglynOrgBilling>;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* THE FORM CATALOG, WITH ITS SHAPE VISIBLE BEFORE THERE IS ANYTHING IN IT.
|
|
26
|
+
*
|
|
27
|
+
* The list is the components list's table, deliberately and not
|
|
28
|
+
* approximately: `ListTable` gives it the row grammar every artifact list has
|
|
29
|
+
* — the row opens the detail page, rows are not selectable, one quick action
|
|
30
|
+
* then the overflow — and `ListPagination` gives it the console's one footer.
|
|
31
|
+
* A form is an artifact like a component, so a reader who has used one list
|
|
32
|
+
* has used this one.
|
|
33
|
+
*
|
|
34
|
+
* ## Why the empty state is a table and not a sentence
|
|
35
|
+
*
|
|
36
|
+
* A paragraph and a button reads as a smaller feature than this is: a form is
|
|
37
|
+
* a thing with a slug, a submission count and a version history, and none of
|
|
38
|
+
* that would be visible until the reader had already committed to making one.
|
|
39
|
+
* Rendering the columns with the empty overlay inside them teaches the shape
|
|
40
|
+
* of the artifact BEFORE the first one exists, which is what the components
|
|
41
|
+
* list has always done.
|
|
42
|
+
*
|
|
43
|
+
* ## The two numeric columns, and when one is honestly blank
|
|
44
|
+
*
|
|
45
|
+
* `stats.submissions` and `stats.leads` are both incremented by
|
|
46
|
+
* `/api/forms/submit` on a write that was happening anyway, so both are real
|
|
47
|
+
* numbers. `leads` is counted only for a form whose `routing.lead` is set, so
|
|
48
|
+
* a form that does not route to leads carries no figure at all and renders as
|
|
49
|
+
* a dash rather than as `0`. A zero would say this form has produced no leads,
|
|
50
|
+
* which for an unrouted form is a measurement nobody took.
|
|
51
|
+
*/
|
|
52
|
+
export declare function HostFormsCard(props: HostFormsCardProps): import("react").JSX.Element;
|
|
53
|
+
export declare namespace HostFormsCard {
|
|
54
|
+
var displayName: string;
|
|
55
|
+
}
|
|
56
|
+
export default HostFormsCard;
|