@ai-matrx/records-ui 0.106.0 → 0.107.1
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/CHANGELOG.md +12 -0
- package/dist/index.cjs +1389 -875
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +38 -2
- package/dist/index.d.ts +38 -2
- package/dist/index.js +1385 -871
- package/dist/index.js.map +1 -1
- package/dist/pickers.cjs +34 -9
- package/dist/pickers.cjs.map +1 -1
- package/dist/pickers.js +34 -9
- package/dist/pickers.js.map +1 -1
- package/package.json +5 -5
package/dist/index.js
CHANGED
|
@@ -5637,7 +5637,8 @@ function formatControl(args) {
|
|
|
5637
5637
|
{
|
|
5638
5638
|
id,
|
|
5639
5639
|
type: def.editor,
|
|
5640
|
-
|
|
5640
|
+
inputMode: def.editor,
|
|
5641
|
+
...ownDetails ? { autoComplete: def.editor } : {},
|
|
5641
5642
|
value: text2,
|
|
5642
5643
|
disabled,
|
|
5643
5644
|
onChange: (e) => commit(e.target.value)
|
|
@@ -6128,7 +6129,7 @@ function isPersonsName(field2) {
|
|
|
6128
6129
|
const words2 = `${field2.key} ${field2.label ?? ""}`.toLowerCase();
|
|
6129
6130
|
return /(^|[\s_])(full[\s_]?)?name(\s|$)/.test(words2) && !/(therapist|doctor|physician|company|business|pet|room)/.test(words2);
|
|
6130
6131
|
}
|
|
6131
|
-
function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCommit, onCancel, addsChoices, initialSearch, asForm }) {
|
|
6132
|
+
function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCommit, onCancel, addsChoices, initialSearch, asForm, onChosen }) {
|
|
6132
6133
|
const host = useRecordsUi();
|
|
6133
6134
|
const kind = editorKindFor(field2);
|
|
6134
6135
|
const inputId = id ?? `field-${field2.key}`;
|
|
@@ -6192,7 +6193,8 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
|
|
|
6192
6193
|
{
|
|
6193
6194
|
id: inputId,
|
|
6194
6195
|
type: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
|
|
6195
|
-
|
|
6196
|
+
inputMode: kind === "email" ? "email" : kind === "phone" ? "tel" : "url",
|
|
6197
|
+
...ownDetails ? { autoComplete: kind === "email" ? "email" : kind === "phone" ? "tel" : "url" } : {},
|
|
6196
6198
|
value: value === null || value === void 0 ? "" : String(value),
|
|
6197
6199
|
disabled,
|
|
6198
6200
|
onChange: (e) => onChange(e.target.value === "" ? null : e.target.value)
|
|
@@ -6202,7 +6204,7 @@ function FieldControl({ field: field2, value, onChange, disabled, id, autoOpen,
|
|
|
6202
6204
|
return /* @__PURE__ */ jsx23(CheckboxControl, { field: field2, value, onChange, disabled, id: inputId });
|
|
6203
6205
|
case "select":
|
|
6204
6206
|
case "multi_select":
|
|
6205
|
-
return /* @__PURE__ */ jsx23(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm });
|
|
6207
|
+
return /* @__PURE__ */ jsx23(OptionControl, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm, onChosen });
|
|
6206
6208
|
case "member":
|
|
6207
6209
|
return /* @__PURE__ */ jsx23(PersonPicker, { field: field2, value, onChange, disabled, id: inputId, autoOpen, onClose, onCancel, initialSearch });
|
|
6208
6210
|
case "entity_reference":
|
|
@@ -6384,7 +6386,7 @@ function inTheStoresOrder(options) {
|
|
|
6384
6386
|
const made = (o) => String(o.created_at ?? "");
|
|
6385
6387
|
return [...options].sort((a, b) => position(a) - position(b) || made(a).localeCompare(made(b)));
|
|
6386
6388
|
}
|
|
6387
|
-
function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm }) {
|
|
6389
|
+
function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen, onClose, onCancel, addsChoices, initialSearch, asForm, onChosen }) {
|
|
6388
6390
|
const client = useOptionalRecordsClient7();
|
|
6389
6391
|
const [options, setOptions] = useState20(null);
|
|
6390
6392
|
const [refusal4, setRefusal] = useState20(null);
|
|
@@ -6403,7 +6405,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen,
|
|
|
6403
6405
|
}, [client, field2.id, reread]);
|
|
6404
6406
|
const handed = publicChoicesOf(field2);
|
|
6405
6407
|
if (!client && handed.length > 0) {
|
|
6406
|
-
return /* @__PURE__ */ jsx23(ChoiceAnswer, { field: field2, choices: handed, value, onChange, disabled, id });
|
|
6408
|
+
return /* @__PURE__ */ jsx23(ChoiceAnswer, { field: field2, choices: handed, value, onChange, disabled, id, onChosen });
|
|
6407
6409
|
}
|
|
6408
6410
|
if (!client) {
|
|
6409
6411
|
return /* @__PURE__ */ jsxs21("div", { className: "flex flex-col gap-1", children: [
|
|
@@ -6433,6 +6435,7 @@ function OptionControl({ field: field2, value, onChange, disabled, id, autoOpen,
|
|
|
6433
6435
|
onChange,
|
|
6434
6436
|
disabled,
|
|
6435
6437
|
id,
|
|
6438
|
+
onChosen,
|
|
6436
6439
|
isChosen: (choice, v) => {
|
|
6437
6440
|
const option = options.find((o) => optionKey2(o) === choice.key);
|
|
6438
6441
|
return option ? isTheChosen(option, v) : false;
|
|
@@ -6703,20 +6706,24 @@ function ChoiceAnswer({
|
|
|
6703
6706
|
onChange,
|
|
6704
6707
|
disabled,
|
|
6705
6708
|
id,
|
|
6706
|
-
isChosen
|
|
6709
|
+
isChosen,
|
|
6710
|
+
onChosen
|
|
6707
6711
|
}) {
|
|
6708
6712
|
const [search, setSearch] = useState20("");
|
|
6709
6713
|
const held = field2.multi ? choiceValuesOf(value) : value === null || value === void 0 || value === "" ? [] : [value];
|
|
6710
6714
|
const on = (choice) => held.some((v) => isChosen ? isChosen(choice, v) : String(v) === choice.key || String(v) === choice.label);
|
|
6711
6715
|
const press = (choice) => {
|
|
6712
6716
|
if (!field2.multi) {
|
|
6713
|
-
|
|
6717
|
+
const picking = !on(choice);
|
|
6718
|
+
onChange(picking ? choice.key : null);
|
|
6719
|
+
if (picking) onChosen?.();
|
|
6714
6720
|
return;
|
|
6715
6721
|
}
|
|
6716
6722
|
const keys = choices.filter((c) => c.key === choice.key ? !on(c) : on(c)).map((c) => c.key);
|
|
6717
6723
|
onChange(keys.length === 0 ? null : keys);
|
|
6718
6724
|
};
|
|
6719
6725
|
const lettered = choices.length <= CHOICE_LETTERS.length;
|
|
6726
|
+
const tabStop = choices.find((c) => on(c))?.key ?? choices[0]?.key;
|
|
6720
6727
|
const option = (choice) => {
|
|
6721
6728
|
const chosen = on(choice);
|
|
6722
6729
|
const letter = lettered ? CHOICE_LETTERS[choices.indexOf(choice)] : null;
|
|
@@ -6728,6 +6735,7 @@ function ChoiceAnswer({
|
|
|
6728
6735
|
"aria-checked": chosen,
|
|
6729
6736
|
"aria-label": choice.label,
|
|
6730
6737
|
disabled,
|
|
6738
|
+
tabIndex: field2.multi || choice.key === tabStop ? 0 : -1,
|
|
6731
6739
|
onClick: () => press(choice),
|
|
6732
6740
|
"data-records-choice-answer": choice.key,
|
|
6733
6741
|
className: cn16(
|
|
@@ -6754,8 +6762,25 @@ function ChoiceAnswer({
|
|
|
6754
6762
|
);
|
|
6755
6763
|
};
|
|
6756
6764
|
const onKeyDown = (event) => {
|
|
6757
|
-
if (
|
|
6765
|
+
if (disabled || event.metaKey || event.ctrlKey || event.altKey) return;
|
|
6758
6766
|
if (event.target.tagName === "INPUT") return;
|
|
6767
|
+
const arrow = { ArrowDown: 1, ArrowRight: 1, ArrowUp: -1, ArrowLeft: -1 }[event.key];
|
|
6768
|
+
if (arrow) {
|
|
6769
|
+
const buttons = Array.from(
|
|
6770
|
+
event.currentTarget.querySelectorAll("[data-records-choice-answer]")
|
|
6771
|
+
);
|
|
6772
|
+
const here = buttons.indexOf(event.target);
|
|
6773
|
+
if (buttons.length === 0) return;
|
|
6774
|
+
event.preventDefault();
|
|
6775
|
+
const next = buttons[(Math.max(here, 0) + arrow + buttons.length) % buttons.length];
|
|
6776
|
+
next.focus();
|
|
6777
|
+
if (!field2.multi) {
|
|
6778
|
+
const choice = choices.find((c) => c.key === next.dataset["recordsChoiceAnswer"]);
|
|
6779
|
+
if (choice && !on(choice)) onChange(choice.key);
|
|
6780
|
+
}
|
|
6781
|
+
return;
|
|
6782
|
+
}
|
|
6783
|
+
if (!lettered) return;
|
|
6759
6784
|
const at = CHOICE_LETTERS.indexOf(event.key.toUpperCase());
|
|
6760
6785
|
if (event.key.length !== 1 || at < 0 || at >= choices.length) return;
|
|
6761
6786
|
event.preventDefault();
|
|
@@ -9833,7 +9858,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
9833
9858
|
{
|
|
9834
9859
|
triggerSize: "compact",
|
|
9835
9860
|
triggerLabel: `Choice for ${field2.label || field2.key}`,
|
|
9836
|
-
triggerText: value || "Choose",
|
|
9861
|
+
triggerText: choices.find((c) => c.key === value || c.label === value)?.label ?? (value || "Choose"),
|
|
9837
9862
|
open,
|
|
9838
9863
|
onOpenChange: (next) => {
|
|
9839
9864
|
setOpen(next);
|
|
@@ -9845,7 +9870,7 @@ function ChoiceValue({ field: field2, value, onChange }) {
|
|
|
9845
9870
|
id: c.key,
|
|
9846
9871
|
label: c.label,
|
|
9847
9872
|
chip: choiceChipLookOf(field2, c.key, c.label).className,
|
|
9848
|
-
picked: c.label === value,
|
|
9873
|
+
picked: c.label === value || c.key === value,
|
|
9849
9874
|
attrs: { "data-pick-value": c.label }
|
|
9850
9875
|
})),
|
|
9851
9876
|
onPick: (key) => {
|
|
@@ -9954,6 +9979,15 @@ function ConditionRow({ lead, expr, fields, onChange, emptyLabel = "always", cla
|
|
|
9954
9979
|
attrs: { "data-condition": "field" }
|
|
9955
9980
|
}
|
|
9956
9981
|
),
|
|
9982
|
+
onField && !fields.some((f) => f.id === onField) ? /* @__PURE__ */ jsx37(
|
|
9983
|
+
"span",
|
|
9984
|
+
{
|
|
9985
|
+
className: "rounded bg-destructive px-1.5 py-0.5 text-[11px] font-medium text-destructive-foreground",
|
|
9986
|
+
title: "This condition names a question that is no longer on the form.",
|
|
9987
|
+
"data-records-condition-missing": "",
|
|
9988
|
+
children: "Missing question"
|
|
9989
|
+
}
|
|
9990
|
+
) : null,
|
|
9957
9991
|
onField ? /* @__PURE__ */ jsxs32(Fragment11, { children: [
|
|
9958
9992
|
/* @__PURE__ */ jsx37(
|
|
9959
9993
|
PickOne,
|
|
@@ -37722,9 +37756,53 @@ function formPresentation(spec) {
|
|
|
37722
37756
|
welcome: welcomeDocument(spec.welcome ?? null),
|
|
37723
37757
|
endings: (spec.endings ?? []).map(endingDocument),
|
|
37724
37758
|
hidden_fields: spec.hiddenFields ?? [],
|
|
37725
|
-
theme: themeDocument(spec.theme ?? null)
|
|
37759
|
+
theme: themeDocument(spec.theme ?? null),
|
|
37760
|
+
...typeof spec.showOwnerHeader === "boolean" ? { show_owner_header: spec.showOwnerHeader } : {},
|
|
37761
|
+
...typeof spec.autoAdvance === "boolean" ? { auto_advance: spec.autoAdvance } : {},
|
|
37762
|
+
...spec.notify && (spec.notify.webhookUrl || spec.notify.webhookAutomationId || spec.notify.emailRespondent) ? {
|
|
37763
|
+
notify: strip({
|
|
37764
|
+
webhook_url: text(spec.notify.webhookUrl),
|
|
37765
|
+
webhook_automation_id: text(spec.notify.webhookAutomationId),
|
|
37766
|
+
email_respondent: spec.notify.emailRespondent ? true : null,
|
|
37767
|
+
respondent_field: spec.notify.emailRespondent ? text(spec.notify.respondentField) : null
|
|
37768
|
+
})
|
|
37769
|
+
} : {}
|
|
37726
37770
|
};
|
|
37727
37771
|
}
|
|
37772
|
+
function notifyFromDocument(raw) {
|
|
37773
|
+
if (!raw || typeof raw !== "object" || Array.isArray(raw)) return null;
|
|
37774
|
+
const d = raw;
|
|
37775
|
+
const n = {
|
|
37776
|
+
webhookUrl: text(d["webhook_url"]),
|
|
37777
|
+
webhookAutomationId: text(d["webhook_automation_id"]),
|
|
37778
|
+
emailRespondent: d["email_respondent"] === true ? true : null,
|
|
37779
|
+
respondentField: text(d["respondent_field"])
|
|
37780
|
+
};
|
|
37781
|
+
return n.webhookUrl || n.webhookAutomationId || n.emailRespondent ? n : null;
|
|
37782
|
+
}
|
|
37783
|
+
var bool = (v) => typeof v === "boolean" ? v : null;
|
|
37784
|
+
function answerRefusal(kind, required, value) {
|
|
37785
|
+
const empty = value === void 0 || value === null || value === "" || Array.isArray(value) && value.length === 0;
|
|
37786
|
+
if (empty) return required ? "This field is required" : null;
|
|
37787
|
+
if (typeof value !== "string") return null;
|
|
37788
|
+
const v = value.trim();
|
|
37789
|
+
if (kind === "email" && !/^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(v)) return "Enter a valid email";
|
|
37790
|
+
if (kind === "phone" && (v.replace(/\D/g, "").length < 7 || /[^\d\s()+.\-x]/i.test(v))) return "Enter a valid phone number";
|
|
37791
|
+
if (kind === "url" && !/^(https?:\/\/)?[^\s.\/]+(\.[^\s.\/]+)+(\/\S*)?$/i.test(v)) return "Enter a valid link";
|
|
37792
|
+
return null;
|
|
37793
|
+
}
|
|
37794
|
+
function pipeAnswers(text2, answers, questions, labelOf) {
|
|
37795
|
+
if (!text2.includes("{{")) return text2;
|
|
37796
|
+
return text2.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (_all, name) => {
|
|
37797
|
+
const wanted = name.trim().toLowerCase();
|
|
37798
|
+
const q = questions.find((x) => x.key.toLowerCase() === wanted) ?? questions.find((x) => (x.ask ?? "").trim().toLowerCase() === wanted);
|
|
37799
|
+
if (!q) return "";
|
|
37800
|
+
const value = answers[q.key];
|
|
37801
|
+
if (value === void 0 || value === null) return "";
|
|
37802
|
+
const one = (v) => labelOf?.(q.key, v) ?? String(v);
|
|
37803
|
+
return Array.isArray(value) ? value.map(one).join(", ") : one(value);
|
|
37804
|
+
});
|
|
37805
|
+
}
|
|
37728
37806
|
var text = (v) => typeof v === "string" && v.trim() !== "" ? v.trim() : null;
|
|
37729
37807
|
var num = (v) => typeof v === "number" && Number.isFinite(v) ? v : null;
|
|
37730
37808
|
var strip = (doc) => Object.fromEntries(Object.entries(doc).filter(([, v]) => v !== null && v !== void 0));
|
|
@@ -37899,6 +37977,9 @@ function formFromSummary(summary) {
|
|
|
37899
37977
|
thankYou: thankYouFromDocument(presentation["thank_you"]),
|
|
37900
37978
|
look: formLookFromDocument(presentation["look"]),
|
|
37901
37979
|
submitLabel: presentation["submit_label"] ?? null,
|
|
37980
|
+
showOwnerHeader: bool(presentation["show_owner_header"]),
|
|
37981
|
+
autoAdvance: bool(presentation["auto_advance"]),
|
|
37982
|
+
notify: notifyFromDocument(presentation["notify"]),
|
|
37902
37983
|
// A form is public when the store says it is OPEN. A screen never decides
|
|
37903
37984
|
// this from a flag of its own: the link answers or it does not.
|
|
37904
37985
|
isPublic: summary.state === "open",
|
|
@@ -38703,6 +38784,50 @@ function compactTick(value) {
|
|
|
38703
38784
|
if (Math.abs(value) < 1e3 && !Number.isInteger(value)) return String(Math.round(value * 10) / 10);
|
|
38704
38785
|
return formatCount5(value, { style: "compact" });
|
|
38705
38786
|
}
|
|
38787
|
+
function wrapTickWords(label, perLine, maxLines = 3) {
|
|
38788
|
+
const words2 = label.split(/\s+/).filter(Boolean).map((w) => w.length > perLine ? `${w.slice(0, perLine - 1)}\u2026` : w);
|
|
38789
|
+
const lines = [];
|
|
38790
|
+
let line = "";
|
|
38791
|
+
for (const word of words2) {
|
|
38792
|
+
const next = line ? `${line} ${word}` : word;
|
|
38793
|
+
if (next.length <= perLine) line = next;
|
|
38794
|
+
else {
|
|
38795
|
+
lines.push(line);
|
|
38796
|
+
line = word;
|
|
38797
|
+
}
|
|
38798
|
+
}
|
|
38799
|
+
if (line) lines.push(line);
|
|
38800
|
+
if (lines.length <= maxLines) return lines;
|
|
38801
|
+
const kept = lines.slice(0, maxLines);
|
|
38802
|
+
const last = kept[maxLines - 1].replace(/…$/, "");
|
|
38803
|
+
kept[maxLines - 1] = `${last.length >= perLine ? last.slice(0, perLine - 1) : last}\u2026`;
|
|
38804
|
+
return kept;
|
|
38805
|
+
}
|
|
38806
|
+
function WrappedTick(props) {
|
|
38807
|
+
const { x = 0, y = 0, payload, across, perLine } = props;
|
|
38808
|
+
const lines = wrapTickWords(String(payload?.value ?? ""), perLine);
|
|
38809
|
+
const lineHeight = 11;
|
|
38810
|
+
return /* @__PURE__ */ jsx120(
|
|
38811
|
+
"text",
|
|
38812
|
+
{
|
|
38813
|
+
x,
|
|
38814
|
+
y,
|
|
38815
|
+
fontSize: 10,
|
|
38816
|
+
fill: "currentColor",
|
|
38817
|
+
textAnchor: across ? "end" : "middle",
|
|
38818
|
+
"data-records-chart-tick": "",
|
|
38819
|
+
children: lines.map((line, i) => /* @__PURE__ */ jsx120(
|
|
38820
|
+
"tspan",
|
|
38821
|
+
{
|
|
38822
|
+
x: across ? x - 4 : x,
|
|
38823
|
+
dy: i === 0 ? across ? -((lines.length - 1) * lineHeight) / 2 + 3 : 10 : lineHeight,
|
|
38824
|
+
children: line
|
|
38825
|
+
},
|
|
38826
|
+
i
|
|
38827
|
+
))
|
|
38828
|
+
}
|
|
38829
|
+
);
|
|
38830
|
+
}
|
|
38706
38831
|
function Drawing({
|
|
38707
38832
|
kind,
|
|
38708
38833
|
points,
|
|
@@ -38808,9 +38933,9 @@ function Drawing({
|
|
|
38808
38933
|
/* @__PURE__ */ jsx120(CartesianGrid, { vertical: horizontal, horizontal: !horizontal }),
|
|
38809
38934
|
horizontal ? /* @__PURE__ */ jsxs110(Fragment48, { children: [
|
|
38810
38935
|
/* @__PURE__ */ jsx120(XAxis, { type: "number", ...axis }),
|
|
38811
|
-
/* @__PURE__ */ jsx120(YAxis, { type: "category", dataKey: "label", width:
|
|
38936
|
+
/* @__PURE__ */ jsx120(YAxis, { type: "category", dataKey: "label", width: 96, ...axis, tick: /* @__PURE__ */ jsx120(WrappedTick, { across: true, perLine: 15 }) })
|
|
38812
38937
|
] }) : /* @__PURE__ */ jsxs110(Fragment48, { children: [
|
|
38813
|
-
/* @__PURE__ */ jsx120(XAxis, { dataKey: "label", ...axis }),
|
|
38938
|
+
/* @__PURE__ */ jsx120(XAxis, { dataKey: "label", ...axis, interval: 0, height: 40, tick: /* @__PURE__ */ jsx120(WrappedTick, { perLine: Math.max(6, Math.floor(width / Math.max(points.length, 1) / 6)) }) }),
|
|
38814
38939
|
/* @__PURE__ */ jsx120(YAxis, { width: 44, ...axis })
|
|
38815
38940
|
] }),
|
|
38816
38941
|
tooltip,
|
|
@@ -40459,7 +40584,7 @@ function FormLookLogo({ view, size = "md" }) {
|
|
|
40459
40584
|
}
|
|
40460
40585
|
);
|
|
40461
40586
|
}
|
|
40462
|
-
function FormLookFrame({ look, title, children, contained = false, className }) {
|
|
40587
|
+
function FormLookFrame({ look, title, children, contained = false, showOwnerHeader = true, className }) {
|
|
40463
40588
|
const view = formLookView(look, title);
|
|
40464
40589
|
return /* @__PURE__ */ jsxs117(
|
|
40465
40590
|
"div",
|
|
@@ -40476,10 +40601,10 @@ function FormLookFrame({ look, title, children, contained = false, className })
|
|
|
40476
40601
|
}
|
|
40477
40602
|
) : null,
|
|
40478
40603
|
view.coverUrl ? /* @__PURE__ */ jsx127("div", { "data-records-form-cover": "", className: "aspect-[3/1] w-full overflow-hidden rounded-xl border border-border bg-muted", children: /* @__PURE__ */ jsx127("img", { src: view.coverUrl, alt: "", className: "h-full w-full object-cover" }) }) : null,
|
|
40479
|
-
/* @__PURE__ */ jsxs117("div", { className: "flex items-center gap-3", children: [
|
|
40604
|
+
showOwnerHeader ? /* @__PURE__ */ jsxs117("div", { className: "flex items-center gap-3", "data-records-form-owner": "", children: [
|
|
40480
40605
|
/* @__PURE__ */ jsx127(FormLookLogo, { view }),
|
|
40481
40606
|
/* @__PURE__ */ jsx127("p", { className: "min-w-0 truncate text-base font-semibold tracking-tight text-foreground", children: view.name })
|
|
40482
|
-
] }),
|
|
40607
|
+
] }) : null,
|
|
40483
40608
|
/* @__PURE__ */ jsx127("h1", { className: "text-2xl font-semibold tracking-tight text-foreground", children: title }),
|
|
40484
40609
|
/* @__PURE__ */ jsx127("div", { className: "w-full", children }),
|
|
40485
40610
|
view.footerLinks.length > 0 ? /* @__PURE__ */ jsx127("footer", { className: "mt-10 flex flex-wrap items-center justify-center gap-x-5 gap-y-2 pb-6 text-sm text-muted-foreground", children: view.footerLinks.map((link) => /* @__PURE__ */ jsx127(
|
|
@@ -41302,17 +41427,20 @@ function SubscriptionsPanel({ tableId, activeRuleId, className }) {
|
|
|
41302
41427
|
// src/FormBuilder.tsx
|
|
41303
41428
|
import { copyText as copyText7 } from "@ai-matrx/kit/clipboard";
|
|
41304
41429
|
import { ErrorBox as ErrorBox7 } from "@ai-matrx/design-system";
|
|
41305
|
-
import { useCallback as
|
|
41306
|
-
import {
|
|
41430
|
+
import { useCallback as useCallback54, useEffect as useEffect96, useMemo as useMemo68, useRef as useRef56, useState as useState110 } from "react";
|
|
41431
|
+
import { DndContext, KeyboardSensor, PointerSensor, closestCenter, useSensor, useSensors } from "@dnd-kit/core";
|
|
41432
|
+
import { SortableContext, arrayMove, sortableKeyboardCoordinates, useSortable, verticalListSortingStrategy } from "@dnd-kit/sortable";
|
|
41433
|
+
import { CSS as CSS2 } from "@dnd-kit/utilities";
|
|
41434
|
+
import { useFields as useFields25, useRecordsClient as useRecordsClient79, useTable as useTable26 } from "@ai-matrx/records/react";
|
|
41307
41435
|
import { publicFormPath } from "@ai-matrx/records";
|
|
41308
41436
|
import {
|
|
41309
41437
|
BasicInput as BasicInput28,
|
|
41310
41438
|
BasicTextarea as BasicTextarea11,
|
|
41311
|
-
Button as
|
|
41439
|
+
Button as Button86,
|
|
41312
41440
|
Checkbox as Checkbox15,
|
|
41313
|
-
Label as
|
|
41314
|
-
Skeleton as
|
|
41315
|
-
cn as
|
|
41441
|
+
Label as Label23,
|
|
41442
|
+
Skeleton as Skeleton29,
|
|
41443
|
+
cn as cn78
|
|
41316
41444
|
} from "@ai-matrx/design-system";
|
|
41317
41445
|
|
|
41318
41446
|
// src/FormFlowEditor.tsx
|
|
@@ -41570,7 +41698,7 @@ function EmbedSnippets({ url, label }) {
|
|
|
41570
41698
|
// src/FormRunner.tsx
|
|
41571
41699
|
import { useCallback as useCallback53, useEffect as useEffect95, useMemo as useMemo67, useRef as useRef55, useState as useState109 } from "react";
|
|
41572
41700
|
import { useFields as useFields24, useOptionalRecordsClient as useOptionalRecordsClient14 } from "@ai-matrx/records/react";
|
|
41573
|
-
import { Button as Button85, Progress, Skeleton as Skeleton28, cn as cn77, formThemeClasses } from "@ai-matrx/design-system";
|
|
41701
|
+
import { Button as Button85, Label as Label22, Progress, Skeleton as Skeleton28, cn as cn77, formThemeClasses } from "@ai-matrx/design-system";
|
|
41574
41702
|
import { humanizeIdentifier as humanizeIdentifier18 } from "@ai-matrx/kit/text-case";
|
|
41575
41703
|
import { Fragment as Fragment53, jsx as jsx132, jsxs as jsxs122 } from "react/jsx-runtime";
|
|
41576
41704
|
function authored(host, text2, where) {
|
|
@@ -41669,7 +41797,8 @@ function FormStage({
|
|
|
41669
41797
|
className,
|
|
41670
41798
|
connected = false,
|
|
41671
41799
|
onStart,
|
|
41672
|
-
onQuestionShown
|
|
41800
|
+
onQuestionShown,
|
|
41801
|
+
autoAdvance
|
|
41673
41802
|
}) {
|
|
41674
41803
|
const host = useRecordsUi();
|
|
41675
41804
|
const [answers, setAnswers] = useState109(() => ({ ...initialAnswers ?? {} }));
|
|
@@ -41682,6 +41811,11 @@ function FormStage({
|
|
|
41682
41811
|
const [ending, setEnding] = useState109(null);
|
|
41683
41812
|
const [started, setStarted] = useState109(() => !form.welcome || resumed);
|
|
41684
41813
|
const routes = formRoutes(form);
|
|
41814
|
+
const [routeKnown, setRouteKnown] = useState109(() => !routes || !whichAsked);
|
|
41815
|
+
const [stopped, setStopped] = useState109(null);
|
|
41816
|
+
const answersRef = useRef55(answers);
|
|
41817
|
+
answersRef.current = answers;
|
|
41818
|
+
const choose = autoAdvance ?? form.autoAdvance ?? true;
|
|
41685
41819
|
const decoy = useRef55("");
|
|
41686
41820
|
const stage = useRef55(null);
|
|
41687
41821
|
const questions = useMemo67(() => {
|
|
@@ -41717,6 +41851,7 @@ function FormStage({
|
|
|
41717
41851
|
};
|
|
41718
41852
|
}
|
|
41719
41853
|
if (cancelled) return;
|
|
41854
|
+
setRouteKnown(true);
|
|
41720
41855
|
if (!told2.ok) {
|
|
41721
41856
|
setHidden({});
|
|
41722
41857
|
setError(told2.error ?? refusal("not_supported", told2.message, "Answer every question shown; none were skipped."));
|
|
@@ -41738,7 +41873,7 @@ function FormStage({
|
|
|
41738
41873
|
) : null
|
|
41739
41874
|
);
|
|
41740
41875
|
})();
|
|
41741
|
-
}, 250);
|
|
41876
|
+
}, routeKnown ? 250 : 0);
|
|
41742
41877
|
return () => {
|
|
41743
41878
|
cancelled = true;
|
|
41744
41879
|
clearTimeout(timer);
|
|
@@ -41773,11 +41908,24 @@ function FormStage({
|
|
|
41773
41908
|
const v = answers[q.key];
|
|
41774
41909
|
return v !== void 0 && v !== null && v !== "";
|
|
41775
41910
|
}).length;
|
|
41776
|
-
const
|
|
41777
|
-
|
|
41778
|
-
|
|
41779
|
-
|
|
41911
|
+
const refusalFor = (q, values = answersRef.current) => answerRefusal(q.field ? String(editorKindFor(q.field)) : "text", q.required, values[q.key]);
|
|
41912
|
+
const refuse = (q, say2) => {
|
|
41913
|
+
setStopped({ key: q.key, say: say2 });
|
|
41914
|
+
const i = live.findIndex((x) => x.key === q.key);
|
|
41915
|
+
if (oneAtATime && i >= 0 && i !== index) setAt(i);
|
|
41916
|
+
window.setTimeout(() => {
|
|
41917
|
+
const box = stage.current?.querySelector(`[data-records-form-question="${CSS.escape(q.key)}"]`);
|
|
41918
|
+
box?.querySelector("[role='radio'][aria-checked='true'], input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']")?.focus();
|
|
41919
|
+
}, 0);
|
|
41920
|
+
};
|
|
41780
41921
|
const submit = useCallback53(async () => {
|
|
41922
|
+
for (const q of live) {
|
|
41923
|
+
const say2 = refusalFor(q);
|
|
41924
|
+
if (say2) {
|
|
41925
|
+
refuse(q, say2);
|
|
41926
|
+
return;
|
|
41927
|
+
}
|
|
41928
|
+
}
|
|
41781
41929
|
if (preview) {
|
|
41782
41930
|
setDone("preview");
|
|
41783
41931
|
return;
|
|
@@ -41809,20 +41957,28 @@ function FormStage({
|
|
|
41809
41957
|
}, [answers, form, honeypotKey, live, onSubmit, onSubmitted, preview, questions]);
|
|
41810
41958
|
async function advance() {
|
|
41811
41959
|
if (!oneAtATime) return;
|
|
41960
|
+
if (current) {
|
|
41961
|
+
const say2 = refusalFor(current);
|
|
41962
|
+
if (say2) {
|
|
41963
|
+
refuse(current, say2);
|
|
41964
|
+
return;
|
|
41965
|
+
}
|
|
41966
|
+
}
|
|
41967
|
+
setStopped(null);
|
|
41812
41968
|
if (!routes || !whichAsked || !current) {
|
|
41813
41969
|
if (index < live.length - 1) setAt(index + 1);
|
|
41814
|
-
else
|
|
41970
|
+
else void submit();
|
|
41815
41971
|
return;
|
|
41816
41972
|
}
|
|
41817
41973
|
let told2;
|
|
41818
41974
|
try {
|
|
41819
|
-
told2 = await whichAsked(
|
|
41975
|
+
told2 = await whichAsked(answersRef.current);
|
|
41820
41976
|
} catch {
|
|
41821
41977
|
told2 = { ok: false, message: "This form could not work out which question comes next, so the next one is shown." };
|
|
41822
41978
|
}
|
|
41823
41979
|
if (!told2.ok) {
|
|
41824
41980
|
if (index < live.length - 1) setAt(index + 1);
|
|
41825
|
-
else
|
|
41981
|
+
else void submit();
|
|
41826
41982
|
return;
|
|
41827
41983
|
}
|
|
41828
41984
|
const next = {};
|
|
@@ -41835,13 +41991,18 @@ function FormStage({
|
|
|
41835
41991
|
setAt(here + 1);
|
|
41836
41992
|
return;
|
|
41837
41993
|
}
|
|
41838
|
-
const stillMissing = onRoute.
|
|
41839
|
-
|
|
41840
|
-
|
|
41841
|
-
});
|
|
41842
|
-
if (stillMissing.length === 0) void submit();
|
|
41994
|
+
const stillMissing = onRoute.find((q) => refusalFor(q) !== null);
|
|
41995
|
+
if (stillMissing) refuse(stillMissing, refusalFor(stillMissing));
|
|
41996
|
+
else void submit();
|
|
41843
41997
|
}
|
|
41998
|
+
const advanceRef = useRef55(advance);
|
|
41999
|
+
advanceRef.current = advance;
|
|
42000
|
+
const chosen = useCallback53(() => {
|
|
42001
|
+
if (!oneAtATime || !choose) return;
|
|
42002
|
+
window.setTimeout(() => void advanceRef.current(), 450);
|
|
42003
|
+
}, [oneAtATime, choose]);
|
|
41844
42004
|
function retreat() {
|
|
42005
|
+
setStopped(null);
|
|
41845
42006
|
if (oneAtATime && index > 0) setAt(index - 1);
|
|
41846
42007
|
}
|
|
41847
42008
|
function onKeyDown(event) {
|
|
@@ -41857,12 +42018,11 @@ function FormStage({
|
|
|
41857
42018
|
else advance();
|
|
41858
42019
|
}
|
|
41859
42020
|
useEffect95(() => {
|
|
41860
|
-
const input = stage.current?.querySelector(
|
|
41861
|
-
// A choice question has no box: its first option takes the caret, so A / B / C answer it.
|
|
42021
|
+
const input = stage.current?.querySelector("[role='radio'][aria-checked='true']") ?? stage.current?.querySelector(
|
|
41862
42022
|
"input:not([tabindex='-1']), textarea, select, [role='combobox'], [role='radio'], [role='checkbox']"
|
|
41863
42023
|
);
|
|
41864
42024
|
input?.focus();
|
|
41865
|
-
}, [index, done]);
|
|
42025
|
+
}, [index, done, started]);
|
|
41866
42026
|
const startTold = useRef55(false);
|
|
41867
42027
|
useEffect95(() => {
|
|
41868
42028
|
if (startTold.current || !onStart) return;
|
|
@@ -41881,6 +42041,10 @@ function FormStage({
|
|
|
41881
42041
|
}
|
|
41882
42042
|
}, [current, live, oneAtATime, onQuestionShown, started, done]);
|
|
41883
42043
|
const theme = formThemeClasses(form.theme ?? null);
|
|
42044
|
+
const pipe = (text2) => pipeAnswers(text2, answers, questions, (key, v) => {
|
|
42045
|
+
const f = questions.find((q) => q.key === key)?.field;
|
|
42046
|
+
return f ? publicChoicesOf(f).find((c) => c.key === String(v))?.label ?? null : null;
|
|
42047
|
+
});
|
|
41884
42048
|
if (done) {
|
|
41885
42049
|
return /* @__PURE__ */ jsx132(
|
|
41886
42050
|
ThankYou,
|
|
@@ -41889,6 +42053,7 @@ function FormStage({
|
|
|
41889
42053
|
done,
|
|
41890
42054
|
ending: (form.endings ?? []).find((e) => e.id === ending) ?? null,
|
|
41891
42055
|
buttonClass: theme.buttonClass,
|
|
42056
|
+
pipe,
|
|
41892
42057
|
className: cn77("mx-auto max-w-xl py-10", centred && "text-center", className)
|
|
41893
42058
|
}
|
|
41894
42059
|
);
|
|
@@ -41900,14 +42065,14 @@ function FormStage({
|
|
|
41900
42065
|
return /* @__PURE__ */ jsxs122(
|
|
41901
42066
|
"section",
|
|
41902
42067
|
{
|
|
41903
|
-
className: cn77("mx-auto flex w-full max-w-
|
|
42068
|
+
className: cn77("mx-auto flex min-h-[min(70dvh,640px)] w-full max-w-2xl flex-col justify-center gap-4 py-8", theme.surface, theme.background !== "plain" && "rounded-lg px-5", centred && "items-center text-center", className),
|
|
41904
42069
|
style: theme.surfaceStyle,
|
|
41905
42070
|
"data-records-form-welcome": "",
|
|
41906
42071
|
children: [
|
|
41907
42072
|
w.pictureUrl ? /* @__PURE__ */ jsx132("img", { src: w.pictureUrl, alt: "", className: "max-h-56 w-full rounded-md object-cover" }) : null,
|
|
41908
|
-
w.title ? /* @__PURE__ */ jsx132("h2", { className: "text-
|
|
41909
|
-
w.body ? /* @__PURE__ */ jsx132("p", { className: "text-
|
|
41910
|
-
/* @__PURE__ */ jsx132("div", { children: /* @__PURE__ */ jsx132(Button85, { size: "
|
|
42073
|
+
w.title ? /* @__PURE__ */ jsx132("h2", { className: "text-2xl font-semibold sm:text-3xl", children: w.title }) : null,
|
|
42074
|
+
w.body ? /* @__PURE__ */ jsx132("p", { className: "text-base text-muted-foreground", children: authored(host, w.body, "form-intro") }) : null,
|
|
42075
|
+
/* @__PURE__ */ jsx132("div", { children: /* @__PURE__ */ jsx132(Button85, { size: "lg", className: theme.buttonClass, onClick: () => setStarted(true), "data-records-form-start": "", children: w.buttonLabel || "Start" }) })
|
|
41911
42076
|
]
|
|
41912
42077
|
}
|
|
41913
42078
|
);
|
|
@@ -41916,7 +42081,15 @@ function FormStage({
|
|
|
41916
42081
|
return /* @__PURE__ */ jsxs122(
|
|
41917
42082
|
"section",
|
|
41918
42083
|
{
|
|
41919
|
-
className: cn77(
|
|
42084
|
+
className: cn77(
|
|
42085
|
+
"mx-auto flex w-full max-w-2xl flex-col gap-4 py-4",
|
|
42086
|
+
// TYPEFORM'S SIZE (TYPEFORM-2): one question at a time fills the screen, centred in it.
|
|
42087
|
+
oneAtATime && "min-h-[min(70dvh,640px)]",
|
|
42088
|
+
theme.surface,
|
|
42089
|
+
theme.background !== "plain" && "rounded-lg px-5 sm:px-8",
|
|
42090
|
+
centred && "text-center",
|
|
42091
|
+
className
|
|
42092
|
+
),
|
|
41920
42093
|
style: theme.surfaceStyle,
|
|
41921
42094
|
"data-records-form-font": theme.font,
|
|
41922
42095
|
onKeyDown,
|
|
@@ -41926,20 +42099,41 @@ function FormStage({
|
|
|
41926
42099
|
Progress,
|
|
41927
42100
|
{
|
|
41928
42101
|
value: live.length === 0 ? 0 : (oneAtATime ? index + 1 : answered) / live.length * 100,
|
|
41929
|
-
className: "h-1 flex-1"
|
|
42102
|
+
className: "h-1 flex-1",
|
|
42103
|
+
"aria-label": "Form progress",
|
|
42104
|
+
...routeKnown ? { "aria-valuetext": oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` } : {}
|
|
41930
42105
|
}
|
|
41931
42106
|
),
|
|
41932
|
-
/* @__PURE__ */ jsx132("span", { className: "tabular-nums", children: oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` })
|
|
42107
|
+
/* @__PURE__ */ jsx132("span", { className: cn77("min-w-[3.5rem] text-right tabular-nums", !routeKnown && "invisible"), "data-records-form-count": "", children: oneAtATime ? `${index + 1} of ${live.length}` : `${answered} of ${live.length}` })
|
|
41933
42108
|
] }),
|
|
41934
|
-
signedInPublic ? /* @__PURE__ */ jsx132(
|
|
41935
|
-
|
|
41936
|
-
|
|
41937
|
-
|
|
41938
|
-
|
|
41939
|
-
|
|
41940
|
-
|
|
41941
|
-
|
|
41942
|
-
|
|
42109
|
+
signedInPublic ? /* @__PURE__ */ jsx132(
|
|
42110
|
+
"p",
|
|
42111
|
+
{
|
|
42112
|
+
className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
|
|
42113
|
+
title: "The record will carry your name. Strangers answer at the public link.",
|
|
42114
|
+
children: "Signed in \xB7 saved as you"
|
|
42115
|
+
}
|
|
42116
|
+
) : null,
|
|
42117
|
+
unanswerable ? /* @__PURE__ */ jsx132(
|
|
42118
|
+
"p",
|
|
42119
|
+
{
|
|
42120
|
+
className: "w-fit rounded border border-dashed px-2 py-0.5 text-left text-xs text-muted-foreground",
|
|
42121
|
+
title: "Nothing here can work out which conditional questions apply, so all of them are shown.",
|
|
42122
|
+
children: "Showing every question"
|
|
42123
|
+
}
|
|
42124
|
+
) : null,
|
|
42125
|
+
unresolved.length > 0 ? /* @__PURE__ */ jsxs122(
|
|
42126
|
+
"p",
|
|
42127
|
+
{
|
|
42128
|
+
className: "w-fit rounded border border-destructive/40 px-2 py-0.5 text-left text-xs text-destructive",
|
|
42129
|
+
title: "The table has no such field. Add the field, or take the question out.",
|
|
42130
|
+
children: [
|
|
42131
|
+
"Missing field: ",
|
|
42132
|
+
unresolved.map((q) => q.ask).join(", ")
|
|
42133
|
+
]
|
|
42134
|
+
}
|
|
42135
|
+
) : null,
|
|
42136
|
+
form.intro && index === 0 ? /* @__PURE__ */ jsx132("p", { className: "text-base text-muted-foreground", children: authored(host, form.intro, "form-intro") }) : null,
|
|
41943
42137
|
error ? /* @__PURE__ */ jsx132(RefusalNotice, { error, className: "text-left" }) : null,
|
|
41944
42138
|
refusal4 ? /* @__PURE__ */ jsx132(
|
|
41945
42139
|
RefusalNotice,
|
|
@@ -41961,14 +42155,20 @@ function FormStage({
|
|
|
41961
42155
|
(oneAtATime ? current ? [current] : [] : live).map((q) => /* @__PURE__ */ jsx132(
|
|
41962
42156
|
Question,
|
|
41963
42157
|
{
|
|
41964
|
-
question: q,
|
|
42158
|
+
question: { ...q, ask: pipe(q.ask), help: q.help ? pipe(q.help) : null },
|
|
41965
42159
|
value: answers[q.key],
|
|
41966
|
-
onChange: (v) =>
|
|
42160
|
+
onChange: (v) => {
|
|
42161
|
+
if (stopped?.key === q.key) setStopped(null);
|
|
42162
|
+
setAnswers((prev) => ({ ...prev, [q.key]: v }));
|
|
42163
|
+
},
|
|
42164
|
+
onChosen: chosen,
|
|
42165
|
+
refused: stopped?.key === q.key ? stopped.say : null,
|
|
42166
|
+
large: oneAtATime,
|
|
41967
42167
|
upload: host.upload
|
|
41968
42168
|
},
|
|
41969
42169
|
q.key
|
|
41970
42170
|
)),
|
|
41971
|
-
live.length === 0 ? /* @__PURE__ */ jsx132("p", { className: "text-xs text-muted-foreground",
|
|
42171
|
+
live.length === 0 ? /* @__PURE__ */ jsx132("p", { className: "text-xs text-muted-foreground", title: "Every question is hidden by its own condition right now.", children: "Nothing to answer yet" }) : null
|
|
41972
42172
|
] }),
|
|
41973
42173
|
honeypotKey ? /* @__PURE__ */ jsxs122("div", { "aria-hidden": true, className: "pointer-events-none absolute left-[-9999px] h-px w-px overflow-hidden", children: [
|
|
41974
42174
|
/* @__PURE__ */ jsx132("label", { htmlFor: `hp-${honeypotKey}`, children: "Leave this field empty" }),
|
|
@@ -41987,16 +42187,14 @@ function FormStage({
|
|
|
41987
42187
|
}
|
|
41988
42188
|
)
|
|
41989
42189
|
] }) : null,
|
|
41990
|
-
/* @__PURE__ */ jsxs122("footer", { className: cn77("flex flex-wrap items-center gap-
|
|
41991
|
-
oneAtATime && index > 0 ? /* @__PURE__ */ jsx132(Button85, { size: "
|
|
41992
|
-
oneAtATime && index < live.length - 1 ? /* @__PURE__ */ jsx132(Button85, { size: "
|
|
41993
|
-
|
|
41994
|
-
|
|
41995
|
-
"
|
|
41996
|
-
missing.length === 1 ? "needs" : "need",
|
|
41997
|
-
" an answer."
|
|
42190
|
+
/* @__PURE__ */ jsxs122("footer", { className: cn77("flex flex-wrap items-center gap-3", centred && "justify-center"), children: [
|
|
42191
|
+
oneAtATime && index > 0 ? /* @__PURE__ */ jsx132(Button85, { size: "lg", variant: "ghost", className: theme.buttonClass, onClick: retreat, children: "Back" }) : null,
|
|
42192
|
+
oneAtATime && index < live.length - 1 ? /* @__PURE__ */ jsx132(Button85, { size: "lg", className: theme.buttonClass, onClick: () => void advance(), children: "Next" }) : /* @__PURE__ */ jsx132(Button85, { size: "lg", className: theme.buttonClass, disabled: writing, onClick: () => void (oneAtATime ? advance() : submit()), children: writing ? "Sending\u2026" : form.submitLabel ?? "Submit" }),
|
|
42193
|
+
oneAtATime ? /* @__PURE__ */ jsxs122("span", { className: "hidden text-xs text-muted-foreground sm:inline", "data-records-form-enter-hint": "", children: [
|
|
42194
|
+
"press ",
|
|
42195
|
+
/* @__PURE__ */ jsx132("strong", { className: "font-semibold text-foreground", children: "Enter \u21B5" })
|
|
41998
42196
|
] }) : null,
|
|
41999
|
-
preview ? /* @__PURE__ */ jsx132("span", { className: "text-xs text-muted-foreground",
|
|
42197
|
+
preview ? /* @__PURE__ */ jsx132("span", { className: "text-xs text-muted-foreground", title: "Nothing is written from a preview.", children: "Preview" }) : null
|
|
42000
42198
|
] })
|
|
42001
42199
|
]
|
|
42002
42200
|
}
|
|
@@ -42008,12 +42206,15 @@ function ThankYou({
|
|
|
42008
42206
|
done,
|
|
42009
42207
|
className,
|
|
42010
42208
|
ending,
|
|
42011
|
-
buttonClass
|
|
42209
|
+
buttonClass,
|
|
42210
|
+
pipe = (t) => t
|
|
42012
42211
|
}) {
|
|
42013
42212
|
const host = useRecordsUi();
|
|
42014
42213
|
const redirect = (ending ? ending.redirectUrl?.trim() : form.thankYou?.redirectUrl?.trim()) || null;
|
|
42015
|
-
const
|
|
42016
|
-
const
|
|
42214
|
+
const rawTitle = ending ? ending.title : form.thankYou?.title;
|
|
42215
|
+
const rawBody = ending ? ending.body : form.thankYou?.body;
|
|
42216
|
+
const title = rawTitle ? pipe(rawTitle) : rawTitle;
|
|
42217
|
+
const body = rawBody ? pipe(rawBody) : rawBody;
|
|
42017
42218
|
const going = redirect !== null && done !== "preview";
|
|
42018
42219
|
useEffect95(() => {
|
|
42019
42220
|
if (!going || !redirect) return;
|
|
@@ -42030,8 +42231,8 @@ function ThankYou({
|
|
|
42030
42231
|
}
|
|
42031
42232
|
),
|
|
42032
42233
|
ending?.pictureUrl ? /* @__PURE__ */ jsx132("img", { src: ending.pictureUrl, alt: "", className: "mb-3 max-h-48 w-full rounded-md object-cover" }) : null,
|
|
42033
|
-
/* @__PURE__ */ jsx132("h2", { className: "text-
|
|
42034
|
-
body ? /* @__PURE__ */ jsx132("p", { className: "mt-
|
|
42234
|
+
/* @__PURE__ */ jsx132("h2", { className: "text-2xl font-semibold", "data-records-form-ending": ending?.id ?? "", children: title || "Thank you" }),
|
|
42235
|
+
body ? /* @__PURE__ */ jsx132("p", { className: "mt-2 text-base text-muted-foreground", children: authored(host, body, "form-thank-you") }) : null,
|
|
42035
42236
|
going ? /* @__PURE__ */ jsxs122("p", { className: "mt-4 flex flex-wrap items-center gap-3 text-sm", children: [
|
|
42036
42237
|
/* @__PURE__ */ jsx132(
|
|
42037
42238
|
"a",
|
|
@@ -42044,11 +42245,11 @@ function ThankYou({
|
|
|
42044
42245
|
),
|
|
42045
42246
|
/* @__PURE__ */ jsx132("span", { className: "text-muted-foreground", children: "Taking you there\u2026" })
|
|
42046
42247
|
] }) : null,
|
|
42047
|
-
/* @__PURE__ */ jsx132("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `
|
|
42248
|
+
/* @__PURE__ */ jsx132("p", { className: "mt-3 text-xs text-muted-foreground", children: done === "preview" ? redirect ? `Preview \xB7 then ${redirect}` : "Preview \xB7 nothing saved" : done === "sent" ? (
|
|
42048
42249
|
// SAID TO THE PERSON WHO ANSWERED (lane HANDOVER, 2026-09-29): "Saved to Form 1, with
|
|
42049
42250
|
// this form stamped on it" was the owner's bookkeeping read out to a front-desk member
|
|
42050
42251
|
// and to strangers. Where it landed is the owner's to see on the table.
|
|
42051
|
-
"
|
|
42252
|
+
"Answers received"
|
|
42052
42253
|
) : done })
|
|
42053
42254
|
] });
|
|
42054
42255
|
}
|
|
@@ -42056,6 +42257,9 @@ function Question({
|
|
|
42056
42257
|
question,
|
|
42057
42258
|
value,
|
|
42058
42259
|
onChange,
|
|
42260
|
+
onChosen,
|
|
42261
|
+
refused,
|
|
42262
|
+
large,
|
|
42059
42263
|
upload
|
|
42060
42264
|
}) {
|
|
42061
42265
|
const host = useRecordsUi();
|
|
@@ -42063,297 +42267,62 @@ function Question({
|
|
|
42063
42267
|
const field2 = question.field;
|
|
42064
42268
|
if (!field2) return null;
|
|
42065
42269
|
const id = `form-${field2.key}`;
|
|
42270
|
+
const sayId = `${id}-refused`;
|
|
42066
42271
|
const isAttachment = editorKindFor(field2) === "attachment";
|
|
42067
|
-
return /* @__PURE__ */ jsxs122(
|
|
42068
|
-
|
|
42069
|
-
|
|
42070
|
-
|
|
42071
|
-
|
|
42072
|
-
"
|
|
42073
|
-
{
|
|
42074
|
-
id,
|
|
42075
|
-
type: "file",
|
|
42076
|
-
className: "text-xs",
|
|
42077
|
-
onChange: (e) => {
|
|
42078
|
-
const file = e.target.files?.[0];
|
|
42079
|
-
if (!file) return;
|
|
42080
|
-
setUploadError(null);
|
|
42081
|
-
void upload?.(file).then((result) => {
|
|
42082
|
-
if (!result) return;
|
|
42083
|
-
if (result.ok) onChange(result.fileId);
|
|
42084
|
-
else
|
|
42085
|
-
setUploadError(
|
|
42086
|
-
refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
|
|
42087
|
-
);
|
|
42088
|
-
});
|
|
42089
|
-
}
|
|
42090
|
-
}
|
|
42272
|
+
return /* @__PURE__ */ jsxs122(
|
|
42273
|
+
"div",
|
|
42274
|
+
{
|
|
42275
|
+
className: cn77(
|
|
42276
|
+
"flex flex-col",
|
|
42277
|
+
large ? "gap-3 [&_input]:text-base [&_textarea]:text-base [&_input]:min-h-12" : "gap-1.5 [&_input]:text-base [&_textarea]:text-base"
|
|
42091
42278
|
),
|
|
42092
|
-
|
|
42093
|
-
|
|
42094
|
-
|
|
42095
|
-
|
|
42096
|
-
|
|
42097
|
-
|
|
42098
|
-
}
|
|
42099
|
-
|
|
42100
|
-
|
|
42101
|
-
|
|
42102
|
-
|
|
42103
|
-
import { hasQuietHours as hasQuietHours3 } from "@ai-matrx/records";
|
|
42104
|
-
import { Button as Button86, Skeleton as Skeleton29, cn as cn78 } from "@ai-matrx/design-system";
|
|
42105
|
-
import { jsx as jsx133, jsxs as jsxs123 } from "react/jsx-runtime";
|
|
42106
|
-
var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
|
|
42107
|
-
var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
42108
|
-
function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className }) {
|
|
42109
|
-
const client = useRecordsClient79();
|
|
42110
|
-
const host = useRecordsUi();
|
|
42111
|
-
const table = useTable26(tableId);
|
|
42112
|
-
const rights = useTableRights(table.data);
|
|
42113
|
-
const [subscriptions, setSubscriptions] = useState110(null);
|
|
42114
|
-
const [cadences, setCadences] = useState110([]);
|
|
42115
|
-
const [views, setViews] = useState110(null);
|
|
42116
|
-
const [error, setError] = useState110(null);
|
|
42117
|
-
const [busy, setBusy] = useState110(false);
|
|
42118
|
-
const [stopping, setStopping] = useState110(null);
|
|
42119
|
-
const load = useCallback54(async () => {
|
|
42120
|
-
const [held, offered] = await Promise.all([
|
|
42121
|
-
// THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
|
|
42122
|
-
// to them plus — only where they hold admin on this Table — anyone's over
|
|
42123
|
-
// it, and it is narrowed to Tables they can already open.
|
|
42124
|
-
client.subscriptions({ table_id: tableId }),
|
|
42125
|
-
// The cadences are ASKED of the store, never a list typed into a picker
|
|
42126
|
-
// here that could drift from what the digest runner understands.
|
|
42127
|
-
client.subscriptionCadences()
|
|
42128
|
-
]);
|
|
42129
|
-
if (!held.ok) {
|
|
42130
|
-
setError(held.error);
|
|
42131
|
-
return;
|
|
42132
|
-
}
|
|
42133
|
-
setError(null);
|
|
42134
|
-
setSubscriptions(held.data);
|
|
42135
|
-
if (offered.ok) setCadences(offered.data);
|
|
42136
|
-
if (host.savedViews) {
|
|
42137
|
-
setViews(await host.savedViews());
|
|
42138
|
-
return;
|
|
42139
|
-
}
|
|
42140
|
-
const listed = await client.views({ table_id: tableId });
|
|
42141
|
-
if (!listed.ok) {
|
|
42142
|
-
setError(listed.error);
|
|
42143
|
-
return;
|
|
42144
|
-
}
|
|
42145
|
-
setViews(listed.data.map((v) => ({ id: v.view_id, name: v.name })));
|
|
42146
|
-
}, [client, host, tableId]);
|
|
42147
|
-
useEffect96(() => {
|
|
42148
|
-
void load();
|
|
42149
|
-
}, [load]);
|
|
42150
|
-
const write = useCallback54(
|
|
42151
|
-
async (spec) => {
|
|
42152
|
-
const declared2 = await client.subscriptionDeclare({
|
|
42153
|
-
table_id: tableId,
|
|
42154
|
-
spec: {
|
|
42155
|
-
name: spec.name,
|
|
42156
|
-
saved_view_id: spec.savedViewId,
|
|
42157
|
-
cadence: spec.cadence ?? "instant",
|
|
42158
|
-
schedule: spec.schedule ?? null,
|
|
42159
|
-
channel: spec.channel ?? "in_app",
|
|
42160
|
-
quiet_hours: spec.quietHours ?? null,
|
|
42161
|
-
// "Tell me" is the default, and the store refuses a subscription with
|
|
42162
|
-
// nobody to tell rather than writing one that fires at nobody.
|
|
42163
|
-
recipient_user_id: spec.recipientUserId ?? client.config.actor.user_id ?? null,
|
|
42164
|
-
event_key: spec.eventKey ?? "records.changed"
|
|
42165
|
-
}
|
|
42166
|
-
});
|
|
42167
|
-
if (!declared2.ok) {
|
|
42168
|
-
setError(declared2.error);
|
|
42169
|
-
return null;
|
|
42170
|
-
}
|
|
42171
|
-
return declared2.data;
|
|
42172
|
-
},
|
|
42173
|
-
[client, tableId]
|
|
42174
|
-
);
|
|
42175
|
-
useEffect96(() => {
|
|
42176
|
-
if (!subscriptions || !seed || seed.length === 0) return;
|
|
42177
|
-
const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
|
|
42178
|
-
if (missing.length === 0) return;
|
|
42179
|
-
void (async () => {
|
|
42180
|
-
for (const spec of missing) {
|
|
42181
|
-
if (!await write(spec)) return;
|
|
42182
|
-
}
|
|
42183
|
-
await load();
|
|
42184
|
-
})();
|
|
42185
|
-
}, [subscriptions, seed, write, load]);
|
|
42186
|
-
async function subscribe(viewId, viewName, cadence, channel, schedule, quiet2) {
|
|
42187
|
-
setBusy(true);
|
|
42188
|
-
const ok = await write({
|
|
42189
|
-
name: `${viewName} \u2014 ${CADENCE_WORDS2[cadence] ?? cadence}`,
|
|
42190
|
-
savedViewId: viewId,
|
|
42191
|
-
cadence,
|
|
42192
|
-
channel,
|
|
42193
|
-
schedule: schedule.trim() === "" ? null : schedule.trim(),
|
|
42194
|
-
quietHours: quiet2
|
|
42195
|
-
});
|
|
42196
|
-
setBusy(false);
|
|
42197
|
-
if (!ok) return;
|
|
42198
|
-
onChoose?.(ok);
|
|
42199
|
-
await load();
|
|
42200
|
-
}
|
|
42201
|
-
async function makeEveryRecordView() {
|
|
42202
|
-
setBusy(true);
|
|
42203
|
-
const made = await client.viewDeclare({
|
|
42204
|
-
table_id: tableId,
|
|
42205
|
-
spec: { name: `Every ${table.data?.name ?? "record"}`, filters: null }
|
|
42206
|
-
});
|
|
42207
|
-
setBusy(false);
|
|
42208
|
-
if (!made.ok) {
|
|
42209
|
-
setError(made.error);
|
|
42210
|
-
return;
|
|
42211
|
-
}
|
|
42212
|
-
await load();
|
|
42213
|
-
}
|
|
42214
|
-
async function unsubscribe(ruleId) {
|
|
42215
|
-
setStopping(null);
|
|
42216
|
-
setBusy(true);
|
|
42217
|
-
const deleted = await client.recordDelete({ record_id: ruleId });
|
|
42218
|
-
setBusy(false);
|
|
42219
|
-
if (!deleted.ok) {
|
|
42220
|
-
setError(deleted.error);
|
|
42221
|
-
return;
|
|
42222
|
-
}
|
|
42223
|
-
await load();
|
|
42224
|
-
}
|
|
42225
|
-
if (error) {
|
|
42226
|
-
return /* @__PURE__ */ jsx133(
|
|
42227
|
-
RefusalNotice,
|
|
42228
|
-
{
|
|
42229
|
-
error,
|
|
42230
|
-
className,
|
|
42231
|
-
actions: /* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
42232
|
-
}
|
|
42233
|
-
);
|
|
42234
|
-
}
|
|
42235
|
-
if (subscriptions === null) return /* @__PURE__ */ jsx133(Skeleton29, { className: cn78("h-40 w-full", className) });
|
|
42236
|
-
return /* @__PURE__ */ jsxs123("div", { className: cn78("flex flex-col gap-2", className), children: [
|
|
42237
|
-
/* @__PURE__ */ jsxs123("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
42238
|
-
/* @__PURE__ */ jsx133("span", { className: "font-medium text-foreground", children: "Tell me when" }),
|
|
42239
|
-
/* @__PURE__ */ jsxs123("span", { children: [
|
|
42240
|
-
subscriptions.length,
|
|
42241
|
-
" subscription",
|
|
42242
|
-
subscriptions.length === 1 ? "" : "s"
|
|
42243
|
-
] })
|
|
42244
|
-
] }),
|
|
42245
|
-
subscriptions.length === 0 ? /* @__PURE__ */ jsx133("p", { className: "text-xs text-muted-foreground", children: "Nothing is telling anyone about this yet. A subscription is a Rule over a saved view, so what you are told about is exactly what that view holds." }) : /* @__PURE__ */ jsx133("ul", { className: "flex flex-col gap-1", children: subscriptions.map((held) => /* @__PURE__ */ jsxs123("li", { className: "flex items-center gap-2 rounded border px-2 py-1.5 text-xs", children: [
|
|
42246
|
-
/* @__PURE__ */ jsx133("span", { className: "truncate font-medium", children: held.name }),
|
|
42247
|
-
/* @__PURE__ */ jsx133("span", { className: "text-muted-foreground", children: CADENCE_WORDS2[held.cadence] ?? held.cadence }),
|
|
42248
|
-
/* @__PURE__ */ jsx133("span", { className: "text-muted-foreground", children: CHANNEL_WORDS3[held.channel] ?? held.channel }),
|
|
42249
|
-
held.schedule ? /* @__PURE__ */ jsx133("span", { className: "text-muted-foreground", children: scheduleWords(held.schedule, held.quiet_hours?.tz) }) : null,
|
|
42250
|
-
held.next_digest_at ? /* @__PURE__ */ jsxs123("span", { className: "text-muted-foreground", children: [
|
|
42251
|
-
"next ",
|
|
42252
|
-
new Date(held.next_digest_at).toLocaleString()
|
|
42253
|
-
] }) : null,
|
|
42254
|
-
hasQuietHours3(held.quiet_hours) ? /* @__PURE__ */ jsxs123("span", { className: "text-muted-foreground", children: [
|
|
42255
|
-
"quiet ",
|
|
42256
|
-
held.quiet_hours.start,
|
|
42257
|
-
"\u2013",
|
|
42258
|
-
held.quiet_hours.end
|
|
42259
|
-
] }) : null,
|
|
42260
|
-
held.recipient_user_id === null ? /* @__PURE__ */ jsx133("span", { className: "text-destructive", children: "This one has nobody to tell, so it fires at nobody." }) : null,
|
|
42261
|
-
held.saved_view_id === null ? /* @__PURE__ */ jsx133("span", { className: "text-destructive", children: "This one names no view, so the store never admits a record to it." }) : null,
|
|
42262
|
-
held.muted ? /* @__PURE__ */ jsx133("span", { className: "text-muted-foreground", children: "Switched off \u2014 it tells nobody until somebody switches it back on." }) : null,
|
|
42263
|
-
onChoose ? held.rule_id === chosenRuleId ? /* @__PURE__ */ jsxs123("span", { className: "ml-auto flex items-center gap-1", children: [
|
|
42264
|
-
/* @__PURE__ */ jsx133("span", { className: "font-medium text-foreground", children: "Used for this form" }),
|
|
42265
|
-
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onChoose(null), children: "Stop using" })
|
|
42266
|
-
] }) : /* @__PURE__ */ jsx133(
|
|
42267
|
-
Button86,
|
|
42268
|
-
{
|
|
42269
|
-
size: "sm",
|
|
42270
|
-
variant: "outline",
|
|
42271
|
-
className: "ml-auto",
|
|
42272
|
-
disabled: busy,
|
|
42273
|
-
onClick: () => onChoose(held.rule_id),
|
|
42274
|
-
children: "Use for this form"
|
|
42275
|
-
}
|
|
42276
|
-
) : null,
|
|
42277
|
-
rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ jsxs123("span", { className: cn78("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
|
|
42278
|
-
/* @__PURE__ */ jsx133("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
|
|
42279
|
-
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
|
|
42280
|
-
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
|
|
42281
|
-
] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ jsx133(
|
|
42282
|
-
Button86,
|
|
42283
|
-
{
|
|
42284
|
-
size: "sm",
|
|
42285
|
-
variant: "ghost",
|
|
42286
|
-
className: onChoose ? void 0 : "ml-auto",
|
|
42287
|
-
disabled: busy,
|
|
42288
|
-
onClick: () => setStopping(held.rule_id),
|
|
42289
|
-
children: "Stop telling me"
|
|
42290
|
-
}
|
|
42291
|
-
) : null
|
|
42292
|
-
] }, held.rule_id)) }),
|
|
42293
|
-
onChoose && chosenRuleId && !subscriptions.some((held) => held.rule_id === chosenRuleId) ? /* @__PURE__ */ jsx133("p", { className: "text-xs text-destructive", children: "This form points at a subscription that is not in this list any more, so nobody is told when it is answered. Choose one below, or make one." }) : null,
|
|
42294
|
-
views === null ? /* @__PURE__ */ jsx133("p", { className: "rounded border border-dashed px-2 py-1 text-xs text-muted-foreground", children: NO_SAVED_VIEWS_REASON }) : views.length === 0 ? onChoose && rights.write ? /* @__PURE__ */ jsxs123("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
|
|
42295
|
-
/* @__PURE__ */ jsx133("span", { children: "A subscription watches a saved view, and this table has none yet." }),
|
|
42296
|
-
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "outline", disabled: busy, onClick: () => void makeEveryRecordView(), children: "Watch every record" })
|
|
42297
|
-
] }) : /* @__PURE__ */ jsx133("p", { className: "text-xs text-muted-foreground", children: "This organization has no saved views yet, so there is nothing to subscribe to. Save a view first." }) : rights.write ? /* @__PURE__ */ jsxs123(
|
|
42298
|
-
"form",
|
|
42299
|
-
{
|
|
42300
|
-
className: "flex flex-wrap items-center gap-1",
|
|
42301
|
-
onSubmit: (event) => {
|
|
42302
|
-
event.preventDefault();
|
|
42303
|
-
const form = event.currentTarget;
|
|
42304
|
-
const view = form.elements.namedItem("view").value;
|
|
42305
|
-
const cadence = form.elements.namedItem("cadence").value;
|
|
42306
|
-
const channel = form.elements.namedItem("channel").value;
|
|
42307
|
-
const schedule = form.elements.namedItem("schedule").value;
|
|
42308
|
-
const start = form.elements.namedItem("quiet_start").value;
|
|
42309
|
-
const end = form.elements.namedItem("quiet_end").value;
|
|
42310
|
-
const name = views.find((v) => v.id === view)?.name ?? "View";
|
|
42311
|
-
const quiet2 = start && end ? { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone } : null;
|
|
42312
|
-
if (view) void subscribe(view, name, cadence, channel, schedule, quiet2);
|
|
42313
|
-
},
|
|
42314
|
-
children: [
|
|
42315
|
-
/* @__PURE__ */ jsx133("select", { name: "view", "aria-label": "View to be told about", className: "h-7 rounded border bg-background px-1 text-xs", children: views.map((view) => /* @__PURE__ */ jsx133("option", { value: view.id, children: view.name }, view.id)) }),
|
|
42316
|
-
/* @__PURE__ */ jsx133("select", { name: "cadence", "aria-label": "How often", className: "h-7 rounded border bg-background px-1 text-xs", children: (cadences.length > 0 ? cadences : ["instant"]).map((cadence) => /* @__PURE__ */ jsx133("option", { value: cadence, children: CADENCE_WORDS2[cadence] ?? cadence }, cadence)) }),
|
|
42317
|
-
/* @__PURE__ */ jsx133("select", { name: "channel", "aria-label": "Where it arrives", className: "h-7 rounded border bg-background px-1 text-xs", children: ["in_app", "email", "sms"].map((channel) => /* @__PURE__ */ jsx133("option", { value: channel, children: CHANNEL_WORDS3[channel] }, channel)) }),
|
|
42318
|
-
/* @__PURE__ */ jsx133(
|
|
42319
|
-
"input",
|
|
42320
|
-
{
|
|
42321
|
-
name: "schedule",
|
|
42322
|
-
"aria-label": "When the summary goes out",
|
|
42323
|
-
placeholder: "monday 08:00",
|
|
42324
|
-
className: "h-7 w-28 rounded border bg-background px-1 text-xs"
|
|
42325
|
-
}
|
|
42326
|
-
),
|
|
42327
|
-
/* @__PURE__ */ jsx133("span", { className: "text-xs text-muted-foreground", children: "not between" }),
|
|
42328
|
-
/* @__PURE__ */ jsx133(
|
|
42329
|
-
"input",
|
|
42330
|
-
{
|
|
42331
|
-
name: "quiet_start",
|
|
42332
|
-
type: "time",
|
|
42333
|
-
"aria-label": "Quiet hours start",
|
|
42334
|
-
className: "h-7 rounded border bg-background px-1 text-xs"
|
|
42335
|
-
}
|
|
42336
|
-
),
|
|
42337
|
-
/* @__PURE__ */ jsx133("span", { className: "text-xs text-muted-foreground", children: "and" }),
|
|
42338
|
-
/* @__PURE__ */ jsx133(
|
|
42279
|
+
"data-records-form-question": field2.key,
|
|
42280
|
+
"aria-describedby": refused ? sayId : void 0,
|
|
42281
|
+
children: [
|
|
42282
|
+
/* @__PURE__ */ jsxs122("div", { className: "flex flex-wrap items-baseline gap-x-2", children: [
|
|
42283
|
+
/* @__PURE__ */ jsx132(Label22, { htmlFor: id, className: cn77("font-medium leading-snug text-foreground", large ? "text-xl sm:text-2xl" : "text-base"), children: question.ask }),
|
|
42284
|
+
question.required ? /* @__PURE__ */ jsx132("span", { className: "text-sm text-foreground/80", children: "required" }) : null,
|
|
42285
|
+
field2.unit ? /* @__PURE__ */ jsx132("span", { className: "text-sm text-foreground/70", children: field2.unit }) : null
|
|
42286
|
+
] }),
|
|
42287
|
+
question.help ? /* @__PURE__ */ jsx132("p", { className: cn77("text-muted-foreground", large ? "text-base" : "text-sm"), children: authored(host, question.help, "form-help") }) : null,
|
|
42288
|
+
isAttachment && !upload ? /* @__PURE__ */ jsx132("p", { className: "w-fit rounded border border-dashed px-2 py-0.5 text-xs text-muted-foreground", title: NO_UPLOAD_REASON, children: "File upload unavailable here" }) : isAttachment ? /* @__PURE__ */ jsxs122(Fragment53, { children: [
|
|
42289
|
+
/* @__PURE__ */ jsx132(
|
|
42339
42290
|
"input",
|
|
42340
42291
|
{
|
|
42341
|
-
|
|
42342
|
-
type: "
|
|
42343
|
-
|
|
42344
|
-
|
|
42292
|
+
id,
|
|
42293
|
+
type: "file",
|
|
42294
|
+
className: "text-sm",
|
|
42295
|
+
onChange: (e) => {
|
|
42296
|
+
const file = e.target.files?.[0];
|
|
42297
|
+
if (!file) return;
|
|
42298
|
+
setUploadError(null);
|
|
42299
|
+
void upload?.(file).then((result) => {
|
|
42300
|
+
if (!result) return;
|
|
42301
|
+
if (result.ok) onChange(result.fileId);
|
|
42302
|
+
else
|
|
42303
|
+
setUploadError(
|
|
42304
|
+
refusal("invalid_argument", result.reason, "Pick another file, or try this one again.")
|
|
42305
|
+
);
|
|
42306
|
+
});
|
|
42307
|
+
}
|
|
42345
42308
|
}
|
|
42346
42309
|
),
|
|
42347
|
-
/* @__PURE__ */
|
|
42348
|
-
|
|
42349
|
-
|
|
42350
|
-
|
|
42351
|
-
|
|
42352
|
-
|
|
42310
|
+
uploadError ? /* @__PURE__ */ jsx132(RefusalLine, { error: uploadError }) : null
|
|
42311
|
+
] }) : (
|
|
42312
|
+
// A choice is laid out as lettered rows in the form's colour (`ChoiceAnswer`).
|
|
42313
|
+
/* @__PURE__ */ jsx132(FieldControl, { field: field2, value, onChange, id, asForm: true, onChosen })
|
|
42314
|
+
),
|
|
42315
|
+
/* @__PURE__ */ jsx132("p", { id: sayId, role: "alert", "aria-live": "assertive", className: "min-h-0 empty:hidden", "data-records-form-refused": "", children: refused ? /* @__PURE__ */ jsxs122("span", { className: "inline-flex items-center gap-1.5 rounded bg-destructive px-2 py-1 text-sm font-medium text-destructive-foreground", children: [
|
|
42316
|
+
/* @__PURE__ */ jsx132("span", { "aria-hidden": "true", children: "!" }),
|
|
42317
|
+
refused
|
|
42318
|
+
] }) : null })
|
|
42319
|
+
]
|
|
42320
|
+
}
|
|
42321
|
+
);
|
|
42353
42322
|
}
|
|
42354
42323
|
|
|
42355
42324
|
// src/FormBuilder.tsx
|
|
42356
|
-
import { Fragment as Fragment54, jsx as
|
|
42325
|
+
import { Fragment as Fragment54, jsx as jsx133, jsxs as jsxs123 } from "react/jsx-runtime";
|
|
42357
42326
|
function freshFormName(base, taken) {
|
|
42358
42327
|
const names = new Set(taken.map((n) => n.trim().toLowerCase()));
|
|
42359
42328
|
if (!names.has(base.toLowerCase())) return base;
|
|
@@ -42370,7 +42339,21 @@ function isLookRefusal(error) {
|
|
|
42370
42339
|
return Boolean(error && LOOK_REFUSAL.test(refusalSentence(error)));
|
|
42371
42340
|
}
|
|
42372
42341
|
var KEPT_DRAFT_PREFIX = "records-ui:form-draft:";
|
|
42373
|
-
var SPEC_KEYS = [
|
|
42342
|
+
var SPEC_KEYS = [
|
|
42343
|
+
"name",
|
|
42344
|
+
"intro",
|
|
42345
|
+
"questions",
|
|
42346
|
+
"flow",
|
|
42347
|
+
"theme",
|
|
42348
|
+
"look",
|
|
42349
|
+
"thankYou",
|
|
42350
|
+
"submitLabel",
|
|
42351
|
+
"welcome",
|
|
42352
|
+
"endings",
|
|
42353
|
+
"hiddenFields",
|
|
42354
|
+
"showOwnerHeader",
|
|
42355
|
+
"autoAdvance"
|
|
42356
|
+
];
|
|
42374
42357
|
function readKeptDraft(formId) {
|
|
42375
42358
|
try {
|
|
42376
42359
|
const raw = window.localStorage.getItem(KEPT_DRAFT_PREFIX + formId);
|
|
@@ -42401,31 +42384,32 @@ function withKept(stored, spec) {
|
|
|
42401
42384
|
return out;
|
|
42402
42385
|
}
|
|
42403
42386
|
function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne, createOnMount, className }) {
|
|
42404
|
-
const client =
|
|
42387
|
+
const client = useRecordsClient79();
|
|
42405
42388
|
const host = useRecordsUi();
|
|
42406
42389
|
const claimSeed = useSeedGuard();
|
|
42407
|
-
const table =
|
|
42390
|
+
const table = useTable26(tableId);
|
|
42408
42391
|
const rights = useTableRights(table.data);
|
|
42409
42392
|
const fields = useFields25(tableId);
|
|
42410
|
-
const [forms, setForms] =
|
|
42411
|
-
const [error, setError] =
|
|
42412
|
-
const [refused, setRefused] =
|
|
42413
|
-
const [activeId, setActiveId] =
|
|
42414
|
-
const [draft, setDraft] =
|
|
42415
|
-
const [saving, setSaving] =
|
|
42416
|
-
const [saved, setSaved] =
|
|
42417
|
-
const [publishing, setPublishing] =
|
|
42418
|
-
const [copied, setCopied] =
|
|
42419
|
-
const [shownUrl, setShownUrl] =
|
|
42420
|
-
const [lookOptions, setLookOptions] =
|
|
42421
|
-
const [lookUnread, setLookUnread] =
|
|
42422
|
-
const [
|
|
42423
|
-
const [
|
|
42424
|
-
const [
|
|
42393
|
+
const [forms, setForms] = useState110(null);
|
|
42394
|
+
const [error, setError] = useState110(null);
|
|
42395
|
+
const [refused, setRefused] = useState110(null);
|
|
42396
|
+
const [activeId, setActiveId] = useState110(activeFormId ?? null);
|
|
42397
|
+
const [draft, setDraft] = useState110(null);
|
|
42398
|
+
const [saving, setSaving] = useState110(false);
|
|
42399
|
+
const [saved, setSaved] = useState110(null);
|
|
42400
|
+
const [publishing, setPublishing] = useState110(false);
|
|
42401
|
+
const [copied, setCopied] = useState110(false);
|
|
42402
|
+
const [shownUrl, setShownUrl] = useState110(null);
|
|
42403
|
+
const [lookOptions, setLookOptions] = useState110(null);
|
|
42404
|
+
const [lookUnread, setLookUnread] = useState110(null);
|
|
42405
|
+
const [orgKnobs, setOrgKnobs] = useState110(null);
|
|
42406
|
+
const [restoredAt, setRestoredAt] = useState110(null);
|
|
42407
|
+
const [showResults, setShowResults] = useState110(false);
|
|
42408
|
+
const [adding, setAdding] = useState110(false);
|
|
42425
42409
|
const keysBeforeAdd = useRef56(null);
|
|
42426
|
-
const [makeOnMount, setMakeOnMount] =
|
|
42410
|
+
const [makeOnMount, setMakeOnMount] = useState110("waiting");
|
|
42427
42411
|
const publicOrigin = host.publicOrigin ?? (typeof window === "undefined" ? "" : window.location.origin);
|
|
42428
|
-
const load =
|
|
42412
|
+
const load = useCallback54(async () => {
|
|
42429
42413
|
const answered = await client.forms({ table_id: tableId });
|
|
42430
42414
|
if (!answered.ok) {
|
|
42431
42415
|
setError(answered.error);
|
|
@@ -42452,10 +42436,19 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42452
42436
|
setError(null);
|
|
42453
42437
|
setForms(mine);
|
|
42454
42438
|
}, [client, tableId, seed, claimSeed]);
|
|
42455
|
-
|
|
42439
|
+
useEffect96(() => {
|
|
42456
42440
|
void load();
|
|
42457
42441
|
}, [load]);
|
|
42458
|
-
|
|
42442
|
+
useEffect96(() => {
|
|
42443
|
+
let cancelled = false;
|
|
42444
|
+
Promise.resolve().then(() => client.formKnobs()).then((r) => {
|
|
42445
|
+
if (!cancelled && r.ok) setOrgKnobs(r.data);
|
|
42446
|
+
}).catch(() => void 0);
|
|
42447
|
+
return () => {
|
|
42448
|
+
cancelled = true;
|
|
42449
|
+
};
|
|
42450
|
+
}, [client]);
|
|
42451
|
+
useEffect96(() => {
|
|
42459
42452
|
if (!forms || forms.length === 0) return;
|
|
42460
42453
|
if (createOnMount && makeOnMount !== "made") return;
|
|
42461
42454
|
const chosen = forms.find((f) => f.id === (activeFormId ?? activeId)) ?? forms[0];
|
|
@@ -42479,7 +42472,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42479
42472
|
);
|
|
42480
42473
|
const stored = useMemo68(() => draft ? (forms ?? []).find((f) => f.id === draft.id) ?? null : null, [draft, forms]);
|
|
42481
42474
|
const dirty = Boolean(draft && stored && !sameForm(draft, stored));
|
|
42482
|
-
|
|
42475
|
+
useEffect96(() => {
|
|
42483
42476
|
if (!draft || !stored) return;
|
|
42484
42477
|
const timer = window.setTimeout(() => {
|
|
42485
42478
|
if (sameForm(draft, stored)) dropKeptDraft(draft.id);
|
|
@@ -42488,7 +42481,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42488
42481
|
return () => window.clearTimeout(timer);
|
|
42489
42482
|
}, [draft, stored]);
|
|
42490
42483
|
const lookKey = draft ? JSON.stringify(formLookDocument(draft.look ?? null)) : null;
|
|
42491
|
-
|
|
42484
|
+
useEffect96(() => {
|
|
42492
42485
|
if (lookKey === null) return;
|
|
42493
42486
|
let cancelled = false;
|
|
42494
42487
|
const timer = window.setTimeout(() => {
|
|
@@ -42505,7 +42498,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42505
42498
|
window.clearTimeout(timer);
|
|
42506
42499
|
};
|
|
42507
42500
|
}, [client, lookKey]);
|
|
42508
|
-
|
|
42501
|
+
useEffect96(() => {
|
|
42509
42502
|
const before = keysBeforeAdd.current;
|
|
42510
42503
|
if (!before || !fields.data) return;
|
|
42511
42504
|
const made = askableFields(fields.data).filter((f) => !before.has(f.key));
|
|
@@ -42544,7 +42537,7 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42544
42537
|
}
|
|
42545
42538
|
dropKeptDraft(draft.id);
|
|
42546
42539
|
setRestoredAt(null);
|
|
42547
|
-
setSaved("Saved
|
|
42540
|
+
setSaved("Saved");
|
|
42548
42541
|
await load();
|
|
42549
42542
|
return true;
|
|
42550
42543
|
}
|
|
@@ -42583,7 +42576,22 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42583
42576
|
setRefused(written.error);
|
|
42584
42577
|
return;
|
|
42585
42578
|
}
|
|
42586
|
-
setSaved(
|
|
42579
|
+
setSaved("Saved");
|
|
42580
|
+
await load();
|
|
42581
|
+
}
|
|
42582
|
+
async function chooseWebhook(notify) {
|
|
42583
|
+
if (!draft) return;
|
|
42584
|
+
const next = { ...draft, notify };
|
|
42585
|
+
setDraft(next);
|
|
42586
|
+
setSaving(true);
|
|
42587
|
+
setRefused(null);
|
|
42588
|
+
const written = await client.formDeclare(formDeclareArgs(next));
|
|
42589
|
+
setSaving(false);
|
|
42590
|
+
if (!written.ok) {
|
|
42591
|
+
setRefused(written.error);
|
|
42592
|
+
return;
|
|
42593
|
+
}
|
|
42594
|
+
setSaved("Saved");
|
|
42587
42595
|
await load();
|
|
42588
42596
|
}
|
|
42589
42597
|
async function create(name) {
|
|
@@ -42600,15 +42608,15 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42600
42608
|
await load();
|
|
42601
42609
|
return true;
|
|
42602
42610
|
}
|
|
42603
|
-
const [started, setStarted] =
|
|
42604
|
-
|
|
42611
|
+
const [started, setStarted] = useState110(false);
|
|
42612
|
+
useEffect96(() => {
|
|
42605
42613
|
if (!startWithOne || started || forms === null || forms.length > 0) return;
|
|
42606
42614
|
if (askableFields(fields.data ?? []).length === 0) return;
|
|
42607
42615
|
if (!table.data && !table.error) return;
|
|
42608
42616
|
setStarted(true);
|
|
42609
42617
|
void create(table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form 1");
|
|
42610
42618
|
}, [startWithOne, started, forms, fields.data, create, table.data, table.error]);
|
|
42611
|
-
|
|
42619
|
+
useEffect96(() => {
|
|
42612
42620
|
if (!createOnMount || makeOnMount !== "waiting" || forms === null) return;
|
|
42613
42621
|
if (!table.data && !table.error) return;
|
|
42614
42622
|
if (askableFields(fields.data ?? []).length === 0) {
|
|
@@ -42621,396 +42629,325 @@ function FormBuilder({ tableId, seed, activeFormId, onActiveForm, startWithOne,
|
|
|
42621
42629
|
(made) => setMakeOnMount(made ? "made" : "not-made")
|
|
42622
42630
|
);
|
|
42623
42631
|
}, [createOnMount, makeOnMount, forms, fields.data, create, table.data, table.error]);
|
|
42624
|
-
if (fields.error) return /* @__PURE__ */
|
|
42632
|
+
if (fields.error) return /* @__PURE__ */ jsx133(RefusalNotice, { error: fields.error, className });
|
|
42625
42633
|
if (error) {
|
|
42626
|
-
return /* @__PURE__ */
|
|
42634
|
+
return /* @__PURE__ */ jsx133(
|
|
42627
42635
|
RefusalNotice,
|
|
42628
42636
|
{
|
|
42629
42637
|
error,
|
|
42630
42638
|
className,
|
|
42631
|
-
actions: /* @__PURE__ */
|
|
42639
|
+
actions: /* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
42632
42640
|
}
|
|
42633
42641
|
);
|
|
42634
42642
|
}
|
|
42635
|
-
if (fields.loading || forms === null) return /* @__PURE__ */
|
|
42643
|
+
if (fields.loading || forms === null) return /* @__PURE__ */ jsx133(Skeleton29, { className: cn78("h-64 w-full", className) });
|
|
42636
42644
|
if (createOnMount && (makeOnMount === "waiting" || makeOnMount === "making") && !refused) {
|
|
42637
|
-
return /* @__PURE__ */
|
|
42645
|
+
return /* @__PURE__ */ jsx133(Skeleton29, { className: cn78("h-64 w-full", className) });
|
|
42638
42646
|
}
|
|
42639
42647
|
if (!rights.admin) {
|
|
42640
|
-
return /* @__PURE__ */
|
|
42648
|
+
return /* @__PURE__ */ jsx133("p", { className: cn78("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
42641
42649
|
}
|
|
42642
|
-
return
|
|
42643
|
-
|
|
42644
|
-
|
|
42645
|
-
|
|
42646
|
-
|
|
42647
|
-
|
|
42648
|
-
|
|
42649
|
-
|
|
42650
|
-
|
|
42651
|
-
|
|
42652
|
-
|
|
42653
|
-
|
|
42650
|
+
return (
|
|
42651
|
+
// THE CONTAINER IS THE PARENT (TYPEFORM-2): a container query asks an ANCESTOR, so `@3xl:` on the
|
|
42652
|
+
// element that is itself `@container` never fired and the preview sat 3,000 px below the editor.
|
|
42653
|
+
/* @__PURE__ */ jsx133("div", { className: cn78("@container min-h-0", className), children: /* @__PURE__ */ jsxs123("div", { className: "flex min-h-0 flex-col gap-3 @3xl:flex-row @3xl:items-start", children: [
|
|
42654
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex min-w-0 flex-1 flex-col gap-2", children: [
|
|
42655
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Forms", children: [
|
|
42656
|
+
forms.map((form) => /* @__PURE__ */ jsx133(
|
|
42657
|
+
Button86,
|
|
42658
|
+
{
|
|
42659
|
+
size: "sm",
|
|
42660
|
+
variant: form.id === activeId ? "secondary" : "ghost",
|
|
42661
|
+
"aria-pressed": form.id === activeId,
|
|
42662
|
+
onClick: () => {
|
|
42663
|
+
setActiveId(form.id);
|
|
42664
|
+
setDraft(form);
|
|
42665
|
+
},
|
|
42666
|
+
children: form.name
|
|
42654
42667
|
},
|
|
42655
|
-
|
|
42656
|
-
|
|
42657
|
-
|
|
42658
|
-
|
|
42659
|
-
|
|
42660
|
-
|
|
42661
|
-
|
|
42662
|
-
|
|
42663
|
-
|
|
42664
|
-
|
|
42665
|
-
|
|
42666
|
-
|
|
42667
|
-
|
|
42668
|
-
|
|
42669
|
-
|
|
42670
|
-
|
|
42671
|
-
|
|
42672
|
-
|
|
42673
|
-
|
|
42674
|
-
|
|
42675
|
-
|
|
42676
|
-
/* @__PURE__ */ jsx134(Button87, { size: "sm", disabled: saving || !draft, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
|
|
42677
|
-
] })
|
|
42678
|
-
] }),
|
|
42679
|
-
refused && !isLookRefusal(refused) ? /* @__PURE__ */ jsx134(
|
|
42680
|
-
RefusalNotice,
|
|
42681
|
-
{
|
|
42682
|
-
error: refused,
|
|
42683
|
-
onKeepEditing: () => setRefused(null),
|
|
42684
|
-
onDiscard: () => {
|
|
42685
|
-
const kept = forms.find((f) => f.id === activeId) ?? null;
|
|
42686
|
-
if (kept) dropKeptDraft(kept.id);
|
|
42687
|
-
setDraft(kept);
|
|
42688
|
-
setRestoredAt(null);
|
|
42689
|
-
setSaved(null);
|
|
42690
|
-
setRefused(null);
|
|
42691
|
-
}
|
|
42692
|
-
}
|
|
42693
|
-
) : null,
|
|
42694
|
-
restoredAt && dirty && stored ? /* @__PURE__ */ jsxs124("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-form-restored": "", children: [
|
|
42695
|
-
/* @__PURE__ */ jsxs124("span", { children: [
|
|
42696
|
-
"Unsaved changes from",
|
|
42697
|
-
" ",
|
|
42698
|
-
new Date(restoredAt).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" }),
|
|
42699
|
-
" restored"
|
|
42668
|
+
form.id
|
|
42669
|
+
)),
|
|
42670
|
+
(fields.data ?? []).length > 0 ? /* @__PURE__ */ jsx133(
|
|
42671
|
+
Button86,
|
|
42672
|
+
{
|
|
42673
|
+
size: "sm",
|
|
42674
|
+
variant: "ghost",
|
|
42675
|
+
onClick: () => void create(
|
|
42676
|
+
freshFormName(
|
|
42677
|
+
table.data?.name?.trim() ? `${table.data.name.trim()} form` : "Form",
|
|
42678
|
+
forms.map((f) => f.name)
|
|
42679
|
+
)
|
|
42680
|
+
),
|
|
42681
|
+
children: "New form"
|
|
42682
|
+
}
|
|
42683
|
+
) : null,
|
|
42684
|
+
/* @__PURE__ */ jsxs123("span", { className: "ml-auto flex items-center gap-2", children: [
|
|
42685
|
+
saved ? /* @__PURE__ */ jsx133("span", { className: "text-xs text-muted-foreground", children: saved }) : null,
|
|
42686
|
+
stored ? /* @__PURE__ */ jsx133(Button86, { size: "sm", variant: showResults ? "default" : "ghost", onClick: () => setShowResults((v) => !v), "data-records-form-results-toggle": "", children: "Results" }) : null,
|
|
42687
|
+
/* @__PURE__ */ jsx133(Button86, { size: "sm", disabled: saving || !draft, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
|
|
42688
|
+
] })
|
|
42700
42689
|
] }),
|
|
42701
|
-
/* @__PURE__ */
|
|
42702
|
-
|
|
42690
|
+
refused && !isLookRefusal(refused) ? /* @__PURE__ */ jsx133(
|
|
42691
|
+
RefusalNotice,
|
|
42703
42692
|
{
|
|
42704
|
-
|
|
42705
|
-
|
|
42706
|
-
|
|
42707
|
-
|
|
42708
|
-
|
|
42693
|
+
error: refused,
|
|
42694
|
+
onKeepEditing: () => setRefused(null),
|
|
42695
|
+
onDiscard: () => {
|
|
42696
|
+
const kept = forms.find((f) => f.id === activeId) ?? null;
|
|
42697
|
+
if (kept) dropKeptDraft(kept.id);
|
|
42698
|
+
setDraft(kept);
|
|
42709
42699
|
setRestoredAt(null);
|
|
42710
|
-
|
|
42711
|
-
|
|
42712
|
-
|
|
42713
|
-
)
|
|
42714
|
-
] }) : null,
|
|
42715
|
-
!draft ? /* @__PURE__ */ jsx134("p", { className: "text-xs text-muted-foreground", children: (fields.data ?? []).length === 0 ? "This table has no fields yet, so there is nothing a form could ask for. Add a field first \u2014 every question is one of this table's own fields." : 'No form collects into this table yet. "New form" makes one, or you ask an agent for the whole thing in a sentence.' }) : /* @__PURE__ */ jsxs124(Fragment54, { children: [
|
|
42716
|
-
/* @__PURE__ */ jsx134(
|
|
42717
|
-
PublishRow,
|
|
42718
|
-
{
|
|
42719
|
-
url: `${publicOrigin}${publicFormPath(draft.id)}`,
|
|
42720
|
-
state: draft.state,
|
|
42721
|
-
mayPublish: rights.structure,
|
|
42722
|
-
why: rights.why("structure"),
|
|
42723
|
-
busy: publishing,
|
|
42724
|
-
copied,
|
|
42725
|
-
shownUrl,
|
|
42726
|
-
onPublish: (wanted) => void publish(wanted),
|
|
42727
|
-
onCopy: (url) => void copyLink(url),
|
|
42728
|
-
unsaved: dirty
|
|
42700
|
+
setSaved(null);
|
|
42701
|
+
setRefused(null);
|
|
42702
|
+
}
|
|
42729
42703
|
}
|
|
42730
|
-
),
|
|
42731
|
-
/* @__PURE__ */
|
|
42732
|
-
|
|
42733
|
-
|
|
42734
|
-
|
|
42735
|
-
|
|
42704
|
+
) : null,
|
|
42705
|
+
restoredAt && dirty && stored ? /* @__PURE__ */ jsxs123("p", { className: "flex items-center gap-2 text-xs text-muted-foreground", "data-records-form-restored": "", children: [
|
|
42706
|
+
/* @__PURE__ */ jsxs123("span", { children: [
|
|
42707
|
+
"Unsaved changes from",
|
|
42708
|
+
" ",
|
|
42709
|
+
new Date(restoredAt).toLocaleString(void 0, { dateStyle: "medium", timeStyle: "short" }),
|
|
42710
|
+
" restored"
|
|
42711
|
+
] }),
|
|
42712
|
+
/* @__PURE__ */ jsx133(
|
|
42713
|
+
Button86,
|
|
42736
42714
|
{
|
|
42737
|
-
|
|
42738
|
-
|
|
42739
|
-
|
|
42715
|
+
size: "sm",
|
|
42716
|
+
variant: "ghost",
|
|
42717
|
+
onClick: () => {
|
|
42718
|
+
dropKeptDraft(stored.id);
|
|
42719
|
+
setDraft(stored);
|
|
42720
|
+
setRestoredAt(null);
|
|
42721
|
+
},
|
|
42722
|
+
children: "Discard"
|
|
42740
42723
|
}
|
|
42741
42724
|
)
|
|
42742
|
-
] }),
|
|
42743
|
-
/* @__PURE__ */
|
|
42744
|
-
/* @__PURE__ */
|
|
42745
|
-
|
|
42746
|
-
|
|
42747
|
-
|
|
42748
|
-
|
|
42749
|
-
|
|
42750
|
-
|
|
42751
|
-
|
|
42752
|
-
|
|
42753
|
-
|
|
42754
|
-
|
|
42755
|
-
)
|
|
42756
|
-
|
|
42757
|
-
|
|
42758
|
-
|
|
42759
|
-
|
|
42760
|
-
|
|
42725
|
+
] }) : null,
|
|
42726
|
+
!draft ? /* @__PURE__ */ jsx133("p", { className: "text-xs text-muted-foreground", children: (fields.data ?? []).length === 0 ? "No fields yet" : "No form yet" }) : /* @__PURE__ */ jsxs123(Fragment54, { children: [
|
|
42727
|
+
/* @__PURE__ */ jsx133(
|
|
42728
|
+
PublishRow,
|
|
42729
|
+
{
|
|
42730
|
+
url: `${publicOrigin}${publicFormPath(draft.id)}`,
|
|
42731
|
+
state: draft.state,
|
|
42732
|
+
mayPublish: rights.structure,
|
|
42733
|
+
why: rights.why("structure"),
|
|
42734
|
+
busy: publishing,
|
|
42735
|
+
copied,
|
|
42736
|
+
shownUrl,
|
|
42737
|
+
onPublish: (wanted) => void publish(wanted),
|
|
42738
|
+
onCopy: (url) => void copyLink(url),
|
|
42739
|
+
unsaved: dirty
|
|
42740
|
+
}
|
|
42741
|
+
),
|
|
42742
|
+
/* @__PURE__ */ jsx133(EmbedSnippets, { url: `${publicOrigin}${publicFormPath(draft.id)}`, label: draft.name }),
|
|
42743
|
+
/* @__PURE__ */ jsx133(
|
|
42744
|
+
NotifyOnAnswer,
|
|
42745
|
+
{
|
|
42746
|
+
tableId,
|
|
42747
|
+
tableName: table.data?.name ?? "record",
|
|
42748
|
+
formName: draft.name,
|
|
42749
|
+
ruleId: draft.notifyRuleId,
|
|
42750
|
+
notify: draft.notify ?? null,
|
|
42751
|
+
emailQuestion: (
|
|
42752
|
+
// An email field first; else a question whose field is named for an email (an imported
|
|
42753
|
+
// "Contact email" kept as text). The store's resolver refuses an answer that is not one.
|
|
42754
|
+
(draft.questions.map((q) => byKey.get(q.field)).find((f) => f && String(editorKindFor(f)) === "email") ?? draft.questions.map((q) => byKey.get(q.field)).find((f) => f && /e-?mail/i.test(`${f.key} ${f.label ?? ""}`)))?.key ?? null
|
|
42755
|
+
),
|
|
42756
|
+
onRule: (ruleId) => void chooseNotify(ruleId),
|
|
42757
|
+
onNotify: (notify) => void chooseWebhook(notify)
|
|
42758
|
+
}
|
|
42759
|
+
),
|
|
42760
|
+
/* @__PURE__ */ jsxs123("section", { className: "flex flex-wrap items-center gap-x-4 gap-y-1 rounded border px-2.5 py-2 text-xs", "aria-label": "Options", children: [
|
|
42761
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex items-center gap-2", title: "Off hides your logo and name \u2014 for a client's form.", children: [
|
|
42762
|
+
/* @__PURE__ */ jsx133(
|
|
42763
|
+
Checkbox15,
|
|
42761
42764
|
{
|
|
42762
|
-
|
|
42763
|
-
|
|
42764
|
-
|
|
42765
|
-
placeholder: "https://",
|
|
42766
|
-
onChange: (e) => patch({ thankYou: { ...draft.thankYou, redirectUrl: e.target.value || null } })
|
|
42765
|
+
checked: draft.showOwnerHeader ?? orgKnobs?.show_owner_header ?? true,
|
|
42766
|
+
onCheckedChange: (checked) => patch({ showOwnerHeader: checked === true }),
|
|
42767
|
+
"aria-label": "Show our logo and name"
|
|
42767
42768
|
}
|
|
42768
|
-
)
|
|
42769
|
+
),
|
|
42770
|
+
"Show our logo and name"
|
|
42769
42771
|
] }),
|
|
42770
|
-
/* @__PURE__ */
|
|
42771
|
-
/* @__PURE__ */
|
|
42772
|
-
|
|
42773
|
-
BasicTextarea11,
|
|
42772
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex items-center gap-2", title: "A single choice goes on to the next question when picked.", children: [
|
|
42773
|
+
/* @__PURE__ */ jsx133(
|
|
42774
|
+
Checkbox15,
|
|
42774
42775
|
{
|
|
42775
|
-
|
|
42776
|
-
|
|
42777
|
-
|
|
42778
|
-
onChange: (e) => patch({ thankYou: { ...draft.thankYou, body: e.target.value || null } })
|
|
42776
|
+
checked: draft.autoAdvance ?? orgKnobs?.choice_auto_advance ?? true,
|
|
42777
|
+
onCheckedChange: (checked) => patch({ autoAdvance: checked === true }),
|
|
42778
|
+
"aria-label": "Next after a choice"
|
|
42779
42779
|
}
|
|
42780
|
-
)
|
|
42780
|
+
),
|
|
42781
|
+
"Next after a choice"
|
|
42781
42782
|
] })
|
|
42782
|
-
] })
|
|
42783
|
-
] }),
|
|
42784
|
-
/* @__PURE__ */ jsx134(WelcomeEditor, { value: draft.welcome, pictures: lookOptions?.pictures ?? null, onChange: (welcome) => patch({ welcome }) }),
|
|
42785
|
-
/* @__PURE__ */ jsx134(
|
|
42786
|
-
EndingsEditor,
|
|
42787
|
-
{
|
|
42788
|
-
endings: draft.endings ?? [],
|
|
42789
|
-
pictures: lookOptions?.pictures ?? null,
|
|
42790
|
-
fieldKeys: conditionFields,
|
|
42791
|
-
scored: draft.questions.some((q) => q.points),
|
|
42792
|
-
onChange: (endings) => patch({ endings })
|
|
42793
|
-
}
|
|
42794
|
-
),
|
|
42795
|
-
/* @__PURE__ */ jsxs124("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
42796
|
-
/* @__PURE__ */ jsxs124("label", { className: "flex flex-col gap-1", children: [
|
|
42797
|
-
/* @__PURE__ */ jsx134(Label22, { className: "text-xs font-medium", children: "Name" }),
|
|
42798
|
-
/* @__PURE__ */ jsx134(BasicInput28, { value: draft.name, onChange: (e) => patch({ name: e.target.value }) })
|
|
42799
|
-
] }),
|
|
42800
|
-
/* @__PURE__ */ jsxs124("label", { className: "flex flex-col gap-1", children: [
|
|
42801
|
-
/* @__PURE__ */ jsx134(Label22, { className: "text-xs font-medium", children: "Flow" }),
|
|
42802
|
-
/* @__PURE__ */ jsx134(
|
|
42803
|
-
"select",
|
|
42804
|
-
{
|
|
42805
|
-
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
42806
|
-
value: draft.flow ?? "one-at-a-time",
|
|
42807
|
-
onChange: (e) => patch({ flow: e.target.value }),
|
|
42808
|
-
children: FORM_FLOWS.map((flow) => /* @__PURE__ */ jsx134("option", { value: flow, children: flow === "one-at-a-time" ? "One question at a time" : "All on one page" }, flow))
|
|
42809
|
-
}
|
|
42810
|
-
)
|
|
42811
42783
|
] }),
|
|
42812
|
-
/* @__PURE__ */
|
|
42813
|
-
/* @__PURE__ */
|
|
42814
|
-
/* @__PURE__ */
|
|
42815
|
-
|
|
42816
|
-
|
|
42817
|
-
|
|
42818
|
-
|
|
42819
|
-
|
|
42820
|
-
|
|
42821
|
-
|
|
42822
|
-
|
|
42823
|
-
|
|
42824
|
-
|
|
42825
|
-
|
|
42826
|
-
|
|
42827
|
-
|
|
42828
|
-
|
|
42829
|
-
|
|
42830
|
-
|
|
42831
|
-
|
|
42832
|
-
|
|
42784
|
+
/* @__PURE__ */ jsxs123("section", { className: "flex flex-col gap-2 rounded border px-2.5 py-2", "aria-label": "After somebody sends it", children: [
|
|
42785
|
+
/* @__PURE__ */ jsx133("span", { className: "text-xs font-medium", title: "What people see after sending, and an optional page they go on to.", children: "After sending" }),
|
|
42786
|
+
/* @__PURE__ */ jsxs123("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
42787
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex flex-col gap-1", children: [
|
|
42788
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Thank-you title" }),
|
|
42789
|
+
/* @__PURE__ */ jsx133(
|
|
42790
|
+
BasicInput28,
|
|
42791
|
+
{
|
|
42792
|
+
value: draft.thankYou?.title ?? "",
|
|
42793
|
+
placeholder: "Thank you",
|
|
42794
|
+
onChange: (e) => patch({ thankYou: { ...draft.thankYou, title: e.target.value || null } })
|
|
42795
|
+
}
|
|
42796
|
+
)
|
|
42797
|
+
] }),
|
|
42798
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex flex-col gap-1", children: [
|
|
42799
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Then go to (optional)" }),
|
|
42800
|
+
/* @__PURE__ */ jsx133(
|
|
42801
|
+
BasicInput28,
|
|
42802
|
+
{
|
|
42803
|
+
type: "url",
|
|
42804
|
+
inputMode: "url",
|
|
42805
|
+
value: draft.thankYou?.redirectUrl ?? "",
|
|
42806
|
+
placeholder: "https://",
|
|
42807
|
+
onChange: (e) => patch({ thankYou: { ...draft.thankYou, redirectUrl: e.target.value || null } })
|
|
42808
|
+
}
|
|
42809
|
+
)
|
|
42810
|
+
] }),
|
|
42811
|
+
/* @__PURE__ */ jsxs123("label", { className: "col-span-2 flex flex-col gap-1", children: [
|
|
42812
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Thank-you message" }),
|
|
42813
|
+
/* @__PURE__ */ jsx133(
|
|
42814
|
+
BasicTextarea11,
|
|
42815
|
+
{
|
|
42816
|
+
rows: 2,
|
|
42817
|
+
value: draft.thankYou?.body ?? "",
|
|
42818
|
+
placeholder: "What happens next, in your words.",
|
|
42819
|
+
onChange: (e) => patch({ thankYou: { ...draft.thankYou, body: e.target.value || null } })
|
|
42820
|
+
}
|
|
42821
|
+
)
|
|
42822
|
+
] })
|
|
42823
|
+
] })
|
|
42833
42824
|
] }),
|
|
42834
|
-
/* @__PURE__ */
|
|
42835
|
-
|
|
42836
|
-
|
|
42837
|
-
/* @__PURE__ */ jsx134(Label22, { className: "text-xs font-medium", children: "Look" }),
|
|
42838
|
-
/* @__PURE__ */ jsx134(
|
|
42839
|
-
AccentSwatches,
|
|
42825
|
+
/* @__PURE__ */ jsx133(WelcomeEditor, { value: draft.welcome, pictures: lookOptions?.pictures ?? null, onChange: (welcome) => patch({ welcome }) }),
|
|
42826
|
+
/* @__PURE__ */ jsx133(
|
|
42827
|
+
EndingsEditor,
|
|
42840
42828
|
{
|
|
42841
|
-
|
|
42842
|
-
|
|
42843
|
-
|
|
42829
|
+
endings: draft.endings ?? [],
|
|
42830
|
+
pictures: lookOptions?.pictures ?? null,
|
|
42831
|
+
fieldKeys: conditionFields,
|
|
42832
|
+
scored: draft.questions.some((q) => q.points),
|
|
42833
|
+
onChange: (endings) => patch({ endings })
|
|
42844
42834
|
}
|
|
42845
42835
|
),
|
|
42846
|
-
/* @__PURE__ */
|
|
42847
|
-
|
|
42848
|
-
|
|
42849
|
-
/* @__PURE__ */
|
|
42850
|
-
|
|
42851
|
-
|
|
42836
|
+
/* @__PURE__ */ jsxs123("div", { className: "grid grid-cols-2 gap-2", children: [
|
|
42837
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex flex-col gap-1", children: [
|
|
42838
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Name" }),
|
|
42839
|
+
/* @__PURE__ */ jsx133(BasicInput28, { value: draft.name, onChange: (e) => patch({ name: e.target.value }) })
|
|
42840
|
+
] }),
|
|
42841
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex flex-col gap-1", children: [
|
|
42842
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Flow" }),
|
|
42843
|
+
/* @__PURE__ */ jsx133(
|
|
42844
|
+
"select",
|
|
42852
42845
|
{
|
|
42853
|
-
|
|
42854
|
-
|
|
42855
|
-
|
|
42856
|
-
|
|
42857
|
-
onChange: (id) => patchLook({ logoFileId: id || null })
|
|
42846
|
+
className: "h-9 rounded border bg-background px-2 text-sm",
|
|
42847
|
+
value: draft.flow ?? "one-at-a-time",
|
|
42848
|
+
onChange: (e) => patch({ flow: e.target.value }),
|
|
42849
|
+
children: FORM_FLOWS.map((flow) => /* @__PURE__ */ jsx133("option", { value: flow, children: flow === "one-at-a-time" ? "One question at a time" : "All on one page" }, flow))
|
|
42858
42850
|
}
|
|
42859
42851
|
)
|
|
42860
42852
|
] }),
|
|
42861
|
-
/* @__PURE__ */
|
|
42862
|
-
/* @__PURE__ */
|
|
42863
|
-
/* @__PURE__ */
|
|
42864
|
-
|
|
42853
|
+
/* @__PURE__ */ jsxs123("label", { className: "col-span-2 flex flex-col gap-1", children: [
|
|
42854
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Intro" }),
|
|
42855
|
+
/* @__PURE__ */ jsx133(
|
|
42856
|
+
BasicTextarea11,
|
|
42865
42857
|
{
|
|
42866
|
-
|
|
42867
|
-
|
|
42868
|
-
|
|
42869
|
-
noneLabel: "No cover",
|
|
42870
|
-
onChange: (id) => patchLook({ coverFileId: id || null })
|
|
42858
|
+
rows: 2,
|
|
42859
|
+
value: draft.intro ?? "",
|
|
42860
|
+
onChange: (e) => patch({ intro: e.target.value || null })
|
|
42871
42861
|
}
|
|
42872
42862
|
)
|
|
42873
|
-
] })
|
|
42874
|
-
|
|
42875
|
-
|
|
42876
|
-
|
|
42877
|
-
|
|
42878
|
-
|
|
42879
|
-
|
|
42880
|
-
|
|
42881
|
-
|
|
42882
|
-
|
|
42883
|
-
|
|
42884
|
-
|
|
42885
|
-
|
|
42886
|
-
] })
|
|
42863
|
+
] }),
|
|
42864
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex flex-col gap-1", children: [
|
|
42865
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Submit button" }),
|
|
42866
|
+
/* @__PURE__ */ jsx133(
|
|
42867
|
+
BasicInput28,
|
|
42868
|
+
{
|
|
42869
|
+
value: draft.submitLabel ?? "",
|
|
42870
|
+
placeholder: "Submit",
|
|
42871
|
+
onChange: (e) => patch({ submitLabel: e.target.value || null })
|
|
42872
|
+
}
|
|
42873
|
+
)
|
|
42874
|
+
] }),
|
|
42875
|
+
/* @__PURE__ */ jsx133(HiddenFieldsEditor, { value: draft.hiddenFields ?? [], onChange: (hiddenFields) => patch({ hiddenFields }) })
|
|
42887
42876
|
] }),
|
|
42888
|
-
|
|
42889
|
-
|
|
42890
|
-
|
|
42891
|
-
|
|
42892
|
-
|
|
42893
|
-
|
|
42894
|
-
|
|
42895
|
-
|
|
42877
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-1.5", "data-records-form-look-editor": "", children: [
|
|
42878
|
+
/* @__PURE__ */ jsx133(Label23, { className: "text-xs font-medium", children: "Look" }),
|
|
42879
|
+
/* @__PURE__ */ jsx133(
|
|
42880
|
+
AccentSwatches,
|
|
42881
|
+
{
|
|
42882
|
+
value: draft.look?.accent ?? "",
|
|
42883
|
+
noneLabel: "Organization colour",
|
|
42884
|
+
onChange: (accent) => patchLook({ accent: accent || null })
|
|
42885
|
+
}
|
|
42886
|
+
),
|
|
42887
|
+
/* @__PURE__ */ jsx133(ThemeEditor, { theme: draft.theme, pictures: lookOptions?.pictures ?? null, onChange: (theme) => patch({ theme }) }),
|
|
42888
|
+
lookOptions ? /* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-2", children: [
|
|
42889
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-1", children: [
|
|
42890
|
+
/* @__PURE__ */ jsx133("span", { className: "text-xs", children: "Logo" }),
|
|
42891
|
+
/* @__PURE__ */ jsx133(
|
|
42892
|
+
PublicPicturePicker,
|
|
42896
42893
|
{
|
|
42897
|
-
|
|
42898
|
-
|
|
42899
|
-
|
|
42900
|
-
|
|
42901
|
-
})
|
|
42894
|
+
label: "Logo",
|
|
42895
|
+
pictures: lookOptions.pictures,
|
|
42896
|
+
value: draft.look?.logoFileId ?? "",
|
|
42897
|
+
noneLabel: "Organization logo",
|
|
42898
|
+
onChange: (id) => patchLook({ logoFileId: id || null })
|
|
42902
42899
|
}
|
|
42903
|
-
)
|
|
42904
|
-
/* @__PURE__ */ jsx134("span", { className: "text-xs font-medium", children: fieldName(field2) }),
|
|
42905
|
-
field2.required ? /* @__PURE__ */ jsx134("span", { className: "text-xs text-destructive", children: "required" }) : null
|
|
42900
|
+
)
|
|
42906
42901
|
] }),
|
|
42907
|
-
|
|
42908
|
-
/* @__PURE__ */
|
|
42909
|
-
|
|
42910
|
-
|
|
42911
|
-
className: "h-8 text-xs",
|
|
42912
|
-
placeholder: fieldName(field2),
|
|
42913
|
-
"aria-label": `How to ask for ${fieldName(field2)}`,
|
|
42914
|
-
value: question.ask ?? "",
|
|
42915
|
-
onChange: (e) => patchQuestion(index, { ask: e.target.value || null })
|
|
42916
|
-
}
|
|
42917
|
-
),
|
|
42918
|
-
/* @__PURE__ */ jsx134(
|
|
42919
|
-
BasicInput28,
|
|
42902
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-1", children: [
|
|
42903
|
+
/* @__PURE__ */ jsx133("span", { className: "text-xs", children: "Cover" }),
|
|
42904
|
+
/* @__PURE__ */ jsx133(
|
|
42905
|
+
PublicPicturePicker,
|
|
42920
42906
|
{
|
|
42921
|
-
|
|
42922
|
-
|
|
42923
|
-
|
|
42924
|
-
|
|
42925
|
-
onChange: (
|
|
42926
|
-
}
|
|
42927
|
-
),
|
|
42928
|
-
/* @__PURE__ */ jsxs124("label", { className: "col-span-2 flex items-center gap-2 text-xs", children: [
|
|
42929
|
-
/* @__PURE__ */ jsx134(
|
|
42930
|
-
Checkbox15,
|
|
42931
|
-
{
|
|
42932
|
-
checked: field2.required ? true : question.required === true,
|
|
42933
|
-
disabled: field2.required === true,
|
|
42934
|
-
"aria-label": `${fieldName(field2)} is required`,
|
|
42935
|
-
onCheckedChange: (checked) => patchQuestion(index, { required: checked === true ? true : null })
|
|
42936
|
-
}
|
|
42937
|
-
),
|
|
42938
|
-
"Required"
|
|
42939
|
-
] }),
|
|
42940
|
-
/* @__PURE__ */ jsx134(
|
|
42941
|
-
Condition,
|
|
42942
|
-
{
|
|
42943
|
-
question,
|
|
42944
|
-
fieldKeys: (fields.data ?? []).map((f) => ({
|
|
42945
|
-
id: String(f.id),
|
|
42946
|
-
key: f.key,
|
|
42947
|
-
label: f.label || f.key,
|
|
42948
|
-
field: f
|
|
42949
|
-
})),
|
|
42950
|
-
onChange: (showIf) => patchQuestion(index, { showIf })
|
|
42951
|
-
}
|
|
42952
|
-
),
|
|
42953
|
-
/* @__PURE__ */ jsx134(
|
|
42954
|
-
QuestionLogic,
|
|
42955
|
-
{
|
|
42956
|
-
question,
|
|
42957
|
-
index,
|
|
42958
|
-
questions: draft.questions,
|
|
42959
|
-
endings: draft.endings ?? [],
|
|
42960
|
-
field: field2,
|
|
42961
|
-
fieldKeys: conditionFields,
|
|
42962
|
-
label: (key) => {
|
|
42963
|
-
const f = byKey.get(key);
|
|
42964
|
-
return draft.questions.find((q) => q.field === key)?.ask || (f ? fieldName(f) : key);
|
|
42965
|
-
},
|
|
42966
|
-
onChange: (part) => patchQuestion(index, part)
|
|
42907
|
+
label: "Cover",
|
|
42908
|
+
pictures: lookOptions.pictures,
|
|
42909
|
+
value: draft.look?.coverFileId ?? "",
|
|
42910
|
+
noneLabel: "No cover",
|
|
42911
|
+
onChange: (id) => patchLook({ coverFileId: id || null })
|
|
42967
42912
|
}
|
|
42968
42913
|
)
|
|
42969
|
-
] })
|
|
42970
|
-
] },
|
|
42971
|
-
|
|
42972
|
-
|
|
42973
|
-
|
|
42974
|
-
|
|
42975
|
-
|
|
42976
|
-
className: "rounded border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-xs text-destructive",
|
|
42977
|
-
message: requiredLeftUnasked(fields.data ?? [], draft.questions.map((q) => q.field)) ?? "",
|
|
42978
|
-
operation: "Build a form",
|
|
42979
|
-
origin: "records-ui/FormBuilder"
|
|
42980
|
-
}
|
|
42981
|
-
) : null,
|
|
42982
|
-
draft.questions.filter((q) => !byKey.has(q.field)).map((q) => /* @__PURE__ */ jsxs124("p", { className: "text-xs text-destructive", children: [
|
|
42983
|
-
'This form asks for "',
|
|
42984
|
-
q.field,
|
|
42985
|
-
'" and this table has no such Field. It is shown here rather than quietly dropped.'
|
|
42986
|
-
] }, q.field)),
|
|
42987
|
-
adding ? /* @__PURE__ */ jsx134("div", { className: "rounded border p-2", "data-records-form-add-question": "", children: /* @__PURE__ */ jsx134(
|
|
42988
|
-
FieldEditor,
|
|
42914
|
+
] })
|
|
42915
|
+
] }) : lookUnread ? null : /* @__PURE__ */ jsx133(Skeleton29, { className: "h-12 w-full" }),
|
|
42916
|
+
lookUnread ? /* @__PURE__ */ jsx133(RefusalLine, { error: lookUnread }) : null,
|
|
42917
|
+
refused && isLookRefusal(refused) ? /* @__PURE__ */ jsx133(RefusalLine, { error: refused }) : null
|
|
42918
|
+
] }),
|
|
42919
|
+
/* @__PURE__ */ jsx133(
|
|
42920
|
+
QuestionList,
|
|
42989
42921
|
{
|
|
42990
42922
|
tableId,
|
|
42991
|
-
|
|
42992
|
-
|
|
42993
|
-
|
|
42994
|
-
|
|
42995
|
-
|
|
42996
|
-
|
|
42997
|
-
|
|
42998
|
-
Button87,
|
|
42999
|
-
{
|
|
43000
|
-
size: "sm",
|
|
43001
|
-
variant: "outline",
|
|
43002
|
-
onClick: () => {
|
|
43003
|
-
keysBeforeAdd.current = new Set((fields.data ?? []).map((f) => f.key));
|
|
43004
|
-
setAdding(true);
|
|
42923
|
+
draft,
|
|
42924
|
+
fields: fields.data ?? [],
|
|
42925
|
+
byKey,
|
|
42926
|
+
adding,
|
|
42927
|
+
onAdding: (on) => {
|
|
42928
|
+
keysBeforeAdd.current = on ? new Set((fields.data ?? []).map((f) => f.key)) : null;
|
|
42929
|
+
setAdding(on);
|
|
43005
42930
|
},
|
|
43006
|
-
|
|
42931
|
+
onAdded: () => setAdding(false),
|
|
42932
|
+
onQuestions: (questions) => patch({ questions }),
|
|
42933
|
+
onQuestion: patchQuestion
|
|
43007
42934
|
}
|
|
43008
|
-
)
|
|
42935
|
+
)
|
|
43009
42936
|
] })
|
|
43010
|
-
] })
|
|
43011
|
-
|
|
43012
|
-
|
|
43013
|
-
|
|
42937
|
+
] }),
|
|
42938
|
+
draft ? (
|
|
42939
|
+
// BESIDE THE EDITOR, ALWAYS IN VIEW (TYPEFORM-2): it follows the scroll and updates as you type.
|
|
42940
|
+
/* @__PURE__ */ jsx133(
|
|
42941
|
+
"aside",
|
|
42942
|
+
{
|
|
42943
|
+
className: "w-full shrink-0 overflow-auto rounded border p-2 @3xl:sticky @3xl:top-2 @3xl:max-h-[calc(100dvh-6rem)] @3xl:w-[min(45%,520px)]",
|
|
42944
|
+
"data-records-form-preview": "",
|
|
42945
|
+
children: showResults && stored ? /* @__PURE__ */ jsx133(FormResultsPanel, { form: stored }) : /* @__PURE__ */ jsx133(FormLookFrame, { look: lookOptions?.look ?? null, title: draft.name, contained: true, showOwnerHeader: draft.showOwnerHeader ?? orgKnobs?.show_owner_header ?? true, children: /* @__PURE__ */ jsx133(FormRunner, { form: draft, preview: true, autoAdvance: draft.autoAdvance ?? orgKnobs?.choice_auto_advance ?? true }) })
|
|
42946
|
+
}
|
|
42947
|
+
)
|
|
42948
|
+
) : null
|
|
42949
|
+
] }) })
|
|
42950
|
+
);
|
|
43014
42951
|
}
|
|
43015
42952
|
function PublishRow({
|
|
43016
42953
|
url,
|
|
@@ -43025,10 +42962,10 @@ function PublishRow({
|
|
|
43025
42962
|
unsaved = false
|
|
43026
42963
|
}) {
|
|
43027
42964
|
const open = state === "open";
|
|
43028
|
-
return /* @__PURE__ */
|
|
43029
|
-
/* @__PURE__ */
|
|
43030
|
-
mayPublish ? /* @__PURE__ */
|
|
43031
|
-
|
|
42965
|
+
return /* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-1.5 rounded border px-2.5 py-2", children: [
|
|
42966
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex flex-wrap items-center gap-2", children: [
|
|
42967
|
+
mayPublish ? /* @__PURE__ */ jsx133(
|
|
42968
|
+
Button86,
|
|
43032
42969
|
{
|
|
43033
42970
|
size: "sm",
|
|
43034
42971
|
variant: open ? "ghost" : "default",
|
|
@@ -43037,8 +42974,8 @@ function PublishRow({
|
|
|
43037
42974
|
children: busy ? "\u2026" : open ? "Unpublish" : unsaved ? "Save and publish" : "Publish"
|
|
43038
42975
|
}
|
|
43039
42976
|
) : null,
|
|
43040
|
-
state !== "draft" ? /* @__PURE__ */
|
|
43041
|
-
/* @__PURE__ */
|
|
42977
|
+
state !== "draft" ? /* @__PURE__ */ jsxs123(Fragment54, { children: [
|
|
42978
|
+
/* @__PURE__ */ jsx133(
|
|
43042
42979
|
"a",
|
|
43043
42980
|
{
|
|
43044
42981
|
href: url,
|
|
@@ -43048,14 +42985,14 @@ function PublishRow({
|
|
|
43048
42985
|
children: url
|
|
43049
42986
|
}
|
|
43050
42987
|
),
|
|
43051
|
-
/* @__PURE__ */
|
|
42988
|
+
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "outline", onClick: () => onCopy(url), children: copied ? "Copied" : "Copy link" })
|
|
43052
42989
|
] }) : null
|
|
43053
42990
|
] }),
|
|
43054
|
-
/* @__PURE__ */
|
|
43055
|
-
open && unsaved ? /* @__PURE__ */
|
|
43056
|
-
!mayPublish ? /* @__PURE__ */
|
|
43057
|
-
shownUrl ? /* @__PURE__ */
|
|
43058
|
-
"
|
|
42991
|
+
/* @__PURE__ */ jsx133("p", { className: "w-fit text-xs text-muted-foreground", title: FORM_STATE_WORDS[state], "data-records-form-state": state, children: FORM_STATE_LABELS[state] }),
|
|
42992
|
+
open && unsaved ? /* @__PURE__ */ jsx133("p", { className: "text-xs text-muted-foreground", "data-records-form-unsaved": "", children: "Unsaved changes \u2014 the live form shows your last save" }) : null,
|
|
42993
|
+
!mayPublish ? /* @__PURE__ */ jsx133("p", { className: "w-fit text-xs text-muted-foreground", title: why, children: "Publishing needs table admin" }) : null,
|
|
42994
|
+
shownUrl ? /* @__PURE__ */ jsxs123("p", { className: "break-all rounded border border-dashed px-2 py-1 text-xs", title: "This browser would not copy for you.", children: [
|
|
42995
|
+
"Copy by hand: ",
|
|
43059
42996
|
shownUrl
|
|
43060
42997
|
] }) : null
|
|
43061
42998
|
] });
|
|
@@ -43065,7 +43002,7 @@ function Condition({
|
|
|
43065
43002
|
fieldKeys,
|
|
43066
43003
|
onChange
|
|
43067
43004
|
}) {
|
|
43068
|
-
return /* @__PURE__ */
|
|
43005
|
+
return /* @__PURE__ */ jsx133(
|
|
43069
43006
|
ConditionGroup,
|
|
43070
43007
|
{
|
|
43071
43008
|
lead: "Ask only when",
|
|
@@ -43076,26 +43013,348 @@ function Condition({
|
|
|
43076
43013
|
}
|
|
43077
43014
|
);
|
|
43078
43015
|
}
|
|
43016
|
+
var FORM_STATE_LABELS = {
|
|
43017
|
+
draft: "Not published",
|
|
43018
|
+
open: "Open",
|
|
43019
|
+
closed: "Closed",
|
|
43020
|
+
full: "Full"
|
|
43021
|
+
};
|
|
43022
|
+
function QuestionList({
|
|
43023
|
+
tableId,
|
|
43024
|
+
draft,
|
|
43025
|
+
fields,
|
|
43026
|
+
byKey,
|
|
43027
|
+
adding,
|
|
43028
|
+
onAdding,
|
|
43029
|
+
onAdded,
|
|
43030
|
+
onQuestions,
|
|
43031
|
+
onQuestion
|
|
43032
|
+
}) {
|
|
43033
|
+
const sensors = useSensors(
|
|
43034
|
+
useSensor(PointerSensor, { activationConstraint: { distance: 4 } }),
|
|
43035
|
+
useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
|
|
43036
|
+
);
|
|
43037
|
+
const askable = askableFields([...fields]);
|
|
43038
|
+
const unasked = askable.filter((f) => !draft.questions.some((q) => q.field === f.key));
|
|
43039
|
+
const asked3 = draft.questions.map((q) => byKey.get(q.field)).filter((f) => Boolean(f)).map((f) => ({ id: String(f.id), key: f.key, label: draft.questions.find((q) => q.field === f.key)?.ask || fieldName(f), field: f }));
|
|
43040
|
+
const label = (key) => {
|
|
43041
|
+
const f = byKey.get(key);
|
|
43042
|
+
return draft.questions.find((q) => q.field === key)?.ask || (f ? fieldName(f) : key);
|
|
43043
|
+
};
|
|
43044
|
+
function dragEnd(event) {
|
|
43045
|
+
const from = draft.questions.findIndex((q) => q.field === event.active.id);
|
|
43046
|
+
const to = event.over ? draft.questions.findIndex((q) => q.field === event.over.id) : -1;
|
|
43047
|
+
if (from < 0 || to < 0 || from === to) return;
|
|
43048
|
+
onQuestions(arrayMove(draft.questions, from, to));
|
|
43049
|
+
}
|
|
43050
|
+
const notAskable = whyNotAskable([...fields]);
|
|
43051
|
+
const leftUnasked = requiredLeftUnasked([...fields], draft.questions.map((q) => q.field));
|
|
43052
|
+
return /* @__PURE__ */ jsxs123("div", { className: "flex flex-col gap-1.5", "data-records-form-questions": "", children: [
|
|
43053
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
43054
|
+
/* @__PURE__ */ jsx133("span", { className: "font-medium text-foreground", children: "Questions" }),
|
|
43055
|
+
/* @__PURE__ */ jsx133("span", { className: "tabular-nums", children: draft.questions.length })
|
|
43056
|
+
] }),
|
|
43057
|
+
/* @__PURE__ */ jsx133(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: dragEnd, children: /* @__PURE__ */ jsx133(SortableContext, { items: draft.questions.map((q) => q.field), strategy: verticalListSortingStrategy, children: draft.questions.map((question, index) => /* @__PURE__ */ jsx133(
|
|
43058
|
+
QuestionRow,
|
|
43059
|
+
{
|
|
43060
|
+
index,
|
|
43061
|
+
question,
|
|
43062
|
+
field: byKey.get(question.field) ?? null,
|
|
43063
|
+
draft,
|
|
43064
|
+
conditionFields: asked3.filter((f) => f.key !== question.field),
|
|
43065
|
+
label,
|
|
43066
|
+
onChange: (change) => onQuestion(index, change),
|
|
43067
|
+
onRemove: () => onQuestions(draft.questions.filter((q) => q.field !== question.field))
|
|
43068
|
+
},
|
|
43069
|
+
question.field
|
|
43070
|
+
)) }) }),
|
|
43071
|
+
unasked.length > 0 ? /* @__PURE__ */ jsx133("div", { className: "flex flex-wrap items-center gap-1", "data-records-form-unasked": "", children: unasked.map((f) => /* @__PURE__ */ jsxs123(
|
|
43072
|
+
Button86,
|
|
43073
|
+
{
|
|
43074
|
+
size: "sm",
|
|
43075
|
+
variant: "outline",
|
|
43076
|
+
"aria-label": `Ask for ${fieldName(f)}`,
|
|
43077
|
+
onClick: () => onQuestions([...draft.questions, { field: f.key }]),
|
|
43078
|
+
children: [
|
|
43079
|
+
"+ ",
|
|
43080
|
+
fieldName(f)
|
|
43081
|
+
]
|
|
43082
|
+
},
|
|
43083
|
+
f.id
|
|
43084
|
+
)) }) : null,
|
|
43085
|
+
notAskable ? /* @__PURE__ */ jsx133("p", { className: "w-fit text-xs text-muted-foreground", title: notAskable, children: "Some fields cannot be asked publicly" }) : null,
|
|
43086
|
+
leftUnasked ? /* @__PURE__ */ jsx133(
|
|
43087
|
+
ErrorBox7,
|
|
43088
|
+
{
|
|
43089
|
+
className: "rounded border border-destructive/40 bg-destructive/5 px-2 py-1.5 text-xs text-destructive",
|
|
43090
|
+
message: leftUnasked,
|
|
43091
|
+
operation: "Build a form",
|
|
43092
|
+
origin: "records-ui/FormBuilder"
|
|
43093
|
+
}
|
|
43094
|
+
) : null,
|
|
43095
|
+
adding ? /* @__PURE__ */ jsx133("div", { className: "rounded border p-2", "data-records-form-add-question": "", children: /* @__PURE__ */ jsx133(FieldEditor, { tableId, onCancel: () => onAdding(false), onSaved: onAdded }) }) : /* @__PURE__ */ jsx133("div", { children: /* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "outline", onClick: () => onAdding(true), children: "Add a question" }) })
|
|
43096
|
+
] });
|
|
43097
|
+
}
|
|
43098
|
+
function QuestionRow({
|
|
43099
|
+
index,
|
|
43100
|
+
question,
|
|
43101
|
+
field: field2,
|
|
43102
|
+
draft,
|
|
43103
|
+
conditionFields,
|
|
43104
|
+
label,
|
|
43105
|
+
onChange,
|
|
43106
|
+
onRemove
|
|
43107
|
+
}) {
|
|
43108
|
+
const sortable = useSortable({ id: question.field });
|
|
43109
|
+
const [open, setOpen] = useState110(true);
|
|
43110
|
+
const name = field2 ? fieldName(field2) : question.field;
|
|
43111
|
+
return /* @__PURE__ */ jsxs123(
|
|
43112
|
+
"div",
|
|
43113
|
+
{
|
|
43114
|
+
ref: sortable.setNodeRef,
|
|
43115
|
+
style: { transform: CSS2.Transform.toString(sortable.transform), transition: sortable.transition },
|
|
43116
|
+
className: cn78("rounded border bg-background px-2 py-1.5", sortable.isDragging && "z-10 shadow-md"),
|
|
43117
|
+
"data-records-form-question-row": question.field,
|
|
43118
|
+
children: [
|
|
43119
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex items-center gap-2", children: [
|
|
43120
|
+
/* @__PURE__ */ jsx133(
|
|
43121
|
+
"button",
|
|
43122
|
+
{
|
|
43123
|
+
type: "button",
|
|
43124
|
+
className: "cursor-grab touch-none rounded px-1 text-muted-foreground hover:bg-muted active:cursor-grabbing",
|
|
43125
|
+
"aria-label": `Move ${name}`,
|
|
43126
|
+
title: "Drag to reorder",
|
|
43127
|
+
"data-records-form-drag": "",
|
|
43128
|
+
...sortable.attributes,
|
|
43129
|
+
...sortable.listeners,
|
|
43130
|
+
children: "\u22EE\u22EE"
|
|
43131
|
+
}
|
|
43132
|
+
),
|
|
43133
|
+
/* @__PURE__ */ jsx133("span", { className: "w-5 text-right text-xs tabular-nums text-muted-foreground", children: index + 1 }),
|
|
43134
|
+
/* @__PURE__ */ jsx133("button", { type: "button", className: "min-w-0 flex-1 truncate text-left text-xs font-medium", onClick: () => setOpen((v) => !v), "aria-expanded": open, children: question.ask?.trim() || name }),
|
|
43135
|
+
field2?.required || question.required ? /* @__PURE__ */ jsx133("span", { className: "text-xs text-foreground/80", children: "required" }) : null,
|
|
43136
|
+
!field2 ? /* @__PURE__ */ jsx133("span", { className: "rounded bg-destructive px-1.5 text-[11px] text-destructive-foreground", title: "The table has no such field.", children: "Missing field" }) : null,
|
|
43137
|
+
/* @__PURE__ */ jsx133(Button86, { size: "sm", variant: "ghost", onClick: onRemove, "aria-label": `Stop asking ${name}`, children: "Remove" })
|
|
43138
|
+
] }),
|
|
43139
|
+
open && field2 ? /* @__PURE__ */ jsxs123("div", { className: "mt-1.5 grid grid-cols-2 gap-2 pl-7", children: [
|
|
43140
|
+
/* @__PURE__ */ jsx133(
|
|
43141
|
+
BasicInput28,
|
|
43142
|
+
{
|
|
43143
|
+
className: "h-8 text-xs",
|
|
43144
|
+
placeholder: name,
|
|
43145
|
+
"aria-label": `How to ask for ${name}`,
|
|
43146
|
+
title: "Use {{question}} to recall an earlier answer",
|
|
43147
|
+
value: question.ask ?? "",
|
|
43148
|
+
onChange: (e) => onChange({ ask: e.target.value || null })
|
|
43149
|
+
}
|
|
43150
|
+
),
|
|
43151
|
+
/* @__PURE__ */ jsx133(
|
|
43152
|
+
BasicInput28,
|
|
43153
|
+
{
|
|
43154
|
+
className: "h-8 text-xs",
|
|
43155
|
+
placeholder: "Help text",
|
|
43156
|
+
"aria-label": `Help text for ${name}`,
|
|
43157
|
+
value: question.help ?? "",
|
|
43158
|
+
onChange: (e) => onChange({ help: e.target.value || null })
|
|
43159
|
+
}
|
|
43160
|
+
),
|
|
43161
|
+
/* @__PURE__ */ jsxs123("label", { className: "col-span-2 flex items-center gap-2 text-xs", children: [
|
|
43162
|
+
/* @__PURE__ */ jsx133(
|
|
43163
|
+
Checkbox15,
|
|
43164
|
+
{
|
|
43165
|
+
checked: field2.required ? true : question.required === true,
|
|
43166
|
+
disabled: field2.required === true,
|
|
43167
|
+
"aria-label": `${name} is required`,
|
|
43168
|
+
onCheckedChange: (checked) => onChange({ required: checked === true ? true : null })
|
|
43169
|
+
}
|
|
43170
|
+
),
|
|
43171
|
+
"Required"
|
|
43172
|
+
] }),
|
|
43173
|
+
/* @__PURE__ */ jsx133(Condition, { question, fieldKeys: conditionFields, onChange: (showIf) => onChange({ showIf }) }),
|
|
43174
|
+
/* @__PURE__ */ jsx133(
|
|
43175
|
+
QuestionLogic,
|
|
43176
|
+
{
|
|
43177
|
+
question,
|
|
43178
|
+
index,
|
|
43179
|
+
questions: draft.questions,
|
|
43180
|
+
endings: draft.endings ?? [],
|
|
43181
|
+
field: field2,
|
|
43182
|
+
fieldKeys: conditionFields,
|
|
43183
|
+
label,
|
|
43184
|
+
onChange
|
|
43185
|
+
}
|
|
43186
|
+
)
|
|
43187
|
+
] }) : null
|
|
43188
|
+
]
|
|
43189
|
+
}
|
|
43190
|
+
);
|
|
43191
|
+
}
|
|
43192
|
+
function NotifyOnAnswer({
|
|
43193
|
+
tableId,
|
|
43194
|
+
tableName: tableName2,
|
|
43195
|
+
formName,
|
|
43196
|
+
ruleId,
|
|
43197
|
+
notify,
|
|
43198
|
+
emailQuestion,
|
|
43199
|
+
onRule,
|
|
43200
|
+
onNotify
|
|
43201
|
+
}) {
|
|
43202
|
+
const client = useRecordsClient79();
|
|
43203
|
+
const [subs, setSubs] = useState110(null);
|
|
43204
|
+
const [busy, setBusy] = useState110(false);
|
|
43205
|
+
const [error, setError] = useState110(null);
|
|
43206
|
+
const [said2, setSaid] = useState110(null);
|
|
43207
|
+
const [url, setUrl] = useState110(notify?.webhookUrl ?? "");
|
|
43208
|
+
useEffect96(() => setUrl(notify?.webhookUrl ?? ""), [notify?.webhookUrl]);
|
|
43209
|
+
useEffect96(() => {
|
|
43210
|
+
let cancelled = false;
|
|
43211
|
+
void client.subscriptions({ table_id: tableId }).then((r) => {
|
|
43212
|
+
if (!cancelled && r.ok) setSubs(r.data);
|
|
43213
|
+
});
|
|
43214
|
+
return () => {
|
|
43215
|
+
cancelled = true;
|
|
43216
|
+
};
|
|
43217
|
+
}, [client, tableId, ruleId]);
|
|
43218
|
+
const chosen = subs?.find((s) => s.rule_id === ruleId) ?? null;
|
|
43219
|
+
async function emailMe(on) {
|
|
43220
|
+
setBusy(true);
|
|
43221
|
+
setError(null);
|
|
43222
|
+
try {
|
|
43223
|
+
if (!on) {
|
|
43224
|
+
onRule(null);
|
|
43225
|
+
if (ruleId) await client.recordDelete({ record_id: ruleId });
|
|
43226
|
+
return;
|
|
43227
|
+
}
|
|
43228
|
+
const listed = await client.views({ table_id: tableId });
|
|
43229
|
+
if (!listed.ok) return setError(listed.error);
|
|
43230
|
+
let viewId = listed.data.find((v) => !v.filters || Object.keys(v.filters).length === 0)?.view_id ?? null;
|
|
43231
|
+
if (!viewId) {
|
|
43232
|
+
const made = await client.viewDeclare({ table_id: tableId, spec: { name: `Every ${tableName2}`, filters: null } });
|
|
43233
|
+
if (!made.ok) return setError(made.error);
|
|
43234
|
+
viewId = made.data;
|
|
43235
|
+
}
|
|
43236
|
+
const declared2 = await client.subscriptionDeclare({
|
|
43237
|
+
table_id: tableId,
|
|
43238
|
+
spec: {
|
|
43239
|
+
name: `${formName} \u2014 new answers`,
|
|
43240
|
+
saved_view_id: viewId,
|
|
43241
|
+
cadence: "instant",
|
|
43242
|
+
schedule: null,
|
|
43243
|
+
channel: "email",
|
|
43244
|
+
quiet_hours: null,
|
|
43245
|
+
recipient_user_id: client.config.actor.user_id ?? null,
|
|
43246
|
+
event_key: "records.changed"
|
|
43247
|
+
}
|
|
43248
|
+
});
|
|
43249
|
+
if (!declared2.ok) return setError(declared2.error);
|
|
43250
|
+
onRule(declared2.data);
|
|
43251
|
+
} finally {
|
|
43252
|
+
setBusy(false);
|
|
43253
|
+
}
|
|
43254
|
+
}
|
|
43255
|
+
async function saveWebhook() {
|
|
43256
|
+
const next = url.trim();
|
|
43257
|
+
if (next === (notify?.webhookUrl ?? "")) return;
|
|
43258
|
+
setBusy(true);
|
|
43259
|
+
setError(null);
|
|
43260
|
+
setSaid(null);
|
|
43261
|
+
try {
|
|
43262
|
+
if (next === "") {
|
|
43263
|
+
if (notify?.webhookAutomationId) await client.automationArchive({ automation_id: notify.webhookAutomationId });
|
|
43264
|
+
onNotify(notify?.emailRespondent ? { ...notify, webhookUrl: null, webhookAutomationId: null } : null);
|
|
43265
|
+
return;
|
|
43266
|
+
}
|
|
43267
|
+
const declared2 = await client.automationDeclare({
|
|
43268
|
+
table_id: tableId,
|
|
43269
|
+
automation_id: notify?.webhookAutomationId ?? null,
|
|
43270
|
+
spec: { name: `${formName} \u2014 webhook`, trigger: { on: "form_answered" }, actions: [{ do: "webhook", url: next }] }
|
|
43271
|
+
});
|
|
43272
|
+
if (!declared2.ok) return setError(declared2.error);
|
|
43273
|
+
if (!declared2.data.ok) {
|
|
43274
|
+
setSaid(Object.values(declared2.data._errors)[0] ?? "Not saved");
|
|
43275
|
+
return;
|
|
43276
|
+
}
|
|
43277
|
+
onNotify({ ...notify ?? {}, webhookUrl: next, webhookAutomationId: declared2.data.automation_id });
|
|
43278
|
+
} finally {
|
|
43279
|
+
setBusy(false);
|
|
43280
|
+
}
|
|
43281
|
+
}
|
|
43282
|
+
return /* @__PURE__ */ jsxs123("section", { className: "flex flex-col gap-2 rounded border px-2.5 py-2 text-xs", "aria-label": "Notify on answer", "data-records-form-notify": "", children: [
|
|
43283
|
+
/* @__PURE__ */ jsx133("span", { className: "font-medium", children: "Notify on answer" }),
|
|
43284
|
+
/* @__PURE__ */ jsxs123("div", { className: "flex flex-wrap items-center gap-x-4 gap-y-1", children: [
|
|
43285
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex items-center gap-2", title: "An email to you each time somebody answers.", children: [
|
|
43286
|
+
/* @__PURE__ */ jsx133(Checkbox15, { checked: Boolean(ruleId), disabled: busy, onCheckedChange: (c) => void emailMe(c === true), "aria-label": "Email me" }),
|
|
43287
|
+
"Email me",
|
|
43288
|
+
chosen && chosen.channel !== "email" ? /* @__PURE__ */ jsxs123("span", { className: "text-muted-foreground", children: [
|
|
43289
|
+
"(",
|
|
43290
|
+
chosen.channel === "sms" ? "text" : "in app",
|
|
43291
|
+
")"
|
|
43292
|
+
] }) : null
|
|
43293
|
+
] }),
|
|
43294
|
+
/* @__PURE__ */ jsxs123(
|
|
43295
|
+
"label",
|
|
43296
|
+
{
|
|
43297
|
+
className: cn78("flex items-center gap-2", !emailQuestion && "text-muted-foreground"),
|
|
43298
|
+
title: emailQuestion ? "An email to the person who answered, in your ending's words." : "Add an email question first.",
|
|
43299
|
+
children: [
|
|
43300
|
+
/* @__PURE__ */ jsx133(
|
|
43301
|
+
Checkbox15,
|
|
43302
|
+
{
|
|
43303
|
+
checked: Boolean(notify?.emailRespondent) && Boolean(emailQuestion),
|
|
43304
|
+
disabled: busy || !emailQuestion,
|
|
43305
|
+
onCheckedChange: (c) => onNotify({ ...notify ?? {}, emailRespondent: c === true ? true : null, respondentField: c === true ? emailQuestion : null }),
|
|
43306
|
+
"aria-label": "Email the respondent"
|
|
43307
|
+
}
|
|
43308
|
+
),
|
|
43309
|
+
"Email the respondent"
|
|
43310
|
+
]
|
|
43311
|
+
}
|
|
43312
|
+
)
|
|
43313
|
+
] }),
|
|
43314
|
+
/* @__PURE__ */ jsxs123("label", { className: "flex items-center gap-2", children: [
|
|
43315
|
+
/* @__PURE__ */ jsx133("span", { className: "shrink-0", children: "Webhook" }),
|
|
43316
|
+
/* @__PURE__ */ jsx133(
|
|
43317
|
+
BasicInput28,
|
|
43318
|
+
{
|
|
43319
|
+
type: "url",
|
|
43320
|
+
inputMode: "url",
|
|
43321
|
+
className: "h-8 text-xs",
|
|
43322
|
+
placeholder: "https://",
|
|
43323
|
+
"aria-label": "Webhook URL",
|
|
43324
|
+
value: url,
|
|
43325
|
+
disabled: busy,
|
|
43326
|
+
onChange: (e) => setUrl(e.target.value),
|
|
43327
|
+
onBlur: () => void saveWebhook(),
|
|
43328
|
+
onKeyDown: (e) => {
|
|
43329
|
+
if (e.key === "Enter") void saveWebhook();
|
|
43330
|
+
}
|
|
43331
|
+
}
|
|
43332
|
+
)
|
|
43333
|
+
] }),
|
|
43334
|
+
said2 ? /* @__PURE__ */ jsx133("p", { className: "text-destructive", children: said2 }) : null,
|
|
43335
|
+
error ? /* @__PURE__ */ jsx133(RefusalLine, { error }) : null
|
|
43336
|
+
] });
|
|
43337
|
+
}
|
|
43079
43338
|
|
|
43080
43339
|
// src/DocTemplate.tsx
|
|
43081
|
-
import { useCallback as
|
|
43082
|
-
import { useFields as useFields26, useRecordsClient as
|
|
43083
|
-
import { BasicInput as BasicInput29, BasicTextarea as BasicTextarea12, Button as
|
|
43084
|
-
import { jsx as
|
|
43340
|
+
import { useCallback as useCallback55, useEffect as useEffect97, useState as useState111 } from "react";
|
|
43341
|
+
import { useFields as useFields26, useRecordsClient as useRecordsClient80, useTable as useTable27 } from "@ai-matrx/records/react";
|
|
43342
|
+
import { BasicInput as BasicInput29, BasicTextarea as BasicTextarea12, Button as Button87, Label as Label24, Skeleton as Skeleton30, cn as cn79 } from "@ai-matrx/design-system";
|
|
43343
|
+
import { jsx as jsx134, jsxs as jsxs124 } from "react/jsx-runtime";
|
|
43085
43344
|
function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, className }) {
|
|
43086
|
-
const client =
|
|
43345
|
+
const client = useRecordsClient80();
|
|
43087
43346
|
const claimSeed = useSeedGuard();
|
|
43088
|
-
const table =
|
|
43347
|
+
const table = useTable27(tableId);
|
|
43089
43348
|
const rights = useTableRights(table.data);
|
|
43090
43349
|
const fields = useFields26(tableId);
|
|
43091
|
-
const [templates, setTemplates] =
|
|
43092
|
-
const [error, setError] =
|
|
43093
|
-
const [activeId, setActiveId] =
|
|
43094
|
-
const [draftName, setDraftName] =
|
|
43095
|
-
const [draftBody, setDraftBody] =
|
|
43096
|
-
const [unresolved, setUnresolved] =
|
|
43097
|
-
const [saving, setSaving] =
|
|
43098
|
-
const load =
|
|
43350
|
+
const [templates, setTemplates] = useState111(null);
|
|
43351
|
+
const [error, setError] = useState111(null);
|
|
43352
|
+
const [activeId, setActiveId] = useState111(activeTemplateId ?? null);
|
|
43353
|
+
const [draftName, setDraftName] = useState111("");
|
|
43354
|
+
const [draftBody, setDraftBody] = useState111("");
|
|
43355
|
+
const [unresolved, setUnresolved] = useState111([]);
|
|
43356
|
+
const [saving, setSaving] = useState111(false);
|
|
43357
|
+
const load = useCallback55(async () => {
|
|
43099
43358
|
const held = await client.docTemplates({ table_id: tableId });
|
|
43100
43359
|
if (!held.ok) {
|
|
43101
43360
|
setError(held.error);
|
|
@@ -43126,10 +43385,10 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43126
43385
|
setError(null);
|
|
43127
43386
|
setTemplates(rows);
|
|
43128
43387
|
}, [client, tableId, seed, fields.data]);
|
|
43129
|
-
|
|
43388
|
+
useEffect97(() => {
|
|
43130
43389
|
void load();
|
|
43131
43390
|
}, [load]);
|
|
43132
|
-
|
|
43391
|
+
useEffect97(() => {
|
|
43133
43392
|
if (!templates || templates.length === 0) return;
|
|
43134
43393
|
const chosen = templates.find((t) => t.id === (activeTemplateId ?? activeId)) ?? templates[0];
|
|
43135
43394
|
if (chosen.id !== activeId) setActiveId(chosen.id);
|
|
@@ -43137,7 +43396,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43137
43396
|
setDraftBody(chosen.body);
|
|
43138
43397
|
onActiveTemplate?.(chosen);
|
|
43139
43398
|
}, [templates, activeTemplateId]);
|
|
43140
|
-
|
|
43399
|
+
useEffect97(() => {
|
|
43141
43400
|
let cancelled = false;
|
|
43142
43401
|
if (draftBody.trim() === "") {
|
|
43143
43402
|
setUnresolved([]);
|
|
@@ -43167,25 +43426,25 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43167
43426
|
setActiveId(saved.data);
|
|
43168
43427
|
await load();
|
|
43169
43428
|
}
|
|
43170
|
-
if (fields.error) return /* @__PURE__ */
|
|
43429
|
+
if (fields.error) return /* @__PURE__ */ jsx134(RefusalNotice, { error: fields.error, className });
|
|
43171
43430
|
if (error) {
|
|
43172
|
-
return /* @__PURE__ */
|
|
43431
|
+
return /* @__PURE__ */ jsx134(
|
|
43173
43432
|
RefusalNotice,
|
|
43174
43433
|
{
|
|
43175
43434
|
error,
|
|
43176
43435
|
className,
|
|
43177
|
-
actions: /* @__PURE__ */
|
|
43436
|
+
actions: /* @__PURE__ */ jsx134(Button87, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
43178
43437
|
}
|
|
43179
43438
|
);
|
|
43180
43439
|
}
|
|
43181
|
-
if (templates === null || fields.loading && !fields.data) return /* @__PURE__ */
|
|
43440
|
+
if (templates === null || fields.loading && !fields.data) return /* @__PURE__ */ jsx134(Skeleton30, { className: cn79("h-64 w-full", className) });
|
|
43182
43441
|
if (!rights.admin) {
|
|
43183
|
-
return /* @__PURE__ */
|
|
43442
|
+
return /* @__PURE__ */ jsx134("p", { className: cn79("text-xs text-muted-foreground", className), children: rights.why("structure") });
|
|
43184
43443
|
}
|
|
43185
|
-
return /* @__PURE__ */
|
|
43186
|
-
/* @__PURE__ */
|
|
43187
|
-
templates.map((t) => /* @__PURE__ */
|
|
43188
|
-
|
|
43444
|
+
return /* @__PURE__ */ jsxs124("div", { className: cn79("flex flex-col gap-2", className), children: [
|
|
43445
|
+
/* @__PURE__ */ jsxs124("div", { className: "flex items-center gap-1 overflow-x-auto", role: "group", "aria-label": "Templates", children: [
|
|
43446
|
+
templates.map((t) => /* @__PURE__ */ jsxs124(
|
|
43447
|
+
Button87,
|
|
43189
43448
|
{
|
|
43190
43449
|
size: "sm",
|
|
43191
43450
|
variant: t.id === activeId ? "secondary" : "ghost",
|
|
@@ -43197,7 +43456,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43197
43456
|
},
|
|
43198
43457
|
children: [
|
|
43199
43458
|
t.name,
|
|
43200
|
-
/* @__PURE__ */
|
|
43459
|
+
/* @__PURE__ */ jsxs124("span", { className: "ml-1 text-[10px] opacity-70", children: [
|
|
43201
43460
|
"v",
|
|
43202
43461
|
t.template_version
|
|
43203
43462
|
] })
|
|
@@ -43205,8 +43464,8 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43205
43464
|
},
|
|
43206
43465
|
t.id
|
|
43207
43466
|
)),
|
|
43208
|
-
/* @__PURE__ */
|
|
43209
|
-
|
|
43467
|
+
/* @__PURE__ */ jsx134(
|
|
43468
|
+
Button87,
|
|
43210
43469
|
{
|
|
43211
43470
|
size: "sm",
|
|
43212
43471
|
variant: "ghost",
|
|
@@ -43218,16 +43477,16 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43218
43477
|
children: "New template"
|
|
43219
43478
|
}
|
|
43220
43479
|
),
|
|
43221
|
-
/* @__PURE__ */
|
|
43480
|
+
/* @__PURE__ */ jsx134(Button87, { size: "sm", className: "ml-auto", disabled: saving, onClick: () => void save(), children: saving ? "Saving\u2026" : "Save" })
|
|
43222
43481
|
] }),
|
|
43223
|
-
/* @__PURE__ */
|
|
43224
|
-
/* @__PURE__ */
|
|
43225
|
-
/* @__PURE__ */
|
|
43482
|
+
/* @__PURE__ */ jsxs124("label", { className: "flex flex-col gap-1", children: [
|
|
43483
|
+
/* @__PURE__ */ jsx134(Label24, { className: "text-xs font-medium", children: "Name" }),
|
|
43484
|
+
/* @__PURE__ */ jsx134(BasicInput29, { value: draftName, onChange: (e) => setDraftName(e.target.value), placeholder: "Agreement" })
|
|
43226
43485
|
] }),
|
|
43227
|
-
/* @__PURE__ */
|
|
43228
|
-
/* @__PURE__ */
|
|
43229
|
-
(fields.data ?? []).map((field2) => /* @__PURE__ */
|
|
43230
|
-
|
|
43486
|
+
/* @__PURE__ */ jsxs124("div", { className: "flex flex-wrap items-center gap-1", children: [
|
|
43487
|
+
/* @__PURE__ */ jsx134("span", { className: "text-xs text-muted-foreground", children: "Insert" }),
|
|
43488
|
+
(fields.data ?? []).map((field2) => /* @__PURE__ */ jsx134(
|
|
43489
|
+
Button87,
|
|
43231
43490
|
{
|
|
43232
43491
|
size: "sm",
|
|
43233
43492
|
variant: "ghost",
|
|
@@ -43238,7 +43497,7 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43238
43497
|
field2.id
|
|
43239
43498
|
))
|
|
43240
43499
|
] }),
|
|
43241
|
-
/* @__PURE__ */
|
|
43500
|
+
/* @__PURE__ */ jsx134(
|
|
43242
43501
|
BasicTextarea12,
|
|
43243
43502
|
{
|
|
43244
43503
|
rows: 10,
|
|
@@ -43248,33 +43507,33 @@ function DocTemplate({ tableId, seed, activeTemplateId, onActiveTemplate, classN
|
|
|
43248
43507
|
onChange: (e) => setDraftBody(e.target.value)
|
|
43249
43508
|
}
|
|
43250
43509
|
),
|
|
43251
|
-
unresolved.length > 0 ? /* @__PURE__ */
|
|
43252
|
-
/* @__PURE__ */
|
|
43253
|
-
/* @__PURE__ */
|
|
43254
|
-
/* @__PURE__ */
|
|
43510
|
+
unresolved.length > 0 ? /* @__PURE__ */ jsx134("ul", { className: "flex flex-col gap-0.5 rounded border border-dashed px-2 py-1 text-xs", children: unresolved.map((token) => /* @__PURE__ */ jsxs124("li", { className: "flex items-baseline gap-1", children: [
|
|
43511
|
+
/* @__PURE__ */ jsx134("code", { className: "text-destructive", children: token.raw }),
|
|
43512
|
+
/* @__PURE__ */ jsx134("span", { "aria-hidden": "true", children: "\u2014" }),
|
|
43513
|
+
/* @__PURE__ */ jsx134(
|
|
43255
43514
|
RefusalLine,
|
|
43256
43515
|
{
|
|
43257
43516
|
error: refusal("invalid_argument", token.why, "Change it to a Field this table has, or take it out.")
|
|
43258
43517
|
}
|
|
43259
43518
|
)
|
|
43260
|
-
] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */
|
|
43519
|
+
] }, token.raw)) }) : draftBody.trim() !== "" ? /* @__PURE__ */ jsx134("p", { className: "text-xs text-muted-foreground", children: "Every token in this body points at a Field this table can answer." }) : null
|
|
43261
43520
|
] });
|
|
43262
43521
|
}
|
|
43263
43522
|
|
|
43264
43523
|
// src/DocRender.tsx
|
|
43265
|
-
import { useCallback as
|
|
43266
|
-
import { useRecordsClient as
|
|
43267
|
-
import { Button as
|
|
43268
|
-
import { jsx as
|
|
43524
|
+
import { useCallback as useCallback56, useEffect as useEffect98, useRef as useRef57, useState as useState112 } from "react";
|
|
43525
|
+
import { useRecordsClient as useRecordsClient81 } from "@ai-matrx/records/react";
|
|
43526
|
+
import { Button as Button88, Skeleton as Skeleton31, cn as cn80 } from "@ai-matrx/design-system";
|
|
43527
|
+
import { jsx as jsx135, jsxs as jsxs125 } from "react/jsx-runtime";
|
|
43269
43528
|
function DocRender({ templateId, recordId, filename, onRendered, className }) {
|
|
43270
|
-
const client =
|
|
43271
|
-
const [preview, setPreview] =
|
|
43272
|
-
const [renders, setRenders] =
|
|
43273
|
-
const [showing, setShowing] =
|
|
43274
|
-
const [error, setError] =
|
|
43275
|
-
const [busy, setBusy] =
|
|
43529
|
+
const client = useRecordsClient81();
|
|
43530
|
+
const [preview, setPreview] = useState112(null);
|
|
43531
|
+
const [renders, setRenders] = useState112(null);
|
|
43532
|
+
const [showing, setShowing] = useState112(null);
|
|
43533
|
+
const [error, setError] = useState112(null);
|
|
43534
|
+
const [busy, setBusy] = useState112(null);
|
|
43276
43535
|
const paper = useRef57(null);
|
|
43277
|
-
const load =
|
|
43536
|
+
const load = useCallback56(async () => {
|
|
43278
43537
|
const [body, held] = await Promise.all([
|
|
43279
43538
|
client.docRenderBody({ template_id: templateId, record_id: recordId }),
|
|
43280
43539
|
client.docRenders({ record_id: recordId })
|
|
@@ -43291,7 +43550,7 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
|
|
|
43291
43550
|
setPreview(body.data);
|
|
43292
43551
|
setRenders(held.data.filter((r) => r.template_id === templateId));
|
|
43293
43552
|
}, [client, templateId, recordId]);
|
|
43294
|
-
|
|
43553
|
+
useEffect98(() => {
|
|
43295
43554
|
void load();
|
|
43296
43555
|
}, [load]);
|
|
43297
43556
|
async function freeze() {
|
|
@@ -43331,22 +43590,22 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
|
|
|
43331
43590
|
}
|
|
43332
43591
|
}
|
|
43333
43592
|
if (error) {
|
|
43334
|
-
return /* @__PURE__ */
|
|
43593
|
+
return /* @__PURE__ */ jsx135(
|
|
43335
43594
|
RefusalNotice,
|
|
43336
43595
|
{
|
|
43337
43596
|
error,
|
|
43338
43597
|
className,
|
|
43339
|
-
actions: /* @__PURE__ */
|
|
43598
|
+
actions: /* @__PURE__ */ jsx135(Button88, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
43340
43599
|
}
|
|
43341
43600
|
);
|
|
43342
43601
|
}
|
|
43343
|
-
if (preview === null || renders === null) return /* @__PURE__ */
|
|
43602
|
+
if (preview === null || renders === null) return /* @__PURE__ */ jsx135(Skeleton31, { className: cn80("h-64 w-full", className) });
|
|
43344
43603
|
const frozen = showing ? renders.find((r) => r.id === showing) ?? null : null;
|
|
43345
43604
|
const text2 = frozen ? frozen.body : preview;
|
|
43346
|
-
return /* @__PURE__ */
|
|
43347
|
-
/* @__PURE__ */
|
|
43348
|
-
/* @__PURE__ */
|
|
43349
|
-
|
|
43605
|
+
return /* @__PURE__ */ jsxs125("div", { className: cn80("flex flex-col gap-2", className), children: [
|
|
43606
|
+
/* @__PURE__ */ jsxs125("div", { className: "flex items-center gap-1 text-xs", children: [
|
|
43607
|
+
/* @__PURE__ */ jsx135(
|
|
43608
|
+
Button88,
|
|
43350
43609
|
{
|
|
43351
43610
|
size: "sm",
|
|
43352
43611
|
variant: frozen ? "ghost" : "secondary",
|
|
@@ -43355,8 +43614,8 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
|
|
|
43355
43614
|
children: "As it reads now"
|
|
43356
43615
|
}
|
|
43357
43616
|
),
|
|
43358
|
-
renders.map((render) => /* @__PURE__ */
|
|
43359
|
-
|
|
43617
|
+
renders.map((render) => /* @__PURE__ */ jsx135(
|
|
43618
|
+
Button88,
|
|
43360
43619
|
{
|
|
43361
43620
|
size: "sm",
|
|
43362
43621
|
variant: render.id === showing ? "secondary" : "ghost",
|
|
@@ -43367,33 +43626,33 @@ function DocRender({ templateId, recordId, filename, onRendered, className }) {
|
|
|
43367
43626
|
},
|
|
43368
43627
|
render.id
|
|
43369
43628
|
)),
|
|
43370
|
-
/* @__PURE__ */
|
|
43371
|
-
/* @__PURE__ */
|
|
43372
|
-
/* @__PURE__ */
|
|
43629
|
+
/* @__PURE__ */ jsxs125("span", { className: "ml-auto flex items-center gap-1", children: [
|
|
43630
|
+
/* @__PURE__ */ jsx135(Button88, { size: "sm", disabled: busy !== null, onClick: () => void freeze(), children: busy === "freeze" ? "Freezing\u2026" : "Freeze this version" }),
|
|
43631
|
+
/* @__PURE__ */ jsx135(Button88, { size: "sm", variant: "ghost", disabled: busy !== null, onClick: () => void toPdf(), children: busy === "pdf" ? "Making the PDF\u2026" : "PDF" })
|
|
43373
43632
|
] })
|
|
43374
43633
|
] }),
|
|
43375
|
-
/* @__PURE__ */
|
|
43376
|
-
/* @__PURE__ */
|
|
43634
|
+
/* @__PURE__ */ jsx135("div", { ref: paper, className: "whitespace-pre-wrap rounded border bg-background p-4 text-sm text-foreground", children: text2 }),
|
|
43635
|
+
/* @__PURE__ */ jsx135("p", { className: "text-xs text-muted-foreground", children: frozen ? `Frozen ${new Date(frozen.rendered_at).toLocaleString()} from template v${frozen.template_version}. Its text and its SHA-256 (${frozen.content_hash.slice(0, 12)}\u2026) are what any signature seals.` : "This is how the template reads against this record right now. Nothing has been frozen, so there is no version for anyone to sign yet." })
|
|
43377
43636
|
] });
|
|
43378
43637
|
}
|
|
43379
43638
|
|
|
43380
43639
|
// src/SignBlock.tsx
|
|
43381
|
-
import { useCallback as
|
|
43382
|
-
import { useFields as useFields27, useRecordsClient as
|
|
43383
|
-
import { BasicInput as BasicInput30, Button as
|
|
43384
|
-
import { useTable as
|
|
43385
|
-
import { jsx as
|
|
43640
|
+
import { useCallback as useCallback57, useEffect as useEffect99, useState as useState113 } from "react";
|
|
43641
|
+
import { useFields as useFields27, useRecordsClient as useRecordsClient82 } from "@ai-matrx/records/react";
|
|
43642
|
+
import { BasicInput as BasicInput30, Button as Button89, Skeleton as Skeleton32, cn as cn81 } from "@ai-matrx/design-system";
|
|
43643
|
+
import { useTable as useTable28 } from "@ai-matrx/records/react";
|
|
43644
|
+
import { jsx as jsx136, jsxs as jsxs126 } from "react/jsx-runtime";
|
|
43386
43645
|
function SignBlock({ tableId, recordId, render, className }) {
|
|
43387
|
-
const client =
|
|
43388
|
-
const table =
|
|
43646
|
+
const client = useRecordsClient82();
|
|
43647
|
+
const table = useTable28(tableId);
|
|
43389
43648
|
const rights = useTableRights(table.data);
|
|
43390
43649
|
const fields = useFields27(tableId);
|
|
43391
|
-
const [signatures, setSignatures] =
|
|
43392
|
-
const [verdicts, setVerdicts] =
|
|
43393
|
-
const [error, setError] =
|
|
43394
|
-
const [name, setName] =
|
|
43395
|
-
const [busy, setBusy] =
|
|
43396
|
-
const load =
|
|
43650
|
+
const [signatures, setSignatures] = useState113(null);
|
|
43651
|
+
const [verdicts, setVerdicts] = useState113({});
|
|
43652
|
+
const [error, setError] = useState113(null);
|
|
43653
|
+
const [name, setName] = useState113("");
|
|
43654
|
+
const [busy, setBusy] = useState113(false);
|
|
43655
|
+
const load = useCallback57(async () => {
|
|
43397
43656
|
const held = await client.docSignatures({ record_id: recordId });
|
|
43398
43657
|
if (!held.ok) {
|
|
43399
43658
|
setError(held.error);
|
|
@@ -43408,7 +43667,7 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43408
43667
|
}
|
|
43409
43668
|
setVerdicts(answers);
|
|
43410
43669
|
}, [client, recordId]);
|
|
43411
|
-
|
|
43670
|
+
useEffect99(() => {
|
|
43412
43671
|
void load();
|
|
43413
43672
|
}, [load]);
|
|
43414
43673
|
async function sign(field2) {
|
|
@@ -43428,36 +43687,36 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43428
43687
|
setName("");
|
|
43429
43688
|
await load();
|
|
43430
43689
|
}
|
|
43431
|
-
if (fields.error) return /* @__PURE__ */
|
|
43690
|
+
if (fields.error) return /* @__PURE__ */ jsx136(RefusalNotice, { error: fields.error, className });
|
|
43432
43691
|
if (error) {
|
|
43433
|
-
return /* @__PURE__ */
|
|
43692
|
+
return /* @__PURE__ */ jsx136(
|
|
43434
43693
|
RefusalNotice,
|
|
43435
43694
|
{
|
|
43436
43695
|
error,
|
|
43437
43696
|
className,
|
|
43438
|
-
actions: /* @__PURE__ */
|
|
43697
|
+
actions: /* @__PURE__ */ jsx136(Button89, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
43439
43698
|
}
|
|
43440
43699
|
);
|
|
43441
43700
|
}
|
|
43442
|
-
if (fields.loading || signatures === null) return /* @__PURE__ */
|
|
43701
|
+
if (fields.loading || signatures === null) return /* @__PURE__ */ jsx136(Skeleton32, { className: cn81("h-32 w-full", className) });
|
|
43443
43702
|
const signable = (fields.data ?? []).filter(isSignatureField);
|
|
43444
|
-
return /* @__PURE__ */
|
|
43445
|
-
/* @__PURE__ */
|
|
43446
|
-
/* @__PURE__ */
|
|
43447
|
-
/* @__PURE__ */
|
|
43703
|
+
return /* @__PURE__ */ jsxs126("div", { className: cn81("flex flex-col gap-2", className), children: [
|
|
43704
|
+
/* @__PURE__ */ jsxs126("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
43705
|
+
/* @__PURE__ */ jsx136("span", { className: "font-medium text-foreground", children: "Signatures" }),
|
|
43706
|
+
/* @__PURE__ */ jsxs126("span", { children: [
|
|
43448
43707
|
signatures.length,
|
|
43449
43708
|
" on this record"
|
|
43450
43709
|
] })
|
|
43451
43710
|
] }),
|
|
43452
|
-
signatures.length > 0 ? /* @__PURE__ */
|
|
43711
|
+
signatures.length > 0 ? /* @__PURE__ */ jsx136("ul", { className: "flex flex-col gap-1", children: signatures.map((signature) => {
|
|
43453
43712
|
const verdict = verdicts[signature.id];
|
|
43454
43713
|
const intact = typeof verdict === "object" && verdict !== null ? verdict.intact : void 0;
|
|
43455
|
-
return /* @__PURE__ */
|
|
43456
|
-
/* @__PURE__ */
|
|
43457
|
-
/* @__PURE__ */
|
|
43458
|
-
/* @__PURE__ */
|
|
43459
|
-
/* @__PURE__ */
|
|
43460
|
-
/* @__PURE__ */
|
|
43714
|
+
return /* @__PURE__ */ jsxs126("li", { className: "rounded border px-2 py-1.5 text-xs", children: [
|
|
43715
|
+
/* @__PURE__ */ jsxs126("div", { className: "flex items-center gap-2", children: [
|
|
43716
|
+
/* @__PURE__ */ jsx136("span", { className: "font-medium", children: signature.signer_name }),
|
|
43717
|
+
/* @__PURE__ */ jsx136("span", { className: "text-muted-foreground", children: signature.field_key }),
|
|
43718
|
+
/* @__PURE__ */ jsx136("span", { className: "text-muted-foreground", children: new Date(signature.signed_at).toLocaleString() }),
|
|
43719
|
+
/* @__PURE__ */ jsxs126("span", { className: "ml-auto font-mono text-[10px] text-muted-foreground", children: [
|
|
43461
43720
|
"v",
|
|
43462
43721
|
signature.document_version,
|
|
43463
43722
|
" \xB7 ",
|
|
@@ -43467,7 +43726,7 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43467
43726
|
intact === false ? (
|
|
43468
43727
|
// THE STORE REFUSED TO CALL IT INTACT: its reason goes through the one
|
|
43469
43728
|
// formatter, with what to do next, never raw in red (lane REFUSAL-SWEEP).
|
|
43470
|
-
/* @__PURE__ */
|
|
43729
|
+
/* @__PURE__ */ jsx136(
|
|
43471
43730
|
RefusalLine,
|
|
43472
43731
|
{
|
|
43473
43732
|
className: "mt-0.5",
|
|
@@ -43478,22 +43737,22 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43478
43737
|
)
|
|
43479
43738
|
}
|
|
43480
43739
|
)
|
|
43481
|
-
) : /* @__PURE__ */
|
|
43740
|
+
) : /* @__PURE__ */ jsx136("p", { className: "mt-0.5 text-muted-foreground", children: intact === true ? "The store says what was signed is still exactly what is there." : String(verdict ?? "") })
|
|
43482
43741
|
] }, signature.id);
|
|
43483
43742
|
}) }) : null,
|
|
43484
|
-
!render ? /* @__PURE__ */
|
|
43743
|
+
!render ? /* @__PURE__ */ jsx136("p", { className: "text-xs text-muted-foreground", children: 'Nothing has been frozen yet, so there is no version to sign. A signature seals one exact document version and its hash \u2014 "the document as it reads today" is not something anyone can agree to.' }) : signable.length === 0 ? /* @__PURE__ */ jsxs126("p", { className: "text-xs text-muted-foreground", children: [
|
|
43485
43744
|
"This table has no signature Field, so there is nowhere to write a signature. A signature is a Value on a text Field whose format is ",
|
|
43486
|
-
/* @__PURE__ */
|
|
43745
|
+
/* @__PURE__ */ jsx136("code", { children: "signature" }),
|
|
43487
43746
|
" \u2014 add one and the signing appears."
|
|
43488
|
-
] }) : !rights.write ? /* @__PURE__ */
|
|
43747
|
+
] }) : !rights.write ? /* @__PURE__ */ jsx136("p", { className: "text-xs text-muted-foreground", children: rights.why("write") }) : /* @__PURE__ */ jsx136("div", { className: "flex flex-col gap-1", children: signable.map((field2) => {
|
|
43489
43748
|
const already = signatures.find((s) => s.field_key === field2.key);
|
|
43490
43749
|
if (already) {
|
|
43491
|
-
return /* @__PURE__ */
|
|
43750
|
+
return /* @__PURE__ */ jsxs126("p", { className: "text-xs text-muted-foreground", children: [
|
|
43492
43751
|
fieldName(field2),
|
|
43493
43752
|
" is signed, and a signature is immutable once made. A further agreement is a further Field with its own signature, or a further version with its own seal."
|
|
43494
43753
|
] }, field2.id);
|
|
43495
43754
|
}
|
|
43496
|
-
return /* @__PURE__ */
|
|
43755
|
+
return /* @__PURE__ */ jsxs126(
|
|
43497
43756
|
"form",
|
|
43498
43757
|
{
|
|
43499
43758
|
className: "flex items-center gap-1",
|
|
@@ -43502,7 +43761,7 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43502
43761
|
if (name.trim() !== "") void sign(field2);
|
|
43503
43762
|
},
|
|
43504
43763
|
children: [
|
|
43505
|
-
/* @__PURE__ */
|
|
43764
|
+
/* @__PURE__ */ jsx136(
|
|
43506
43765
|
BasicInput30,
|
|
43507
43766
|
{
|
|
43508
43767
|
className: "h-8 w-56 text-xs",
|
|
@@ -43512,8 +43771,8 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43512
43771
|
onChange: (e) => setName(e.target.value)
|
|
43513
43772
|
}
|
|
43514
43773
|
),
|
|
43515
|
-
/* @__PURE__ */
|
|
43516
|
-
/* @__PURE__ */
|
|
43774
|
+
/* @__PURE__ */ jsx136(Button89, { size: "sm", type: "submit", disabled: busy || name.trim() === "", children: "Sign" }),
|
|
43775
|
+
/* @__PURE__ */ jsxs126("span", { className: "text-xs text-muted-foreground", children: [
|
|
43517
43776
|
"seals version ",
|
|
43518
43777
|
render.template_version,
|
|
43519
43778
|
" \xB7 ",
|
|
@@ -43528,6 +43787,261 @@ function SignBlock({ tableId, recordId, render, className }) {
|
|
|
43528
43787
|
] });
|
|
43529
43788
|
}
|
|
43530
43789
|
|
|
43790
|
+
// src/NotifyRuleEditor.tsx
|
|
43791
|
+
import { useCallback as useCallback58, useEffect as useEffect100, useState as useState114 } from "react";
|
|
43792
|
+
import { useRecordsClient as useRecordsClient83, useTable as useTable29 } from "@ai-matrx/records/react";
|
|
43793
|
+
import { hasQuietHours as hasQuietHours3 } from "@ai-matrx/records";
|
|
43794
|
+
import { Button as Button90, Skeleton as Skeleton33, cn as cn82 } from "@ai-matrx/design-system";
|
|
43795
|
+
import { jsx as jsx137, jsxs as jsxs127 } from "react/jsx-runtime";
|
|
43796
|
+
var CADENCE_WORDS2 = SUBSCRIPTION_CADENCE_LABEL;
|
|
43797
|
+
var CHANNEL_WORDS3 = SUBSCRIPTION_CHANNEL_LABEL;
|
|
43798
|
+
function NotifyRuleEditor({ tableId, seed, chosenRuleId, onChoose, className }) {
|
|
43799
|
+
const client = useRecordsClient83();
|
|
43800
|
+
const host = useRecordsUi();
|
|
43801
|
+
const table = useTable29(tableId);
|
|
43802
|
+
const rights = useTableRights(table.data);
|
|
43803
|
+
const [subscriptions, setSubscriptions] = useState114(null);
|
|
43804
|
+
const [cadences, setCadences] = useState114([]);
|
|
43805
|
+
const [views, setViews] = useState114(null);
|
|
43806
|
+
const [error, setError] = useState114(null);
|
|
43807
|
+
const [busy, setBusy] = useState114(false);
|
|
43808
|
+
const [stopping, setStopping] = useState114(null);
|
|
43809
|
+
const load = useCallback58(async () => {
|
|
43810
|
+
const [held, offered] = await Promise.all([
|
|
43811
|
+
// THE PERSON'S OWN DOOR, not the notifier's. It answers what is addressed
|
|
43812
|
+
// to them plus — only where they hold admin on this Table — anyone's over
|
|
43813
|
+
// it, and it is narrowed to Tables they can already open.
|
|
43814
|
+
client.subscriptions({ table_id: tableId }),
|
|
43815
|
+
// The cadences are ASKED of the store, never a list typed into a picker
|
|
43816
|
+
// here that could drift from what the digest runner understands.
|
|
43817
|
+
client.subscriptionCadences()
|
|
43818
|
+
]);
|
|
43819
|
+
if (!held.ok) {
|
|
43820
|
+
setError(held.error);
|
|
43821
|
+
return;
|
|
43822
|
+
}
|
|
43823
|
+
setError(null);
|
|
43824
|
+
setSubscriptions(held.data);
|
|
43825
|
+
if (offered.ok) setCadences(offered.data);
|
|
43826
|
+
if (host.savedViews) {
|
|
43827
|
+
setViews(await host.savedViews());
|
|
43828
|
+
return;
|
|
43829
|
+
}
|
|
43830
|
+
const listed = await client.views({ table_id: tableId });
|
|
43831
|
+
if (!listed.ok) {
|
|
43832
|
+
setError(listed.error);
|
|
43833
|
+
return;
|
|
43834
|
+
}
|
|
43835
|
+
setViews(listed.data.map((v) => ({ id: v.view_id, name: v.name })));
|
|
43836
|
+
}, [client, host, tableId]);
|
|
43837
|
+
useEffect100(() => {
|
|
43838
|
+
void load();
|
|
43839
|
+
}, [load]);
|
|
43840
|
+
const write = useCallback58(
|
|
43841
|
+
async (spec) => {
|
|
43842
|
+
const declared2 = await client.subscriptionDeclare({
|
|
43843
|
+
table_id: tableId,
|
|
43844
|
+
spec: {
|
|
43845
|
+
name: spec.name,
|
|
43846
|
+
saved_view_id: spec.savedViewId,
|
|
43847
|
+
cadence: spec.cadence ?? "instant",
|
|
43848
|
+
schedule: spec.schedule ?? null,
|
|
43849
|
+
channel: spec.channel ?? "in_app",
|
|
43850
|
+
quiet_hours: spec.quietHours ?? null,
|
|
43851
|
+
// "Tell me" is the default, and the store refuses a subscription with
|
|
43852
|
+
// nobody to tell rather than writing one that fires at nobody.
|
|
43853
|
+
recipient_user_id: spec.recipientUserId ?? client.config.actor.user_id ?? null,
|
|
43854
|
+
event_key: spec.eventKey ?? "records.changed"
|
|
43855
|
+
}
|
|
43856
|
+
});
|
|
43857
|
+
if (!declared2.ok) {
|
|
43858
|
+
setError(declared2.error);
|
|
43859
|
+
return null;
|
|
43860
|
+
}
|
|
43861
|
+
return declared2.data;
|
|
43862
|
+
},
|
|
43863
|
+
[client, tableId]
|
|
43864
|
+
);
|
|
43865
|
+
useEffect100(() => {
|
|
43866
|
+
if (!subscriptions || !seed || seed.length === 0) return;
|
|
43867
|
+
const missing = seed.filter((s) => !subscriptions.some((held) => held.name === s.name));
|
|
43868
|
+
if (missing.length === 0) return;
|
|
43869
|
+
void (async () => {
|
|
43870
|
+
for (const spec of missing) {
|
|
43871
|
+
if (!await write(spec)) return;
|
|
43872
|
+
}
|
|
43873
|
+
await load();
|
|
43874
|
+
})();
|
|
43875
|
+
}, [subscriptions, seed, write, load]);
|
|
43876
|
+
async function subscribe(viewId, viewName, cadence, channel, schedule, quiet2) {
|
|
43877
|
+
setBusy(true);
|
|
43878
|
+
const ok = await write({
|
|
43879
|
+
name: `${viewName} \u2014 ${CADENCE_WORDS2[cadence] ?? cadence}`,
|
|
43880
|
+
savedViewId: viewId,
|
|
43881
|
+
cadence,
|
|
43882
|
+
channel,
|
|
43883
|
+
schedule: schedule.trim() === "" ? null : schedule.trim(),
|
|
43884
|
+
quietHours: quiet2
|
|
43885
|
+
});
|
|
43886
|
+
setBusy(false);
|
|
43887
|
+
if (!ok) return;
|
|
43888
|
+
onChoose?.(ok);
|
|
43889
|
+
await load();
|
|
43890
|
+
}
|
|
43891
|
+
async function makeEveryRecordView() {
|
|
43892
|
+
setBusy(true);
|
|
43893
|
+
const made = await client.viewDeclare({
|
|
43894
|
+
table_id: tableId,
|
|
43895
|
+
spec: { name: `Every ${table.data?.name ?? "record"}`, filters: null }
|
|
43896
|
+
});
|
|
43897
|
+
setBusy(false);
|
|
43898
|
+
if (!made.ok) {
|
|
43899
|
+
setError(made.error);
|
|
43900
|
+
return;
|
|
43901
|
+
}
|
|
43902
|
+
await load();
|
|
43903
|
+
}
|
|
43904
|
+
async function unsubscribe(ruleId) {
|
|
43905
|
+
setStopping(null);
|
|
43906
|
+
setBusy(true);
|
|
43907
|
+
const deleted = await client.recordDelete({ record_id: ruleId });
|
|
43908
|
+
setBusy(false);
|
|
43909
|
+
if (!deleted.ok) {
|
|
43910
|
+
setError(deleted.error);
|
|
43911
|
+
return;
|
|
43912
|
+
}
|
|
43913
|
+
await load();
|
|
43914
|
+
}
|
|
43915
|
+
if (error) {
|
|
43916
|
+
return /* @__PURE__ */ jsx137(
|
|
43917
|
+
RefusalNotice,
|
|
43918
|
+
{
|
|
43919
|
+
error,
|
|
43920
|
+
className,
|
|
43921
|
+
actions: /* @__PURE__ */ jsx137(Button90, { size: "sm", variant: "ghost", onClick: () => void load(), children: "Try again" })
|
|
43922
|
+
}
|
|
43923
|
+
);
|
|
43924
|
+
}
|
|
43925
|
+
if (subscriptions === null) return /* @__PURE__ */ jsx137(Skeleton33, { className: cn82("h-40 w-full", className) });
|
|
43926
|
+
return /* @__PURE__ */ jsxs127("div", { className: cn82("flex flex-col gap-2", className), children: [
|
|
43927
|
+
/* @__PURE__ */ jsxs127("div", { className: "flex items-center gap-2 text-xs text-muted-foreground", children: [
|
|
43928
|
+
/* @__PURE__ */ jsx137("span", { className: "font-medium text-foreground", children: "Tell me when" }),
|
|
43929
|
+
/* @__PURE__ */ jsxs127("span", { children: [
|
|
43930
|
+
subscriptions.length,
|
|
43931
|
+
" subscription",
|
|
43932
|
+
subscriptions.length === 1 ? "" : "s"
|
|
43933
|
+
] })
|
|
43934
|
+
] }),
|
|
43935
|
+
subscriptions.length === 0 ? /* @__PURE__ */ jsx137("p", { className: "text-xs text-muted-foreground", children: "Nothing is telling anyone about this yet. A subscription is a Rule over a saved view, so what you are told about is exactly what that view holds." }) : /* @__PURE__ */ jsx137("ul", { className: "flex flex-col gap-1", children: subscriptions.map((held) => /* @__PURE__ */ jsxs127("li", { className: "flex items-center gap-2 rounded border px-2 py-1.5 text-xs", children: [
|
|
43936
|
+
/* @__PURE__ */ jsx137("span", { className: "truncate font-medium", children: held.name }),
|
|
43937
|
+
/* @__PURE__ */ jsx137("span", { className: "text-muted-foreground", children: CADENCE_WORDS2[held.cadence] ?? held.cadence }),
|
|
43938
|
+
/* @__PURE__ */ jsx137("span", { className: "text-muted-foreground", children: CHANNEL_WORDS3[held.channel] ?? held.channel }),
|
|
43939
|
+
held.schedule ? /* @__PURE__ */ jsx137("span", { className: "text-muted-foreground", children: scheduleWords(held.schedule, held.quiet_hours?.tz) }) : null,
|
|
43940
|
+
held.next_digest_at ? /* @__PURE__ */ jsxs127("span", { className: "text-muted-foreground", children: [
|
|
43941
|
+
"next ",
|
|
43942
|
+
new Date(held.next_digest_at).toLocaleString()
|
|
43943
|
+
] }) : null,
|
|
43944
|
+
hasQuietHours3(held.quiet_hours) ? /* @__PURE__ */ jsxs127("span", { className: "text-muted-foreground", children: [
|
|
43945
|
+
"quiet ",
|
|
43946
|
+
held.quiet_hours.start,
|
|
43947
|
+
"\u2013",
|
|
43948
|
+
held.quiet_hours.end
|
|
43949
|
+
] }) : null,
|
|
43950
|
+
held.recipient_user_id === null ? /* @__PURE__ */ jsx137("span", { className: "text-destructive", children: "This one has nobody to tell, so it fires at nobody." }) : null,
|
|
43951
|
+
held.saved_view_id === null ? /* @__PURE__ */ jsx137("span", { className: "text-destructive", children: "This one names no view, so the store never admits a record to it." }) : null,
|
|
43952
|
+
held.muted ? /* @__PURE__ */ jsx137("span", { className: "text-muted-foreground", children: "Switched off \u2014 it tells nobody until somebody switches it back on." }) : null,
|
|
43953
|
+
onChoose ? held.rule_id === chosenRuleId ? /* @__PURE__ */ jsxs127("span", { className: "ml-auto flex items-center gap-1", children: [
|
|
43954
|
+
/* @__PURE__ */ jsx137("span", { className: "font-medium text-foreground", children: "Used for this form" }),
|
|
43955
|
+
/* @__PURE__ */ jsx137(Button90, { size: "sm", variant: "ghost", disabled: busy, onClick: () => onChoose(null), children: "Stop using" })
|
|
43956
|
+
] }) : /* @__PURE__ */ jsx137(
|
|
43957
|
+
Button90,
|
|
43958
|
+
{
|
|
43959
|
+
size: "sm",
|
|
43960
|
+
variant: "outline",
|
|
43961
|
+
className: "ml-auto",
|
|
43962
|
+
disabled: busy,
|
|
43963
|
+
onClick: () => onChoose(held.rule_id),
|
|
43964
|
+
children: "Use for this form"
|
|
43965
|
+
}
|
|
43966
|
+
) : null,
|
|
43967
|
+
rights.write && !(onChoose && held.rule_id === chosenRuleId) && stopping === held.rule_id ? /* @__PURE__ */ jsxs127("span", { className: cn82("flex flex-wrap items-center gap-2", onChoose ? void 0 : "ml-auto"), children: [
|
|
43968
|
+
/* @__PURE__ */ jsx137("span", { className: "text-xs text-destructive", children: archiveConfirmSentence("this subscription") }),
|
|
43969
|
+
/* @__PURE__ */ jsx137(Button90, { size: "sm", variant: "destructive", disabled: busy, onClick: () => void unsubscribe(held.rule_id), children: "Stop telling me" }),
|
|
43970
|
+
/* @__PURE__ */ jsx137(Button90, { size: "sm", variant: "ghost", onClick: () => setStopping(null), children: "Keep it" })
|
|
43971
|
+
] }) : rights.write && !(onChoose && held.rule_id === chosenRuleId) ? /* @__PURE__ */ jsx137(
|
|
43972
|
+
Button90,
|
|
43973
|
+
{
|
|
43974
|
+
size: "sm",
|
|
43975
|
+
variant: "ghost",
|
|
43976
|
+
className: onChoose ? void 0 : "ml-auto",
|
|
43977
|
+
disabled: busy,
|
|
43978
|
+
onClick: () => setStopping(held.rule_id),
|
|
43979
|
+
children: "Stop telling me"
|
|
43980
|
+
}
|
|
43981
|
+
) : null
|
|
43982
|
+
] }, held.rule_id)) }),
|
|
43983
|
+
onChoose && chosenRuleId && !subscriptions.some((held) => held.rule_id === chosenRuleId) ? /* @__PURE__ */ jsx137("p", { className: "text-xs text-destructive", children: "This form points at a subscription that is not in this list any more, so nobody is told when it is answered. Choose one below, or make one." }) : null,
|
|
43984
|
+
views === null ? /* @__PURE__ */ jsx137("p", { className: "rounded border border-dashed px-2 py-1 text-xs text-muted-foreground", children: NO_SAVED_VIEWS_REASON }) : views.length === 0 ? onChoose && rights.write ? /* @__PURE__ */ jsxs127("div", { className: "flex flex-wrap items-center gap-2 text-xs text-muted-foreground", children: [
|
|
43985
|
+
/* @__PURE__ */ jsx137("span", { children: "A subscription watches a saved view, and this table has none yet." }),
|
|
43986
|
+
/* @__PURE__ */ jsx137(Button90, { size: "sm", variant: "outline", disabled: busy, onClick: () => void makeEveryRecordView(), children: "Watch every record" })
|
|
43987
|
+
] }) : /* @__PURE__ */ jsx137("p", { className: "text-xs text-muted-foreground", children: "This organization has no saved views yet, so there is nothing to subscribe to. Save a view first." }) : rights.write ? /* @__PURE__ */ jsxs127(
|
|
43988
|
+
"form",
|
|
43989
|
+
{
|
|
43990
|
+
className: "flex flex-wrap items-center gap-1",
|
|
43991
|
+
onSubmit: (event) => {
|
|
43992
|
+
event.preventDefault();
|
|
43993
|
+
const form = event.currentTarget;
|
|
43994
|
+
const view = form.elements.namedItem("view").value;
|
|
43995
|
+
const cadence = form.elements.namedItem("cadence").value;
|
|
43996
|
+
const channel = form.elements.namedItem("channel").value;
|
|
43997
|
+
const schedule = form.elements.namedItem("schedule").value;
|
|
43998
|
+
const start = form.elements.namedItem("quiet_start").value;
|
|
43999
|
+
const end = form.elements.namedItem("quiet_end").value;
|
|
44000
|
+
const name = views.find((v) => v.id === view)?.name ?? "View";
|
|
44001
|
+
const quiet2 = start && end ? { start, end, tz: Intl.DateTimeFormat().resolvedOptions().timeZone } : null;
|
|
44002
|
+
if (view) void subscribe(view, name, cadence, channel, schedule, quiet2);
|
|
44003
|
+
},
|
|
44004
|
+
children: [
|
|
44005
|
+
/* @__PURE__ */ jsx137("select", { name: "view", "aria-label": "View to be told about", className: "h-7 rounded border bg-background px-1 text-xs", children: views.map((view) => /* @__PURE__ */ jsx137("option", { value: view.id, children: view.name }, view.id)) }),
|
|
44006
|
+
/* @__PURE__ */ jsx137("select", { name: "cadence", "aria-label": "How often", className: "h-7 rounded border bg-background px-1 text-xs", children: (cadences.length > 0 ? cadences : ["instant"]).map((cadence) => /* @__PURE__ */ jsx137("option", { value: cadence, children: CADENCE_WORDS2[cadence] ?? cadence }, cadence)) }),
|
|
44007
|
+
/* @__PURE__ */ jsx137("select", { name: "channel", "aria-label": "Where it arrives", className: "h-7 rounded border bg-background px-1 text-xs", children: ["in_app", "email", "sms"].map((channel) => /* @__PURE__ */ jsx137("option", { value: channel, children: CHANNEL_WORDS3[channel] }, channel)) }),
|
|
44008
|
+
/* @__PURE__ */ jsx137(
|
|
44009
|
+
"input",
|
|
44010
|
+
{
|
|
44011
|
+
name: "schedule",
|
|
44012
|
+
"aria-label": "When the summary goes out",
|
|
44013
|
+
placeholder: "monday 08:00",
|
|
44014
|
+
className: "h-7 w-28 rounded border bg-background px-1 text-xs"
|
|
44015
|
+
}
|
|
44016
|
+
),
|
|
44017
|
+
/* @__PURE__ */ jsx137("span", { className: "text-xs text-muted-foreground", children: "not between" }),
|
|
44018
|
+
/* @__PURE__ */ jsx137(
|
|
44019
|
+
"input",
|
|
44020
|
+
{
|
|
44021
|
+
name: "quiet_start",
|
|
44022
|
+
type: "time",
|
|
44023
|
+
"aria-label": "Quiet hours start",
|
|
44024
|
+
className: "h-7 rounded border bg-background px-1 text-xs"
|
|
44025
|
+
}
|
|
44026
|
+
),
|
|
44027
|
+
/* @__PURE__ */ jsx137("span", { className: "text-xs text-muted-foreground", children: "and" }),
|
|
44028
|
+
/* @__PURE__ */ jsx137(
|
|
44029
|
+
"input",
|
|
44030
|
+
{
|
|
44031
|
+
name: "quiet_end",
|
|
44032
|
+
type: "time",
|
|
44033
|
+
"aria-label": "Quiet hours end",
|
|
44034
|
+
className: "h-7 rounded border bg-background px-1 text-xs"
|
|
44035
|
+
}
|
|
44036
|
+
),
|
|
44037
|
+
/* @__PURE__ */ jsx137(Button90, { size: "sm", type: "submit", disabled: busy, children: "Tell me" }),
|
|
44038
|
+
/* @__PURE__ */ jsx137("p", { className: "w-full text-xs text-muted-foreground", children: "A send inside your quiet hours is held until they end \u2014 it is never dropped. Leave the schedule blank for the store's own default of 08:00." })
|
|
44039
|
+
]
|
|
44040
|
+
}
|
|
44041
|
+
) : /* @__PURE__ */ jsx137("p", { className: "text-xs text-muted-foreground", children: rights.why("write") })
|
|
44042
|
+
] });
|
|
44043
|
+
}
|
|
44044
|
+
|
|
43531
44045
|
// src/DashboardCanvas.tsx
|
|
43532
44046
|
import { useCallback as useCallback59, useEffect as useEffect102, useMemo as useMemo71, useState as useState117 } from "react";
|
|
43533
44047
|
import { useFields as useFields30, useRecordsClient as useRecordsClient85, useTable as useTable31 } from "@ai-matrx/records/react";
|
|
@@ -43548,20 +44062,20 @@ import {
|
|
|
43548
44062
|
|
|
43549
44063
|
// src/DashboardGrid.tsx
|
|
43550
44064
|
import {
|
|
43551
|
-
DndContext,
|
|
43552
|
-
KeyboardSensor,
|
|
43553
|
-
PointerSensor,
|
|
44065
|
+
DndContext as DndContext2,
|
|
44066
|
+
KeyboardSensor as KeyboardSensor2,
|
|
44067
|
+
PointerSensor as PointerSensor2,
|
|
43554
44068
|
pointerWithin,
|
|
43555
|
-
useSensor,
|
|
43556
|
-
useSensors
|
|
44069
|
+
useSensor as useSensor2,
|
|
44070
|
+
useSensors as useSensors2
|
|
43557
44071
|
} from "@dnd-kit/core";
|
|
43558
44072
|
import {
|
|
43559
|
-
SortableContext,
|
|
44073
|
+
SortableContext as SortableContext2,
|
|
43560
44074
|
rectSortingStrategy,
|
|
43561
|
-
sortableKeyboardCoordinates,
|
|
43562
|
-
useSortable
|
|
44075
|
+
sortableKeyboardCoordinates as sortableKeyboardCoordinates2,
|
|
44076
|
+
useSortable as useSortable2
|
|
43563
44077
|
} from "@dnd-kit/sortable";
|
|
43564
|
-
import { CSS } from "@dnd-kit/utilities";
|
|
44078
|
+
import { CSS as CSS3 } from "@dnd-kit/utilities";
|
|
43565
44079
|
import { cn as cn83 } from "@ai-matrx/design-system";
|
|
43566
44080
|
|
|
43567
44081
|
// src/dashboardLayout.ts
|
|
@@ -43625,9 +44139,9 @@ var dashboardCollision = (args) => {
|
|
|
43625
44139
|
}).sort((a, b) => a.data.value - b.data.value);
|
|
43626
44140
|
};
|
|
43627
44141
|
function DashboardGrid({ items, arranging, onMove, onSpan }) {
|
|
43628
|
-
const sensors =
|
|
43629
|
-
|
|
43630
|
-
|
|
44142
|
+
const sensors = useSensors2(
|
|
44143
|
+
useSensor2(PointerSensor2, { activationConstraint: { distance: 4 } }),
|
|
44144
|
+
useSensor2(KeyboardSensor2, { coordinateGetter: sortableKeyboardCoordinates2 })
|
|
43631
44145
|
);
|
|
43632
44146
|
const ids = items.map((item) => item.id);
|
|
43633
44147
|
function dragEnd(event) {
|
|
@@ -43637,7 +44151,7 @@ function DashboardGrid({ items, arranging, onMove, onSpan }) {
|
|
|
43637
44151
|
}
|
|
43638
44152
|
const grid = /* @__PURE__ */ jsx138("div", { className: "grid grid-cols-1 gap-2 sm:grid-cols-12", children: items.map((item, index) => /* @__PURE__ */ jsx138(GridCell2, { item, index, arranging, onSpan }, item.id)) });
|
|
43639
44153
|
if (!arranging) return grid;
|
|
43640
|
-
return /* @__PURE__ */ jsx138(
|
|
44154
|
+
return /* @__PURE__ */ jsx138(DndContext2, { sensors, collisionDetection: dashboardCollision, onDragEnd: dragEnd, children: /* @__PURE__ */ jsx138(SortableContext2, { items: ids, strategy: rectSortingStrategy, children: grid }) });
|
|
43641
44155
|
}
|
|
43642
44156
|
function GridCell2({
|
|
43643
44157
|
item,
|
|
@@ -43645,8 +44159,8 @@ function GridCell2({
|
|
|
43645
44159
|
arranging,
|
|
43646
44160
|
onSpan
|
|
43647
44161
|
}) {
|
|
43648
|
-
const sortable =
|
|
43649
|
-
const style = arranging ? { transform:
|
|
44162
|
+
const sortable = useSortable2({ id: item.id, disabled: !arranging });
|
|
44163
|
+
const style = arranging ? { transform: CSS3.Transform.toString(sortable.transform), transition: sortable.transition } : void 0;
|
|
43650
44164
|
return /* @__PURE__ */ jsxs128(
|
|
43651
44165
|
"div",
|
|
43652
44166
|
{
|
|
@@ -44852,7 +45366,7 @@ function TargetControl({
|
|
|
44852
45366
|
// src/QuestionPanel.tsx
|
|
44853
45367
|
import { copyText as copyText8 } from "@ai-matrx/kit/clipboard";
|
|
44854
45368
|
import { useEffect as useEffect103, useMemo as useMemo72, useRef as useRef59, useState as useState118 } from "react";
|
|
44855
|
-
import { Badge as Badge23, BasicInput as BasicInput32, Button as Button94, Checkbox as Checkbox16, Label as
|
|
45369
|
+
import { Badge as Badge23, BasicInput as BasicInput32, Button as Button94, Checkbox as Checkbox16, Label as Label25, Skeleton as Skeleton35, cn as cn85 } from "@ai-matrx/design-system";
|
|
44856
45370
|
import { useFields as useFields31, useRecordsClient as useRecordsClient86 } from "@ai-matrx/records/react";
|
|
44857
45371
|
|
|
44858
45372
|
// src/question.ts
|
|
@@ -45434,9 +45948,9 @@ function QuestionPanel({ tableId, activeQuestionId, draft, className }) {
|
|
|
45434
45948
|
linkMissed ? /* @__PURE__ */ jsx143("p", { className: "text-muted-foreground", children: "That question is not here or not shared with you." }) : null,
|
|
45435
45949
|
saveError ? /* @__PURE__ */ jsx143(RefusalNotice, { error: saveError }) : null,
|
|
45436
45950
|
/* @__PURE__ */ jsxs132("div", { className: "grid grid-cols-[6.5rem_minmax(0,1fr)] items-start gap-x-3 gap-y-2", "data-testid": "question-builder", children: [
|
|
45437
|
-
/* @__PURE__ */ jsx143(
|
|
45951
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "Measure" }),
|
|
45438
45952
|
/* @__PURE__ */ jsx143(Measures, { measures: spec.measures, fields: offered, onChange: (measures) => patch({ measures }) }),
|
|
45439
|
-
/* @__PURE__ */ jsx143(
|
|
45953
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "Filter" }),
|
|
45440
45954
|
/* @__PURE__ */ jsxs132("div", { className: "flex flex-col gap-1.5", children: [
|
|
45441
45955
|
/* @__PURE__ */ jsx143(
|
|
45442
45956
|
ConditionGroup,
|
|
@@ -45450,9 +45964,9 @@ function QuestionPanel({ tableId, activeQuestionId, draft, className }) {
|
|
|
45450
45964
|
),
|
|
45451
45965
|
/* @__PURE__ */ jsx143(Through, { conditions: spec.through, related, onChange: (through) => patch({ through }) })
|
|
45452
45966
|
] }),
|
|
45453
|
-
/* @__PURE__ */ jsx143(
|
|
45967
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "Summarize by" }),
|
|
45454
45968
|
/* @__PURE__ */ jsx143(GroupPicker, { spec, fields: offered, related, onChange: (group) => patch({ group }) }),
|
|
45455
|
-
/* @__PURE__ */ jsx143(
|
|
45969
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "As of" }),
|
|
45456
45970
|
/* @__PURE__ */ jsxs132("div", { className: "flex items-center gap-1.5", children: [
|
|
45457
45971
|
/* @__PURE__ */ jsx143(
|
|
45458
45972
|
Checkbox16,
|
|
@@ -45476,7 +45990,7 @@ function QuestionPanel({ tableId, activeQuestionId, draft, className }) {
|
|
|
45476
45990
|
) : /* @__PURE__ */ jsx143("span", { className: "text-muted-foreground", children: "Now" })
|
|
45477
45991
|
] }),
|
|
45478
45992
|
spec.group ? /* @__PURE__ */ jsxs132(Fragment57, { children: [
|
|
45479
|
-
/* @__PURE__ */ jsx143(
|
|
45993
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "Sort" }),
|
|
45480
45994
|
/* @__PURE__ */ jsxs132("div", { className: "flex flex-wrap items-center gap-1.5", children: [
|
|
45481
45995
|
/* @__PURE__ */ jsx143(
|
|
45482
45996
|
PickOne,
|
|
@@ -45507,7 +46021,7 @@ function QuestionPanel({ tableId, activeQuestionId, draft, className }) {
|
|
|
45507
46021
|
}
|
|
45508
46022
|
)
|
|
45509
46023
|
] }),
|
|
45510
|
-
/* @__PURE__ */ jsx143(
|
|
46024
|
+
/* @__PURE__ */ jsx143(Label25, { className: "pt-1.5 text-xs text-muted-foreground", children: "Show as" }),
|
|
45511
46025
|
/* @__PURE__ */ jsx143("div", { className: "flex flex-wrap gap-1", role: "radiogroup", "aria-label": "Show as", children: SHOWS.map((s) => /* @__PURE__ */ jsx143(
|
|
45512
46026
|
Button94,
|
|
45513
46027
|
{
|