strapi-plugin-hubspot 0.15.0 → 0.16.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (37) hide show
  1. package/CHANGELOG.md +21 -0
  2. package/README.md +28 -13
  3. package/dist/_chunks/{Forms-ClyI4MJ1.js → Forms-D0QXkYAN.js} +216 -112
  4. package/dist/_chunks/{Forms-B7eIm0Fi.mjs → Forms-DEoZGrEt.mjs} +218 -114
  5. package/dist/_chunks/{HubspotFormInput-Do7F83V6.js → HubspotFormInput-4ZMYXWM_.js} +1 -1
  6. package/dist/_chunks/{HubspotFormInput-cZAiaAaq.mjs → HubspotFormInput-CPep55GL.mjs} +1 -1
  7. package/dist/_chunks/{HubspotObjectInput-DWqk2C26.js → HubspotObjectInput-BTqYXJIa.js} +3 -3
  8. package/dist/_chunks/{HubspotObjectInput-ZZk_7hWq.mjs → HubspotObjectInput-CQszIx1W.mjs} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-v9B3wb1a.js → HubspotPropertyInput-2MgIKlR_.js} +3 -3
  10. package/dist/_chunks/{HubspotPropertyInput-5bkDGmGo.mjs → HubspotPropertyInput-ClNph0f1.mjs} +3 -3
  11. package/dist/_chunks/{Settings-QcFbm3s5.mjs → Settings-CZ-qQSKT.mjs} +78 -5
  12. package/dist/_chunks/{Settings-X3EHnTaO.js → Settings-DhiHQC6U.js} +77 -4
  13. package/dist/_chunks/{en-ZHvaZLi7.js → en-CSgFmKmM.js} +16 -3
  14. package/dist/_chunks/{en-DMJeF7EN.mjs → en-Dh3bMS6N.mjs} +16 -3
  15. package/dist/_chunks/{fr-Ds_2EAzZ.js → fr-D_EhZS0P.js} +16 -3
  16. package/dist/_chunks/{fr-CVAntLmc.mjs → fr-aWOIOlS9.mjs} +16 -3
  17. package/dist/_chunks/{index-BpcilDy1.js → index-ciliGv0E.js} +6 -6
  18. package/dist/_chunks/{index-BSJAhj8r.mjs → index-gKM4jVf8.mjs} +6 -6
  19. package/dist/_chunks/{objectLabels-CYXXFpNB.js → objectLabels-BjYfdhRB.js} +1 -1
  20. package/dist/_chunks/{objectLabels-CrYRzsqe.mjs → objectLabels-DQpSLUaJ.mjs} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-CBF4GkmK.mjs → useHubspotSchema-CE7tJgOM.mjs} +1 -1
  22. package/dist/_chunks/{useHubspotSchema-Ba6xZUCL.js → useHubspotSchema-KKVppjC2.js} +1 -1
  23. package/dist/admin/index.js +1 -1
  24. package/dist/admin/index.mjs +1 -1
  25. package/dist/admin/src/builder/Sortable.d.ts +25 -12
  26. package/dist/admin/src/builder/__tests__/relocateField.test.d.ts +1 -0
  27. package/dist/admin/src/builder/relocateField.d.ts +14 -0
  28. package/dist/server/index.js +216 -7
  29. package/dist/server/index.mjs +216 -7
  30. package/dist/server/src/__tests__/formSync.test.d.ts +1 -0
  31. package/dist/server/src/formSync.d.ts +37 -0
  32. package/dist/server/src/forms.d.ts +6 -8
  33. package/dist/server/src/hsforms.d.ts +8 -0
  34. package/dist/server/src/importHubspot.d.ts +4 -0
  35. package/dist/server/src/index.d.ts +6 -0
  36. package/dist/server/src/settings.d.ts +32 -3
  37. package/package.json +1 -1
@@ -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-BpcilDy1.js");
8
+ const index$1 = require("./index-ciliGv0E.js");
9
9
  const reactRouterDom = require("react-router-dom");
10
- const objectLabels = require("./objectLabels-CYXXFpNB.js");
10
+ const objectLabels = require("./objectLabels-BjYfdhRB.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" } });
@@ -201,6 +201,9 @@ const HubspotSettings = () => {
201
201
  const [portalId, setPortalId] = React__namespace.useState("");
202
202
  const [region, setRegion] = React__namespace.useState("eu1");
203
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);
204
207
  const [forms, setForms] = React__namespace.useState([]);
205
208
  const [busy, setBusy] = React__namespace.useState(false);
206
209
  const [feedback, setFeedback] = React__namespace.useState(
@@ -212,6 +215,9 @@ const HubspotSettings = () => {
212
215
  setPortalId(data.portalId ?? "");
213
216
  setRegion(data.region || "eu1");
214
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);
215
221
  setForms(Array.isArray(data.forms) ? data.forms : []);
216
222
  };
217
223
  const load = React__namespace.useCallback(async () => {
@@ -231,7 +237,10 @@ const HubspotSettings = () => {
231
237
  ...apiKey.trim() ? { apiKey: apiKey.trim() } : {},
232
238
  portalId,
233
239
  region,
234
- defaultFormId
240
+ defaultFormId,
241
+ submissionMode,
242
+ writeExtraProperties,
243
+ syncFieldsOnPublish
235
244
  });
236
245
  setApiKey("");
237
246
  await load();
@@ -401,7 +410,71 @@ const HubspotSettings = () => {
401
410
  sourceHint(settings.formSource)
402
411
  ]
403
412
  }
404
- )
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
+ ) })
405
478
  ] }) }),
406
479
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
407
480
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: save, loading: busy, children: t("settings.save", "Save") }),
@@ -30,7 +30,18 @@ const en = {
30
30
  "picker.refresh": "Refresh the properties from HubSpot",
31
31
  "picker.import": "Import the {count} options from HubSpot",
32
32
  "picker.imported": "Options imported — save to keep them.",
33
- "settings.subtitle": "Private app token and the HubSpot account that receives form conversions. Switch test → production here — nothing is hardcoded.",
33
+ "settings.subtitle": "Private app token, HubSpot account, and how submissions reach the CRM. Switch test → production here — nothing is hardcoded.",
34
+ "settings.policy.title": "How submissions reach HubSpot",
35
+ "settings.policy.hint": "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.",
36
+ "settings.mode.label": "Submission path",
37
+ "settings.mode.hint": "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.",
38
+ "settings.mode.auto": "Automatic — form conversion if linked, else CRM",
39
+ "settings.mode.forms": "Marketing form — native HubSpot conversions",
40
+ "settings.mode.crm": "CRM only — upsert the contact, no form conversion",
41
+ "settings.extra.label": "After a conversion, also write mapped contact fields that the HubSpot form doesn't have",
42
+ "settings.extra.hint": "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.",
43
+ "settings.sync.label": "On publish, add mapped contact fields that are missing on the HubSpot form",
44
+ "settings.sync.hint": "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.",
34
45
  "settings.configured": "Configured {hint}",
35
46
  "settings.not-configured": "Not configured",
36
47
  "settings.source": "source: {source}",
@@ -62,7 +73,7 @@ const en = {
62
73
  "settings.test-ok": "Connection established — {count} readable properties.",
63
74
  "settings.test-no-key": "No key configured.",
64
75
  "settings.test-error": "HubSpot unreachable — invalid or revoked key?",
65
- "settings.scopes": "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.",
76
+ "settings.scopes": "The token needs crm.schemas.contacts.read, crm.schemas.companies.read and forms (to list, submit, and optionally update marketing forms). The token is never returned to the browser.",
66
77
  "audit.title": "Mapping audit",
67
78
  "audit.subtitle": "Scans every entry of the validated content types and lists the mappings the portal would reject today — including properties deleted in HubSpot since they were saved.",
68
79
  "audit.run": "Run the audit",
@@ -122,6 +133,8 @@ const en = {
122
133
  "editor.invalid": "The structure has problems — see the flagged cards.",
123
134
  "editor.publish": "Publish",
124
135
  "editor.published": "Form published.",
136
+ "editor.published-synced": "Form published. {count, plural, one {# field added} other {# fields added}} to the HubSpot form.",
137
+ "editor.published-sync-warn": "Form published. HubSpot field sync failed: {error}",
125
138
  "editor.publish-blocked": "Publishing is blocked — fix the flagged mappings first.",
126
139
  "editor.unpublish": "Unpublish",
127
140
  "editor.unpublished": "Form unpublished.",
@@ -284,6 +297,6 @@ const en = {
284
297
  "company.datum.nafLabel": "NAF label",
285
298
  "company.datum.headcount": "Headcount range",
286
299
  "editor.drag-step": "Drag to reorder the step",
287
- "editor.drag-field": "Drag to reorder the field"
300
+ "editor.drag-field": "Drag to move the field, including to another step"
288
301
  };
289
302
  exports.default = en;
@@ -28,7 +28,18 @@ const en = {
28
28
  "picker.refresh": "Refresh the properties from HubSpot",
29
29
  "picker.import": "Import the {count} options from HubSpot",
30
30
  "picker.imported": "Options imported — save to keep them.",
31
- "settings.subtitle": "Private app token and the HubSpot account that receives form conversions. Switch test → production here — nothing is hardcoded.",
31
+ "settings.subtitle": "Private app token, HubSpot account, and how submissions reach the CRM. Switch test → production here — nothing is hardcoded.",
32
+ "settings.policy.title": "How submissions reach HubSpot",
33
+ "settings.policy.hint": "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.",
34
+ "settings.mode.label": "Submission path",
35
+ "settings.mode.hint": "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.",
36
+ "settings.mode.auto": "Automatic — form conversion if linked, else CRM",
37
+ "settings.mode.forms": "Marketing form — native HubSpot conversions",
38
+ "settings.mode.crm": "CRM only — upsert the contact, no form conversion",
39
+ "settings.extra.label": "After a conversion, also write mapped contact fields that the HubSpot form doesn't have",
40
+ "settings.extra.hint": "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.",
41
+ "settings.sync.label": "On publish, add mapped contact fields that are missing on the HubSpot form",
42
+ "settings.sync.hint": "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.",
32
43
  "settings.configured": "Configured {hint}",
33
44
  "settings.not-configured": "Not configured",
34
45
  "settings.source": "source: {source}",
@@ -60,7 +71,7 @@ const en = {
60
71
  "settings.test-ok": "Connection established — {count} readable properties.",
61
72
  "settings.test-no-key": "No key configured.",
62
73
  "settings.test-error": "HubSpot unreachable — invalid or revoked key?",
63
- "settings.scopes": "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.",
74
+ "settings.scopes": "The token needs crm.schemas.contacts.read, crm.schemas.companies.read and forms (to list, submit, and optionally update marketing forms). The token is never returned to the browser.",
64
75
  "audit.title": "Mapping audit",
65
76
  "audit.subtitle": "Scans every entry of the validated content types and lists the mappings the portal would reject today — including properties deleted in HubSpot since they were saved.",
66
77
  "audit.run": "Run the audit",
@@ -120,6 +131,8 @@ const en = {
120
131
  "editor.invalid": "The structure has problems — see the flagged cards.",
121
132
  "editor.publish": "Publish",
122
133
  "editor.published": "Form published.",
134
+ "editor.published-synced": "Form published. {count, plural, one {# field added} other {# fields added}} to the HubSpot form.",
135
+ "editor.published-sync-warn": "Form published. HubSpot field sync failed: {error}",
123
136
  "editor.publish-blocked": "Publishing is blocked — fix the flagged mappings first.",
124
137
  "editor.unpublish": "Unpublish",
125
138
  "editor.unpublished": "Form unpublished.",
@@ -282,7 +295,7 @@ const en = {
282
295
  "company.datum.nafLabel": "NAF label",
283
296
  "company.datum.headcount": "Headcount range",
284
297
  "editor.drag-step": "Drag to reorder the step",
285
- "editor.drag-field": "Drag to reorder the field"
298
+ "editor.drag-field": "Drag to move the field, including to another step"
286
299
  };
287
300
  export {
288
301
  en as default
@@ -30,7 +30,18 @@ const fr = {
30
30
  "picker.refresh": "Rafraîchir les propriétés depuis HubSpot",
31
31
  "picker.import": "Importer les {count} options depuis HubSpot",
32
32
  "picker.imported": "Options importées — enregistrez pour les conserver.",
33
- "settings.subtitle": "Jeton d'application privée et compte HubSpot qui reçoit les conversions. Passez du test à la prod ici — rien n'est figé dans le code.",
33
+ "settings.subtitle": "Jeton d'application privée, compte HubSpot, et la façon dont les envois atteignent le CRM. Passez du test à la prod ici — rien n'est figé dans le code.",
34
+ "settings.policy.title": "Comment les envois atteignent HubSpot",
35
+ "settings.policy.hint": "Choisissez la politique de ce portail. Les défauts restent sûrs pour toute install : conversion si un formulaire est lié, réponses en trop écrites sur le contact, formulaires HubSpot jamais mutés sauf si vous le demandez.",
36
+ "settings.mode.label": "Chemin d'envoi",
37
+ "settings.mode.hint": "Automatique utilise le formulaire marketing s'il est lié, sinon un upsert CRM. CRM seul ne compte jamais comme conversion. Formulaire marketing privilégie l'API Forms et retombe sur le CRM si HubSpot refuse l'envoi.",
38
+ "settings.mode.auto": "Automatique — conversion si un formulaire est lié, sinon CRM",
39
+ "settings.mode.forms": "Formulaire marketing — conversions HubSpot natives",
40
+ "settings.mode.crm": "CRM uniquement — upsert du contact, pas de conversion",
41
+ "settings.extra.label": "Après une conversion, écrire aussi sur le contact les champs mappés absents du formulaire HubSpot",
42
+ "settings.extra.hint": "L'API Forms écarte les champs inconnus (ils font échouer tout l'envoi). Activé, les champs restants atterrissent quand même sur le contact. Désactivez si HubSpot ne doit recevoir que ce que le formulaire marketing déclare.",
43
+ "settings.sync.label": "À la publication, ajouter au formulaire HubSpot les champs mappés manquants",
44
+ "settings.sync.hint": "Install Strapi-first : un champ ajouté ici apparaît sur le formulaire HubSpot à la publication suivante. Install HubSpot-first : laissez désactivé — l'import porte déjà les champs. Les propriétés CRM ne sont jamais créées ; mappez vers des propriétés déjà présentes dans le portail.",
34
45
  "settings.configured": "Configurée {hint}",
35
46
  "settings.not-configured": "Non configurée",
36
47
  "settings.source": "source : {source}",
@@ -62,7 +73,7 @@ const fr = {
62
73
  "settings.test-ok": "Connexion établie — {count} propriétés lisibles.",
63
74
  "settings.test-no-key": "Aucune clé configurée.",
64
75
  "settings.test-error": "HubSpot injoignable — clé invalide ou révoquée ?",
65
- "settings.scopes": "Le jeton a besoin de crm.schemas.contacts.read, crm.schemas.companies.read et forms (lister et soumettre les formulaires marketing). Il n'est jamais renvoyé au navigateur.",
76
+ "settings.scopes": "Le jeton a besoin de crm.schemas.contacts.read, crm.schemas.companies.read et forms (lister, soumettre et éventuellement mettre à jour les formulaires marketing). Il n'est jamais renvoyé au navigateur.",
66
77
  "audit.title": "Audit des mappings",
67
78
  "audit.subtitle": "Parcourt toutes les entrées des content-types validés et liste les mappings que le portail refuserait aujourd'hui — y compris les propriétés supprimées dans HubSpot depuis leur enregistrement.",
68
79
  "audit.run": "Lancer l'audit",
@@ -122,6 +133,8 @@ const fr = {
122
133
  "editor.invalid": "La structure a des problèmes — voir les cartes signalées.",
123
134
  "editor.publish": "Publier",
124
135
  "editor.published": "Formulaire publié.",
136
+ "editor.published-synced": "Formulaire publié. {count, plural, one {# champ ajouté} other {# champs ajoutés}} au formulaire HubSpot.",
137
+ "editor.published-sync-warn": "Formulaire publié. La sync des champs HubSpot a échoué : {error}",
125
138
  "editor.publish-blocked": "Publication bloquée — corrigez d'abord les mappings signalés.",
126
139
  "editor.unpublish": "Dépublier",
127
140
  "editor.unpublished": "Formulaire dépublié.",
@@ -284,6 +297,6 @@ const fr = {
284
297
  "company.datum.nafLabel": "Libellé NAF",
285
298
  "company.datum.headcount": "Tranche d'effectif",
286
299
  "editor.drag-step": "Glisser pour réordonner l'étape",
287
- "editor.drag-field": "Glisser pour réordonner le champ"
300
+ "editor.drag-field": "Glisser pour déplacer le champ, y compris vers une autre étape"
288
301
  };
289
302
  exports.default = fr;
@@ -28,7 +28,18 @@ const fr = {
28
28
  "picker.refresh": "Rafraîchir les propriétés depuis HubSpot",
29
29
  "picker.import": "Importer les {count} options depuis HubSpot",
30
30
  "picker.imported": "Options importées — enregistrez pour les conserver.",
31
- "settings.subtitle": "Jeton d'application privée et compte HubSpot qui reçoit les conversions. Passez du test à la prod ici — rien n'est figé dans le code.",
31
+ "settings.subtitle": "Jeton d'application privée, compte HubSpot, et la façon dont les envois atteignent le CRM. Passez du test à la prod ici — rien n'est figé dans le code.",
32
+ "settings.policy.title": "Comment les envois atteignent HubSpot",
33
+ "settings.policy.hint": "Choisissez la politique de ce portail. Les défauts restent sûrs pour toute install : conversion si un formulaire est lié, réponses en trop écrites sur le contact, formulaires HubSpot jamais mutés sauf si vous le demandez.",
34
+ "settings.mode.label": "Chemin d'envoi",
35
+ "settings.mode.hint": "Automatique utilise le formulaire marketing s'il est lié, sinon un upsert CRM. CRM seul ne compte jamais comme conversion. Formulaire marketing privilégie l'API Forms et retombe sur le CRM si HubSpot refuse l'envoi.",
36
+ "settings.mode.auto": "Automatique — conversion si un formulaire est lié, sinon CRM",
37
+ "settings.mode.forms": "Formulaire marketing — conversions HubSpot natives",
38
+ "settings.mode.crm": "CRM uniquement — upsert du contact, pas de conversion",
39
+ "settings.extra.label": "Après une conversion, écrire aussi sur le contact les champs mappés absents du formulaire HubSpot",
40
+ "settings.extra.hint": "L'API Forms écarte les champs inconnus (ils font échouer tout l'envoi). Activé, les champs restants atterrissent quand même sur le contact. Désactivez si HubSpot ne doit recevoir que ce que le formulaire marketing déclare.",
41
+ "settings.sync.label": "À la publication, ajouter au formulaire HubSpot les champs mappés manquants",
42
+ "settings.sync.hint": "Install Strapi-first : un champ ajouté ici apparaît sur le formulaire HubSpot à la publication suivante. Install HubSpot-first : laissez désactivé — l'import porte déjà les champs. Les propriétés CRM ne sont jamais créées ; mappez vers des propriétés déjà présentes dans le portail.",
32
43
  "settings.configured": "Configurée {hint}",
33
44
  "settings.not-configured": "Non configurée",
34
45
  "settings.source": "source : {source}",
@@ -60,7 +71,7 @@ const fr = {
60
71
  "settings.test-ok": "Connexion établie — {count} propriétés lisibles.",
61
72
  "settings.test-no-key": "Aucune clé configurée.",
62
73
  "settings.test-error": "HubSpot injoignable — clé invalide ou révoquée ?",
63
- "settings.scopes": "Le jeton a besoin de crm.schemas.contacts.read, crm.schemas.companies.read et forms (lister et soumettre les formulaires marketing). Il n'est jamais renvoyé au navigateur.",
74
+ "settings.scopes": "Le jeton a besoin de crm.schemas.contacts.read, crm.schemas.companies.read et forms (lister, soumettre et éventuellement mettre à jour les formulaires marketing). Il n'est jamais renvoyé au navigateur.",
64
75
  "audit.title": "Audit des mappings",
65
76
  "audit.subtitle": "Parcourt toutes les entrées des content-types validés et liste les mappings que le portail refuserait aujourd'hui — y compris les propriétés supprimées dans HubSpot depuis leur enregistrement.",
66
77
  "audit.run": "Lancer l'audit",
@@ -120,6 +131,8 @@ const fr = {
120
131
  "editor.invalid": "La structure a des problèmes — voir les cartes signalées.",
121
132
  "editor.publish": "Publier",
122
133
  "editor.published": "Formulaire publié.",
134
+ "editor.published-synced": "Formulaire publié. {count, plural, one {# champ ajouté} other {# champs ajoutés}} au formulaire HubSpot.",
135
+ "editor.published-sync-warn": "Formulaire publié. La sync des champs HubSpot a échoué : {error}",
123
136
  "editor.publish-blocked": "Publication bloquée — corrigez d'abord les mappings signalés.",
124
137
  "editor.unpublish": "Dépublier",
125
138
  "editor.unpublished": "Formulaire dépublié.",
@@ -282,7 +295,7 @@ const fr = {
282
295
  "company.datum.nafLabel": "Libellé NAF",
283
296
  "company.datum.headcount": "Tranche d'effectif",
284
297
  "editor.drag-step": "Glisser pour réordonner l'étape",
285
- "editor.drag-field": "Glisser pour réordonner le champ"
298
+ "editor.drag-field": "Glisser pour déplacer le champ, y compris vers une autre étape"
286
299
  };
287
300
  export {
288
301
  fr as default
@@ -26,7 +26,7 @@ const index = {
26
26
  icon: icons.PaperPlane,
27
27
  intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "HubSpot Forms" },
28
28
  permissions: [{ action: `plugin::${PLUGIN_ID}.forms`, subject: null }],
29
- Component: async () => (await Promise.resolve().then(() => require("./Forms-ClyI4MJ1.js"))).default
29
+ Component: async () => (await Promise.resolve().then(() => require("./Forms-D0QXkYAN.js"))).default
30
30
  });
31
31
  app.customFields.register({
32
32
  name: "property",
@@ -41,7 +41,7 @@ const index = {
41
41
  defaultMessage: "Picked from the portal's real properties"
42
42
  },
43
43
  components: {
44
- Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-v9B3wb1a.js"))
44
+ Input: async () => Promise.resolve().then(() => require("./HubspotPropertyInput-2MgIKlR_.js"))
45
45
  },
46
46
  // Exposed in the Content-Type Builder. Without this, `objectField` could
47
47
  // only be set by hand-editing schema.json — which is not how anyone
@@ -104,7 +104,7 @@ const index = {
104
104
  defaultMessage: "Picked from the portal's configured objects"
105
105
  },
106
106
  components: {
107
- Input: async () => Promise.resolve().then(() => require("./HubspotObjectInput-DWqk2C26.js"))
107
+ Input: async () => Promise.resolve().then(() => require("./HubspotObjectInput-BTqYXJIa.js"))
108
108
  }
109
109
  });
110
110
  app.customFields.register({
@@ -120,7 +120,7 @@ const index = {
120
120
  defaultMessage: "A form built in the HubSpot form builder, stored by slug"
121
121
  },
122
122
  components: {
123
- Input: async () => Promise.resolve().then(() => require("./HubspotFormInput-Do7F83V6.js"))
123
+ Input: async () => Promise.resolve().then(() => require("./HubspotFormInput-4ZMYXWM_.js"))
124
124
  }
125
125
  });
126
126
  app.createSettingSection(
@@ -136,7 +136,7 @@ const index = {
136
136
  // Mirrors the RBAC action registered server-side: a role without it
137
137
  // doesn't see the link at all — the routes refuse it anyway.
138
138
  permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
139
- Component: async () => (await Promise.resolve().then(() => require("./Settings-X3EHnTaO.js"))).default
139
+ Component: async () => (await Promise.resolve().then(() => require("./Settings-DhiHQC6U.js"))).default
140
140
  }
141
141
  ]
142
142
  );
@@ -146,7 +146,7 @@ const index = {
146
146
  return Promise.all(
147
147
  locales.map(async (locale) => {
148
148
  try {
149
- const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-ZHvaZLi7.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-Ds_2EAzZ.js")) }), `./translations/${locale}.json`, 3);
149
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => Promise.resolve().then(() => require("./en-CSgFmKmM.js")), "./translations/fr.json": () => Promise.resolve().then(() => require("./fr-D_EhZS0P.js")) }), `./translations/${locale}.json`, 3);
150
150
  return { data: prefixPluginTranslations(data), locale };
151
151
  } catch {
152
152
  return { data: {}, locale };
@@ -25,7 +25,7 @@ const index = {
25
25
  icon: PaperPlane,
26
26
  intlLabel: { id: `${PLUGIN_ID}.menu.label`, defaultMessage: "HubSpot Forms" },
27
27
  permissions: [{ action: `plugin::${PLUGIN_ID}.forms`, subject: null }],
28
- Component: async () => (await import("./Forms-B7eIm0Fi.mjs")).default
28
+ Component: async () => (await import("./Forms-DEoZGrEt.mjs")).default
29
29
  });
30
30
  app.customFields.register({
31
31
  name: "property",
@@ -40,7 +40,7 @@ const index = {
40
40
  defaultMessage: "Picked from the portal's real properties"
41
41
  },
42
42
  components: {
43
- Input: async () => import("./HubspotPropertyInput-5bkDGmGo.mjs")
43
+ Input: async () => import("./HubspotPropertyInput-ClNph0f1.mjs")
44
44
  },
45
45
  // Exposed in the Content-Type Builder. Without this, `objectField` could
46
46
  // only be set by hand-editing schema.json — which is not how anyone
@@ -103,7 +103,7 @@ const index = {
103
103
  defaultMessage: "Picked from the portal's configured objects"
104
104
  },
105
105
  components: {
106
- Input: async () => import("./HubspotObjectInput-ZZk_7hWq.mjs")
106
+ Input: async () => import("./HubspotObjectInput-CQszIx1W.mjs")
107
107
  }
108
108
  });
109
109
  app.customFields.register({
@@ -119,7 +119,7 @@ const index = {
119
119
  defaultMessage: "A form built in the HubSpot form builder, stored by slug"
120
120
  },
121
121
  components: {
122
- Input: async () => import("./HubspotFormInput-cZAiaAaq.mjs")
122
+ Input: async () => import("./HubspotFormInput-CPep55GL.mjs")
123
123
  }
124
124
  });
125
125
  app.createSettingSection(
@@ -135,7 +135,7 @@ const index = {
135
135
  // Mirrors the RBAC action registered server-side: a role without it
136
136
  // doesn't see the link at all — the routes refuse it anyway.
137
137
  permissions: [{ action: `plugin::${PLUGIN_ID}.settings`, subject: null }],
138
- Component: async () => (await import("./Settings-QcFbm3s5.mjs")).default
138
+ Component: async () => (await import("./Settings-CZ-qQSKT.mjs")).default
139
139
  }
140
140
  ]
141
141
  );
@@ -145,7 +145,7 @@ const index = {
145
145
  return Promise.all(
146
146
  locales.map(async (locale) => {
147
147
  try {
148
- const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-DMJeF7EN.mjs"), "./translations/fr.json": () => import("./fr-CVAntLmc.mjs") }), `./translations/${locale}.json`, 3);
148
+ const { default: data } = await __variableDynamicImportRuntimeHelper(/* @__PURE__ */ Object.assign({ "./translations/en.json": () => import("./en-Dh3bMS6N.mjs"), "./translations/fr.json": () => import("./fr-aWOIOlS9.mjs") }), `./translations/${locale}.json`, 3);
149
149
  return { data: prefixPluginTranslations(data), locale };
150
150
  } catch {
151
151
  return { data: {}, locale };
@@ -1,5 +1,5 @@
1
1
  "use strict";
2
- const index = require("./index-BpcilDy1.js");
2
+ const index = require("./index-ciliGv0E.js");
3
3
  const STANDARD = /* @__PURE__ */ new Set([
4
4
  "contact",
5
5
  "company",
@@ -1,4 +1,4 @@
1
- import { g as getTranslation } from "./index-BSJAhj8r.mjs";
1
+ import { g as getTranslation } from "./index-gKM4jVf8.mjs";
2
2
  const STANDARD = /* @__PURE__ */ new Set([
3
3
  "contact",
4
4
  "company",
@@ -1,6 +1,6 @@
1
1
  import * as React from "react";
2
2
  import { useFetchClient } from "@strapi/strapi/admin";
3
- import { P as PLUGIN_ID } from "./index-BSJAhj8r.mjs";
3
+ import { P as PLUGIN_ID } from "./index-gKM4jVf8.mjs";
4
4
  let cached = null;
5
5
  let inFlight = null;
6
6
  const listeners = /* @__PURE__ */ new Set();
@@ -1,7 +1,7 @@
1
1
  "use strict";
2
2
  const React = require("react");
3
3
  const admin = require("@strapi/strapi/admin");
4
- const index = require("./index-BpcilDy1.js");
4
+ const index = require("./index-ciliGv0E.js");
5
5
  function _interopNamespace(e) {
6
6
  if (e && e.__esModule) return e;
7
7
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -1,4 +1,4 @@
1
1
  "use strict";
2
- const index = require("../_chunks/index-BpcilDy1.js");
2
+ const index = require("../_chunks/index-ciliGv0E.js");
3
3
  require("@strapi/icons");
4
4
  module.exports = index.index;
@@ -1,4 +1,4 @@
1
- import { i } from "../_chunks/index-BSJAhj8r.mjs";
1
+ import { i } from "../_chunks/index-gKM4jVf8.mjs";
2
2
  import "@strapi/icons";
3
3
  export {
4
4
  i as default
@@ -1,25 +1,38 @@
1
1
  import * as React from "react";
2
- import { arrayMove } from "@dnd-kit/sortable";
3
- export { arrayMove };
2
+ import { type DragEndEvent } from "@dnd-kit/core";
3
+ import { arrayMove, relocateField } from "./relocateField";
4
+ export { arrayMove, relocateField };
5
+ export type { DragEndEvent };
4
6
  /** The drag-handle props to spread on the grip element. */
5
7
  export interface HandleProps {
6
8
  attributes: React.HTMLAttributes<HTMLElement>;
7
9
  listeners: Record<string, unknown> | undefined;
8
10
  }
9
- /**
10
- * One vertical sortable list. Each list gets its OWN DndContext: steps sort
11
- * among steps, a step's fields sort among themselves — no cross-container
12
- * ambiguity, and the arrow buttons keep covering cross-step moves (they also
13
- * stay the accessible path).
14
- */
15
- export declare function SortableList({ ids, onReorder, children, }: {
11
+ export type SortableData = {
12
+ type: "step";
13
+ } | {
14
+ type: "field";
15
+ stepId: string;
16
+ } | {
17
+ type: "container";
18
+ stepId: string;
19
+ };
20
+ export declare function SortableBoard({ children, onDragEnd, }: {
21
+ children: React.ReactNode;
22
+ onDragEnd: (event: DragEndEvent) => void;
23
+ }): React.JSX.Element;
24
+ export declare function SortableGroup({ ids, children, }: {
16
25
  ids: string[];
17
- /** Called with the reordered ids after a completed drag. */
18
- onReorder: (ids: string[]) => void;
26
+ children: React.ReactNode;
27
+ }): React.JSX.Element;
28
+ /** Drop target for a step's field list — including when the list is empty. */
29
+ export declare function SortableContainer({ stepId, children, }: {
30
+ stepId: string;
19
31
  children: React.ReactNode;
20
32
  }): React.JSX.Element;
21
33
  /** A sortable row: wraps the card, hands the grip props to its children. */
22
- export declare function SortableItem({ id, children, }: {
34
+ export declare function SortableItem({ id, data, children, }: {
23
35
  id: string;
36
+ data?: SortableData;
24
37
  children: (handle: HandleProps) => React.ReactNode;
25
38
  }): React.JSX.Element;
@@ -0,0 +1,14 @@
1
+ import { arrayMove } from "@dnd-kit/sortable";
2
+ export { arrayMove };
3
+ /**
4
+ * Move `fieldId` onto `overId` across steps.
5
+ *
6
+ * `overId` is another field (insert at that index), a step id (append),
7
+ * or `container:${stepId}` (the empty-area droppable of that step).
8
+ */
9
+ export declare function relocateField<S extends {
10
+ id: string;
11
+ fields: F[];
12
+ }, F extends {
13
+ id: string;
14
+ }>(steps: S[], fieldId: string, overId: string): S[];