strapi-plugin-hubspot 0.6.1 → 0.8.0

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 (36) hide show
  1. package/CHANGELOG.md +32 -0
  2. package/dist/_chunks/{Forms-B2y_0-x4.mjs → Forms-77Mornxt.mjs} +115 -18
  3. package/dist/_chunks/{Forms-CaT0YEJo.js → Forms-B8kkPY0c.js} +133 -36
  4. package/dist/_chunks/HubspotFormInput-BVknnlmR.mjs +61 -0
  5. package/dist/_chunks/HubspotFormInput-kx3LlQYF.js +79 -0
  6. package/dist/_chunks/{HubspotObjectInput-DdLAUh0N.mjs → HubspotObjectInput-B-Y1PZ6c.mjs} +4 -3
  7. package/dist/_chunks/{HubspotObjectInput-Crpcpa8T.js → HubspotObjectInput-BOO1PvOj.js} +8 -7
  8. package/dist/_chunks/{HubspotPropertyInput-k7wMSmeF.mjs → HubspotPropertyInput-Drxn_Ddt.mjs} +4 -3
  9. package/dist/_chunks/{HubspotPropertyInput-SnJMFsII.js → HubspotPropertyInput-awfD1opH.js} +18 -17
  10. package/dist/_chunks/{Settings-BPAAD-QW.js → Settings-BT1NhvxO.js} +16 -15
  11. package/dist/_chunks/{Settings-BMOmj4NJ.mjs → Settings-DrujhMxS.mjs} +3 -2
  12. package/dist/_chunks/{en-C_-N8cjv.js → en-B8adhJhO.js} +17 -3
  13. package/dist/_chunks/{en-CJ9wYHgN.mjs → en-BYKKi1LK.mjs} +17 -3
  14. package/dist/_chunks/{fr-BHObPoUp.js → fr-DnKtGXaB.js} +17 -3
  15. package/dist/_chunks/{fr-DttDn-2Y.mjs → fr-DzgArgi3.mjs} +17 -3
  16. package/dist/_chunks/{objectLabels-C9ocOgVY.mjs → index-CEh8vkxY.mjs} +0 -14
  17. package/dist/_chunks/{objectLabels-VTt_-qTZ.js → index-DkTxsEqL.js} +0 -14
  18. package/dist/_chunks/{index-foVSGFWb.mjs → index-LrCSAHoH.mjs} +21 -5
  19. package/dist/_chunks/{index-D_CPrIHe.js → index-P7p8ASb6.js} +21 -5
  20. package/dist/_chunks/objectLabels-Dn-zriuZ.js +15 -0
  21. package/dist/_chunks/objectLabels-yNrnTOBo.mjs +16 -0
  22. package/dist/_chunks/{useHubspotSchema-B8SctALV.js → useHubspotSchema-BottzdRR.js} +1 -1
  23. package/dist/_chunks/{useHubspotSchema-Du3rLY_O.mjs → useHubspotSchema-DccG2KbZ.mjs} +1 -1
  24. package/dist/admin/index.js +1 -1
  25. package/dist/admin/index.mjs +1 -1
  26. package/dist/admin/src/builder/types.d.ts +1 -0
  27. package/dist/admin/src/components/HubspotFormInput.d.ts +29 -0
  28. package/dist/server/index.js +100 -8
  29. package/dist/server/index.mjs +100 -8
  30. package/dist/server/src/content-types.d.ts +3 -0
  31. package/dist/server/src/forms.d.ts +2 -0
  32. package/dist/server/src/formsAdmin.d.ts +13 -0
  33. package/dist/server/src/importLegacy.d.ts +1 -0
  34. package/dist/server/src/index.d.ts +5 -0
  35. package/dist/shared/types.d.ts +2 -0
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -1,5 +1,37 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.8.0 — 2026-08-29
4
+
5
+ ### Improved (builder UX pass, from a live session)
6
+ - **The whole step card now selects the step** — the click target was the tiny
7
+ "Step N" text, which read as "step titles can't be edited". Inner controls
8
+ stop propagation, so field selection and the action buttons are unaffected.
9
+ - Field panel gains the imported-but-invisible settings: **icon**, **answer
10
+ memory (persist)** and **CSS classes**; the step and form panels gain **CSS
11
+ classes** too.
12
+ - **Form-level `class`** now exists end to end: content-type attribute, save,
13
+ public API, legacy import (it used to be silently dropped on migration).
14
+ - **Duplicate** action on the forms list — every locale of the draft is
15
+ copied under a fresh slug.
16
+ - Leaving the editor with unsaved changes now warns before the tab closes.
17
+ - Cosmetics: the actions column no longer shows a raw translation key, and the
18
+ structure column pluralizes properly.
19
+
20
+ ## 0.7.0 — 2026-08-29
21
+
22
+ ### Added
23
+ - `hubspot.form-picker` custom field: a searchable select over the forms
24
+ built in the builder, storing the chosen form's **slug** as a plain string —
25
+ for host content that references a form (a hero block, a landing…).
26
+ Unpublished forms stay selectable but are flagged; a slug whose form was
27
+ deleted is kept and flagged instead of silently dropped; a fetch failure
28
+ degrades to free text. Fed by `GET /hubspot/forms-options`, open to any
29
+ authenticated admin (the builder itself stays RBAC-gated).
30
+
31
+ ### Fixed
32
+ - Removed an accidental self-dependency (`strapi-plugin-hubspot` listed in its
33
+ own `dependencies`) introduced by a stray `npm install`.
34
+
3
35
  ## 0.6.1 — 2026-08-28
4
36
 
5
37
  ### Fixed
@@ -1,12 +1,13 @@
1
1
  import { jsx, jsxs } from "react/jsx-runtime";
2
2
  import { useParams, useSearchParams, Link, useNavigate, Routes, Route } from "react-router-dom";
3
3
  import * as React from "react";
4
- import { u as useIntl, o as objectLabel } from "./objectLabels-C9ocOgVY.mjs";
4
+ import { u as useIntl } from "./index-CEh8vkxY.mjs";
5
5
  import { Typography, Flex, Box, SingleSelect, SingleSelectOption, TextInput, IconButton, Button, Field, Checkbox, Divider, Combobox, ComboboxOption, Loader, Badge, Textarea, Table, Thead, Tr, Th, Tbody, Td } from "@strapi/design-system";
6
- import { Trash, Plus, ArrowClockwise, ArrowLeft, ArrowUp, ArrowDown } from "@strapi/icons";
6
+ import { Trash, Plus, ArrowClockwise, ArrowLeft, ArrowUp, ArrowDown, Duplicate } from "@strapi/icons";
7
7
  import { useFetchClient } from "@strapi/strapi/admin";
8
- import { g as getTranslation, P as PLUGIN_ID } from "./index-foVSGFWb.mjs";
9
- import { u as useHubspotSchema } from "./useHubspotSchema-Du3rLY_O.mjs";
8
+ import { g as getTranslation, P as PLUGIN_ID } from "./index-LrCSAHoH.mjs";
9
+ import { o as objectLabel } from "./objectLabels-yNrnTOBo.mjs";
10
+ import { u as useHubspotSchema } from "./useHubspotSchema-DccG2KbZ.mjs";
10
11
  const OPERATORS = ["eq", "neq", "contains", "empty", "notEmpty", "gt", "lt"];
11
12
  const NEEDS_VALUE = /* @__PURE__ */ new Set(["eq", "neq", "contains", "gt", "lt"]);
12
13
  const ConditionEditor = ({ condition, candidates, onChange }) => {
@@ -223,6 +224,43 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
223
224
  children: t("panel.required", "Required")
224
225
  }
225
226
  ),
227
+ /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
228
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Field.Root, { children: [
229
+ /* @__PURE__ */ jsx(Field.Label, { children: t("panel.icon", "Icon") }),
230
+ /* @__PURE__ */ jsx(
231
+ TextInput,
232
+ {
233
+ value: field.icon ?? "",
234
+ placeholder: "i-lucide-building-2",
235
+ onChange: (e) => onChange({ icon: e.target.value })
236
+ }
237
+ )
238
+ ] }) }),
239
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Field.Root, { children: [
240
+ /* @__PURE__ */ jsx(Field.Label, { children: t("panel.persist", "Answer memory") }),
241
+ /* @__PURE__ */ jsxs(
242
+ SingleSelect,
243
+ {
244
+ value: field.persist ?? "session",
245
+ onChange: (v) => onChange({ persist: v }),
246
+ children: [
247
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "session", children: t("panel.persist-session", "This visit only") }),
248
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "days30", children: t("panel.persist-days30", "30 days (non-identifying fields only)") })
249
+ ]
250
+ }
251
+ )
252
+ ] }) })
253
+ ] }),
254
+ /* @__PURE__ */ jsxs(Field.Root, { children: [
255
+ /* @__PURE__ */ jsx(Field.Label, { children: t("panel.class", "CSS classes") }),
256
+ /* @__PURE__ */ jsx(
257
+ TextInput,
258
+ {
259
+ value: field.class ?? "",
260
+ onChange: (e) => onChange({ class: e.target.value })
261
+ }
262
+ )
263
+ ] }),
226
264
  isChoice && /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
