strapi-plugin-hubspot 0.16.0 → 0.16.2

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 (33) hide show
  1. package/CHANGELOG.md +15 -0
  2. package/README.md +4 -4
  3. package/dist/_chunks/{Forms-6EoEYzAc.mjs → Forms-BOQWely0.mjs} +240 -114
  4. package/dist/_chunks/{Forms-BW_zGc1P.js → Forms-mMNP7hc9.js} +238 -112
  5. package/dist/_chunks/{HubspotFormInput-V5w4y1fb.mjs → HubspotFormInput-DCkoavrN.mjs} +1 -1
  6. package/dist/_chunks/{HubspotFormInput-oMhm1UJm.js → HubspotFormInput-DI35Aqiu.js} +1 -1
  7. package/dist/_chunks/{HubspotObjectInput-DhsnSguG.mjs → HubspotObjectInput-BbnvhdbD.mjs} +3 -3
  8. package/dist/_chunks/{HubspotObjectInput-Bmo6YQgs.js → HubspotObjectInput-Cq1TuL_l.js} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-CrYP4pc0.mjs → HubspotPropertyInput-BHfpBD36.mjs} +3 -3
  10. package/dist/_chunks/{HubspotPropertyInput-luFfnxqy.js → HubspotPropertyInput-BLZzsW08.js} +3 -3
  11. package/dist/_chunks/{Settings-B3A22i7Z.js → Settings-BAsx0LGv.js} +2 -2
  12. package/dist/_chunks/{Settings-CjcWj9I6.mjs → Settings-DqRpHtnT.mjs} +2 -2
  13. package/dist/_chunks/{en-D6GHgMOt.mjs → en-Djd3TID0.mjs} +7 -3
  14. package/dist/_chunks/{en-C7ls2EUl.js → en-sfzopB5K.js} +7 -3
  15. package/dist/_chunks/{fr-CfohMY8n.mjs → fr-C7t1elmR.mjs} +7 -3
  16. package/dist/_chunks/{fr-pOP8ZXqM.js → fr-sF5Josfh.js} +7 -3
  17. package/dist/_chunks/{index-7cL9PRh1.mjs → index-CLAy5dVN.mjs} +6 -6
  18. package/dist/_chunks/{index-CodpLKZx.js → index-R3ReZXF1.js} +6 -6
  19. package/dist/_chunks/{objectLabels-Dnj14fsp.mjs → objectLabels--zOmqsfm.mjs} +1 -1
  20. package/dist/_chunks/{objectLabels-CSWGIgNb.js → objectLabels-CW2iBOkD.js} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-DiRN3HrN.js → useHubspotSchema-BRzzvs9t.js} +1 -1
  22. package/dist/_chunks/{useHubspotSchema-BsmQgJgx.mjs → useHubspotSchema-Bz2o1BU-.mjs} +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 +129 -12
  29. package/dist/server/index.mjs +129 -12
  30. package/dist/server/src/formsAdmin.d.ts +8 -3
  31. package/dist/server/src/importHubspot.d.ts +12 -0
  32. package/dist/server/src/index.d.ts +1 -0
  33. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,21 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.16.2 — 2026-09-22
6
+
7
+ ### Added
8
+ - **Resync from HubSpot** in the form editor (and a safer list re-import):
9
+ mapped fields update in place (label, type, required, options), new HubSpot
10
+ fields append to the last step, Strapi steps / conditions / extra fields
11
+ stay. Matching uses the linked form GUID rather than the slug.
12
+
13
+ ## 0.16.1 — 2026-09-22
14
+
15
+ ### Changed
16
+ - Fields in the builder can be dragged from one step to another (the grip
17
+ handle is no longer limited to reordering inside the current step). Arrow
18
+ buttons still move a field into the neighbouring step.
19
+
5
20
  ## 0.16.0 — 2026-09-22
6
21
 
7
22
  ### Added — submission policies
package/README.md CHANGED
@@ -174,10 +174,10 @@ the portal. The translation carries over what the builder can express:
174
174
  - dependent fields become `visibleIf` conditions (value lists are expanded
175
175
  into OR'd/AND'd rules).
176
176
 
177
- Everything else — file uploads, hidden fields, content blocks, GDPR consent
178
- blocks, an operator with no equivalent — is **skipped and reported** after the
179
- import, so nothing is silently half-migrated. The HubSpot original is never
180
- modified; re-importing overwrites the draft, never the published version.
177
+ The HubSpot original is never modified. Re-importing (or **Resync from HubSpot**
178
+ in the editor) updates mapped fields in place and appends new ones — Strapi
179
+ steps, conditions and extra fields stay. Matching is by HubSpot form GUID,
180
+ then slug. The published version is never touched.
181
181
 
182
182
  ### i18n and publishing
183
183
 
@@ -5,12 +5,12 @@ import { u as useIntl } from "./index-CEh8vkxY.mjs";
5
5
  import { Typography, Flex, Box, SingleSelect, SingleSelectOption, MultiSelect, MultiSelectOption, TextInput, IconButton, Button, Combobox, ComboboxOption, Field, Textarea, Checkbox, Divider, Loader, Badge, Table, Thead, Tr, Th, Tbody, Td, Modal } from "@strapi/design-system";
6
6
  import { Trash, Plus, ArrowClockwise, ArrowLeft, Drag, ArrowUp, ArrowDown, List, Duplicate, Download } from "@strapi/icons";
7
7
  import { useFetchClient } from "@strapi/strapi/admin";
8
- import { g as getTranslation, P as PLUGIN_ID } from "./index-7cL9PRh1.mjs";
9
- import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from "@dnd-kit/core";
10
- import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from "@dnd-kit/sortable";
8
+ import { g as getTranslation, P as PLUGIN_ID } from "./index-CLAy5dVN.mjs";
9
+ import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, useDroppable, closestCorners } from "@dnd-kit/core";
10
+ import { arrayMove, sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable } from "@dnd-kit/sortable";
11
11
  import { CSS } from "@dnd-kit/utilities";
12
- import { o as objectLabel } from "./objectLabels-Dnj14fsp.mjs";
13
- import { u as useHubspotSchema } from "./useHubspotSchema-BsmQgJgx.mjs";
12
+ import { o as objectLabel } from "./objectLabels--zOmqsfm.mjs";
13
+ import { u as useHubspotSchema } from "./useHubspotSchema-Bz2o1BU-.mjs";
14
14
  const OPERATORS = [
15
15
  "eq",
16
16
  "neq",
@@ -153,32 +153,107 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
153
153
  ] })
154
154
  ] });
155
155
  };
156
- function SortableList({
157
- ids,
158
- onReorder,
159
- children
156
+ function relocateField(steps, fieldId, overId) {
157
+ const next = steps.map((step) => ({ ...step, fields: [...step.fields] }));
158
+ let fromStep = -1;
159
+ let fromIndex = -1;
160
+ for (let i = 0; i < next.length; i += 1) {
161
+ const index = next[i].fields.findIndex((field2) => field2.id === fieldId);
162
+ if (index !== -1) {
163
+ fromStep = i;
164
+ fromIndex = index;
165
+ break;
166
+ }
167
+ }
168
+ if (fromStep === -1) return steps;
169
+ let toStep = -1;
170
+ let toIndex = -1;
171
+ const containerId = overId.startsWith("container:") ? overId.slice("container:".length) : "";
172
+ if (containerId) {
173
+ toStep = next.findIndex((step) => step.id === containerId);
174
+ toIndex = toStep === -1 ? -1 : next[toStep].fields.length;
175
+ } else {
176
+ for (let i = 0; i < next.length; i += 1) {
177
+ const index = next[i].fields.findIndex((field2) => field2.id === overId);
178
+ if (index !== -1) {
179
+ toStep = i;
180
+ toIndex = index;
181
+ break;
182
+ }
183
+ }
184
+ if (toStep === -1) {
185
+ toStep = next.findIndex((step) => step.id === overId);
186
+ toIndex = toStep === -1 ? -1 : next[toStep].fields.length;
187
+ }
188
+ }
189
+ if (toStep === -1 || toIndex === -1) return steps;
190
+ if (fromStep === toStep) {
191
+ if (fromIndex === toIndex) return steps;
192
+ next[fromStep].fields = arrayMove(next[fromStep].fields, fromIndex, toIndex);
193
+ return next;
194
+ }
195
+ const [field] = next[fromStep].fields.splice(fromIndex, 1);
196
+ if (!field) return steps;
197
+ next[toStep].fields.splice(toIndex, 0, field);
198
+ return next;
199
+ }
200
+ const collisionDetection = (args) => {
201
+ const type = args.active.data.current?.type;
202
+ const droppableContainers = args.droppableContainers.filter((container) => {
203
+ const other = container.data.current?.type;
204
+ if (type === "step") return other === "step";
205
+ if (type === "field") return other === "field" || other === "container";
206
+ return true;
207
+ });
208
+ return closestCorners({ ...args, droppableContainers });
209
+ };
210
+ function SortableBoard({
211
+ children,
212
+ onDragEnd
160
213
  }) {
161
214
  const sensors = useSensors(
162
215
  // An 8px activation distance keeps clicks (selection!) from starting drags.
163
216
  useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
164
217
  useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
165
218
  );
166
- const handleDragEnd = (event) => {
167
- const { active, over } = event;
168
- if (!over || active.id === over.id) return;
169
- const from = ids.indexOf(String(active.id));
170
- const to = ids.indexOf(String(over.id));
171
- if (from === -1 || to === -1) return;
172
- onReorder(arrayMove(ids, from, to));
173
- };
174
- return /* @__PURE__ */ jsx(DndContext, { sensors, collisionDetection: closestCenter, onDragEnd: handleDragEnd, children: /* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children }) });
219
+ return /* @__PURE__ */ jsx(DndContext, { sensors, collisionDetection, onDragEnd, children });
220
+ }
221
+ function SortableGroup({
222
+ ids,
223
+ children
224
+ }) {
225
+ return /* @__PURE__ */ jsx(SortableContext, { items: ids, strategy: verticalListSortingStrategy, children });
226
+ }
227
+ function SortableContainer({
228
+ stepId,
229
+ children
230
+ }) {
231
+ const { setNodeRef, isOver } = useDroppable({
232
+ id: `container:${stepId}`,
233
+ data: { type: "container", stepId }
234
+ });
235
+ return /* @__PURE__ */ jsx(
236
+ "div",
237
+ {
238
+ ref: setNodeRef,
239
+ style: {
240
+ minHeight: 8,
241
+ borderRadius: 4,
242
+ outline: isOver ? "2px dashed currentColor" : void 0,
243
+ outlineOffset: 4
244
+ },
245
+ children
246
+ }
247
+ );
175
248
  }
176
249
  function SortableItem({
177
250
  id,
251
+ data,
178
252
  children
179
253
  }) {
180
254
  const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
181
- id
255
+ id,
256
+ data
182
257
  });
183
258
  return /* @__PURE__ */ jsx(
184
259
  "div",
@@ -824,6 +899,40 @@ const FormEditor = () => {
824
899
  setBusy(false);
825
900
  }
826
901
  };
902
+ const resync = async () => {
903
+ if (dirty && !await save()) return;
904
+ setBusy(true);
905
+ setFeedback(null);
906
+ try {
907
+ const { data } = await post(`/${PLUGIN_ID}/builder/forms/${documentId}/resync${query}`);
908
+ if (data.form) {
909
+ setEntry(data.form);
910
+ setDirty(false);
911
+ }
912
+ const added = data.added?.length ?? 0;
913
+ const updated = data.updated?.length ?? 0;
914
+ const skipped = data.skipped?.length ?? 0;
915
+ setFeedback({
916
+ tone: "success",
917
+ text: skipped > 0 ? t(
918
+ "editor.resynced-gaps",
919
+ "Resynced from HubSpot — {updated, plural, one {# updated} other {# updated}}, {added, plural, one {# added} other {# added}}, {skipped, plural, one {# skipped} other {# skipped}}.",
920
+ { updated, added, skipped }
921
+ ) : t(
922
+ "editor.resynced",
923
+ "Resynced from HubSpot — {updated, plural, one {# field updated} other {# fields updated}}, {added, plural, one {# added} other {# added}}.",
924
+ { updated, added }
925
+ )
926
+ });
927
+ } catch {
928
+ setFeedback({
929
+ tone: "danger",
930
+ text: t("editor.resync-error", "Could not resync from HubSpot — is the form still linked?")
931
+ });
932
+ } finally {
933
+ setBusy(false);
934
+ }
935
+ };
827
936
  const fieldFlags = (fieldId) => ({
828
937
  error: errors.find((e) => e.fieldId === fieldId),
829
938
  problem: problems.find((p) => p.fieldId === fieldId)
@@ -861,6 +970,15 @@ const FormEditor = () => {
861
970
  }
862
971
  ),
863
972
  /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: save, disabled: busy || !dirty, children: t("editor.save", "Save") }),
973
+ /* @__PURE__ */ jsx(
974
+ Button,
975
+ {
976
+ variant: "tertiary",
977
+ onClick: resync,
978
+ disabled: busy || !entry.hubspotFormId,
979
+ children: t("editor.resync", "Resync from HubSpot")
980
+ }
981
+ ),
864
982
  /* @__PURE__ */ jsx(Button, { onClick: publish, disabled: busy, children: t("editor.publish", "Publish") }),
865
983
  published && /* @__PURE__ */ jsx(Button, { variant: "tertiary", onClick: unpublish, disabled: busy, children: t("editor.unpublish", "Unpublish") })
866
984
  ] })
@@ -869,14 +987,28 @@ const FormEditor = () => {
869
987
  /* @__PURE__ */ jsxs(Flex, { gap: 6, alignItems: "flex-start", children: [
870
988
  /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
871
989
  /* @__PURE__ */ jsx(
872
- SortableList,
990
+ SortableBoard,
873
991
  {
874
- ids: definition.steps.map((s) => s.id),
875
- onReorder: (ids) => patchDefinition((steps) => reorderByIds(steps, ids)),
876
- children: definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsx(
992
+ onDragEnd: ({ active, over }) => {
993
+ if (!over || active.id === over.id) return;
994
+ const kind = active.data.current?.type;
995
+ if (kind === "step") {
996
+ const ids = definition.steps.map((s) => s.id);
997
+ const from = ids.indexOf(String(active.id));
998
+ const to = ids.indexOf(String(over.id));
999
+ if (from === -1 || to === -1) return;
1000
+ patchDefinition((steps) => reorderByIds(steps, arrayMove(ids, from, to)));
1001
+ return;
1002
+ }
1003
+ if (kind === "field") {
1004
+ patchDefinition((steps) => relocateField(steps, String(active.id), String(over.id)));
1005
+ }
1006
+ },
1007
+ children: /* @__PURE__ */ jsx(SortableGroup, { ids: definition.steps.map((s) => s.id), children: definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsx(
877
1008
  SortableItem,
878
1009
  {
879
1010
  id: step.id,
1011
+ data: { type: "step" },
880
1012
  children: (stepHandle) => /* @__PURE__ */ jsxs(
881
1013
  Box,
882
1014
  {
@@ -952,95 +1084,89 @@ const FormEditor = () => {
952
1084
  ] })
953
1085
  ] }),
954
1086
  /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
955
- /* @__PURE__ */ jsx(
956
- SortableList,
957
- {
958
- ids: step.fields.map((f) => f.id),
959
- onReorder: (ids) => patchStep(step.id, { fields: reorderByIds(step.fields, ids) }),
960
- children: step.fields.map((field, fieldIndex) => {
961
- const flags = fieldFlags(field.id);
962
- const isSelected = selection.kind === "field" && selection.fieldId === field.id;
963
- return /* @__PURE__ */ jsx(
964
- SortableItem,
1087
+ /* @__PURE__ */ jsx(SortableContainer, { stepId: step.id, children: /* @__PURE__ */ jsx(SortableGroup, { ids: step.fields.map((f) => f.id), children: step.fields.map((field, fieldIndex) => {
1088
+ const flags = fieldFlags(field.id);
1089
+ const isSelected = selection.kind === "field" && selection.fieldId === field.id;
1090
+ return /* @__PURE__ */ jsx(
1091
+ SortableItem,
1092
+ {
1093
+ id: field.id,
1094
+ data: { type: "field", stepId: step.id },
1095
+ children: (fieldHandle) => /* @__PURE__ */ jsx(
1096
+ Box,
965
1097
  {
966
- id: field.id,
967
- children: (fieldHandle) => /* @__PURE__ */ jsx(
968
- Box,
969
- {
970
- background: isSelected ? "primary100" : "neutral100",
971
- hasRadius: true,
972
- padding: 3,
973
- onClick: (e) => {
974
- e.stopPropagation();
975
- setSelection({ kind: "field", fieldId: field.id });
976
- },
977
- style: { cursor: "pointer" },
978
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
979
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
980
- /* @__PURE__ */ jsx(
981
- "span",
982
- {
983
- ...fieldHandle.attributes,
984
- ...fieldHandle.listeners,
985
- onClick: (e) => e.stopPropagation(),
986
- style: { cursor: "grab", display: "inline-flex" },
987
- "aria-label": t("editor.drag-field", "Drag to reorder the field"),
988
- children: /* @__PURE__ */ jsx(Drag, {})
989
- }
990
- ),
991
- /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
992
- /* @__PURE__ */ jsx(Badge, { children: field.type }),
993
- field.required && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
994
- field.hubspot?.property && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
995
- field.hubspot.object ?? "contact",
996
- ".",
997
- field.hubspot.property
998
- ] }),
999
- field.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
1000
- (flags.error || flags.problem) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
1001
- ] }),
1002
- /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
1003
- /* @__PURE__ */ jsx(
1004
- IconButton,
1005
- {
1006
- label: t("editor.field-up", "Move up"),
1007
- onClick: () => moveField(stepIndex, fieldIndex, -1),
1008
- disabled: stepIndex === 0 && fieldIndex === 0,
1009
- children: /* @__PURE__ */ jsx(ArrowUp, {})
1010
- }
1011
- ),
1012
- /* @__PURE__ */ jsx(
1013
- IconButton,
1014
- {
1015
- label: t("editor.field-down", "Move down"),
1016
- onClick: () => moveField(stepIndex, fieldIndex, 1),
1017
- disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
1018
- children: /* @__PURE__ */ jsx(ArrowDown, {})
1019
- }
1020
- ),
1021
- /* @__PURE__ */ jsx(
1022
- IconButton,
1023
- {
1024
- label: t("editor.field-delete", "Delete the field"),
1025
- onClick: () => {
1026
- setSelection({ kind: "form" });
1027
- patchStep(step.id, {
1028
- fields: step.fields.filter((f) => f.id !== field.id)
1029
- });
1030
- },
1031
- children: /* @__PURE__ */ jsx(Trash, {})
1032
- }
1033
- )
1034
- ] })
1035
- ] })
1036
- }
1037
- )
1038
- },
1039
- field.id
1040
- );
1041
- })
1042
- }
1043
- ),
1098
+ background: isSelected ? "primary100" : "neutral100",
1099
+ hasRadius: true,
1100
+ padding: 3,
1101
+ onClick: (e) => {
1102
+ e.stopPropagation();
1103
+ setSelection({ kind: "field", fieldId: field.id });
1104
+ },
1105
+ style: { cursor: "pointer" },
1106
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
1107
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
1108
+ /* @__PURE__ */ jsx(
1109
+ "span",
1110
+ {
1111
+ ...fieldHandle.attributes,
1112
+ ...fieldHandle.listeners,
1113
+ onClick: (e) => e.stopPropagation(),
1114
+ style: { cursor: "grab", display: "inline-flex" },
1115
+ "aria-label": t("editor.drag-field", "Drag to move the field, including to another step"),
1116
+ children: /* @__PURE__ */ jsx(Drag, {})
1117
+ }
1118
+ ),
1119
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
1120
+ /* @__PURE__ */ jsx(Badge, { children: field.type }),
1121
+ field.required && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
1122
+ field.hubspot?.property && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
1123
+ field.hubspot.object ?? "contact",
1124
+ ".",
1125
+ field.hubspot.property
1126
+ ] }),
1127
+ field.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
1128
+ (flags.error || flags.problem) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
1129
+ ] }),
1130
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
1131
+ /* @__PURE__ */ jsx(
1132
+ IconButton,
1133
+ {
1134
+ label: t("editor.field-up", "Move up"),
1135
+ onClick: () => moveField(stepIndex, fieldIndex, -1),
1136
+ disabled: stepIndex === 0 && fieldIndex === 0,
1137
+ children: /* @__PURE__ */ jsx(ArrowUp, {})
1138
+ }
1139
+ ),
1140
+ /* @__PURE__ */ jsx(
1141
+ IconButton,
1142
+ {
1143
+ label: t("editor.field-down", "Move down"),
1144
+ onClick: () => moveField(stepIndex, fieldIndex, 1),
1145
+ disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
1146
+ children: /* @__PURE__ */ jsx(ArrowDown, {})
1147
+ }
1148
+ ),
1149
+ /* @__PURE__ */ jsx(
1150
+ IconButton,
1151
+ {
1152
+ label: t("editor.field-delete", "Delete the field"),
1153
+ onClick: () => {
1154
+ setSelection({ kind: "form" });
1155
+ patchStep(step.id, {
1156
+ fields: step.fields.filter((f) => f.id !== field.id)
1157
+ });
1158
+ },
1159
+ children: /* @__PURE__ */ jsx(Trash, {})
1160
+ }
1161
+ )
1162
+ ] })
1163
+ ] })
1164
+ }
1165
+ )
1166
+ },
1167
+ field.id
1168
+ );
1169
+ }) }) }),
1044
1170
  /* @__PURE__ */ jsx(
1045
1171
  Button,
1046
1172
  {
@@ -1061,7 +1187,7 @@ const FormEditor = () => {
1061
1187
  )
1062
1188
  },
1063
1189
  step.id
1064
- ))
1190
+ )) })
1065
1191
  }
1066
1192
  ),
1067
1193
  /* @__PURE__ */ jsx(
@@ -1228,7 +1354,7 @@ const FormEditor = () => {
1228
1354
  ),
1229
1355
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1230
1356
  "editor.hubspot-form-hint",
1231
- "Submissions go through HubSpot’s Forms API so they count as conversions. Switch the connected portal (test → production) and pick the matching form — nothing is hardcoded."
1357
+ "Submissions go through HubSpot’s Forms API so they count as conversions. After you change the form in HubSpot, use Resync from HubSpot — mapped fields update in place, new ones are appended, your steps stay."
1232
1358
  ) })
1233
1359
  ] }),
1234
1360
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
@@ -1407,7 +1533,7 @@ const FormsList = () => {
1407
1533
  /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: t("forms.import-hubspot-title", "Import from your HubSpot portal") }),
1408
1534
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", tag: "p", children: t(
1409
1535
  "forms.import-hubspot-hint",
1410
- "Translates a form built in HubSpot into a draft here, CRM mapping included. What the builder can't express is skipped and reported. Re-importing overwrites the draft."
1536
+ "Translates a form built in HubSpot into a draft here, CRM mapping included. What the builder can't express is skipped and reported. Re-importing updates mapped fields in place and appends new ones."
1411
1537
  ) })
1412
1538
  ] }),
1413
1539
  /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [