strapi-plugin-hubspot 0.14.0 → 0.16.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.
- package/CHANGELOG.md +22 -0
- package/README.md +39 -15
- package/dist/_chunks/{Forms-ws6d8rdC.mjs → Forms-6EoEYzAc.mjs} +26 -5
- package/dist/_chunks/{Forms-Cch-7zei.js → Forms-BW_zGc1P.js} +26 -5
- package/dist/_chunks/{HubspotFormInput-18pWTITT.mjs → HubspotFormInput-V5w4y1fb.mjs} +1 -1
- package/dist/_chunks/{HubspotFormInput-M2l_8w_e.js → HubspotFormInput-oMhm1UJm.js} +1 -1
- package/dist/_chunks/{HubspotObjectInput-BDO99qXs.js → HubspotObjectInput-Bmo6YQgs.js} +3 -3
- package/dist/_chunks/{HubspotObjectInput-DshusXZP.mjs → HubspotObjectInput-DhsnSguG.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-C-kHDgwx.mjs → HubspotPropertyInput-CrYP4pc0.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-XGnH2tHn.js → HubspotPropertyInput-luFfnxqy.js} +3 -3
- package/dist/_chunks/{Settings-CRnsu6ob.js → Settings-B3A22i7Z.js} +204 -35
- package/dist/_chunks/{Settings-Bm7WkzK1.mjs → Settings-CjcWj9I6.mjs} +205 -36
- package/dist/_chunks/{en-BXMU5CjT.js → en-C7ls2EUl.js} +28 -6
- package/dist/_chunks/{en-DKV-8FWP.mjs → en-D6GHgMOt.mjs} +28 -6
- package/dist/_chunks/{fr-GB6T3ZyU.mjs → fr-CfohMY8n.mjs} +27 -5
- package/dist/_chunks/{fr-Dx4XmZ4X.js → fr-pOP8ZXqM.js} +27 -5
- package/dist/_chunks/{index-CPVfElYB.mjs → index-7cL9PRh1.mjs} +6 -6
- package/dist/_chunks/{index-BlaG8oIq.js → index-CodpLKZx.js} +6 -6
- package/dist/_chunks/{objectLabels-CPc21Tdd.js → objectLabels-CSWGIgNb.js} +1 -1
- package/dist/_chunks/{objectLabels-6OUnIDB5.mjs → objectLabels-Dnj14fsp.mjs} +1 -1
- package/dist/_chunks/{useHubspotSchema-qCUs2gIX.mjs → useHubspotSchema-BsmQgJgx.mjs} +1 -1
- package/dist/_chunks/{useHubspotSchema-0MvCM1AC.js → useHubspotSchema-DiRN3HrN.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/server/index.js +301 -15
- package/dist/server/index.mjs +301 -15
- package/dist/server/src/__tests__/formSync.test.d.ts +1 -0
- package/dist/server/src/__tests__/settings.test.d.ts +1 -0
- package/dist/server/src/formSync.d.ts +37 -0
- package/dist/server/src/forms.d.ts +6 -8
- package/dist/server/src/hsforms.d.ts +8 -0
- package/dist/server/src/importHubspot.d.ts +4 -0
- package/dist/server/src/index.d.ts +11 -1
- package/dist/server/src/settings.d.ts +65 -5
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## Unreleased
|
|
4
4
|
|
|
5
|
+
## 0.16.0 — 2026-09-22
|
|
6
|
+
|
|
7
|
+
### Added — submission policies
|
|
8
|
+
The plugin no longer assumes one HubSpot workflow. **Settings → HubSpot**
|
|
9
|
+
(and `forms.*` in `config/plugins.ts`) now expose three independent choices:
|
|
10
|
+
|
|
11
|
+
- **`submissionMode`** (`auto` / `forms` / `crm`): native marketing-form
|
|
12
|
+
conversions when a GUID is linked, CRM upsert only, or automatic.
|
|
13
|
+
- **`writeExtraProperties`** (default `true`): after a Forms API conversion,
|
|
14
|
+
leftover mapped contact fields still land on the contact via CRM upsert.
|
|
15
|
+
- **`syncFieldsOnPublish`** (default `false`): on publish, PATCH missing
|
|
16
|
+
mapped contact fields onto the linked HubSpot form. Off so HubSpot-first
|
|
17
|
+
installs aren't mutated. CRM properties are never created.
|
|
18
|
+
|
|
19
|
+
## 0.15.0 — 2026-09-21
|
|
20
|
+
|
|
21
|
+
### Added — account settings in the admin
|
|
22
|
+
- **Settings → HubSpot** now holds the portal id, region (`eu1` / `na1`) and
|
|
23
|
+
the default marketing form (picked from the connected portal), not just the
|
|
24
|
+
private app token. Values saved here override `config/plugins.ts` and env.
|
|
25
|
+
Switching test → production is an admin change, not a deploy.
|
|
26
|
+
|
|
5
27
|
## 0.14.0 — 2026-09-21
|
|
6
28
|
|
|
7
29
|
### Changed — native HubSpot form conversions
|
package/README.md
CHANGED
|
@@ -44,14 +44,25 @@ Permissions**):
|
|
|
44
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 |
|
|
45
45
|
| `POST` | `/api/hubspot/forms/:slug/submit` | Validates (bounds, conditions, required), maps server-side, syncs HubSpot, stores the submission |
|
|
46
46
|
|
|
47
|
-
The submit pipeline
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
47
|
+
The submit pipeline is a **policy**, not a single path. Settings → HubSpot
|
|
48
|
+
(or `forms.*` in `config/plugins.ts`) pick how this install talks to HubSpot —
|
|
49
|
+
so a portal that wants native conversions and a portal that only wants CRM
|
|
50
|
+
upserts can share the same plugin:
|
|
51
|
+
|
|
52
|
+
| Setting | Default | What it does |
|
|
53
|
+
|---|---|---|
|
|
54
|
+
| `forms.submissionMode` | `auto` | `auto`: Forms API when a marketing GUID is linked, else CRM upsert. `forms`: always try the Forms API. `crm`: never count as a conversion |
|
|
55
|
+
| `forms.writeExtraProperties` | `true` | After a conversion, CRM-write mapped contact fields the HubSpot form dropped (they 400 the Forms API) |
|
|
56
|
+
| `forms.syncFieldsOnPublish` | `false` | On publish, PATCH missing mapped **contact** fields onto the HubSpot form. Off by default so HubSpot-first installs aren't mutated |
|
|
57
|
+
|
|
58
|
+
The visitor's `hutk` is forwarded so attribution sticks. Then the contact is
|
|
59
|
+
looked up by email and, when the address is on a corporate domain, the company
|
|
60
|
+
is found-or-created and associated. Timeline notes are opt-in and only used
|
|
61
|
+
on the CRM-upsert path.
|
|
62
|
+
|
|
63
|
+
CRM properties are **never created**. Map to properties that already exist in
|
|
64
|
+
the portal; opting into field sync only adds form fields that point at those
|
|
65
|
+
properties.
|
|
55
66
|
|
|
56
67
|
Portal id, region and form GUIDs are configuration. A test portal today,
|
|
57
68
|
production tomorrow: swap `portalId` / `region` / the GUID, no code change.
|
|
@@ -66,6 +77,9 @@ hubspot: {
|
|
|
66
77
|
companyFromDomain: true, // Company by corporate domain + association
|
|
67
78
|
timelineNote: false, // recap note on the CRM-upsert fallback only
|
|
68
79
|
defaultFormId: env("HUBSPOT_DEFAULT_FORM_ID", ""),
|
|
80
|
+
submissionMode: "auto", // auto | forms | crm
|
|
81
|
+
writeExtraProperties: true,
|
|
82
|
+
syncFieldsOnPublish: false,
|
|
69
83
|
},
|
|
70
84
|
},
|
|
71
85
|
}
|
|
@@ -365,10 +379,18 @@ its equivalent per object you send to).
|
|
|
365
379
|
|
|
366
380
|
### A settings screen
|
|
367
381
|
|
|
368
|
-
**Settings → HubSpot** holds the private app token
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
382
|
+
**Settings → HubSpot** holds the private app token, the HubSpot account that
|
|
383
|
+
receives conversions (portal id, region, default marketing form), **and** the
|
|
384
|
+
submission policy: conversion vs CRM upsert, leftover contact writes, form
|
|
385
|
+
field sync on publish. Values saved here override `config/plugins.ts` and env
|
|
386
|
+
— switching test → production, or switching HubSpot-first ↔ Strapi-first, is
|
|
387
|
+
an admin change, not a deploy.
|
|
388
|
+
|
|
389
|
+
The token is stored server-side and never returned to the browser: the UI only
|
|
390
|
+
receives whether a key exists, where it comes from, and its last four
|
|
391
|
+
characters. Portal id / region / form GUID are not secrets and round-trip so
|
|
392
|
+
they can be edited. A "Test connection" button round-trips to HubSpot and
|
|
393
|
+
reports how many properties it can read.
|
|
372
394
|
|
|
373
395
|
Access is gated by a dedicated RBAC permission — **Settings →
|
|
374
396
|
Roles → Plugins → Hubspot**. A role without it neither sees the settings link
|
|
@@ -479,11 +501,13 @@ lives on `app-eu1.hubspot.com` and submits to `api-eu1.hsforms.com`. Set
|
|
|
479
501
|
`config.region` (`eu1`, `na1`, …) to match. Deep links are built from the
|
|
480
502
|
`uiDomain` HubSpot reports for your portal rather than a hardcoded host.
|
|
481
503
|
|
|
482
|
-
The key
|
|
504
|
+
The key, portal id, region and default form GUID are resolved in this order,
|
|
505
|
+
first match wins:
|
|
483
506
|
|
|
484
507
|
1. saved from **Settings → HubSpot**
|
|
485
|
-
2. `config.apiKey`
|
|
486
|
-
3.
|
|
508
|
+
2. `config/plugins.ts` (`apiKey`, `portalId`, `region`, `forms.defaultFormId`)
|
|
509
|
+
3. environment variables (`HUBSPOT_API_KEY`, `HUBSPOT_PORTAL_ID`,
|
|
510
|
+
`HUBSPOT_REGION`, `HUBSPOT_DEFAULT_FORM_ID`)
|
|
487
511
|
|
|
488
512
|
## Migrating an existing field
|
|
489
513
|
|
|
@@ -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-
|
|
8
|
+
import { g as getTranslation, P as PLUGIN_ID } from "./index-7cL9PRh1.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-
|
|
13
|
-
import { u as useHubspotSchema } from "./useHubspotSchema-
|
|
12
|
+
import { o as objectLabel } from "./objectLabels-Dnj14fsp.mjs";
|
|
13
|
+
import { u as useHubspotSchema } from "./useHubspotSchema-BsmQgJgx.mjs";
|
|
14
14
|
const OPERATORS = [
|
|
15
15
|
"eq",
|
|
16
16
|
"neq",
|
|
@@ -774,11 +774,32 @@ const FormEditor = () => {
|
|
|
774
774
|
setBusy(true);
|
|
775
775
|
setFeedback(null);
|
|
776
776
|
try {
|
|
777
|
-
await post(`/${PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
|
|
777
|
+
const { data } = await post(`/${PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
|
|
778
778
|
setPublished(true);
|
|
779
779
|
setErrors([]);
|
|
780
780
|
setProblems([]);
|
|
781
|
-
|
|
781
|
+
const added = Array.isArray(data?.addedFields) ? data.addedFields.length : 0;
|
|
782
|
+
if (data?.syncWarning) {
|
|
783
|
+
setFeedback({
|
|
784
|
+
tone: "success",
|
|
785
|
+
text: t(
|
|
786
|
+
"editor.published-sync-warn",
|
|
787
|
+
"Form published. HubSpot field sync failed: {error}",
|
|
788
|
+
{ error: data.syncWarning }
|
|
789
|
+
)
|
|
790
|
+
});
|
|
791
|
+
} else if (added) {
|
|
792
|
+
setFeedback({
|
|
793
|
+
tone: "success",
|
|
794
|
+
text: t(
|
|
795
|
+
"editor.published-synced",
|
|
796
|
+
"Form published. {count, plural, one {# field added} other {# fields added}} to the HubSpot form.",
|
|
797
|
+
{ count: added }
|
|
798
|
+
)
|
|
799
|
+
});
|
|
800
|
+
} else {
|
|
801
|
+
setFeedback({ tone: "success", text: t("editor.published", "Form published.") });
|
|
802
|
+
}
|
|
782
803
|
} catch (err) {
|
|
783
804
|
const data = err.response?.data;
|
|
784
805
|
setErrors(data?.errors ?? []);
|
|
@@ -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-
|
|
10
|
+
const index$1 = require("./index-CodpLKZx.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-
|
|
15
|
-
const useHubspotSchema = require("./useHubspotSchema-
|
|
14
|
+
const objectLabels = require("./objectLabels-CSWGIgNb.js");
|
|
15
|
+
const useHubspotSchema = require("./useHubspotSchema-DiRN3HrN.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" } });
|
|
@@ -794,11 +794,32 @@ const FormEditor = () => {
|
|
|
794
794
|
setBusy(true);
|
|
795
795
|
setFeedback(null);
|
|
796
796
|
try {
|
|
797
|
-
await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
|
|
797
|
+
const { data } = await post(`/${index$1.PLUGIN_ID}/builder/forms/${documentId}/publish${query}`);
|
|
798
798
|
setPublished(true);
|
|
799
799
|
setErrors([]);
|
|
800
800
|
setProblems([]);
|
|
801
|
-
|
|
801
|
+
const added = Array.isArray(data?.addedFields) ? data.addedFields.length : 0;
|
|
802
|
+
if (data?.syncWarning) {
|
|
803
|
+
setFeedback({
|
|
804
|
+
tone: "success",
|
|
805
|
+
text: t(
|
|
806
|
+
"editor.published-sync-warn",
|
|
807
|
+
"Form published. HubSpot field sync failed: {error}",
|
|
808
|
+
{ error: data.syncWarning }
|
|
809
|
+
)
|
|
810
|
+
});
|
|
811
|
+
} else if (added) {
|
|
812
|
+
setFeedback({
|
|
813
|
+
tone: "success",
|
|
814
|
+
text: t(
|
|
815
|
+
"editor.published-synced",
|
|
816
|
+
"Form published. {count, plural, one {# field added} other {# fields added}} to the HubSpot form.",
|
|
817
|
+
{ count: added }
|
|
818
|
+
)
|
|
819
|
+
});
|
|
820
|
+
} else {
|
|
821
|
+
setFeedback({ tone: "success", text: t("editor.published", "Form published.") });
|
|
822
|
+
}
|
|
802
823
|
} catch (err) {
|
|
803
824
|
const data = err.response?.data;
|
|
804
825
|
setErrors(data?.errors ?? []);
|
|
@@ -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-
|
|
6
|
+
import { P as PLUGIN_ID, g as getTranslation } from "./index-7cL9PRh1.mjs";
|
|
7
7
|
const HubspotFormInput = React.forwardRef(
|
|
8
8
|
({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
9
9
|
const { formatMessage } = useIntl();
|
|
@@ -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-
|
|
8
|
+
const index$1 = require("./index-CodpLKZx.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" } });
|
|
@@ -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-
|
|
8
|
-
const objectLabels = require("./objectLabels-
|
|
9
|
-
const useHubspotSchema = require("./useHubspotSchema-
|
|
7
|
+
const index$1 = require("./index-CodpLKZx.js");
|
|
8
|
+
const objectLabels = require("./objectLabels-CSWGIgNb.js");
|
|
9
|
+
const useHubspotSchema = require("./useHubspotSchema-DiRN3HrN.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-
|
|
6
|
-
import { o as objectLabel } from "./objectLabels-
|
|
7
|
-
import { u as useHubspotSchema } from "./useHubspotSchema-
|
|
5
|
+
import { g as getTranslation } from "./index-7cL9PRh1.mjs";
|
|
6
|
+
import { o as objectLabel } from "./objectLabels-Dnj14fsp.mjs";
|
|
7
|
+
import { u as useHubspotSchema } from "./useHubspotSchema-BsmQgJgx.mjs";
|
|
8
8
|
const HubspotObjectInput = React.forwardRef(
|
|
9
9
|
({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
10
10
|
const intl = useIntl();
|
package/dist/_chunks/{HubspotPropertyInput-C-kHDgwx.mjs → HubspotPropertyInput-CrYP4pc0.mjs}
RENAMED
|
@@ -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-
|
|
8
|
-
import { o as objectLabel } from "./objectLabels-
|
|
9
|
-
import { u as useHubspotSchema } from "./useHubspotSchema-
|
|
7
|
+
import { g as getTranslation } from "./index-7cL9PRh1.mjs";
|
|
8
|
+
import { o as objectLabel } from "./objectLabels-Dnj14fsp.mjs";
|
|
9
|
+
import { u as useHubspotSchema } from "./useHubspotSchema-BsmQgJgx.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-
|
|
10
|
-
const objectLabels = require("./objectLabels-
|
|
11
|
-
const useHubspotSchema = require("./useHubspotSchema-
|
|
9
|
+
const index$1 = require("./index-CodpLKZx.js");
|
|
10
|
+
const objectLabels = require("./objectLabels-CSWGIgNb.js");
|
|
11
|
+
const useHubspotSchema = require("./useHubspotSchema-DiRN3HrN.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" } });
|
|
@@ -5,9 +5,9 @@ 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-
|
|
8
|
+
const index$1 = require("./index-CodpLKZx.js");
|
|
9
9
|
const reactRouterDom = require("react-router-dom");
|
|
10
|
-
const objectLabels = require("./objectLabels-
|
|
10
|
+
const objectLabels = require("./objectLabels-CSWGIgNb.js");
|
|
11
11
|
function _interopNamespace(e) {
|
|
12
12
|
if (e && e.__esModule) return e;
|
|
13
13
|
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
@@ -192,19 +192,37 @@ const FailuresSection = ({ configured }) => {
|
|
|
192
192
|
) : null
|
|
193
193
|
] });
|
|
194
194
|
};
|
|
195
|
+
const REGIONS = ["eu1", "na1"];
|
|
195
196
|
const HubspotSettings = () => {
|
|
196
197
|
const { formatMessage } = index.useIntl();
|
|
197
198
|
const { get, put, del } = admin.useFetchClient();
|
|
198
199
|
const [settings, setSettings] = React__namespace.useState(null);
|
|
199
200
|
const [apiKey, setApiKey] = React__namespace.useState("");
|
|
201
|
+
const [portalId, setPortalId] = React__namespace.useState("");
|
|
202
|
+
const [region, setRegion] = React__namespace.useState("eu1");
|
|
203
|
+
const [defaultFormId, setDefaultFormId] = React__namespace.useState("");
|
|
204
|
+
const [submissionMode, setSubmissionMode] = React__namespace.useState("auto");
|
|
205
|
+
const [writeExtraProperties, setWriteExtraProperties] = React__namespace.useState(true);
|
|
206
|
+
const [syncFieldsOnPublish, setSyncFieldsOnPublish] = React__namespace.useState(false);
|
|
207
|
+
const [forms, setForms] = React__namespace.useState([]);
|
|
200
208
|
const [busy, setBusy] = React__namespace.useState(false);
|
|
201
209
|
const [feedback, setFeedback] = React__namespace.useState(
|
|
202
210
|
null
|
|
203
211
|
);
|
|
204
212
|
const t = (id, defaultMessage, values) => formatMessage({ id: index$1.getTranslation(id), defaultMessage }, values);
|
|
213
|
+
const apply = (data) => {
|
|
214
|
+
setSettings(data);
|
|
215
|
+
setPortalId(data.portalId ?? "");
|
|
216
|
+
setRegion(data.region || "eu1");
|
|
217
|
+
setDefaultFormId(data.defaultFormId ?? "");
|
|
218
|
+
setSubmissionMode(data.submissionMode === "crm" || data.submissionMode === "forms" ? data.submissionMode : "auto");
|
|
219
|
+
setWriteExtraProperties(data.writeExtraProperties !== false);
|
|
220
|
+
setSyncFieldsOnPublish(data.syncFieldsOnPublish === true);
|
|
221
|
+
setForms(Array.isArray(data.forms) ? data.forms : []);
|
|
222
|
+
};
|
|
205
223
|
const load = React__namespace.useCallback(async () => {
|
|
206
224
|
const { data } = await get(`/${index$1.PLUGIN_ID}/settings`);
|
|
207
|
-
|
|
225
|
+
apply(data);
|
|
208
226
|
}, [get]);
|
|
209
227
|
React__namespace.useEffect(() => {
|
|
210
228
|
load().catch(
|
|
@@ -215,12 +233,20 @@ const HubspotSettings = () => {
|
|
|
215
233
|
setBusy(true);
|
|
216
234
|
setFeedback(null);
|
|
217
235
|
try {
|
|
218
|
-
|
|
219
|
-
|
|
236
|
+
await put(`/${index$1.PLUGIN_ID}/settings`, {
|
|
237
|
+
...apiKey.trim() ? { apiKey: apiKey.trim() } : {},
|
|
238
|
+
portalId,
|
|
239
|
+
region,
|
|
240
|
+
defaultFormId,
|
|
241
|
+
submissionMode,
|
|
242
|
+
writeExtraProperties,
|
|
243
|
+
syncFieldsOnPublish
|
|
244
|
+
});
|
|
220
245
|
setApiKey("");
|
|
221
|
-
|
|
246
|
+
await load();
|
|
247
|
+
setFeedback({ tone: "success", text: t("settings.saved", "Settings saved.") });
|
|
222
248
|
} catch {
|
|
223
|
-
setFeedback({ tone: "danger", text: t("settings.save-error", "Could not save
|
|
249
|
+
setFeedback({ tone: "danger", text: t("settings.save-error", "Could not save settings.") });
|
|
224
250
|
} finally {
|
|
225
251
|
setBusy(false);
|
|
226
252
|
}
|
|
@@ -229,9 +255,9 @@ const HubspotSettings = () => {
|
|
|
229
255
|
setBusy(true);
|
|
230
256
|
setFeedback(null);
|
|
231
257
|
try {
|
|
232
|
-
|
|
233
|
-
setSettings(data);
|
|
258
|
+
await del(`/${index$1.PLUGIN_ID}/settings`);
|
|
234
259
|
setApiKey("");
|
|
260
|
+
await load();
|
|
235
261
|
setFeedback({ tone: "success", text: t("settings.removed", "Key removed.") });
|
|
236
262
|
} catch {
|
|
237
263
|
setFeedback({ tone: "danger", text: t("settings.remove-error", "Could not remove the key.") });
|
|
@@ -254,6 +280,7 @@ const HubspotSettings = () => {
|
|
|
254
280
|
})
|
|
255
281
|
} : { tone: "danger", text: t("settings.test-no-key", "No key configured.") }
|
|
256
282
|
);
|
|
283
|
+
if (data.configured) await load();
|
|
257
284
|
} catch {
|
|
258
285
|
setFeedback({
|
|
259
286
|
tone: "danger",
|
|
@@ -271,44 +298,186 @@ const HubspotSettings = () => {
|
|
|
271
298
|
config: t("settings.source.config", "config/plugins.ts"),
|
|
272
299
|
env: t("settings.source.env", "environment variable")
|
|
273
300
|
};
|
|
301
|
+
const sourceHint = (source) => source && source !== "settings" ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t("settings.source", "source: {source}", { source: sourceLabel[source] }) }) : null;
|
|
302
|
+
const regionOptions = REGIONS.includes(region) ? [...REGIONS] : [region, ...REGIONS];
|
|
274
303
|
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
275
304
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
|
|
276
305
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "HubSpot" }),
|
|
277
306
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: t(
|
|
278
307
|
"settings.subtitle",
|
|
279
|
-
"
|
|
308
|
+
"Private app token and the HubSpot account that receives form conversions. Switch test → production here — nothing is hardcoded."
|
|
280
309
|
) })
|
|
281
310
|
] }),
|
|
282
311
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
283
312
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: settings.configured, children: settings.configured ? t("settings.configured", "Configured {hint}", { hint: settings.hint }) : t("settings.not-configured", "Not configured") }),
|
|
284
313
|
settings.keySource ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t("settings.source", "source: {source}", { source: sourceLabel[settings.keySource] }) }) : null
|
|
285
314
|
] }),
|
|
286
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { maxWidth: "32rem", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
315
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { maxWidth: "32rem", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
|
|
316
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
317
|
+
designSystem.Field.Root,
|
|
318
|
+
{
|
|
319
|
+
name: "apiKey",
|
|
320
|
+
hint: t(
|
|
321
|
+
"settings.key.hint",
|
|
322
|
+
"A key entered here overrides config/plugins.ts and HUBSPOT_API_KEY."
|
|
323
|
+
),
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("settings.key.label", "Private app token") }),
|
|
326
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
327
|
+
designSystem.Field.Input,
|
|
328
|
+
{
|
|
329
|
+
type: "password",
|
|
330
|
+
value: apiKey,
|
|
331
|
+
autoComplete: "off",
|
|
332
|
+
placeholder: settings.configured ? t("settings.key.placeholder.set", "•••••••• (leave empty to keep)") : t("settings.key.placeholder.empty", "pat-eu1-…"),
|
|
333
|
+
onChange: (e) => setApiKey(e.target.value)
|
|
334
|
+
}
|
|
335
|
+
),
|
|
336
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
),
|
|
340
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
341
|
+
designSystem.Field.Root,
|
|
342
|
+
{
|
|
343
|
+
name: "portalId",
|
|
344
|
+
hint: t(
|
|
345
|
+
"settings.portal.hint",
|
|
346
|
+
"HubSpot account id (Settings → Account → Account setup). Test portal today, production later."
|
|
347
|
+
),
|
|
348
|
+
children: [
|
|
349
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("settings.portal.label", "Portal ID") }),
|
|
350
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
351
|
+
designSystem.Field.Input,
|
|
352
|
+
{
|
|
353
|
+
type: "text",
|
|
354
|
+
value: portalId,
|
|
355
|
+
autoComplete: "off",
|
|
356
|
+
placeholder: "148991818",
|
|
357
|
+
onChange: (e) => setPortalId(e.target.value)
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
361
|
+
sourceHint(settings.portalSource)
|
|
362
|
+
]
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "region", children: [
|
|
366
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("settings.region.label", "Region") }),
|
|
367
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
368
|
+
designSystem.SingleSelect,
|
|
369
|
+
{
|
|
370
|
+
value: region,
|
|
371
|
+
onChange: (value) => setRegion(String(value)),
|
|
372
|
+
children: regionOptions.map((r) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: r, children: r === "eu1" ? t("settings.region.eu1", "Europe (eu1)") : r === "na1" ? t("settings.region.na1", "North America (na1)") : r }, r))
|
|
373
|
+
}
|
|
293
374
|
),
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
onChange: (e) => setApiKey(e.target.value)
|
|
304
|
-
}
|
|
375
|
+
sourceHint(settings.regionSource)
|
|
376
|
+
] }),
|
|
377
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
378
|
+
designSystem.Field.Root,
|
|
379
|
+
{
|
|
380
|
+
name: "defaultFormId",
|
|
381
|
+
hint: t(
|
|
382
|
+
"settings.form.hint",
|
|
383
|
+
"Marketing form that receives submissions when a builder form doesn't pick its own. Create the form in HubSpot, then pick it here."
|
|
305
384
|
),
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
385
|
+
children: [
|
|
386
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("settings.form.label", "Default marketing form") }),
|
|
387
|
+
forms.length ? /* @__PURE__ */ jsxRuntime.jsxs(
|
|
388
|
+
designSystem.SingleSelect,
|
|
389
|
+
{
|
|
390
|
+
value: defaultFormId,
|
|
391
|
+
placeholder: t("settings.form.placeholder", "Pick a form of the connected portal"),
|
|
392
|
+
onChange: (value) => setDefaultFormId(String(value)),
|
|
393
|
+
children: [
|
|
394
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "", children: t("settings.form.none", "None — CRM upsert only") }),
|
|
395
|
+
defaultFormId && !forms.some((f) => f.id === defaultFormId) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: defaultFormId, children: defaultFormId }),
|
|
396
|
+
forms.map((f) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: f.id, children: f.name }, f.id))
|
|
397
|
+
]
|
|
398
|
+
}
|
|
399
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
400
|
+
designSystem.Field.Input,
|
|
401
|
+
{
|
|
402
|
+
type: "text",
|
|
403
|
+
value: defaultFormId,
|
|
404
|
+
autoComplete: "off",
|
|
405
|
+
placeholder: "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx",
|
|
406
|
+
onChange: (e) => setDefaultFormId(e.target.value.trim())
|
|
407
|
+
}
|
|
408
|
+
),
|
|
409
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
410
|
+
sourceHint(settings.formSource)
|
|
411
|
+
]
|
|
412
|
+
}
|
|
413
|
+
),
|
|
414
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: t("settings.policy.title", "How submissions reach HubSpot") }),
|
|
415
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
|
|
416
|
+
"settings.policy.hint",
|
|
417
|
+
"Pick the policy that matches this portal. Defaults stay safe for every install: conversions when a form is linked, leftover answers still written on the contact, HubSpot forms never mutated unless you ask."
|
|
418
|
+
) }),
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
420
|
+
designSystem.Field.Root,
|
|
421
|
+
{
|
|
422
|
+
name: "submissionMode",
|
|
423
|
+
hint: t(
|
|
424
|
+
"settings.mode.hint",
|
|
425
|
+
"Automatic uses the marketing form when one is linked, otherwise a CRM upsert. CRM-only never counts as a conversion. Forms-only prefers the Forms API and falls back to the CRM if HubSpot refuses the submit."
|
|
426
|
+
),
|
|
427
|
+
children: [
|
|
428
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("settings.mode.label", "Submission path") }),
|
|
429
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
430
|
+
designSystem.SingleSelect,
|
|
431
|
+
{
|
|
432
|
+
value: submissionMode,
|
|
433
|
+
onChange: (value) => {
|
|
434
|
+
const next = String(value);
|
|
435
|
+
if (next === "crm" || next === "forms" || next === "auto") setSubmissionMode(next);
|
|
436
|
+
},
|
|
437
|
+
children: [
|
|
438
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "auto", children: t("settings.mode.auto", "Automatic — form conversion if linked, else CRM") }),
|
|
439
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "forms", children: t("settings.mode.forms", "Marketing form — native HubSpot conversions") }),
|
|
440
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: "crm", children: t("settings.mode.crm", "CRM only — upsert the contact, no form conversion") })
|
|
441
|
+
]
|
|
442
|
+
}
|
|
443
|
+
),
|
|
444
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
445
|
+
]
|
|
446
|
+
}
|
|
447
|
+
),
|
|
448
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
449
|
+
designSystem.Checkbox,
|
|
450
|
+
{
|
|
451
|
+
checked: writeExtraProperties,
|
|
452
|
+
onCheckedChange: (checked) => setWriteExtraProperties(checked === true),
|
|
453
|
+
children: t(
|
|
454
|
+
"settings.extra.label",
|
|
455
|
+
"After a conversion, also write mapped contact fields that the HubSpot form doesn't have"
|
|
456
|
+
)
|
|
457
|
+
}
|
|
458
|
+
),
|
|
459
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
|
|
460
|
+
"settings.extra.hint",
|
|
461
|
+
"The Forms API drops unknown fields (they 400 the submit). With this on, leftovers still land on the contact. Turn it off if HubSpot must only receive what the marketing form declares."
|
|
462
|
+
) }),
|
|
463
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
464
|
+
designSystem.Checkbox,
|
|
465
|
+
{
|
|
466
|
+
checked: syncFieldsOnPublish,
|
|
467
|
+
onCheckedChange: (checked) => setSyncFieldsOnPublish(checked === true),
|
|
468
|
+
children: t(
|
|
469
|
+
"settings.sync.label",
|
|
470
|
+
"On publish, add mapped contact fields that are missing on the HubSpot form"
|
|
471
|
+
)
|
|
472
|
+
}
|
|
473
|
+
),
|
|
474
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
|
|
475
|
+
"settings.sync.hint",
|
|
476
|
+
"Strapi-first installs: a field added here appears on the HubSpot form next publish. HubSpot-first installs should leave this off — importing a form already carries its fields. Properties themselves are never created; map to ones that exist in the portal."
|
|
477
|
+
) })
|
|
478
|
+
] }) }),
|
|
310
479
|
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
311
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: save, loading: busy,
|
|
480
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: save, loading: busy, children: t("settings.save", "Save") }),
|
|
312
481
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: test, loading: busy, disabled: !settings.configured, children: t("settings.test", "Test connection") }),
|
|
313
482
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
314
483
|
designSystem.Button,
|
|
@@ -317,7 +486,7 @@ const HubspotSettings = () => {
|
|
|
317
486
|
onClick: remove,
|
|
318
487
|
loading: busy,
|
|
319
488
|
disabled: settings.keySource !== "settings",
|
|
320
|
-
children: t("settings.remove", "Remove")
|
|
489
|
+
children: t("settings.remove", "Remove key")
|
|
321
490
|
}
|
|
322
491
|
)
|
|
323
492
|
] }),
|
|
@@ -331,7 +500,7 @@ const HubspotSettings = () => {
|
|
|
331
500
|
) : null,
|
|
332
501
|
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
|
|
333
502
|
"settings.scopes",
|
|
334
|
-
"The token needs
|
|
503
|
+
"The token needs crm.schemas.contacts.read, crm.schemas.companies.read and forms (to list and submit marketing forms). The token is never returned to the browser."
|
|
335
504
|
) }) }),
|
|
336
505
|
/* @__PURE__ */ jsxRuntime.jsx(AuditSection, { configured: settings.configured }),
|
|
337
506
|
/* @__PURE__ */ jsxRuntime.jsx(FailuresSection, { configured: settings.configured })
|