strapi-plugin-hubspot 0.11.1 → 0.13.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 (35) hide show
  1. package/CHANGELOG.md +34 -0
  2. package/README.md +45 -0
  3. package/dist/_chunks/{Forms-DadTbe9Q.mjs → Forms-CHB-sKem.mjs} +218 -121
  4. package/dist/_chunks/{Forms-Dn2W6OS4.js → Forms-lrzuWzoB.js} +217 -120
  5. package/dist/_chunks/{HubspotFormInput-S_Z2t_DN.js → HubspotFormInput-Bb6it0nP.js} +1 -1
  6. package/dist/_chunks/{HubspotFormInput-NO59AtM_.mjs → HubspotFormInput-YI09keTC.mjs} +1 -1
  7. package/dist/_chunks/{HubspotObjectInput-DAo3FAMz.mjs → HubspotObjectInput-CzLKT9oj.mjs} +3 -3
  8. package/dist/_chunks/{HubspotObjectInput-BIgIa_Dm.js → HubspotObjectInput-D3A6xzb3.js} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-KX1Bhcsl.mjs → HubspotPropertyInput-DicskLcN.mjs} +3 -3
  10. package/dist/_chunks/{HubspotPropertyInput-B-piLcIy.js → HubspotPropertyInput-Dss3eUlL.js} +3 -3
  11. package/dist/_chunks/{Settings-Dz0PEFOv.mjs → Settings-4CbX3SPK.mjs} +2 -2
  12. package/dist/_chunks/{Settings-DMu_QThB.js → Settings-Ch8tPZAV.js} +2 -2
  13. package/dist/_chunks/{en-CH5LSPkL.mjs → en-D8DJiSxd.mjs} +3 -1
  14. package/dist/_chunks/{en-BDGqW_yS.js → en-PYFibnDc.js} +3 -1
  15. package/dist/_chunks/{fr-65-RaB3w.mjs → fr-BMjixfXn.mjs} +3 -1
  16. package/dist/_chunks/{fr-C82LMqUM.js → fr-yHk4sSEl.js} +3 -1
  17. package/dist/_chunks/{index-B7Ru1fu5.js → index-BAvedd2m.js} +6 -6
  18. package/dist/_chunks/{index-DA31TY2n.mjs → index-BE2ribUL.mjs} +6 -6
  19. package/dist/_chunks/{objectLabels-FTh984_h.js → objectLabels-CycoU0zY.js} +1 -1
  20. package/dist/_chunks/{objectLabels-Bl7sO5pb.mjs → objectLabels-DGJxYjKz.mjs} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-C2_PuXEm.js → useHubspotSchema-DRfaI6mF.js} +1 -1
  22. package/dist/_chunks/{useHubspotSchema-BlDilCHc.mjs → useHubspotSchema-e31UsYb2.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 -0
  26. package/dist/admin/src/builder/__tests__/CompanyMapEditor.test.d.ts +1 -0
  27. package/dist/admin/src/builder/__tests__/ConditionEditor.test.d.ts +1 -0
  28. package/dist/admin/src/builder/__tests__/testUtils.d.ts +4 -0
  29. package/dist/server/index.js +109 -42
  30. package/dist/server/index.mjs +109 -42
  31. package/dist/server/src/__tests__/contract.test.d.ts +1 -0
  32. package/dist/server/src/__tests__/rateLimit.test.d.ts +1 -0
  33. package/dist/server/src/index.d.ts +12 -3
  34. package/dist/server/src/rateLimit.d.ts +18 -0
  35. package/package.json +11 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,39 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.13.1 — 2026-09-21
6
+
7
+ ### Added
8
+ - **GDPR proof on submissions.** `meta.consent` / `meta.consentedAt` sent by
9
+ the frontend are stored on the submission row and written on the HubSpot
10
+ timeline note. They are not CRM properties — an unknown HubSpot key would
11
+ fail the whole upsert. Rebuild HubSpot's native legal-consent block as a
12
+ required checkbox named `consent` (see README).
13
+
14
+ ## 0.13.0 — 2026-08-31
15
+
16
+ ### Added
17
+ - **Drag & drop in the builder** (@dnd-kit): steps reorder among themselves,
18
+ fields reorder within their step — grab the grip handle; an 8px activation
19
+ distance keeps plain clicks selecting as before. The arrow buttons stay:
20
+ they remain the accessible path and the way to slide a field into the
21
+ neighbouring step.
22
+
23
+ ### Also in this release — anti-spam & frozen contract
24
+ - **Per-IP rate limiting** on the public routes (sliding window, in-memory):
25
+ submissions 6/min, company searches 30/min — `forms.rateLimit`
26
+ config, `0` disables one. Over the limit → `429`.
27
+ - **Honeypot**: a submission whose hidden `__hp` key is filled gets a normal
28
+ success and is neither sent nor stored — no signal for the bot to adapt to.
29
+ (Frontends: render a visually hidden `__hp` input.)
30
+ - **Contract test suite** freezing the 1.0 public surface: public form
31
+ payload keys, CRM mappings never leaked, submission conventions
32
+ (`__siret`/`__company`/`__hp`, structured `missingRequired`), the 12
33
+ condition operators. A failure there means "bump the major".
34
+ - README: *Stability & versioning* section documenting the frozen contract
35
+ and the `version: 2` migration promise.
36
+
3
37
  ## 0.11.1 — 2026-08-29
4
38
 
5
39
  Ships the company-search sharpening that missed the 0.11.0 tarball (the
package/README.md CHANGED
@@ -66,6 +66,33 @@ Every submission is stored in **HubSpot form submissions** (Content Manager),
66
66
  synced or not, with the CRM ids when the sync succeeded — the source of truth
67
67
  lives in your database, not in HubSpot's availability.
68
68
 
69
+ ### GDPR consent
70
+
71
+ HubSpot's native *legal consent* block is **not** a CRM field. Importing a
72
+ HubSpot form skips it (you'll see "rebuild it as a field") because this
73
+ builder maps answers onto Contact/Company properties, and an unknown property
74
+ makes HubSpot reject the whole upsert.
75
+
76
+ To connect consent so the CRM and Strapi both keep a proof:
77
+
78
+ 1. **In HubSpot** (Settings → Properties → Contact), create a checkbox
79
+ property, e.g. `rgpd_consent`. Optionally a datetime `rgpd_consented_at`.
80
+ 2. **In the form builder** (Admin → HubSpot Forms), last step: add a
81
+ **Checkbox**, name it `consent` (that name is what the site recognizes —
82
+ it will not inject a second box), mark it **required**.
83
+ 3. **Map it**: Object = Contact, property = `rgpd_consent`. The visitor's tick
84
+ then lands on the contact. Leave `consentedAt` unmapped; the frontend
85
+ sends it in `meta.consentedAt`, stored on the submission and on the
86
+ timeline note.
87
+ 4. **Publish** the form. The public submit pipeline already refuses a payload
88
+ without consent when the site is in front (Nuxt BFF). Direct calls to
89
+ `/api/hubspot/forms/:slug/submit` still accept a form that has no consent
90
+ field — add the checkbox so required-field validation covers them too.
91
+
92
+ The newsletter block on the site is **not** a HubSpot form: it upserts the
93
+ email as a contact and writes a timeline note with the consent timestamp.
94
+ No form to create in the builder for that one.
95
+
69
96
  ### Browsing submissions
70
97
 
71
98
  The **Submissions** button on the forms list (also reachable from a form's
@@ -342,6 +369,24 @@ authenticated admin, since the picker needs it in the Content Manager.
342
369
  > reaches the browser, but treat it like any other secret in your database. Use
343
370
  > `HUBSPOT_API_KEY` if your deployment already manages secrets properly.
344
371
 
372
+ ## Stability & versioning
373
+
374
+ From 1.0 onward this plugin follows semver against a **frozen public
375
+ contract**, enforced by a dedicated test suite (`contract.test.ts`):
376
+
377
+ - the **definition document** (`version: 1`): steps → fields → `visibleIf`
378
+ conditions, the 12 operators, stable `stp_`/`fld_` ids;
379
+ - the **public API payloads**: `GET /forms/:slug` (rendering props served,
380
+ CRM mappings never), `POST /forms/:slug/submit` and its structured 422,
381
+ `GET /company-search`;
382
+ - the **submission conventions**: values keyed by field `name`, company
383
+ companion keys `<name>__siret` / `<name>__company`, honeypot `__hp`;
384
+ - the exported types (`strapi-plugin-hubspot/types`).
385
+
386
+ Anything else — admin UI, internals, HubSpot plumbing — may improve in minor
387
+ versions. Breaking any of the above means a major version, and a `version: 2`
388
+ definition would ship with an automatic migration from `version: 1`.
389
+
345
390
  ## Install
346
391
 
347
392
  ```bash
@@ -3,11 +3,14 @@ import { useParams, useSearchParams, Link, useNavigate, Routes, Route } from "re
3
3
  import * as React from "react";
4
4
  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
- import { Trash, Plus, ArrowClockwise, ArrowLeft, ArrowUp, ArrowDown, List, Duplicate, Download } from "@strapi/icons";
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-DA31TY2n.mjs";
9
- import { o as objectLabel } from "./objectLabels-Bl7sO5pb.mjs";
10
- import { u as useHubspotSchema } from "./useHubspotSchema-BlDilCHc.mjs";
8
+ import { g as getTranslation, P as PLUGIN_ID } from "./index-BE2ribUL.mjs";
9
+ import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from "@dnd-kit/core";
10
+ import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from "@dnd-kit/sortable";
11
+ import { CSS } from "@dnd-kit/utilities";
12
+ import { o as objectLabel } from "./objectLabels-DGJxYjKz.mjs";
13
+ import { u as useHubspotSchema } from "./useHubspotSchema-e31UsYb2.mjs";
11
14
  const OPERATORS = [
12
15
  "eq",
13
16
  "neq",
@@ -150,6 +153,48 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
150
153
  ] })
151
154
  ] });
152
155
  };
156
+ function SortableList({
157
+ ids,
158
+ onReorder,
159
+ children
160
+ }) {
161
+ const sensors = useSensors(
162
+ // An 8px activation distance keeps clicks (selection!) from starting drags.
163
+ useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
164
+ useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates })
165
+ );
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 }) });
175
+ }
176
+ function SortableItem({
177
+ id,
178
+ children
179
+ }) {
180
+ const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
181
+ id
182
+ });
183
+ return /* @__PURE__ */ jsx(
184
+ "div",
185
+ {
186
+ ref: setNodeRef,
187
+ style: {
188
+ transform: CSS.Transform.toString(transform),
189
+ transition,
190
+ opacity: isDragging ? 0.5 : void 0,
191
+ zIndex: isDragging ? 2 : void 0,
192
+ position: "relative"
193
+ },
194
+ children: children({ attributes, listeners })
195
+ }
196
+ );
197
+ }
153
198
  const FIELD_TYPES = [
154
199
  "text",
155
200
  "email",
@@ -672,6 +717,10 @@ const FormEditor = () => {
672
717
  }
673
718
  return next;
674
719
  });
720
+ function reorderByIds(items, ids) {
721
+ const byId = new Map(items.map((item) => [item.id, item]));
722
+ return ids.map((id) => byId.get(id)).filter((item) => Boolean(item));
723
+ }
675
724
  const moveStep = (stepIndex, delta) => patchDefinition((steps) => {
676
725
  const target = stepIndex + delta;
677
726
  if (target < 0 || target >= steps.length) return steps;
@@ -793,154 +842,202 @@ const FormEditor = () => {
793
842
  feedback && /* @__PURE__ */ jsx(Box, { paddingBottom: 4, children: /* @__PURE__ */ jsx(Typography, { textColor: feedback.tone === "success" ? "success600" : "danger600", children: feedback.text }) }),
794
843
  /* @__PURE__ */ jsxs(Flex, { gap: 6, alignItems: "flex-start", children: [
795
844
  /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
796
- definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsxs(
797
- Box,
845
+ /* @__PURE__ */ jsx(
846
+ SortableList,
798
847
  {
799
- background: "neutral0",
800
- hasRadius: true,
801
- shadow: "tableShadow",
802
- padding: 4,
803
- onClick: () => setSelection({ kind: "step", stepId: step.id }),
804
- style: { cursor: "pointer" },
805
- borderColor: selection.kind === "step" && selection.stepId === step.id ? "primary600" : void 0,
806
- borderStyle: selection.kind === "step" && selection.stepId === step.id ? "solid" : void 0,
807
- borderWidth: selection.kind === "step" && selection.stepId === step.id ? "1px" : void 0,
808
- children: [
809
- /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
810
- /* @__PURE__ */ jsxs(
811
- Flex,
848
+ ids: definition.steps.map((s) => s.id),
849
+ onReorder: (ids) => patchDefinition((steps) => reorderByIds(steps, ids)),
850
+ children: definition.steps.map((step, stepIndex) => /* @__PURE__ */ jsx(
851
+ SortableItem,
852
+ {
853
+ id: step.id,
854
+ children: (stepHandle) => /* @__PURE__ */ jsxs(
855
+ Box,
812
856
  {
813
- gap: 2,
814
- alignItems: "center",
857
+ background: "neutral0",
858
+ hasRadius: true,
859
+ shadow: "tableShadow",
860
+ padding: 4,
815
861
  onClick: () => setSelection({ kind: "step", stepId: step.id }),
816
862
  style: { cursor: "pointer" },
863
+ borderColor: selection.kind === "step" && selection.stepId === step.id ? "primary600" : void 0,
864
+ borderStyle: selection.kind === "step" && selection.stepId === step.id ? "solid" : void 0,
865
+ borderWidth: selection.kind === "step" && selection.stepId === step.id ? "1px" : void 0,
817
866
  children: [
818
- /* @__PURE__ */ jsxs(Typography, { variant: "delta", children: [
819
- t("editor.step", "Step {n}", { n: stepIndex + 1 }),
820
- step.title ? ` — ${step.title}` : ""
821
- ] }),
822
- step.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
823
- errors.some((e) => e.stepId === step.id) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: t("editor.flag-error", "To fix") })
824
- ]
825
- }
826
- ),
827
- /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
828
- /* @__PURE__ */ jsx(
829
- IconButton,
830
- {
831
- label: t("editor.step-up", "Move the step up"),
832
- onClick: () => moveStep(stepIndex, -1),
833
- disabled: stepIndex === 0,
834
- children: /* @__PURE__ */ jsx(ArrowUp, {})
835
- }
836
- ),
837
- /* @__PURE__ */ jsx(
838
- IconButton,
839
- {
840
- label: t("editor.step-down", "Move the step down"),
841
- onClick: () => moveStep(stepIndex, 1),
842
- disabled: stepIndex === definition.steps.length - 1,
843
- children: /* @__PURE__ */ jsx(ArrowDown, {})
844
- }
845
- ),
846
- /* @__PURE__ */ jsx(
847
- IconButton,
848
- {
849
- label: t("editor.step-delete", "Delete the step and its fields"),
850
- onClick: () => {
851
- setSelection({ kind: "form" });
852
- patchDefinition((steps) => steps.filter((s) => s.id !== step.id));
853
- },
854
- children: /* @__PURE__ */ jsx(Trash, {})
855
- }
856
- )
857
- ] })
858
- ] }),
859
- /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
860
- step.fields.map((field, fieldIndex) => {
861
- const flags = fieldFlags(field.id);
862
- const isSelected = selection.kind === "field" && selection.fieldId === field.id;
863
- return /* @__PURE__ */ jsx(
864
- Box,
865
- {
866
- background: isSelected ? "primary100" : "neutral100",
867
- hasRadius: true,
868
- padding: 3,
869
- onClick: (e) => {
870
- e.stopPropagation();
871
- setSelection({ kind: "field", fieldId: field.id });
872
- },
873
- style: { cursor: "pointer" },
874
- children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
875
- /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
876
- /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
877
- /* @__PURE__ */ jsx(Badge, { children: field.type }),
878
- field.required && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
879
- field.hubspot?.property && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
880
- field.hubspot.object ?? "contact",
881
- ".",
882
- field.hubspot.property
883
- ] }),
884
- field.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
885
- (flags.error || flags.problem) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
886
- ] }),
867
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 2, children: [
868
+ /* @__PURE__ */ jsxs(
869
+ Flex,
870
+ {
871
+ gap: 2,
872
+ alignItems: "center",
873
+ onClick: () => setSelection({ kind: "step", stepId: step.id }),
874
+ style: { cursor: "pointer" },
875
+ children: [
876
+ /* @__PURE__ */ jsx(
877
+ "span",
878
+ {
879
+ ...stepHandle.attributes,
880
+ ...stepHandle.listeners,
881
+ onClick: (e) => e.stopPropagation(),
882
+ style: { cursor: "grab", display: "inline-flex", color: "inherit" },
883
+ "aria-label": t("editor.drag-step", "Drag to reorder the step"),
884
+ children: /* @__PURE__ */ jsx(Drag, {})
885
+ }
886
+ ),
887
+ /* @__PURE__ */ jsxs(Typography, { variant: "delta", children: [
888
+ t("editor.step", "Step {n}", { n: stepIndex + 1 }),
889
+ step.title ? ` — ${step.title}` : ""
890
+ ] }),
891
+ step.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
892
+ errors.some((e) => e.stepId === step.id) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: t("editor.flag-error", "To fix") })
893
+ ]
894
+ }
895
+ ),
887
896
  /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
888
897
  /* @__PURE__ */ jsx(
889
898
  IconButton,
890
899
  {
891
- label: t("editor.field-up", "Move up"),
892
- onClick: () => moveField(stepIndex, fieldIndex, -1),
893
- disabled: stepIndex === 0 && fieldIndex === 0,
900
+ label: t("editor.step-up", "Move the step up"),
901
+ onClick: () => moveStep(stepIndex, -1),
902
+ disabled: stepIndex === 0,
894
903
  children: /* @__PURE__ */ jsx(ArrowUp, {})
895
904
  }
896
905
  ),
897
906
  /* @__PURE__ */ jsx(
898
907
  IconButton,
899
908
  {
900
- label: t("editor.field-down", "Move down"),
901
- onClick: () => moveField(stepIndex, fieldIndex, 1),
902
- disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
909
+ label: t("editor.step-down", "Move the step down"),
910
+ onClick: () => moveStep(stepIndex, 1),
911
+ disabled: stepIndex === definition.steps.length - 1,
903
912
  children: /* @__PURE__ */ jsx(ArrowDown, {})
904
913
  }
905
914
  ),
906
915
  /* @__PURE__ */ jsx(
907
916
  IconButton,
908
917
  {
909
- label: t("editor.field-delete", "Delete the field"),
918
+ label: t("editor.step-delete", "Delete the step and its fields"),
910
919
  onClick: () => {
911
920
  setSelection({ kind: "form" });
912
- patchStep(step.id, {
913
- fields: step.fields.filter((f) => f.id !== field.id)
914
- });
921
+ patchDefinition((steps) => steps.filter((s) => s.id !== step.id));
915
922
  },
916
923
  children: /* @__PURE__ */ jsx(Trash, {})
917
924
  }
918
925
  )
919
926
  ] })
927
+ ] }),
928
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
929
+ /* @__PURE__ */ jsx(
930
+ SortableList,
931
+ {
932
+ ids: step.fields.map((f) => f.id),
933
+ onReorder: (ids) => patchStep(step.id, { fields: reorderByIds(step.fields, ids) }),
934
+ children: step.fields.map((field, fieldIndex) => {
935
+ const flags = fieldFlags(field.id);
936
+ const isSelected = selection.kind === "field" && selection.fieldId === field.id;
937
+ return /* @__PURE__ */ jsx(
938
+ SortableItem,
939
+ {
940
+ id: field.id,
941
+ children: (fieldHandle) => /* @__PURE__ */ jsx(
942
+ Box,
943
+ {
944
+ background: isSelected ? "primary100" : "neutral100",
945
+ hasRadius: true,
946
+ padding: 3,
947
+ onClick: (e) => {
948
+ e.stopPropagation();
949
+ setSelection({ kind: "field", fieldId: field.id });
950
+ },
951
+ style: { cursor: "pointer" },
952
+ children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
953
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
954
+ /* @__PURE__ */ jsx(
955
+ "span",
956
+ {
957
+ ...fieldHandle.attributes,
958
+ ...fieldHandle.listeners,
959
+ onClick: (e) => e.stopPropagation(),
960
+ style: { cursor: "grab", display: "inline-flex" },
961
+ "aria-label": t("editor.drag-field", "Drag to reorder the field"),
962
+ children: /* @__PURE__ */ jsx(Drag, {})
963
+ }
964
+ ),
965
+ /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: field.label || field.name || "—" }),
966
+ /* @__PURE__ */ jsx(Badge, { children: field.type }),
967
+ field.required && /* @__PURE__ */ jsx(Badge, { backgroundColor: "neutral150", textColor: "neutral600", children: t("editor.required", "required") }),
968
+ field.hubspot?.property && /* @__PURE__ */ jsxs(Badge, { backgroundColor: "primary100", textColor: "primary700", children: [
969
+ field.hubspot.object ?? "contact",
970
+ ".",
971
+ field.hubspot.property
972
+ ] }),
973
+ field.visibleIf?.rules?.length ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "alternative100", textColor: "alternative700", children: t("editor.conditional", "Conditional") }) : null,
974
+ (flags.error || flags.problem) && /* @__PURE__ */ jsx(Badge, { backgroundColor: "danger100", textColor: "danger700", children: flags.error?.code ?? flags.problem?.code })
975
+ ] }),
976
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
977
+ /* @__PURE__ */ jsx(
978
+ IconButton,
979
+ {
980
+ label: t("editor.field-up", "Move up"),
981
+ onClick: () => moveField(stepIndex, fieldIndex, -1),
982
+ disabled: stepIndex === 0 && fieldIndex === 0,
983
+ children: /* @__PURE__ */ jsx(ArrowUp, {})
984
+ }
985
+ ),
986
+ /* @__PURE__ */ jsx(
987
+ IconButton,
988
+ {
989
+ label: t("editor.field-down", "Move down"),
990
+ onClick: () => moveField(stepIndex, fieldIndex, 1),
991
+ disabled: stepIndex === definition.steps.length - 1 && fieldIndex === step.fields.length - 1,
992
+ children: /* @__PURE__ */ jsx(ArrowDown, {})
993
+ }
994
+ ),
995
+ /* @__PURE__ */ jsx(
996
+ IconButton,
997
+ {
998
+ label: t("editor.field-delete", "Delete the field"),
999
+ onClick: () => {
1000
+ setSelection({ kind: "form" });
1001
+ patchStep(step.id, {
1002
+ fields: step.fields.filter((f) => f.id !== field.id)
1003
+ });
1004
+ },
1005
+ children: /* @__PURE__ */ jsx(Trash, {})
1006
+ }
1007
+ )
1008
+ ] })
1009
+ ] })
1010
+ }
1011
+ )
1012
+ },
1013
+ field.id
1014
+ );
1015
+ })
1016
+ }
1017
+ ),
1018
+ /* @__PURE__ */ jsx(
1019
+ Button,
1020
+ {
1021
+ variant: "tertiary",
1022
+ startIcon: /* @__PURE__ */ jsx(Plus, {}),
1023
+ onClick: (e) => {
1024
+ e.stopPropagation();
1025
+ const field = newField(t("editor.new-field", "New field"));
1026
+ patchStep(step.id, { fields: [...step.fields, field] });
1027
+ setSelection({ kind: "field", fieldId: field.id });
1028
+ },
1029
+ children: t("editor.add-field", "Add a field")
1030
+ }
1031
+ )
920
1032
  ] })
921
- },
922
- field.id
923
- );
924
- }),
925
- /* @__PURE__ */ jsx(
926
- Button,
927
- {
928
- variant: "tertiary",
929
- startIcon: /* @__PURE__ */ jsx(Plus, {}),
930
- onClick: (e) => {
931
- e.stopPropagation();
932
- const field = newField(t("editor.new-field", "New field"));
933
- patchStep(step.id, { fields: [...step.fields, field] });
934
- setSelection({ kind: "field", fieldId: field.id });
935
- },
936
- children: t("editor.add-field", "Add a field")
1033
+ ]
937
1034
  }
938
1035
  )
939
- ] })
940
- ]
941
- },
942
- step.id
943
- )),
1036
+ },
1037
+ step.id
1038
+ ))
1039
+ }
1040
+ ),
944
1041
  /* @__PURE__ */ jsx(
945
1042
  Button,
946
1043
  {