strapi-plugin-hubspot 0.13.1 → 0.15.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 +33 -0
  2. package/README.md +58 -32
  3. package/dist/_chunks/{Forms-CHB-sKem.mjs → Forms-B7eIm0Fi.mjs} +38 -3
  4. package/dist/_chunks/{Forms-lrzuWzoB.js → Forms-ClyI4MJ1.js} +38 -3
  5. package/dist/_chunks/{HubspotFormInput-Bb6it0nP.js → HubspotFormInput-Do7F83V6.js} +1 -1
  6. package/dist/_chunks/{HubspotFormInput-YI09keTC.mjs → HubspotFormInput-cZAiaAaq.mjs} +1 -1
  7. package/dist/_chunks/{HubspotObjectInput-D3A6xzb3.js → HubspotObjectInput-DWqk2C26.js} +3 -3
  8. package/dist/_chunks/{HubspotObjectInput-CzLKT9oj.mjs → HubspotObjectInput-ZZk_7hWq.mjs} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-DicskLcN.mjs → HubspotPropertyInput-5bkDGmGo.mjs} +3 -3
  10. package/dist/_chunks/{HubspotPropertyInput-Dss3eUlL.js → HubspotPropertyInput-v9B3wb1a.js} +3 -3
  11. package/dist/_chunks/{Settings-4CbX3SPK.mjs → Settings-QcFbm3s5.mjs} +132 -36
  12. package/dist/_chunks/{Settings-Ch8tPZAV.js → Settings-X3EHnTaO.js} +131 -35
  13. package/dist/_chunks/{en-D8DJiSxd.mjs → en-DMJeF7EN.mjs} +19 -6
  14. package/dist/_chunks/{en-PYFibnDc.js → en-ZHvaZLi7.js} +19 -6
  15. package/dist/_chunks/{fr-BMjixfXn.mjs → fr-CVAntLmc.mjs} +18 -5
  16. package/dist/_chunks/{fr-yHk4sSEl.js → fr-Ds_2EAzZ.js} +18 -5
  17. package/dist/_chunks/{index-BE2ribUL.mjs → index-BSJAhj8r.mjs} +6 -6
  18. package/dist/_chunks/{index-BAvedd2m.js → index-BpcilDy1.js} +6 -6
  19. package/dist/_chunks/{objectLabels-CycoU0zY.js → objectLabels-CYXXFpNB.js} +1 -1
  20. package/dist/_chunks/{objectLabels-DGJxYjKz.mjs → objectLabels-CrYRzsqe.mjs} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-DRfaI6mF.js → useHubspotSchema-Ba6xZUCL.js} +1 -1
  22. package/dist/_chunks/{useHubspotSchema-e31UsYb2.mjs → useHubspotSchema-CBF4GkmK.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/types.d.ts +2 -0
  26. package/dist/server/index.js +502 -197
  27. package/dist/server/index.mjs +502 -197
  28. package/dist/server/src/__tests__/hsforms.test.d.ts +1 -0
  29. package/dist/server/src/__tests__/settings.test.d.ts +1 -0
  30. package/dist/server/src/content-types.d.ts +8 -0
  31. package/dist/server/src/forms.d.ts +10 -6
  32. package/dist/server/src/hsforms.d.ts +75 -0
  33. package/dist/server/src/importHubspot.d.ts +1 -0
  34. package/dist/server/src/index.d.ts +17 -2
  35. package/dist/server/src/settings.d.ts +36 -5
  36. package/package.json +1 -1
package/CHANGELOG.md CHANGED
@@ -2,6 +2,39 @@
2
2
 
3
3
  ## Unreleased
4
4
 
5
+ ## 0.15.0 — 2026-09-21
6
+
7
+ ### Added — account settings in the admin
8
+ - **Settings → HubSpot** now holds the portal id, region (`eu1` / `na1`) and
9
+ the default marketing form (picked from the connected portal), not just the
10
+ private app token. Values saved here override `config/plugins.ts` and env.
11
+ Switching test → production is an admin change, not a deploy.
12
+
13
+ ## 0.14.0 — 2026-09-21
14
+
15
+ ### Changed — native HubSpot form conversions
16
+ - Submissions of a built form now post to HubSpot's **Forms API**
17
+ (`/submissions/v3/integration/secure/submit/{portalId}/{formGuid}`) when a
18
+ marketing form GUID is set. That is what HubSpot counts as a conversion:
19
+ Original Source, workflows, lead-center notifications, reporting.
20
+ - The GUID is configuration, not code: pick it in the builder (or set
21
+ `forms.defaultFormId`), and swap `portalId` / `region` when moving from a
22
+ test portal to production.
23
+ - The visitor's `hubspotutk` (`meta.hutk`) is forwarded as `context.hutk` so
24
+ attribution sticks to the session.
25
+ - Extra fields not declared on the HubSpot form are dropped (they 400 the
26
+ whole submit). GDPR `legalConsentOptions` is sent when the HubSpot form
27
+ has a consent block.
28
+ - **CRM contact upsert is the fallback** when no GUID is configured. Timeline
29
+ notes remain available (`forms.timelineNote`, default `true`) on that
30
+ fallback only — they are not form conversions.
31
+ - Company find-or-create (SIRET / domain) still runs after a successful
32
+ submit, once the contact is looked up by email.
33
+
34
+ ### Added
35
+ - Form attribute `hubspotFormId` and a picker in the builder.
36
+ - Plugin config `portalId`, `region` (`eu1` by default), `forms.defaultFormId`.
37
+
5
38
  ## 0.13.1 — 2026-09-21
6
39
 
7
40
  ### Added
package/README.md CHANGED
@@ -5,9 +5,10 @@
5
5
  Build multi-step lead forms in a dedicated admin page: fields mapped to your
6
6
  portal's *real* CRM properties through a searchable picker, conditional fields
7
7
  and steps (AND/OR rules re-evaluated server-side), a public API your frontend
8
- renders and posts to, and a submission pipeline that upserts the Contact,
9
- finds-or-creates the Company by corporate domain, drops a recap note on the
10
- timeline, and stores every submission in Strapi whatever the CRM's mood.
8
+ renders and posts to, and a submission pipeline that posts a **native HubSpot
9
+ form conversion** (Forms API + visitor `hutk`), finds-or-creates the Company
10
+ by corporate domain, and stores every submission in Strapi whatever the CRM's
11
+ mood.
11
12
 
12
13
  Under it, the safety net this plugin has always been: property pickers as
13
14
  custom fields for your own content types, save-time mapping validation, a
@@ -43,20 +44,28 @@ Permissions**):
43
44
  | `GET` | `/api/hubspot/forms/:slug?locale=` | The published form — meta, steps, fields, conditions. The CRM mapping is stripped: the browser never learns your property names |
44
45
  | `POST` | `/api/hubspot/forms/:slug/submit` | Validates (bounds, conditions, required), maps server-side, syncs HubSpot, stores the submission |
45
46
 
46
- The submit pipeline, in order: contact upsert (`email` is the find-or-create
47
- key) through the plugin's sending service — pre-validation against the portal
48
- schema (a stale mapping costs one answer, never the lead), retries, replay
49
- queue; then, when the email is on a corporate domain, company found-or-created
50
- by `domain` and associated to the contact; then a timeline note recapping the
51
- answers and the lead's origin. Company and note are best-effort and can be
52
- turned off:
47
+ The submit pipeline, in order: HubSpot **Forms API** (native conversion —
48
+ Original Source, workflows, lead-center notifications) when a marketing form
49
+ GUID is set on the builder form or via `forms.defaultFormId`; the visitor's
50
+ `hutk` is forwarded so attribution sticks. Then the contact is looked up by
51
+ email and, when the address is on a corporate domain, the company is
52
+ found-or-created and associated. A CRM contact upsert is the **fallback**
53
+ when no GUID is configured — it does not count as a form conversion.
54
+ Timeline notes are opt-in and only used on that fallback.
55
+
56
+ Portal id, region and form GUIDs are configuration. A test portal today,
57
+ production tomorrow: swap `portalId` / `region` / the GUID, no code change.
53
58
 
54
59
  ```ts
55
60
  hubspot: {
56
61
  config: {
62
+ apiKey: env("HUBSPOT_API_KEY"),
63
+ portalId: env("HUBSPOT_PORTAL_ID"), // test now, production later
64
+ region: env("HUBSPOT_REGION", "eu1"), // eu1 → api-eu1.hsforms.com
57
65
  forms: {
58
66
  companyFromDomain: true, // Company by corporate domain + association
59
- timelineNote: true, // recap note on the contact (and company)
67
+ timelineNote: false, // recap note on the CRM-upsert fallback only
68
+ defaultFormId: env("HUBSPOT_DEFAULT_FORM_ID", ""),
60
69
  },
61
70
  },
62
71
  }
@@ -69,9 +78,9 @@ lives in your database, not in HubSpot's availability.
69
78
  ### GDPR consent
70
79
 
71
80
  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.
81
+ HubSpot form skips it (you'll see "rebuild it as a field"). The Forms API
82
+ submit sends `legalConsentOptions` when the HubSpot marketing form has a
83
+ GDPR block and the visitor ticked consent.
75
84
 
76
85
  To connect consent so the CRM and Strapi both keep a proof:
77
86
 
@@ -81,17 +90,19 @@ To connect consent so the CRM and Strapi both keep a proof:
81
90
  **Checkbox**, name it `consent` (that name is what the site recognizes —
82
91
  it will not inject a second box), mark it **required**.
83
92
  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
93
+ then lands on the contact (if that property is on the HubSpot form) and in
94
+ `legalConsentOptions`. Leave `consentedAt` unmapped; the frontend sends it
95
+ in `meta.consentedAt`, stored on the submission.
96
+ 4. **Point the builder form at a HubSpot marketing form** (right-hand panel)
97
+ so the tick is a native conversion, not a CRM note.
98
+ 5. **Publish** the form. The public submit pipeline already refuses a payload
88
99
  without consent when the site is in front (Nuxt BFF). Direct calls to
89
100
  `/api/hubspot/forms/:slug/submit` still accept a form that has no consent
90
101
  field — add the checkbox so required-field validation covers them too.
91
102
 
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.
103
+ The frontend should also send `meta.hutk` (the `hubspotutk` cookie set by
104
+ HubSpot's tracking script) and `meta.pageUrl` so Original Source is Organic
105
+ Search / Direct / etc. rather than *Offline sources*.
95
106
 
96
107
  ### Browsing submissions
97
108
 
@@ -354,10 +365,17 @@ its equivalent per object you send to).
354
365
 
355
366
  ### A settings screen
356
367
 
357
- **Settings → HubSpot** holds the private app token. It is stored server-side and
358
- never returned to the browser: the UI only receives whether a key exists, where
359
- it comes from, and its last four characters. A "Test connection" button
360
- round-trips to HubSpot and reports how many properties it can read.
368
+ **Settings → HubSpot** holds the private app token **and** the HubSpot account
369
+ that receives conversions: portal id, region (`eu1` / `na1`), and the default
370
+ marketing form (picked from the connected portal). Values saved here override
371
+ `config/plugins.ts` and env — switching test → production is an admin change,
372
+ not a deploy.
373
+
374
+ The token is stored server-side and never returned to the browser: the UI only
375
+ receives whether a key exists, where it comes from, and its last four
376
+ characters. Portal id / region / form GUID are not secrets and round-trip so
377
+ they can be edited. A "Test connection" button round-trips to HubSpot and
378
+ reports how many properties it can read.
361
379
 
362
380
  Access is gated by a dedicated RBAC permission — **Settings →
363
381
  Roles → Plugins → Hubspot**. A role without it neither sees the settings link
@@ -402,6 +420,8 @@ export default ({ env }) => ({
402
420
  config: {
403
421
  // Optional — the key can also be set from Settings → HubSpot.
404
422
  apiKey: env("HUBSPOT_API_KEY", ""),
423
+ portalId: env("HUBSPOT_PORTAL_ID", ""),
424
+ region: env("HUBSPOT_REGION", "eu1"),
405
425
 
406
426
  // Optional — objects whose properties are offered.
407
427
  // Defaults to ["contact", "company"]. Standard names: contact, company,
@@ -448,6 +468,10 @@ Create a **private app** in HubSpot with a read scope per object you list:
448
468
  - `crm.schemas.contacts.read`
449
469
  - `crm.schemas.companies.read`
450
470
  - `crm.schemas.deals.read`, `crm.schemas.custom.read`… as needed
471
+ - `crm.objects.contacts.read` / `write`, `crm.objects.companies.read` / `write`
472
+ - `forms` — list portal forms (import + builder picker) and **submit** them
473
+ via the Forms API (native conversions). Without this scope the pipeline
474
+ falls back to a CRM upsert, which HubSpot treats as an offline source.
451
475
 
452
476
  An object the token can't read is **skipped, not fatal**: the picker keeps
453
477
  working for the others and explains which one is missing a scope. `oauth` is
@@ -457,16 +481,18 @@ turn on the *view in HubSpot* links.
457
481
  ### Regions
458
482
 
459
483
  The REST API is global: `api.hubapi.com` routes by token, whatever the portal's
460
- hosting region. The **web app is not** — an EU-hosted portal lives on
461
- `app-eu1.hubspot.com`. Deep links are built from the `uiDomain` HubSpot reports
462
- for your portal rather than a hardcoded host, so they point at the right region
463
- without any configuration.
484
+ hosting region. The **web app and the Forms API are not** — an EU-hosted portal
485
+ lives on `app-eu1.hubspot.com` and submits to `api-eu1.hsforms.com`. Set
486
+ `config.region` (`eu1`, `na1`, …) to match. Deep links are built from the
487
+ `uiDomain` HubSpot reports for your portal rather than a hardcoded host.
464
488
 
465
- The key is resolved in this order, first match wins:
489
+ The key, portal id, region and default form GUID are resolved in this order,
490
+ first match wins:
466
491
 
467
492
  1. saved from **Settings → HubSpot**
468
- 2. `config.apiKey` in `config/plugins.ts`
469
- 3. the `HUBSPOT_API_KEY` environment variable
493
+ 2. `config/plugins.ts` (`apiKey`, `portalId`, `region`, `forms.defaultFormId`)
494
+ 3. environment variables (`HUBSPOT_API_KEY`, `HUBSPOT_PORTAL_ID`,
495
+ `HUBSPOT_REGION`, `HUBSPOT_DEFAULT_FORM_ID`)
470
496
 
471
497
  ## Migrating an existing field
472
498
 
@@ -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-BE2ribUL.mjs";
8
+ import { g as getTranslation, P as PLUGIN_ID } from "./index-BSJAhj8r.mjs";
9
9
  import { useSensors, useSensor, PointerSensor, KeyboardSensor, DndContext, closestCenter } from "@dnd-kit/core";
10
10
  import { sortableKeyboardCoordinates, SortableContext, verticalListSortingStrategy, useSortable, arrayMove } from "@dnd-kit/sortable";
11
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";
12
+ import { o as objectLabel } from "./objectLabels-CrYRzsqe.mjs";
13
+ import { u as useHubspotSchema } from "./useHubspotSchema-CBF4GkmK.mjs";
14
14
  const OPERATORS = [
15
15
  "eq",
16
16
  "neq",
@@ -637,6 +637,7 @@ const FormEditor = () => {
637
637
  const [errors, setErrors] = React.useState([]);
638
638
  const [problems, setProblems] = React.useState([]);
639
639
  const [feedback, setFeedback] = React.useState(null);
640
+ const [hsForms, setHsForms] = React.useState([]);
640
641
  const t = (id, defaultMessage, values) => formatMessage({ id: getTranslation(id), defaultMessage }, values);
641
642
  const query = locale ? `?locale=${locale}` : "";
642
643
  React.useEffect(() => {
@@ -650,6 +651,9 @@ const FormEditor = () => {
650
651
  React.useEffect(() => {
651
652
  get("/i18n/locales").then(({ data }) => Array.isArray(data) && setLocales(data)).catch(() => setLocales([]));
652
653
  }, [get]);
654
+ React.useEffect(() => {
655
+ get(`/${PLUGIN_ID}/builder/import/hubspot`).then(({ data }) => setHsForms(Array.isArray(data.forms) ? data.forms : [])).catch(() => setHsForms([]));
656
+ }, [get]);
653
657
  React.useEffect(() => {
654
658
  let cancelled = false;
655
659
  setEntry(null);
@@ -744,6 +748,7 @@ const FormEditor = () => {
744
748
  submitLabel: entry.submitLabel,
745
749
  successMessage: entry.successMessage,
746
750
  class: entry.class,
751
+ hubspotFormId: entry.hubspotFormId ?? "",
747
752
  definition
748
753
  }
749
754
  );
@@ -1175,6 +1180,36 @@ const FormEditor = () => {
1175
1180
  }
1176
1181
  )
1177
1182
  ] }),
1183
+ /* @__PURE__ */ jsxs(Field.Root, { children: [
1184
+ /* @__PURE__ */ jsx(Field.Label, { children: t("editor.hubspot-form", "HubSpot marketing form") }),
1185
+ hsForms.length ? /* @__PURE__ */ jsxs(
1186
+ SingleSelect,
1187
+ {
1188
+ value: entry.hubspotFormId ?? "",
1189
+ placeholder: t(
1190
+ "editor.hubspot-form-placeholder",
1191
+ "Pick a form of the connected portal"
1192
+ ),
1193
+ onChange: (id) => patchEntry({ hubspotFormId: String(id) || null }),
1194
+ children: [
1195
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "", children: t("editor.hubspot-form-none", "None — CRM upsert only") }),
1196
+ entry.hubspotFormId && !hsForms.some((f) => f.id === entry.hubspotFormId) && /* @__PURE__ */ jsx(SingleSelectOption, { value: entry.hubspotFormId, children: entry.hubspotFormId }),
1197
+ hsForms.map((f) => /* @__PURE__ */ jsx(SingleSelectOption, { value: f.id, children: f.name }, f.id))
1198
+ ]
1199
+ }
1200
+ ) : /* @__PURE__ */ jsx(
1201
+ TextInput,
1202
+ {
1203
+ value: entry.hubspotFormId ?? "",
1204
+ placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
1205
+ onChange: (e) => patchEntry({ hubspotFormId: e.target.value.trim() || null })
1206
+ }
1207
+ ),
1208
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1209
+ "editor.hubspot-form-hint",
1210
+ "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."
1211
+ ) })
1212
+ ] }),
1178
1213
  /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
1179
1214
  "editor.form-hint",
1180
1215
  "Select a step or a field to edit it. The public endpoint serves this form at /api/hubspot/forms/{slug}.",
@@ -7,12 +7,12 @@ const index = require("./index-DkTxsEqL.js");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const admin = require("@strapi/strapi/admin");
10
- const index$1 = require("./index-BAvedd2m.js");
10
+ const index$1 = require("./index-BpcilDy1.js");
11
11
  const core = require("@dnd-kit/core");
12
12
  const sortable = require("@dnd-kit/sortable");
13
13
  const utilities = require("@dnd-kit/utilities");
14
- const objectLabels = require("./objectLabels-CycoU0zY.js");
15
- const useHubspotSchema = require("./useHubspotSchema-DRfaI6mF.js");
14
+ const objectLabels = require("./objectLabels-CYXXFpNB.js");
15
+ const useHubspotSchema = require("./useHubspotSchema-Ba6xZUCL.js");
16
16
  function _interopNamespace(e) {
17
17
  if (e && e.__esModule) return e;
18
18
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -657,6 +657,7 @@ const FormEditor = () => {
657
657
  const [errors, setErrors] = React__namespace.useState([]);
658
658
  const [problems, setProblems] = React__namespace.useState([]);
659
659
  const [feedback, setFeedback] = React__namespace.useState(null);
660
+ const [hsForms, setHsForms] = React__namespace.useState([]);
660
661
  const t = (id, defaultMessage, values) => formatMessage({ id: index$1.getTranslation(id), defaultMessage }, values);
661
662
  const query = locale ? `?locale=${locale}` : "";
662
663
  React__namespace.useEffect(() => {
@@ -670,6 +671,9 @@ const FormEditor = () => {
670
671
  React__namespace.useEffect(() => {
671
672
  get("/i18n/locales").then(({ data }) => Array.isArray(data) && setLocales(data)).catch(() => setLocales([]));
672
673
  }, [get]);
674
+ React__namespace.useEffect(() => {
675
+ get(`/${index$1.PLUGIN_ID}/builder/import/hubspot`).then(({ data }) => setHsForms(Array.isArray(data.forms) ? data.forms : [])).catch(() => setHsForms([]));
676
+ }, [get]);
673
677
  React__namespace.useEffect(() => {
674
678
  let cancelled = false;
675
679
  setEntry(null);
@@ -764,6 +768,7 @@ const FormEditor = () => {
764
768
  submitLabel: entry.submitLabel,
765
769
  successMessage: entry.successMessage,
766
770
  class: entry.class,
771
+ hubspotFormId: entry.hubspotFormId ?? "",
767
772
  definition
768
773
  }
769
774
  );
@@ -1195,6 +1200,36 @@ const FormEditor = () => {
1195
1200
  }
1196
1201
  )
1197
1202
  ] }),
1203
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
1204
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("editor.hubspot-form", "HubSpot marketing form") }),
1205
+ hsForms.length ? /* @__PURE__ */ jsxRuntime.jsxs(
1206
+ designSystem.SingleSelect,
1207
+ {
1208
+ value: entry.hubspotFormId ?? "",
1209
+ placeholder: t(
1210
+ "editor.hubspot-form-placeholder",
1211
+ "Pick a form of the connected portal"
1212
+ ),
1213
+ onChange: (id) => patchEntry({ hubspotFormId: String(id) || null }),
1214
+ children: [
1215
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "", children: t("editor.hubspot-form-none", "None — CRM upsert only") }),
1216
+ entry.hubspotFormId && !hsForms.some((f) => f.id === entry.hubspotFormId) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: entry.hubspotFormId, children: entry.hubspotFormId }),
1217
+ hsForms.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: f.id, children: f.name }, f.id))
1218
+ ]
1219
+ }
1220
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
1221
+ designSystem.TextInput,
1222
+ {
1223
+ value: entry.hubspotFormId ?? "",
1224
+ placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
1225
+ onChange: (e) => patchEntry({ hubspotFormId: e.target.value.trim() || null })
1226
+ }
1227
+ ),
1228
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
1229
+ "editor.hubspot-form-hint",
1230
+ "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."
1231
+ ) })
1232
+ ] }),
1198
1233
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
1199
1234
  "editor.form-hint",
1200
1235
  "Select a step or a field to edit it. The public endpoint serves this form at /api/hubspot/forms/{slug}.",
@@ -5,7 +5,7 @@ const React = require("react");
5
5
  const index = require("./index-DkTxsEqL.js");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const admin = require("@strapi/strapi/admin");
8
- const index$1 = require("./index-BAvedd2m.js");
8
+ const index$1 = require("./index-BpcilDy1.js");
9
9
  function _interopNamespace(e) {
10
10
  if (e && e.__esModule) return e;
11
11
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -3,7 +3,7 @@ import * as React from "react";
3
3
  import { u as useIntl } from "./index-CEh8vkxY.mjs";
4
4
  import { Field, Flex, Loader, Combobox, ComboboxOption } from "@strapi/design-system";
5
5
  import { useFetchClient } from "@strapi/strapi/admin";
6
- import { P as PLUGIN_ID, g as getTranslation } from "./index-BE2ribUL.mjs";
6
+ import { P as PLUGIN_ID, g as getTranslation } from "./index-BSJAhj8r.mjs";
7
7
  const HubspotFormInput = React.forwardRef(
8
8
  ({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
9
9
  const { formatMessage } = useIntl();
@@ -4,9 +4,9 @@ const jsxRuntime = require("react/jsx-runtime");
4
4
  const React = require("react");
5
5
  const index = require("./index-DkTxsEqL.js");
6
6
  const designSystem = require("@strapi/design-system");
7
- const index$1 = require("./index-BAvedd2m.js");
8
- const objectLabels = require("./objectLabels-CycoU0zY.js");
9
- const useHubspotSchema = require("./useHubspotSchema-DRfaI6mF.js");
7
+ const index$1 = require("./index-BpcilDy1.js");
8
+ const objectLabels = require("./objectLabels-CYXXFpNB.js");
9
+ const useHubspotSchema = require("./useHubspotSchema-Ba6xZUCL.js");
10
10
  function _interopNamespace(e) {
11
11
  if (e && e.__esModule) return e;
12
12
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -2,9 +2,9 @@ import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import * as React from "react";
3
3
  import { u as useIntl } from "./index-CEh8vkxY.mjs";
4
4
  import { Field, Flex, Loader, SingleSelect, SingleSelectOption } from "@strapi/design-system";
5
- import { g as getTranslation } from "./index-BE2ribUL.mjs";
6
- import { o as objectLabel } from "./objectLabels-DGJxYjKz.mjs";
7
- import { u as useHubspotSchema } from "./useHubspotSchema-e31UsYb2.mjs";
5
+ import { g as getTranslation } from "./index-BSJAhj8r.mjs";
6
+ import { o as objectLabel } from "./objectLabels-CrYRzsqe.mjs";
7
+ import { u as useHubspotSchema } from "./useHubspotSchema-CBF4GkmK.mjs";
8
8
  const HubspotObjectInput = React.forwardRef(
9
9
  ({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
10
10
  const intl = useIntl();
@@ -4,9 +4,9 @@ import { u as useIntl } from "./index-CEh8vkxY.mjs";
4
4
  import { Field, Flex, Loader, Combobox, ComboboxOption, IconButton, Button, Typography, Link } from "@strapi/design-system";
5
5
  import { ArrowClockwise } from "@strapi/icons";
6
6
  import { useField } from "@strapi/strapi/admin";
7
- import { g as getTranslation } from "./index-BE2ribUL.mjs";
8
- import { o as objectLabel } from "./objectLabels-DGJxYjKz.mjs";
9
- import { u as useHubspotSchema } from "./useHubspotSchema-e31UsYb2.mjs";
7
+ import { g as getTranslation } from "./index-BSJAhj8r.mjs";
8
+ import { o as objectLabel } from "./objectLabels-CrYRzsqe.mjs";
9
+ import { u as useHubspotSchema } from "./useHubspotSchema-CBF4GkmK.mjs";
10
10
  const oneLine = {
11
11
  display: "block",
12
12
  whiteSpace: "nowrap",
@@ -6,9 +6,9 @@ const index = require("./index-DkTxsEqL.js");
6
6
  const designSystem = require("@strapi/design-system");
7
7
  const icons = require("@strapi/icons");
8
8
  const admin = require("@strapi/strapi/admin");
9
- const index$1 = require("./index-BAvedd2m.js");
10
- const objectLabels = require("./objectLabels-CycoU0zY.js");
11
- const useHubspotSchema = require("./useHubspotSchema-DRfaI6mF.js");
9
+ const index$1 = require("./index-BpcilDy1.js");
10
+ const objectLabels = require("./objectLabels-CYXXFpNB.js");
11
+ const useHubspotSchema = require("./useHubspotSchema-Ba6xZUCL.js");
12
12
  function _interopNamespace(e) {
13
13
  if (e && e.__esModule) return e;
14
14
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });