strapi-plugin-hubspot 0.15.0 → 0.16.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.
Files changed (37) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +28 -13
  3. package/dist/_chunks/{Forms-ClyI4MJ1.js → Forms-D0QXkYAN.js} +216 -112
  4. package/dist/_chunks/{Forms-B7eIm0Fi.mjs → Forms-DEoZGrEt.mjs} +218 -114
  5. package/dist/_chunks/{HubspotFormInput-Do7F83V6.js → HubspotFormInput-4ZMYXWM_.js} +1 -1
  6. package/dist/_chunks/{HubspotFormInput-cZAiaAaq.mjs → HubspotFormInput-CPep55GL.mjs} +1 -1
  7. package/dist/_chunks/{HubspotObjectInput-DWqk2C26.js → HubspotObjectInput-BTqYXJIa.js} +3 -3
  8. package/dist/_chunks/{HubspotObjectInput-ZZk_7hWq.mjs → HubspotObjectInput-CQszIx1W.mjs} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-v9B3wb1a.js → HubspotPropertyInput-2MgIKlR_.js} +3 -3
  10. package/dist/_chunks/{HubspotPropertyInput-5bkDGmGo.mjs → HubspotPropertyInput-ClNph0f1.mjs} +3 -3
  11. package/dist/_chunks/{Settings-QcFbm3s5.mjs → Settings-CZ-qQSKT.mjs} +78 -5
  12. package/dist/_chunks/{Settings-X3EHnTaO.js → Settings-DhiHQC6U.js} +77 -4
  13. package/dist/_chunks/{en-ZHvaZLi7.js → en-CSgFmKmM.js} +16 -3
  14. package/dist/_chunks/{en-DMJeF7EN.mjs → en-Dh3bMS6N.mjs} +16 -3
  15. package/dist/_chunks/{fr-Ds_2EAzZ.js → fr-D_EhZS0P.js} +16 -3
  16. package/dist/_chunks/{fr-CVAntLmc.mjs → fr-aWOIOlS9.mjs} +16 -3
  17. package/dist/_chunks/{index-BpcilDy1.js → index-ciliGv0E.js} +6 -6
  18. package/dist/_chunks/{index-BSJAhj8r.mjs → index-gKM4jVf8.mjs} +6 -6
  19. package/dist/_chunks/{objectLabels-CYXXFpNB.js → objectLabels-BjYfdhRB.js} +1 -1
  20. package/dist/_chunks/{objectLabels-CrYRzsqe.mjs → objectLabels-DQpSLUaJ.mjs} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-CBF4GkmK.mjs → useHubspotSchema-CE7tJgOM.mjs} +1 -1
  22. package/dist/_chunks/{useHubspotSchema-Ba6xZUCL.js → useHubspotSchema-KKVppjC2.js} +1 -1
  23. package/dist/admin/index.js +1 -1
  24. package/dist/admin/index.mjs +1 -1
  25. package/dist/admin/src/builder/Sortable.d.ts +25 -12
  26. package/dist/admin/src/builder/__tests__/relocateField.test.d.ts +1 -0
  27. package/dist/admin/src/builder/relocateField.d.ts +14 -0
  28. package/dist/server/index.js +216 -7
  29. package/dist/server/index.mjs +216 -7
  30. package/dist/server/src/__tests__/formSync.test.d.ts +1 -0
  31. package/dist/server/src/formSync.d.ts +37 -0
  32. package/dist/server/src/forms.d.ts +6 -8
  33. package/dist/server/src/hsforms.d.ts +8 -0
  34. package/dist/server/src/importHubspot.d.ts +4 -0
  35. package/dist/server/src/index.d.ts +6 -0
  36. package/dist/server/src/settings.d.ts +32 -3
  37. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,27 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.16.1 — 2026-09-22
