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.
- package/CHANGELOG.md +34 -0
- package/README.md +45 -0
- package/dist/_chunks/{Forms-DadTbe9Q.mjs → Forms-CHB-sKem.mjs} +218 -121
- package/dist/_chunks/{Forms-Dn2W6OS4.js → Forms-lrzuWzoB.js} +217 -120
- package/dist/_chunks/{HubspotFormInput-S_Z2t_DN.js → HubspotFormInput-Bb6it0nP.js} +1 -1
- package/dist/_chunks/{HubspotFormInput-NO59AtM_.mjs → HubspotFormInput-YI09keTC.mjs} +1 -1
- package/dist/_chunks/{HubspotObjectInput-DAo3FAMz.mjs → HubspotObjectInput-CzLKT9oj.mjs} +3 -3
- package/dist/_chunks/{HubspotObjectInput-BIgIa_Dm.js → HubspotObjectInput-D3A6xzb3.js} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-KX1Bhcsl.mjs → HubspotPropertyInput-DicskLcN.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-B-piLcIy.js → HubspotPropertyInput-Dss3eUlL.js} +3 -3
- package/dist/_chunks/{Settings-Dz0PEFOv.mjs → Settings-4CbX3SPK.mjs} +2 -2
- package/dist/_chunks/{Settings-DMu_QThB.js → Settings-Ch8tPZAV.js} +2 -2
- package/dist/_chunks/{en-CH5LSPkL.mjs → en-D8DJiSxd.mjs} +3 -1
- package/dist/_chunks/{en-BDGqW_yS.js → en-PYFibnDc.js} +3 -1
- package/dist/_chunks/{fr-65-RaB3w.mjs → fr-BMjixfXn.mjs} +3 -1
- package/dist/_chunks/{fr-C82LMqUM.js → fr-yHk4sSEl.js} +3 -1
- package/dist/_chunks/{index-B7Ru1fu5.js → index-BAvedd2m.js} +6 -6
- package/dist/_chunks/{index-DA31TY2n.mjs → index-BE2ribUL.mjs} +6 -6
- package/dist/_chunks/{objectLabels-FTh984_h.js → objectLabels-CycoU0zY.js} +1 -1
- package/dist/_chunks/{objectLabels-Bl7sO5pb.mjs → objectLabels-DGJxYjKz.mjs} +1 -1
- package/dist/_chunks/{useHubspotSchema-C2_PuXEm.js → useHubspotSchema-DRfaI6mF.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-BlDilCHc.mjs → useHubspotSchema-e31UsYb2.mjs} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/builder/Sortable.d.ts +25 -0
- package/dist/admin/src/builder/__tests__/CompanyMapEditor.test.d.ts +1 -0
- package/dist/admin/src/builder/__tests__/ConditionEditor.test.d.ts +1 -0
- package/dist/admin/src/builder/__tests__/testUtils.d.ts +4 -0
- package/dist/server/index.js +109 -42
- package/dist/server/index.mjs +109 -42
- package/dist/server/src/__tests__/contract.test.d.ts +1 -0
- package/dist/server/src/__tests__/rateLimit.test.d.ts +1 -0
- package/dist/server/src/index.d.ts +12 -3
- package/dist/server/src/rateLimit.d.ts +18 -0
- 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-
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
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
|
-
|
|
797
|
-
|
|
845
|
+
/* @__PURE__ */ jsx(
|
|
846
|
+
SortableList,
|
|
798
847
|
{
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
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
|
-
|
|
814
|
-
|
|
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(
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
|
|
826
|
-
|
|
827
|
-
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
|
|
846
|
-
|
|
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.
|
|
892
|
-
onClick: () =>
|
|
893
|
-
disabled: stepIndex === 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.
|
|
901
|
-
onClick: () =>
|
|
902
|
-
disabled: stepIndex === definition.steps.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.
|
|
918
|
+
label: t("editor.step-delete", "Delete the step and its fields"),
|
|
910
919
|
onClick: () => {
|
|
911
920
|
setSelection({ kind: "form" });
|
|
912
|
-
|
|
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
|
-
|
|
943
|
-
)
|
|
1036
|
+
},
|
|
1037
|
+
step.id
|
|
1038
|
+
))
|
|
1039
|
+
}
|
|
1040
|
+
),
|
|
944
1041
|
/* @__PURE__ */ jsx(
|
|
945
1042
|
Button,
|
|
946
1043
|
{
|