227
265
  /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("panel.options", "Choices") }),
228
266
  (field.options ?? []).map((option, index) => /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
@@ -369,6 +407,14 @@ const FormEditor = () => {
369
407
  const [feedback, setFeedback] = React.useState(null);
370
408
  const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
371
409
  const query = locale ? `?locale=${locale}` : "";
410
+ React.useEffect(() => {
411
+ if (!dirty) return void 0;
412
+ const warn = (event) => {
413
+ event.preventDefault();
414
+ };
415
+ window.addEventListener("beforeunload", warn);
416
+ return () => window.removeEventListener("beforeunload", warn);
417
+ }, [dirty]);
372
418
  React.useEffect(() => {
373
419
  get("/i18n/locales").then(({ data }) => Array.isArray(data) && setLocales(data)).catch(() => setLocales([]));
374
420
  }, [get]);
@@ -461,6 +507,7 @@ const FormEditor = () => {
461
507
  nextLabel: entry.nextLabel,
462
508
  submitLabel: entry.submitLabel,
463
509
  successMessage: entry.successMessage,
510
+ class: entry.class,
464
511
  definition
465
512
  }
466
513
  );
@@ -566,6 +613,8 @@ const FormEditor = () => {
566
613
  hasRadius: true,
567
614
  shadow: "tableShadow",
568
615
  padding: 4,
616
+ onClick: () => setSelection({ kind: "step", stepId: step.id }),
617
+ style: { cursor: "pointer" },
569
618
  borderColor: selection.kind === "step" && selection.stepId === step.id ? "primary600" : void 0,
570
619
  borderStyle: selection.kind === "step" && selection.stepId === step.id ? "solid" : void 0,
571
620
  borderWidth: selection.kind === "step" && selection.stepId === step.id ? "1px" : void 0,
@@ -588,7 +637,7 @@ const FormEditor = () => {
588
637
  ]
589
638
  }
590
639
  ),
591
- /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
640
+ /* @__PURE__ */ jsxs(Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
592
641
  /* @__PURE__ */ jsx(
593
642
  IconButton,
594
643
  {
@@ -630,7 +679,10 @@ const FormEditor = () => {
630
679
  background: isSelected ? "primary100" : "neutral100",
631
680
  hasRadius: true,
632
681
  padding: 3,
633
- onClick: () => setSelection({ kind: "field", fieldId: field.id }),
682
+ onClick: (e) => {
683
+ e.stopPropagation();
684
+ setSelection({ kind: "field", fieldId: field.id });
685
+ },
634
686
  style: { cursor: "pointer" },
635
687
  children: /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
636
688
  /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
@@ -688,7 +740,8 @@ const FormEditor = () => {
688
740
  {
689
741
  variant: "tertiary",
690
742
  startIcon: /* @__PURE__ */ jsx(Plus, {}),
691
- onClick: () => {
743
+ onClick: (e) => {
744
+ e.stopPropagation();
692
745
  const field = newField(t("editor.new-field", "New field"));
693
746
  patchStep(step.id, { fields: [...step.fields, field] });
694
747
  setSelection({ kind: "field", fieldId: field.id });
@@ -745,6 +798,16 @@ const FormEditor = () => {
745
798
  }
746
799
  )
747
800
  ] }),
801
+ /* @__PURE__ */ jsxs(Field.Root, { children: [
802
+ /* @__PURE__ */ jsx(Field.Label, { children: t("panel.class", "CSS classes") }),
803
+ /* @__PURE__ */ jsx(
804
+ TextInput,
805
+ {
806
+ value: selectedStep.class ?? "",
807
+ onChange: (e) => patchStep(selectedStep.id, { class: e.target.value })
808
+ }
809
+ )
810
+ ] }),
748
811
  /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("editor.step-condition", "Show this step only when…") }),
749
812
  /* @__PURE__ */ jsx(
750
813
  ConditionEditor,
@@ -818,6 +881,16 @@ const FormEditor = () => {
818
881
  }
819
882
  )
820
883
  ] }),
884
+ /* @__PURE__ */ jsxs(Field.Root, { children: [
885
+ /* @__PURE__ */ jsx(Field.Label, { children: t("panel.class", "CSS classes") }),
886
+ /* @__PURE__ */ jsx(
887
+ TextInput,
888
+ {
889
+ value: entry.class ?? "",
890
+ onChange: (e) => patchEntry({ class: e.target.value })
891
+ }
892
+ )
893
+ ] }),
821
894
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
822
895
  "editor.form-hint",
823
896
  "Select a step or a field to edit it. The public endpoint serves this form at /api/hubspot/forms/{slug}.",
@@ -877,6 +950,19 @@ const FormsList = () => {
877
950
  setBusy(false);
878
951
  }
879
952
  };
953
+ const duplicate = async (row) => {
954
+ setBusy(true);
955
+ setError(null);
956
+ try {
957
+ const { data } = await post(
958
+ `/${PLUGIN_ID}/builder/forms/${row.documentId}/duplicate`
959
+ );
960
+ navigate(data.documentId);
961
+ } catch {
962
+ setError(t("forms.duplicate-error", "Could not duplicate the form."));
963
+ setBusy(false);
964
+ }
965
+ };
880
966
  const remove = async (row) => {
881
967
  if (!window.confirm(t("forms.delete-confirm", 'Delete "{name}" and all its locales?', { name: row.name }))) {
882
968
  return;
@@ -948,7 +1034,7 @@ const FormsList = () => {
948
1034
  /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: t("forms.col-structure", "Structure") }) }),
949
1035
  /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: t("forms.col-status", "Status") }) }),
950
1036
  /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: t("forms.col-updated", "Updated") }) }),
951
- /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: t("forms.col-actions", "") }) })
1037
+ /* @__PURE__ */ jsx(Th, { children: /* @__PURE__ */ jsx(Typography, { variant: "sigma", children: t("forms.col-actions", "Actions") }) })
952
1038
  ] }) }),
953
1039
  /* @__PURE__ */ jsx(Tbody, { children: rows.map((row) => /* @__PURE__ */ jsxs(
954
1040
  Tr,
@@ -958,21 +1044,32 @@ const FormsList = () => {
958
1044
  children: [
959
1045
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { fontWeight: "semiBold", children: row.name }) }),
960
1046
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: row.slug }) }),
961
- /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: t("forms.structure", "{steps} steps · {fields} fields", {
1047
+ /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: t("forms.structure", "{steps, plural, one {# step} other {# steps}} · {fields, plural, one {# field} other {# fields}}", {
962
1048
  steps: row.steps,
963
1049
  fields: row.fields
964
1050
  }) }) }),
965
1051
  /* @__PURE__ */ jsx(Td, { children: row.published ? /* @__PURE__ */ jsx(Badge, { backgroundColor: "success100", textColor: "success700", children: t("forms.published", "Published") }) : /* @__PURE__ */ jsx(Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: t("forms.draft", "Draft") }) }),
966
1052
  /* @__PURE__ */ jsx(Td, { children: /* @__PURE__ */ jsx(Typography, { textColor: "neutral600", children: row.updatedAt ? new Date(row.updatedAt).toLocaleString() : "—" }) }),
967
- /* @__PURE__ */ jsx(Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsx(
968
- IconButton,
969
- {
970
- label: t("forms.delete", "Delete"),
971
- onClick: () => remove(row),
972
- disabled: busy,
973
- children: /* @__PURE__ */ jsx(Trash, {})
974
- }
975
- ) })
1053
+ /* @__PURE__ */ jsx(Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxs(Flex, { gap: 1, children: [
1054
+ /* @__PURE__ */ jsx(
1055
+ IconButton,
1056
+ {
1057
+ label: t("forms.duplicate", "Duplicate"),
1058
+ onClick: () => duplicate(row),
1059
+ disabled: busy,
1060
+ children: /* @__PURE__ */ jsx(Duplicate, {})
1061
+ }
1062
+ ),
1063
+ /* @__PURE__ */ jsx(
1064
+ IconButton,
1065
+ {
1066
+ label: t("forms.delete", "Delete"),
1067
+ onClick: () => remove(row),
1068
+ disabled: busy,
1069
+ children: /* @__PURE__ */ jsx(Trash, {})
1070
+ }
1071
+ )
1072
+ ] }) })
976
1073
  ]
977
1074
  },
978
1075
  row.documentId
@@ -3,12 +3,13 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
3
3
  const jsxRuntime = require("react/jsx-runtime");
4
4
  const reactRouterDom = require("react-router-dom");
5
5
  const React = require("react");
6
- const objectLabels = require("./objectLabels-VTt_-qTZ.js");
6
+ 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 = require("./index-D_CPrIHe.js");
11
- const useHubspotSchema = require("./useHubspotSchema-B8SctALV.js");
10
+ const index$1 = require("./index-P7p8ASb6.js");
11
+ const objectLabels = require("./objectLabels-Dn-zriuZ.js");
12
+ const useHubspotSchema = require("./useHubspotSchema-BottzdRR.js");
12
13
  function _interopNamespace(e) {
13
14
  if (e && e.__esModule) return e;
14
15
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -30,8 +31,8 @@ const React__namespace = /* @__PURE__ */ _interopNamespace(React);
30
31
  const OPERATORS = ["eq", "neq", "contains", "empty", "notEmpty", "gt", "lt"];
31
32
  const NEEDS_VALUE = /* @__PURE__ */ new Set(["eq", "neq", "contains", "gt", "lt"]);
32
33
  const ConditionEditor = ({ condition, candidates, onChange }) => {
33
- const { formatMessage } = objectLabels.useIntl();
34
- const t = (id, defaultMessage) => formatMessage({ id: index.getTranslation(id), defaultMessage });
34
+ const { formatMessage } = index.useIntl();
35
+ const t = (id, defaultMessage) => formatMessage({ id: index$1.getTranslation(id), defaultMessage });
35
36
  const opLabel = {
36
37
  eq: t("condition.op.eq", "is"),
37
38
  neq: t("condition.op.neq", "is not"),
@@ -146,10 +147,10 @@ function fieldsBefore(definition, beforeId) {
146
147
  }
147
148
  const slugifyName = (label) => label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
148
149
  const FieldPanel = ({ field, candidates, problem, onChange }) => {
149
- const intl = objectLabels.useIntl();
150
+ const intl = index.useIntl();
150
151
  const { formatMessage } = intl;
151
152
  const { schema, refresh, refreshing } = useHubspotSchema.useHubspotSchema();
152
- const t = (id, defaultMessage, values) => formatMessage({ id: index.getTranslation(id), defaultMessage }, values);
153
+ const t = (id, defaultMessage, values) => formatMessage({ id: index$1.getTranslation(id), defaultMessage }, values);
153
154
  const object = field.hubspot?.object || "contact";
154
155
  const properties = (schema?.properties ?? []).filter((p) => p.object === object);
155
156
  const selected = properties.find((p) => p.name === field.hubspot?.property);
@@ -243,6 +244,43 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
243
244
  children: t("panel.required", "Required")
244
245
  }
245
246
  ),
247
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
248
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
249
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.icon", "Icon") }),
250
+ /* @__PURE__ */ jsxRuntime.jsx(
251
+ designSystem.TextInput,
252
+ {
253
+ value: field.icon ?? "",
254
+ placeholder: "i-lucide-building-2",
255
+ onChange: (e) => onChange({ icon: e.target.value })
256
+ }
257
+ )
258
+ ] }) }),
259
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
260
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.persist", "Answer memory") }),
261
+ /* @__PURE__ */ jsxRuntime.jsxs(
262
+ designSystem.SingleSelect,
263
+ {
264
+ value: field.persist ?? "session",
265
+ onChange: (v) => onChange({ persist: v }),
266
+ children: [
267
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "session", children: t("panel.persist-session", "This visit only") }),
268
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "days30", children: t("panel.persist-days30", "30 days (non-identifying fields only)") })
269
+ ]
270
+ }
271
+ )
272
+ ] }) })
273
+ ] }),
274
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
275
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.class", "CSS classes") }),
276
+ /* @__PURE__ */ jsxRuntime.jsx(
277
+ designSystem.TextInput,
278
+ {
279
+ value: field.class ?? "",
280
+ onChange: (e) => onChange({ class: e.target.value })
281
+ }
282
+ )
283
+ ] }),
246
284
  isChoice && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
247
285
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: t("panel.options", "Choices") }),
248
286
  (field.options ?? []).map((option, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
@@ -373,7 +411,7 @@ const newField = (label) => ({
373
411
  });
374
412
  const newStep = () => ({ id: makeId("stp"), title: "", fields: [] });
375
413
  const FormEditor = () => {
376
- const { formatMessage } = objectLabels.useIntl();
414
+ const { formatMessage } = index.useIntl();
377
415
  const { get, post, put } = admin.useFetchClient();
378
416
  const { documentId } = reactRouterDom.useParams();
379
417
  const [searchParams, setSearchParams] = reactRouterDom.useSearchParams();
@@ -387,8 +425,16 @@ const FormEditor = () => {
387
425
  const [errors, setErrors] = React__namespace.useState([]);
388
426
  const [problems, setProblems] = React__namespace.useState([]);
389
427
  const [feedback, setFeedback] = React__namespace.useState(null);
390
- const t = (id, defaultMessage, values) => formatMessage({ id: index.getTranslation(id), defaultMessage }, values);
428
+ const t = (id, defaultMessage, values) => formatMessage({ id: index$1.getTranslation(id), defaultMessage }, values);
391
429
  const query = locale ? `?locale=${locale}` : "";
430
+ React__namespace.useEffect(() => {
431
+ if (!dirty) return void 0;
432
+ const warn = (event) => {
433
+ event.preventDefault();
434
+ };
435
+ window.addEventListener("beforeunload", warn);
436
+ return () => window.removeEventListener("beforeunload", warn);
437
+ }, [dirty]);
392
438
  React__namespace.useEffect(() => {
393
439
  get("/i18n/locales").then(({ data }) => Array.isArray(data) && setLocales(data)).catch(() => setLocales([]));
394
440
  }, [get]);
@@ -399,7 +445,7 @@ const FormEditor = () => {
399
445
  setErrors([]);
400
446
  setProblems([]);
401
447
  get(
402
- `/${index.PLUGIN_ID}/builder/forms/${documentId}${query}`
448
+ `/${index$1.PLUGIN_ID}/builder/forms/${documentId}${query}`
403
449
  ).then(({ data }) => {
404
450
  if (cancelled) return;
405
451
  setEntry(data.form);
@@ -409,7 +455,7 @@ const FormEditor = () => {
409
455
  if (cancelled || !locale) return;
410
456
  try {
411
457
  const { data } = await get(
412
- `/${index.PLUGIN_ID}/builder/forms/${documentId}`
458
+ `/${index$1.PLUGIN_ID}/builder/forms/${documentId}`
413
459
  );
414
460
  if (cancelled) return;
415
461
  setEntry({ ...data.form, locale });
@@ -473,7 +519,7 @@ const FormEditor = () => {
473
519
  setErrors([]);
474
520
  try {
475
521
  const { data } = await put(
476
- `/${index.PLUGIN_ID}/builder/forms/${documentId}${query}`,
522
+ `/${index$1.PLUGIN_ID}/builder/forms/${documentId}${query}`,
477
523
  {
478
524
  name: entry.name,
479
525
  title: entry.title,
@@ -481,6 +527,7 @@ const FormEditor = () => {
481
527
  nextLabel: entry.nextLabel,
482
528
  submitLabel: entry.submitLabel,
483
529
  successMessage: entry.successMessage,
530
+ class: entry.class,
484
531
  definition
485
532
  }
486
533
  );
@@ -506,7 +553,7 @@ const FormEditor = () => {
506
553
  setBusy(true);
507
554
  setFeedback(null);
508
555
  try {
509
- await post(`/${index.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
556
+ await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
510
557
  setPublished(true);
511
558
  setErrors([]);
512
559
  setProblems([]);
@@ -526,7 +573,7 @@ const FormEditor = () => {
526
573
  const unpublish = async () => {
527
574
  setBusy(true);
528
575
  try {
529
- await post(`/${index.PLUGIN_ID}/builder/forms/${documentId}/unpublish${query}`);
576
+ await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/unpublish${query}`);
530
577
  setPublished(false);
531
578
  setFeedback({ tone: "success", text: t("editor.unpublished", "Form unpublished.") });
532
579
  } catch {
@@ -544,7 +591,7 @@ const FormEditor = () => {
544
591
  return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, children: [
545
592
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
546
593
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
547
- /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/plugins/${index.PLUGIN_ID}`, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label: t("editor.back", "Back to the list"), tag: "span", children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}) }) }),
594
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Link, { to: `/plugins/${index$1.PLUGIN_ID}`, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.IconButton, { label: t("editor.back", "Back to the list"), tag: "span", children: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowLeft, {}) }) }),
548
595
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
549
596
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
550
597
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h1", children: entry.name }),
@@ -586,6 +633,8 @@ const FormEditor = () => {
586
633
  hasRadius: true,
587
634
  shadow: "tableShadow",
588
635
  padding: 4,
636
+ onClick: () => setSelection({ kind: "step", stepId: step.id }),
637
+ style: { cursor: "pointer" },
589
638
  borderColor: selection.kind === "step" && selection.stepId === step.id ? "primary600" : void 0,
590
639
  borderStyle: selection.kind === "step" && selection.stepId === step.id ? "solid" : void 0,
591
640
  borderWidth: selection.kind === "step" && selection.stepId === step.id ? "1px" : void 0,
@@ -608,7 +657,7 @@ const FormEditor = () => {
608
657
  ]
609
658
  }
610
659
  ),
611
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
660
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, onClick: (e) => e.stopPropagation(), children: [
612
661
  /* @__PURE__ */ jsxRuntime.jsx(
613
662
  designSystem.IconButton,
614
663
  {
@@ -650,7 +699,10 @@ const FormEditor = () => {
650
699
  background: isSelected ? "primary100" : "neutral100",
651
700
  hasRadius: true,
652
701
  padding: 3,
653
- onClick: () => setSelection({ kind: "field", fieldId: field.id }),
702
+ onClick: (e) => {
703
+ e.stopPropagation();
704
+ setSelection({ kind: "field", fieldId: field.id });
705
+ },
654
706
  style: { cursor: "pointer" },
655
707
  children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
656
708
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", wrap: "wrap", children: [
@@ -708,7 +760,8 @@ const FormEditor = () => {
708
760
  {
709
761
  variant: "tertiary",
710
762
  startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
711
- onClick: () => {
763
+ onClick: (e) => {
764
+ e.stopPropagation();
712
765
  const field = newField(t("editor.new-field", "New field"));
713
766
  patchStep(step.id, { fields: [...step.fields, field] });
714
767
  setSelection({ kind: "field", fieldId: field.id });
@@ -765,6 +818,16 @@ const FormEditor = () => {
765
818
  }
766
819
  )
767
820
  ] }),
821
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
822
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.class", "CSS classes") }),
823
+ /* @__PURE__ */ jsxRuntime.jsx(
824
+ designSystem.TextInput,
825
+ {
826
+ value: selectedStep.class ?? "",
827
+ onChange: (e) => patchStep(selectedStep.id, { class: e.target.value })
828
+ }
829
+ )
830
+ ] }),
768
831
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: t("editor.step-condition", "Show this step only when…") }),
769
832
  /* @__PURE__ */ jsxRuntime.jsx(
770
833
  ConditionEditor,
@@ -838,6 +901,16 @@ const FormEditor = () => {
838
901
  }
839
902
  )
840
903
  ] }),
904
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
905
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.class", "CSS classes") }),
906
+ /* @__PURE__ */ jsxRuntime.jsx(
907
+ designSystem.TextInput,
908
+ {
909
+ value: entry.class ?? "",
910
+ onChange: (e) => patchEntry({ class: e.target.value })
911
+ }
912
+ )
913
+ ] }),
841
914
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
842
915
  "editor.form-hint",
843
916
  "Select a step or a field to edit it. The public endpoint serves this form at /api/hubspot/forms/{slug}.",
@@ -848,7 +921,7 @@ const FormEditor = () => {
848
921
  ] });
849
922
  };
850
923
  const FormsList = () => {
851
- const { formatMessage } = objectLabels.useIntl();
924
+ const { formatMessage } = index.useIntl();
852
925
  const { get, post, del } = admin.useFetchClient();
853
926
  const navigate = reactRouterDom.useNavigate();
854
927
  const [rows, setRows] = React__namespace.useState(null);
@@ -857,15 +930,15 @@ const FormsList = () => {
857
930
  const [error, setError] = React__namespace.useState(null);
858
931
  const [sources, setSources] = React__namespace.useState([]);
859
932
  const [sourceId, setSourceId] = React__namespace.useState("");
860
- const t = (id, defaultMessage, values) => formatMessage({ id: index.getTranslation(id), defaultMessage }, values);
933
+ const t = (id, defaultMessage, values) => formatMessage({ id: index$1.getTranslation(id), defaultMessage }, values);
861
934
  const load = React__namespace.useCallback(async () => {
862
- const { data } = await get(`/${index.PLUGIN_ID}/builder/forms`);
935
+ const { data } = await get(`/${index$1.PLUGIN_ID}/builder/forms`);
863
936
  setRows(data.forms);
864
937
  }, [get]);
865
938
  React__namespace.useEffect(() => {
866
939
  load().catch(() => setError(t("forms.load-error", "Could not load the forms.")));
867
940
  get(
868
- `/${index.PLUGIN_ID}/builder/import/sources`
941
+ `/${index$1.PLUGIN_ID}/builder/import/sources`
869
942
  ).then(({ data }) => setSources(data.sources ?? [])).catch(() => setSources([]));
870
943
  }, [load]);
871
944
  const runImport = async () => {
@@ -873,7 +946,7 @@ const FormsList = () => {
873
946
  setBusy(true);
874
947
  setError(null);
875
948
  try {
876
- const { data } = await post(`/${index.PLUGIN_ID}/builder/import`, {
949
+ const { data } = await post(`/${index$1.PLUGIN_ID}/builder/import`, {
877
950
  documentId: sourceId
878
951
  });
879
952
  navigate(data.documentId);
@@ -888,7 +961,7 @@ const FormsList = () => {
888
961
  setError(null);
889
962
  try {
890
963
  const { data } = await post(
891
- `/${index.PLUGIN_ID}/builder/forms`,
964
+ `/${index$1.PLUGIN_ID}/builder/forms`,
892
965
  { name: name.trim() }
893
966
  );
894
967
  navigate(data.form.documentId);
@@ -897,13 +970,26 @@ const FormsList = () => {
897
970
  setBusy(false);
898
971
  }
899
972
  };
973
+ const duplicate = async (row) => {
974
+ setBusy(true);
975
+ setError(null);
976
+ try {
977
+ const { data } = await post(
978
+ `/${index$1.PLUGIN_ID}/builder/forms/${row.documentId}/duplicate`
979
+ );
980
+ navigate(data.documentId);
981
+ } catch {
982
+ setError(t("forms.duplicate-error", "Could not duplicate the form."));
983
+ setBusy(false);
984
+ }
985
+ };
900
986
  const remove = async (row) => {
901
987
  if (!window.confirm(t("forms.delete-confirm", 'Delete "{name}" and all its locales?', { name: row.name }))) {
902
988
  return;
903
989
  }
904
990
  setBusy(true);
905
991
  try {
906
- await del(`/${index.PLUGIN_ID}/builder/forms/${row.documentId}`);
992
+ await del(`/${index$1.PLUGIN_ID}/builder/forms/${row.documentId}`);
907
993
  await load();
908
994
  } catch {
909
995
  setError(t("forms.delete-error", "Could not delete the form."));
@@ -968,7 +1054,7 @@ const FormsList = () => {
968
1054
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("forms.col-structure", "Structure") }) }),
969
1055
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("forms.col-status", "Status") }) }),
970
1056
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("forms.col-updated", "Updated") }) }),
971
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("forms.col-actions", "") }) })
1057
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: t("forms.col-actions", "Actions") }) })
972
1058
  ] }) }),
973
1059
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: rows.map((row) => /* @__PURE__ */ jsxRuntime.jsxs(
974
1060
  designSystem.Tr,
@@ -978,21 +1064,32 @@ const FormsList = () => {
978
1064
  children: [
979
1065
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: row.name }) }),
980
1066
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: row.slug }) }),
981
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("forms.structure", "{steps} steps · {fields} fields", {
1067
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: t("forms.structure", "{steps, plural, one {# step} other {# steps}} · {fields, plural, one {# field} other {# fields}}", {
982
1068
  steps: row.steps,
983
1069
  fields: row.fields
984
1070
  }) }) }),
985
1071
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: row.published ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "success100", textColor: "success700", children: t("forms.published", "Published") }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { backgroundColor: "secondary100", textColor: "secondary700", children: t("forms.draft", "Draft") }) }),
986
1072
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", children: row.updatedAt ? new Date(row.updatedAt).toLocaleString() : "—" }) }),
987
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsx(
988
- designSystem.IconButton,
989
- {
990
- label: t("forms.delete", "Delete"),
991
- onClick: () => remove(row),
992
- disabled: busy,
993
- children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
994
- }
995
- ) })
1073
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { onClick: (e) => e.stopPropagation(), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, children: [
1074
+ /* @__PURE__ */ jsxRuntime.jsx(
1075
+ designSystem.IconButton,
1076
+ {
1077
+ label: t("forms.duplicate", "Duplicate"),
1078
+ onClick: () => duplicate(row),
1079
+ disabled: busy,
1080
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Duplicate, {})
1081
+ }
1082
+ ),
1083
+ /* @__PURE__ */ jsxRuntime.jsx(
1084
+ designSystem.IconButton,
1085
+ {
1086
+ label: t("forms.delete", "Delete"),
1087
+ onClick: () => remove(row),
1088
+ disabled: busy,
1089
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
1090
+ }
1091
+ )
1092
+ ] }) })
996
1093
  ]
997
1094
  },
998
1095
  row.documentId