6
+
7
+ ### Changed
8
+ - Fields in the builder can be dragged from one step to another (the grip
9
+ handle is no longer limited to reordering inside the current step). Arrow
10
+ buttons still move a field into the neighbouring step.
11
+
12
+ ## 0.16.0 — 2026-09-22
13
+
14
+ ### Added — submission policies
15
+ The plugin no longer assumes one HubSpot workflow. **Settings → HubSpot**
16
+ (and `forms.*` in `config/plugins.ts`) now expose three independent choices:
17
+
18
+ - **`submissionMode`** (`auto` / `forms` / `crm`): native marketing-form
19
+ conversions when a GUID is linked, CRM upsert only, or automatic.
20
+ - **`writeExtraProperties`** (default `true`): after a Forms API conversion,
21
+ leftover mapped contact fields still land on the contact via CRM upsert.
22
+ - **`syncFieldsOnPublish`** (default `false`): on publish, PATCH missing
23
+ mapped contact fields onto the linked HubSpot form. Off so HubSpot-first
24
+ installs aren't mutated. CRM properties are never created.
25
+
5
26
  ## 0.15.0 — 2026-09-21
6
27
 
7
28
  ### Added — account settings in the admin
package/README.md CHANGED
@@ -44,14 +44,25 @@ Permissions**):
44
44
  | `GET` | `/api/hubspot/forms/:slug?locale=` | The published form — meta, steps, fields, conditions. The CRM mapping is stripped: the browser never learns your property names |
45
45
  | `POST` | `/api/hubspot/forms/:slug/submit` | Validates (bounds, conditions, required), maps server-side, syncs HubSpot, stores the submission |
46
46
 
47
- The submit pipeline, in order: HubSpot **Forms API** (native conversion —
48
- Original Source, workflows, lead-center notifications) when a marketing form
49
- GUID is set on the builder form or via `forms.defaultFormId`; the visitor's
50
- `hutk` is forwarded so attribution sticks. Then the contact is looked up by
51
- email and, when the address is on a corporate domain, the company is
52
- found-or-created and associated. A CRM contact upsert is the **fallback**
53
- when no GUID is configured — it does not count as a form conversion.
54
- Timeline notes are opt-in and only used on that fallback.
47
+ The submit pipeline is a **policy**, not a single path. Settings → HubSpot
48
+ (or `forms.*` in `config/plugins.ts`) pick how this install talks to HubSpot —
49
+ so a portal that wants native conversions and a portal that only wants CRM
50
+ upserts can share the same plugin:
51
+
52
+ | Setting | Default | What it does |
53
+ |---|---|---|
54
+ | `forms.submissionMode` | `auto` | `auto`: Forms API when a marketing GUID is linked, else CRM upsert. `forms`: always try the Forms API. `crm`: never count as a conversion |
55
+ | `forms.writeExtraProperties` | `true` | After a conversion, CRM-write mapped contact fields the HubSpot form dropped (they 400 the Forms API) |
56
+ | `forms.syncFieldsOnPublish` | `false` | On publish, PATCH missing mapped **contact** fields onto the HubSpot form. Off by default so HubSpot-first installs aren't mutated |
57
+
58
+ The visitor's `hutk` is forwarded so attribution sticks. Then the contact is
59
+ looked up by email and, when the address is on a corporate domain, the company
60
+ is found-or-created and associated. Timeline notes are opt-in and only used
61
+ on the CRM-upsert path.
62
+
63
+ CRM properties are **never created**. Map to properties that already exist in
64
+ the portal; opting into field sync only adds form fields that point at those
65
+ properties.
55
66
 
56
67
  Portal id, region and form GUIDs are configuration. A test portal today,
57
68
  production tomorrow: swap `portalId` / `region` / the GUID, no code change.
@@ -66,6 +77,9 @@ hubspot: {
66
77
  companyFromDomain: true, // Company by corporate domain + association
67
78
  timelineNote: false, // recap note on the CRM-upsert fallback only
68
79
  defaultFormId: env("HUBSPOT_DEFAULT_FORM_ID", ""),
80
+ submissionMode: "auto", // auto | forms | crm
81
+ writeExtraProperties: true,
82
+ syncFieldsOnPublish: false,
69
83
  },
70
84
  },
71
85
  }
@@ -365,11 +379,12 @@ its equivalent per object you send to).
365
379
 
366
380
  ### A settings screen
367
381
 
368
- **Settings → HubSpot** holds the private app token **and** the HubSpot account
369
- that receives conversions: portal id, region (`eu1` / `na1`), and the default
370
- marketing form (picked from the connected portal). Values saved here override
371
- `config/plugins.ts` and env — switching test → production is an admin change,
372
- not a deploy.
382
+ **Settings → HubSpot** holds the private app token, the HubSpot account that
383
+ receives conversions (portal id, region, default marketing form), **and** the
384
+ submission policy: conversion vs CRM upsert, leftover contact writes, form
385
+ field sync on publish. Values saved here override `config/plugins.ts` and env
386
+ — switching test → production, or switching HubSpot-first ↔ Strapi-first, is
387
+ an admin change, not a deploy.
373
388
 
374
389
  The token is stored server-side and never returned to the browser: the UI only
375
390
  receives whether a key exists, where it comes from, and its last four
@@ -7,12 +7,12 @@ const index = require("./index-DkTxsEqL.js");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const admin = require("@strapi/strapi/admin");
10
- const index$1 = require("./index-BpcilDy1.js");
10
+ const index$1 = require("./index-ciliGv0E.js");
11
11
  const core = require("@dnd-kit/core");
12
12
  const sortable = require("@dnd-kit/sortable");
13
13
  const utilities = require("@dnd-kit/utilities");
14
- const objectLabels = require("./objectLabels-CYXXFpNB.js");
15
- const useHubspotSchema = require("./useHubspotSchema-Ba6xZUCL.js");
14
+ const objectLabels = require("./objectLabels-BjYfdhRB.js");
15
+ const useHubspotSchema = require("./useHubspotSchema-KKVppjC2.js");
16
16
  function _interopNamespace(e) {
17
17
  if (e && e.__esModule) return e;
18
18
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -173,32 +173,107 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
173
173
  ] })
174
174
  ] });
175
175
  };
176
- function SortableList({
177
- ids,
178
- onReorder,
179
- children
176
+ function relocateField(steps, fieldId, overId) {
177
+ const next = steps.map((step) => ({ ...step, fields: [...step.fields] }));
178
+ let fromStep = -1;
179
+ let fromIndex = -1;
180
+ for (let i = 0; i < next.length; i += 1) {
181
+ const index2 = next[i].fields.findIndex((field2) => field2.id === fieldId);
182
+ if (index2 !== -1) {
183
+ fromStep = i;
184
+ fromIndex = index2;
185
+ break;
186
+ }
187
+ }
188
+ if (fromStep === -1) return steps;
189
+ let toStep = -1;
190
+ let toIndex = -1;
191
+ const containerId = overId.startsWith("container:") ? overId.slice("container:".length) : "";
192
+ if (containerId) {
193
+ toStep = next.findIndex((step) => step.id === containerId);
194
+ toIndex = toStep === -1 ? -1 : next[toStep].fields.length;
195
+ } else {
196
+ for (let i = 0; i < next.length; i += 1) {
197
+ const index2 = next[i].fields.findIndex((field2) => field2.id === overId);
198
+ if (index2 !== -1) {
199
+ toStep = i;
200
+ toIndex = index2;
201
+ break;
202
+ }
203
+ }
204
+ if (toStep === -1) {
205
+ toStep = next.findIndex((step) => step.id === overId);
206
+ toIndex = toStep === -1 ? -1 : next[toStep].fields.length;
207
+ }
208
+ }
209
+ if (toStep === -1 || toIndex === -1) return steps;
210
+ if (fromStep === toStep) {
211
+ if (fromIndex === toIndex) return steps;
212
+ next[fromStep].fields = sortable.arrayMove(next[fromStep].fields, fromIndex, toIndex);
213
+ return next;
214
+ }
215
+ const [field] = next[fromStep].fields.splice(fromIndex, 1);
216
+ if (!field) return steps;
217
+ next[toStep].fields.splice(toIndex, 0, field);
218
+ return next;
219
+ }
220
+ const collisionDetection = (args) => {
221
+ const type = args.active.data.current?.type;
222
+ const droppableContainers = args.droppableContainers.filter((container) => {
223
+ const other = container.data.current?.type;
224
+ if (type === "step") return other === "step";
225
+ if (type === "field") return other === "field" || other === "container";
226
+ return true;
227
+ });
228
+ return core.closestCorners({ ...args, droppableContainers });
229
+ };
230
+ function SortableBoard({
231
+ children,
232
+ onDragEnd
180
233
  }) {
181
234
  const sensors = core.useSensors(
182
235
  // An 8px activation distance keeps clicks (selection!) from starting drags.
183
236
  core.useSensor(core.PointerSensor, { activationConstraint: { distance: 8 } }),
184
237
  core.useSensor(core.KeyboardSensor, { coordinateGetter: sortable.sortableKeyboardCoordinates })
185
238
  );
186
- const handleDragEnd = (event) => {
187
- const { active, over } = event;
188
- if (!over || active.id === over.id) return;
189
- const from = ids.indexOf(String(active.id));
190
- const to = ids.indexOf(String(over.id));
191
- if (from === -1 || to === -1) return;
192
- onReorder(sortable.arrayMove(ids, from, to));
193
- };
194
- return /* @__PURE__ */ jsxRuntime.jsx(core.DndContext, { sensors, collisionDetection: core.closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: ids, strategy: sortable.verticalListSortingStrategy, children }) });
239
+ return /* @__PURE__ */ jsxRuntime.jsx(core.DndContext, { sensors, collisionDetection, onDragEnd, children });
240
+ }
241
+ function SortableGroup({
242
+ ids,
243
+ children
244
+ }) {
245
+ return /* @__PURE__ */ jsxRuntime.jsx(sortable.SortableContext, { items: ids, strategy: sortable.verticalListSortingStrategy, children });
246
+ }
247
+ function SortableContainer({
248
+ stepId,
249
+ children
250
+ }) {
251
+ const { setNodeRef, isOver } = core.useDroppable({
252
+ id: `container:${stepId}`,
253
+ data: { type: "container", stepId }
254
+ });
255
+ return /* @__PURE__ */ jsxRuntime.jsx(
256
+ "div",
257
+ {
258
+ ref: setNodeRef,
259
+ style: {
260
+ minHeight: 8,
261
+ borderRadius: 4,
262
+ outline: isOver ? "2px dashed currentColor" : void 0,
263
+ outlineOffset: 4
264
+ },
265
+ children
266
+ }
267
+ );
195
268
  }
196
269
  function SortableItem({
197
270
  id,
271
+ data,
198
272
  children
199
273
  }) {
200
274
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = sortable.useSortable({
201
- id
275
+ id,
276
+ data
202
277
  });
203
278
  return /* @__PURE__ */ jsxRuntime.jsx(
204
279
  "div",
@@ -794,11 +869,32 @@ const FormEditor = () => {
794
869
  setBusy(true);
795
870
  setFeedback(null);
796
871
  try {
797
- await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
872
+ const { data } = await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
798
873
  setPublished(true);
799
874
  setErrors([]);
800
875
  setProblems([]);
801
- setFeedback({ tone: "success", text: t("editor.published", "Form published.") });
876
+ const added = Array.isArray(data?.addedFields) ? data.addedFields.length : 0;
877
+ if (data?.syncWarning) {
878
+ setFeedback({
879
+ tone: "success",
880
+ text: t(
881
+ "editor.published-sync-warn",
882
+ "Form published. HubSpot field sync failed: {error}",
883
+ { error: data.syncWarning }
884
+ )
885
+ });
886
+ } else if (added) {
887
+ setFeedback({
888
+ tone: "success",
889
+ text: t(
890
+ "editor.published-synced",
891
+ "Form published. {count, plural, one {# field added} other {# fields added}} to the HubSpot form.",
892
+ { count: added }
893
+ )
894
+ });
895
+ } else {
896
+ setFeedback({ tone: "success", text: t("editor.published", "Form published.") });
897
+ }
802
898
  } catch (err) {
803
899
  const data = err.response?.data;
804
900
  setErrors(data?.errors ?? []);
@@ -868,14 +964,28 @@ const FormEditor = () => {
868
964
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 6, alignItems: "flex-start", children: [
869
965
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
870
966
  /* @__PURE__ */ jsxRuntime.jsx(
871
- SortableList,
967
+ SortableBoard,
872
968
  {
873
- ids: definition.steps.map((s) => s.id),
874
- onReorder: (ids) => patchDefinition((steps) => reorderByIds(steps, ids)),
875
- children: definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsxRuntime.jsx(
969
+ onDragEnd: ({ active, over }) => {
970
+ if (!over || active.id === over.id) return;
971
+ const kind = active.data.current?.type;
972
+ if (kind === "step") {
973
+ const ids = definition.steps.map((s) => s.id);
974
+ const from = ids.indexOf(String(active.id));
975
+ const to = ids.indexOf(String(over.id));
976
+ if (from === -1 || to === -1) return;
977
+ patchDefinition((steps) => reorderByIds(steps, sortable.arrayMove(ids, from, to)));
978
+ return;
979
+ }
980
+ if (kind === "field") {
981
+ patchDefinition((steps) => relocateField(steps, String(active.id), String(over.id)));
982
+ }
983
+ },
984
+ children: /* @__PURE__ */ jsxRuntime.jsx(SortableGroup, { ids: definition.steps.map((s) => s.id), children: definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsxRuntime.jsx(
876
985
  SortableItem,
877
986
  {
878
987
  id: step.id,
988
+ data: { type: "step" },
879
989
  children: (stepHandle) => /* @__PURE__ */ jsxRuntime.jsxs(
880
990
  designSystem.Box,
881
991
  {
@@ -951,95 +1061,89 @@ const FormEditor = () => {
951
1061
  ] })
952
1062
  ] }),
953
1063
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
954
- /* @__PURE__ */ jsxRuntime.jsx(
955
- SortableList,
956
- {
957
- ids: step.fields.map((f) => f.id),
958
- onReorder: (ids) => patchStep(step.id, { fields: reorderByIds(step.fields, ids) }),
959
- children: step.fields.map((field, fieldIndex) => {
960
- const flags = fieldFlags(field.id);
961
- const isSelected = selection.kind === "field" && selection.fieldId === field.id;
962
- return /* @__PURE__ */ jsxRuntime.jsx(
963
- SortableItem,
1064
+ /* @__PURE__ */ jsxRuntime.jsx(SortableContainer, { stepId: step.id, children: /* @__PURE__ */ jsxRuntime.jsx(SortableGroup, { ids: step.fields.map((f) => f.id), children: step.fields.map((field, fieldIndex) => {
1065
+ const flags = fieldFlags(field.id);
1066
+ const isSelected = selection.kind === "field" && selection.fieldId === field.id;
1067
+ return /* @__PURE__ */ jsxRuntime.jsx(
1068
+ SortableItem,
1069
+ {
1070
+ id: field.id,
1071
+ data: { type: "field", stepId: step.id },
1072
+ children: (fieldHandle) => /* @__PURE__ */ jsxRuntime.jsx(
1073
+ designSystem.Box,
964
1074
  {
965
- id: field.id,
966
- children: (fieldHandle) => /* @__PURE__ */ jsxRuntime.jsx(
967
- designSystem.Box,
968
- {
969
- background: isSelected ? "primary100" : "neutral100",
970
- hasRadius: true,
971
- padding: 3,
972
- onClick: (e) => {
973
- e.stopPropagation();
974
- setSelection({ kind: "field", fieldId: field.id });
975
- },
976
- style: { cursor: "pointer" },
977
- children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
978
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
979
- /* @__PURE__ */ jsxRuntime.jsx(
980
- "span",
981
- {
982
- ...fieldHandle.attributes,
983
- ...fieldHandle.listeners,
984
- onClick: (e) => e.stopPropagation(),
985
- style: { cursor: "grab", display: "inline-flex" },
986
- "aria-label": t("editor.drag-field", "Drag to reorder the field"),
987
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Drag, {})
988
- }
989
- ),
990
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
991
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: field.type }),
992
- field.required && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
993
- field.hubspot?.property && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
994
- field.hubspot.object ?? "contact",
995
- ".",
996
- field.hubspot.property
997
- ] }),
998
- field.visibleIf?.rules?.length ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
999
- (flags.error || flags.problem) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
1000
- ] }),
1001
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
1002
- /* @__PURE__ */ jsxRuntime.jsx(
1003
- designSystem.IconButton,
1004
- {
1005
- label: t("editor.field-up", "Move up"),
1006
- onClick: () => moveField(stepIndex, fieldIndex, -1),
1007
- disabled: stepIndex === 0 && fieldIndex === 0,
1008
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUp, {})
1009
- }
1010
- ),
1011
- /* @__PURE__ */ jsxRuntime.jsx(
1012
- designSystem.IconButton,
1013
- {
1014
- label: t("editor.field-down", "Move down"),
1015
- onClick: () => moveField(stepIndex, fieldIndex, 1),
1016
- disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
1017
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowDown, {})
1018
- }
1019
- ),
1020
- /* @__PURE__ */ jsxRuntime.jsx(
1021
- designSystem.IconButton,
1022
- {
1023
- label: t("editor.field-delete", "Delete the field"),
1024
- onClick: () => {
1025
- setSelection({ kind: "form" });
1026
- patchStep(step.id, {
1027
- fields: step.fields.filter((f) => f.id !== field.id)
1028
- });
1029
- },
1030
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1031
- }
1032
- )
1033
- ] })
1034
- ] })
1035
- }
1036
- )
1037
- },
1038
- field.id
1039
- );
1040
- })
1041
- }
1042
- ),
1075
+ background: isSelected ? "primary100" : "neutral100",
1076
+ hasRadius: true,
1077
+ padding: 3,
1078
+ onClick: (e) => {
1079
+ e.stopPropagation();
1080
+ setSelection({ kind: "field", fieldId: field.id });
1081
+ },
1082
+ style: { cursor: "pointer" },
1083
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
1084
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1085
+ /* @__PURE__ */ jsxRuntime.jsx(
1086
+ "span",
1087
+ {
1088
+ ...fieldHandle.attributes,
1089
+ ...fieldHandle.listeners,
1090
+ onClick: (e) => e.stopPropagation(),
1091
+ style: { cursor: "grab", display: "inline-flex" },
1092
+ "aria-label": t("editor.drag-field", "Drag to move the field, including to another step"),
1093
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Drag, {})
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
1097
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: field.type }),
1098
+ field.required && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
1099
+ field.hubspot?.property && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1100
+ field.hubspot.object ?? "contact",
1101
+ ".",
1102
+ field.hubspot.property
1103
+ ] }),
1104
+ field.visibleIf?.rules?.length ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
1105
+ (flags.error || flags.problem) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
1106
+ ] }),
1107
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
1108
+ /* @__PURE__ */ jsxRuntime.jsx(
1109
+ designSystem.IconButton,
1110
+ {
1111
+ label: t("editor.field-up", "Move up"),
1112
+ onClick: () => moveField(stepIndex, fieldIndex, -1),
1113
+ disabled: stepIndex === 0 && fieldIndex === 0,
1114
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowUp, {})
1115
+ }
1116
+ ),
1117
+ /* @__PURE__ */ jsxRuntime.jsx(
1118
+ designSystem.IconButton,
1119
+ {
1120
+ label: t("editor.field-down", "Move down"),
1121
+ onClick: () => moveField(stepIndex, fieldIndex, 1),
1122
+ disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
1123
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowDown, {})
1124
+ }
1125
+ ),
1126
+ /* @__PURE__ */ jsxRuntime.jsx(
1127
+ designSystem.IconButton,
1128
+ {
1129
+ label: t("editor.field-delete", "Delete the field"),
1130
+ onClick: () => {
1131
+ setSelection({ kind: "form" });
1132
+ patchStep(step.id, {
1133
+ fields: step.fields.filter((f) => f.id !== field.id)
1134
+ });
1135
+ },
1136
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1137
+ }
1138
+ )
1139
+ ] })
1140
+ ] })
1141
+ }
1142
+ )
1143
+ },
1144
+ field.id
1145
+ );
1146
+ }) }) }),
1043
1147
  /* @__PURE__ */ jsxRuntime.jsx(
1044
1148
  designSystem.Button,
1045
1149
  {
@@ -1060,7 +1164,7 @@ const FormEditor = () => {
1060
1164
  )
1061
1165
  },
1062
1166
  step.id
1063
- ))
1167
+ )) })
1064
1168
  }
1065
1169
  ),
1066
1170
  /* @__PURE__ */ jsxRuntime.jsx(