strapi-plugin-hubspot 0.16.5 → 0.16.8

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 (29) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/dist/_chunks/{Forms-DyV6KyiO.mjs → Forms-CUNHDvhI.mjs} +52 -6
  3. package/dist/_chunks/{Forms-uUN4XjhW.js → Forms-CylJ0nD3.js} +52 -6
  4. package/dist/_chunks/{HubspotFormInput-BkWat_wu.js → HubspotFormInput-D2H2u9fZ.js} +1 -1
  5. package/dist/_chunks/{HubspotFormInput-DEXPX-6j.mjs → HubspotFormInput-Dppgr_rd.mjs} +1 -1
  6. package/dist/_chunks/{HubspotObjectInput-CFDYW-TM.js → HubspotObjectInput-BjRCaqXr.js} +3 -3
  7. package/dist/_chunks/{HubspotObjectInput-DL8fYK2x.mjs → HubspotObjectInput-CWFddNNw.mjs} +3 -3
  8. package/dist/_chunks/{HubspotPropertyInput-CFMkbQeT.js → HubspotPropertyInput-BlDarkcj.js} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-WU5FMJj3.mjs → HubspotPropertyInput-TGG9ozTa.mjs} +3 -3
  10. package/dist/_chunks/{Settings-B_hhdpHa.js → Settings-DOx7YSqA.js} +2 -2
  11. package/dist/_chunks/{Settings-CiXEps_C.mjs → Settings-tct49GYU.mjs} +2 -2
  12. package/dist/_chunks/{index-BeBn7bJa.mjs → index-Au3W85GX.mjs} +5 -5
  13. package/dist/_chunks/{index-CG_WQzSa.js → index-DhGVA3ZL.js} +5 -5
  14. package/dist/_chunks/{objectLabels-CS_0QCNz.mjs → objectLabels-3rnJ0WUc.mjs} +1 -1
  15. package/dist/_chunks/{objectLabels-BGq-Ypgn.js → objectLabels-Bx7x8fFO.js} +1 -1
  16. package/dist/_chunks/{useHubspotSchema-CP7LRdzO.mjs → useHubspotSchema-BlVXfwju.mjs} +1 -1
  17. package/dist/_chunks/{useHubspotSchema-BZP2qkAu.js → useHubspotSchema-DrGeidXC.js} +1 -1
  18. package/dist/admin/index.js +1 -1
  19. package/dist/admin/index.mjs +1 -1
  20. package/dist/admin/src/builder/types.d.ts +2 -1
  21. package/dist/server/index.js +293 -43
  22. package/dist/server/index.mjs +293 -43
  23. package/dist/server/src/conditions.d.ts +1 -0
  24. package/dist/server/src/formSync.d.ts +20 -0
  25. package/dist/server/src/formsAdmin.d.ts +1 -0
  26. package/dist/server/src/hsforms.d.ts +35 -7
  27. package/dist/server/src/importHubspot.d.ts +2 -1
  28. package/dist/server/src/index.d.ts +1 -0
  29. package/package.json +1 -1
@@ -1600,6 +1600,48 @@ function missingMappedFields(definition, existingNames) {
1600
1600
  const existing = new Set(existingNames);
1601
1601
  return mappedContactFields(definition).filter((field) => !existing.has(field.property));
1602
1602
  }
1603
+ const OBJECT_TYPE_BY_NAME = {
1604
+ contact: "0-1",
1605
+ company: "0-2"
1606
+ };
1607
+ function collectedHubspotKeys(definition) {
1608
+ const keys = /* @__PURE__ */ new Set();
1609
+ const add = (object, property) => {
1610
+ const typeId = OBJECT_TYPE_BY_NAME[object] || object;
1611
+ if (property) keys.add(`${typeId}:${property}`);
1612
+ };
1613
+ for (const step of definition.steps ?? []) {
1614
+ for (const fld of step.fields ?? []) {
1615
+ if (fld.type === "company") {
1616
+ const map = fld.companyMap ?? {};
1617
+ for (const slot of Object.values(map)) {
1618
+ if (slot?.property?.trim()) add(slot.object?.trim() || "company", slot.property.trim());
1619
+ }
1620
+ continue;
1621
+ }
1622
+ const mapping = fld.hubspot ?? {};
1623
+ const object = mapping.object?.trim() || "contact";
1624
+ const property = mapping.property?.trim() || fld.name;
1625
+ if (property) add(object, property);
1626
+ }
1627
+ }
1628
+ return keys;
1629
+ }
1630
+ function missingRequiredHubspotFields(definition, required) {
1631
+ const collected = collectedHubspotKeys(definition);
1632
+ const missing = [];
1633
+ for (const field of required) {
1634
+ if (!field.name) continue;
1635
+ const objectTypeId = field.objectTypeId?.trim() || "0-1";
1636
+ if (collected.has(`${objectTypeId}:${field.name}`)) continue;
1637
+ missing.push({
1638
+ name: field.name,
1639
+ objectTypeId,
1640
+ object: objectTypeId === "0-2" ? "company" : "contact"
1641
+ });
1642
+ }
1643
+ return missing;
1644
+ }
1603
1645
  function hsFieldType(type, options) {
1604
1646
  switch (type) {
1605
1647
  case "email":
@@ -1650,6 +1692,74 @@ function appendFieldGroups(existing, extra) {
1650
1692
  }))
1651
1693
  ];
1652
1694
  }
1695
+ function conditionToHubspot(condition, parentId) {
1696
+ const rules = (condition.rules ?? []).filter((rule) => rule.field === parentId);
1697
+ if (!rules.length || rules.length !== (condition.rules ?? []).length) return null;
1698
+ const op = rules[0]?.operator;
1699
+ if (rules.some((rule) => rule.operator !== op)) return null;
1700
+ const values = rules.map((rule) => rule.value).filter((value) => Boolean(value));
1701
+ switch (op) {
1702
+ case "eq":
1703
+ return { operator: values.length > 1 ? "SET_ANY" : "EQ", values };
1704
+ case "neq":
1705
+ return { operator: values.length > 1 ? "NOT_SET_ANY" : "NEQ", values };
1706
+ case "contains":
1707
+ return { operator: "CONTAINS", values };
1708
+ case "gt":
1709
+ return { operator: "GT", values };
1710
+ case "lt":
1711
+ return { operator: "LT", values };
1712
+ case "notEmpty":
1713
+ return { operator: "SET", values: [] };
1714
+ case "empty":
1715
+ return { operator: "NOT_SET", values: [] };
1716
+ default:
1717
+ return null;
1718
+ }
1719
+ }
1720
+ function applyVisibleIfToFieldGroups(groups, definition) {
1721
+ const byId = /* @__PURE__ */ new Map();
1722
+ for (const step of definition.steps ?? []) {
1723
+ for (const fld of step.fields ?? []) {
1724
+ const mapping = fld.hubspot ?? {};
1725
+ byId.set(fld.id, { name: fld.name, property: mapping.property?.trim() || fld.name });
1726
+ }
1727
+ }
1728
+ const moves = /* @__PURE__ */ new Map();
1729
+ for (const step of definition.steps ?? []) {
1730
+ for (const fld of step.fields ?? []) {
1731
+ const visibleIf = fld.visibleIf;
1732
+ const parentId = visibleIf?.rules?.[0]?.field;
1733
+ if (!visibleIf || !parentId) continue;
1734
+ const converted = conditionToHubspot(visibleIf, parentId);
1735
+ const parent = byId.get(parentId);
1736
+ const mapping = fld.hubspot ?? {};
1737
+ const property = mapping.property?.trim() || fld.name;
1738
+ if (!converted || !parent) continue;
1739
+ moves.set(property, { parent: parent.property, ...converted });
1740
+ }
1741
+ }
1742
+ if (!moves.size) return groups;
1743
+ const cloned = JSON.parse(JSON.stringify(groups));
1744
+ const flat = [];
1745
+ for (const group of cloned) {
1746
+ (group.fields ?? []).forEach((field, index2) => flat.push({ group, index: index2, field }));
1747
+ }
1748
+ for (const [property, move] of moves) {
1749
+ const child = flat.find((item) => item.field.name === property);
1750
+ const parent = flat.find((item) => item.field.name === move.parent);
1751
+ if (!child || !parent || child === parent) continue;
1752
+ parent.field.dependentFields = [
1753
+ ...parent.field.dependentFields ?? [],
1754
+ {
1755
+ dependentCondition: { operator: move.operator, values: move.values },
1756
+ field: child.field
1757
+ }
1758
+ ];
1759
+ child.group.fields?.splice(child.index, 1);
1760
+ }
1761
+ return cloned;
1762
+ }
1653
1763
  const HS_BASE$2 = "https://api.hubapi.com";
1654
1764
  const OBJECT_BY_TYPE_ID = {
1655
1765
  "0-1": "contact",
@@ -1731,7 +1841,7 @@ function convertField(raw, ctx) {
1731
1841
  helpText: raw.description || void 0,
1732
1842
  ...options.length ? { options } : {},
1733
1843
  hubspot: { object, property: name },
1734
- ...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
1844
+ ...ctx.parentCondition ? { visibleIf: ctx.parentCondition, visibleIfSource: "hubspot" } : {}
1735
1845
  };
1736
1846
  const out = [field];
1737
1847
  for (const dependent of raw.dependentFields ?? []) {
@@ -1754,6 +1864,12 @@ function convertHubspotForm(raw) {
1754
1864
  const usedNames = /* @__PURE__ */ new Set();
1755
1865
  const fields = [];
1756
1866
  for (const group of raw.fieldGroups ?? []) {
1867
+ if (group.groupType === "progressive" || group.groupType === "queued") {
1868
+ skipped.push({
1869
+ code: "progressive",
1870
+ detail: group.groupType
1871
+ });
1872
+ }
1757
1873
  if (group.richText && !(group.fields ?? []).length) {
1758
1874
  skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
1759
1875
  continue;
@@ -1825,7 +1941,10 @@ function mergeHubspotImport(current, incoming) {
1825
1941
  existing.helpText = next.helpText;
1826
1942
  if (next.options) existing.options = next.options;
1827
1943
  else delete existing.options;
1828
- if (!existing.visibleIf && next.visibleIf) existing.visibleIf = next.visibleIf;
1944
+ if (next.visibleIf && (existing.visibleIfSource === "hubspot" || !existing.visibleIf)) {
1945
+ existing.visibleIf = next.visibleIf;
1946
+ if (existing.visibleIfSource !== "editor") existing.visibleIfSource = "hubspot";
1947
+ }
1829
1948
  updated.push(key);
1830
1949
  continue;
1831
1950
  }
@@ -1923,10 +2042,6 @@ function formsSubmitUrl(portalId, formGuid, region = "eu1") {
1923
2042
  function toHsFields(props) {
1924
2043
  return Object.entries(props).filter(([, value]) => value !== "").map(([name, value]) => ({ name, value }));
1925
2044
  }
1926
- function filterToFormFields(fields, allowed) {
1927
- if (!allowed) return fields;
1928
- return fields.filter((field) => allowed.has(field.name));
1929
- }
1930
2045
  function formFieldNames(raw) {
1931
2046
  const names = [];
1932
2047
  for (const group of raw.fieldGroups ?? []) {
@@ -1936,20 +2051,42 @@ function formFieldNames(raw) {
1936
2051
  }
1937
2052
  return names;
1938
2053
  }
1939
- function formRequiredNames(raw) {
1940
- const names = [];
2054
+ const OBJECT_BAG = {
2055
+ "0-1": "contact",
2056
+ "0-2": "company"
2057
+ };
2058
+ function formShapeFields(raw) {
2059
+ const fields = [];
1941
2060
  for (const group of raw.fieldGroups ?? []) {
1942
2061
  for (const field of group.fields ?? []) {
1943
- if (field.name && field.required) names.push(field.name);
2062
+ if (!field.name) continue;
2063
+ fields.push({
2064
+ name: field.name,
2065
+ objectTypeId: field.objectTypeId?.trim() || "0-1",
2066
+ required: Boolean(field.required)
2067
+ });
1944
2068
  }
1945
2069
  }
1946
- return names;
2070
+ return fields;
2071
+ }
2072
+ function fieldsForHubspotForm(bags, shape, extra = {}) {
2073
+ const contact = { ...bags.contact ?? {}, ...extra };
2074
+ if (!shape?.fields.length) {
2075
+ return toHsFields(contact).map((field) => ({ ...field, objectTypeId: "0-1" }));
2076
+ }
2077
+ const out = [];
2078
+ for (const field of shape.fields) {
2079
+ const bagName = OBJECT_BAG[field.objectTypeId] || "contact";
2080
+ const value = bags[bagName]?.[field.name] ?? contact[field.name];
2081
+ if (!value) continue;
2082
+ out.push({ name: field.name, value, objectTypeId: field.objectTypeId || "0-1" });
2083
+ }
2084
+ return out;
1947
2085
  }
1948
2086
  function parseFormShape(raw) {
1949
2087
  const legal = raw.legalConsentOptions;
1950
- const hasLegalConsent = Boolean(
1951
- legal && typeof legal === "object" && legal.type && legal.type !== "none"
1952
- );
2088
+ const legalType = typeof legal?.type === "string" ? legal.type : void 0;
2089
+ const hasLegalConsent = Boolean(legalType && legalType !== "none");
1953
2090
  const communications = [];
1954
2091
  for (const box of legal?.communicationsCheckboxes ?? []) {
1955
2092
  const id = Number(box.subscriptionTypeId ?? box.communicationTypeId);
@@ -1959,12 +2096,21 @@ function parseFormShape(raw) {
1959
2096
  text: String(box.label || box.text || "").trim() || "I agree to receive communications."
1960
2097
  });
1961
2098
  }
2099
+ const subscriptionTypeIds = (legal?.subscriptionTypeIds ?? []).map((id) => Number(id)).filter((id) => Number.isFinite(id) && id > 0);
1962
2100
  const consentToProcessText = legal?.consentToProcessText?.trim();
2101
+ const privacyText = legal?.privacyText?.trim();
2102
+ const lawfulRaw = String(legal?.lawfulBasis || "").toUpperCase();
2103
+ const fields = formShapeFields(raw);
1963
2104
  return {
1964
- names: new Set(formFieldNames(raw)),
1965
- required: new Set(formRequiredNames(raw)),
2105
+ names: new Set(fields.map((field) => field.name)),
2106
+ required: new Set(fields.filter((field) => field.required).map((field) => field.name)),
2107
+ fields,
1966
2108
  hasLegalConsent,
1967
- captcha: raw.configuration?.captchaEnabled === true,
2109
+ ...legalType ? { legalType } : {},
2110
+ lawfulBasis: lawfulRaw.includes("CUSTOMER") ? "CUSTOMER" : "LEAD",
2111
+ subscriptionTypeIds,
2112
+ ...privacyText ? { privacyText } : {},
2113
+ captcha: raw.configuration?.recaptchaEnabled === true || raw.configuration?.captchaEnabled === true,
1968
2114
  ...consentToProcessText ? { consentToProcessText } : {},
1969
2115
  communications
1970
2116
  };
@@ -1976,9 +2122,22 @@ function canRetryEmailOnly(required) {
1976
2122
  function buildLegalConsent(shape, given, fallbackText) {
1977
2123
  if (!given) return void 0;
1978
2124
  if (shape && !shape.hasLegalConsent) return void 0;
2125
+ const fallback = fallbackText?.trim() || DEFAULT_CONSENT_TEXT;
2126
+ if (shape?.legalType === "legitimate_interest") {
2127
+ const subscriptionTypeId = shape.subscriptionTypeIds[0];
2128
+ if (!subscriptionTypeId) return void 0;
2129
+ return {
2130
+ legitimateInterest: {
2131
+ value: true,
2132
+ subscriptionTypeId,
2133
+ legalBasis: shape.lawfulBasis || "LEAD",
2134
+ text: shape.privacyText?.trim() || fallback
2135
+ }
2136
+ };
2137
+ }
1979
2138
  const consent = {
1980
2139
  consentToProcess: true,
1981
- text: shape?.consentToProcessText?.trim() || fallbackText?.trim() || DEFAULT_CONSENT_TEXT
2140
+ text: shape?.consentToProcessText?.trim() || fallback
1982
2141
  };
1983
2142
  if (shape?.communications.length) {
1984
2143
  consent.communications = shape.communications.map((item) => ({
@@ -2106,13 +2265,62 @@ async function submitMarketingForm(input) {
2106
2265
  async function syncFieldsToHubspotForm(apiKey, formGuid, definition) {
2107
2266
  const raw = await fetchHubspotForm(apiKey, formGuid);
2108
2267
  const extra = missingMappedFields(definition, formFieldNames(raw));
2109
- if (!extra.length) return [];
2110
- await patchHubspotForm(apiKey, formGuid, {
2111
- fieldGroups: appendFieldGroups(raw.fieldGroups ?? [], extra)
2112
- });
2268
+ const fieldGroups = applyVisibleIfToFieldGroups(
2269
+ appendFieldGroups(raw.fieldGroups ?? [], extra),
2270
+ definition
2271
+ );
2272
+ if (!extra.length && JSON.stringify(fieldGroups) === JSON.stringify(appendFieldGroups(raw.fieldGroups ?? [], []))) {
2273
+ return [];
2274
+ }
2275
+ await patchHubspotForm(apiKey, formGuid, { fieldGroups });
2113
2276
  clearFormShapeCache(formGuid);
2114
2277
  return extra.map((field) => field.property);
2115
2278
  }
2279
+ async function createMarketingForm(apiKey, name, definition) {
2280
+ const fields = mappedContactFields(definition).map((field) => toHsFormField(field));
2281
+ if (!fields.length) {
2282
+ throw new Error("Add at least one mapped contact field before creating a HubSpot form");
2283
+ }
2284
+ const res = await fetch("https://api.hubapi.com/marketing/v3/forms", {
2285
+ method: "POST",
2286
+ headers: {
2287
+ Authorization: `Bearer ${apiKey}`,
2288
+ "Content-Type": "application/json"
2289
+ },
2290
+ body: JSON.stringify({
2291
+ name,
2292
+ formType: "hubspot",
2293
+ fieldGroups: applyVisibleIfToFieldGroups(
2294
+ fields.map((field) => ({
2295
+ groupType: "default_group",
2296
+ richTextType: "text",
2297
+ fields: [field]
2298
+ })),
2299
+ definition
2300
+ ),
2301
+ configuration: {
2302
+ language: "fr",
2303
+ recaptchaEnabled: false,
2304
+ createNewContactForNewEmail: true,
2305
+ prePopulateKnownValues: true,
2306
+ notifyContactOwner: false,
2307
+ notifyRecipients: [],
2308
+ editable: true,
2309
+ archivable: true,
2310
+ cloneable: true,
2311
+ allowLinkToResetKnownValues: false,
2312
+ postSubmitAction: { type: "thank_you", value: "Merci." }
2313
+ },
2314
+ displayOptions: { submitButtonText: "Envoyer", style: { submitButtonText: "Envoyer" } },
2315
+ legalConsentOptions: { type: "none" }
2316
+ })
2317
+ });
2318
+ const body = await res.json().catch(() => ({}));
2319
+ if (!res.ok || !body.id) {
2320
+ throw new Error(body.message || `HubSpot ${res.status}`);
2321
+ }
2322
+ return body.id;
2323
+ }
2116
2324
  function publicForm(entry) {
2117
2325
  return {
2118
2326
  slug: entry.slug,
@@ -2126,7 +2334,9 @@ function publicForm(entry) {
2126
2334
  steps: (entry.definition?.steps ?? []).map((step) => ({
2127
2335
  ...step,
2128
2336
  // The CRM mappings are the server's business — never the browser's.
2129
- fields: (step.fields ?? []).map(({ hubspot: _hubspot, companyMap: _companyMap, ...fld }) => fld)
2337
+ fields: (step.fields ?? []).map(
2338
+ ({ hubspot: _hubspot, companyMap: _companyMap, visibleIfSource: _source, ...fld }) => fld
2339
+ )
2130
2340
  }))
2131
2341
  };
2132
2342
  }
@@ -2500,12 +2710,16 @@ function createFormsService(strapi, _opts = {}) {
2500
2710
  if (tryFormsApi && portalId && formGuid) {
2501
2711
  try {
2502
2712
  const shape = await loadFormShape(apiKey, formGuid);
2503
- const fields = filterToFormFields(
2504
- toHsFields({ ...contactProps, email }),
2505
- shape?.names ?? null
2713
+ const fields = fieldsForHubspotForm(
2714
+ {
2715
+ contact: contactProps,
2716
+ company: stringProps(partition.accepted.company ?? {})
2717
+ },
2718
+ shape,
2719
+ { email }
2506
2720
  );
2507
2721
  if (fields.length && !fields.some((f) => f.name === "email")) {
2508
- fields.unshift({ name: "email", value: email });
2722
+ fields.unshift({ name: "email", value: email, objectTypeId: "0-1" });
2509
2723
  }
2510
2724
  sentFieldNames = fields.map((f) => f.name);
2511
2725
  const submitted = await submitMarketingForm({
@@ -2513,7 +2727,7 @@ function createFormsService(strapi, _opts = {}) {
2513
2727
  portalId,
2514
2728
  formGuid,
2515
2729
  region,
2516
- fields: fields.length ? fields : [{ name: "email", value: email }],
2730
+ fields: fields.length ? fields : [{ name: "email", value: email, objectTypeId: "0-1" }],
2517
2731
  context: {
2518
2732
  hutk: sanitizeHutk(meta.hutk),
2519
2733
  pageUri: typeof meta.pageUrl === "string" ? meta.pageUrl : void 0,
@@ -2866,6 +3080,22 @@ function createFormsAdminController(strapi) {
2866
3080
  ctx.body = { errors: structural, problems: mappings };
2867
3081
  return;
2868
3082
  }
3083
+ const { apiKey } = await resolveApiKey(strapi);
3084
+ const account = await resolveAccount(strapi);
3085
+ const ownGuid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
3086
+ const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(account.defaultFormId) ? account.defaultFormId.trim() : "";
3087
+ if (apiKey && formGuid) {
3088
+ const shape = await loadFormShape(apiKey, formGuid);
3089
+ const missingHubspot = shape ? missingRequiredHubspotFields(
3090
+ entry.definition ?? EMPTY_DEFINITION,
3091
+ [...shape.fields.filter((field) => field.required)]
3092
+ ) : [];
3093
+ if (missingHubspot.length) {
3094
+ ctx.status = 400;
3095
+ ctx.body = { errors: structural, problems: mappings, missingHubspot };
3096
+ return;
3097
+ }
3098
+ }
2869
3099
  await documents().publish({
2870
3100
  documentId: ctx.params.documentId,
2871
3101
  locale: ctx.query.locale
@@ -2873,22 +3103,16 @@ function createFormsAdminController(strapi) {
2873
3103
  const policy = await resolvePolicy(strapi);
2874
3104
  let addedFields = [];
2875
3105
  let syncWarning;
2876
- if (policy.syncFieldsOnPublish) {
2877
- const { apiKey } = await resolveApiKey(strapi);
2878
- const account = await resolveAccount(strapi);
2879
- const ownGuid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
2880
- const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(account.defaultFormId) ? account.defaultFormId.trim() : "";
2881
- if (apiKey && formGuid) {
2882
- try {
2883
- addedFields = await syncFieldsToHubspotForm(
2884
- apiKey,
2885
- formGuid,
2886
- entry.definition ?? EMPTY_DEFINITION
2887
- );
2888
- } catch (err) {
2889
- syncWarning = err.message;
2890
- strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
2891
- }
3106
+ if (policy.syncFieldsOnPublish && apiKey && formGuid) {
3107
+ try {
3108
+ addedFields = await syncFieldsToHubspotForm(
3109
+ apiKey,
3110
+ formGuid,
3111
+ entry.definition ?? EMPTY_DEFINITION
3112
+ );
3113
+ } catch (err) {
3114
+ syncWarning = err.message;
3115
+ strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
2892
3116
  }
2893
3117
  }
2894
3118
  ctx.body = {
@@ -2897,6 +3121,31 @@ function createFormsAdminController(strapi) {
2897
3121
  ...syncWarning ? { syncWarning } : {}
2898
3122
  };
2899
3123
  },
3124
+ async createInHubspot(ctx) {
3125
+ const entry = await documents().findOne({
3126
+ documentId: ctx.params.documentId,
3127
+ locale: ctx.query.locale,
3128
+ status: "draft"
3129
+ });
3130
+ if (!entry) ctx.throw(404, "Form not found");
3131
+ const { apiKey } = await resolveApiKey(strapi);
3132
+ if (!apiKey) ctx.throw(400, "HubSpot API key is missing");
3133
+ try {
3134
+ const id = await createMarketingForm(
3135
+ apiKey,
3136
+ entry.name || entry.slug || "Formulaire",
3137
+ entry.definition ?? EMPTY_DEFINITION
3138
+ );
3139
+ const updated = await documents().update({
3140
+ documentId: ctx.params.documentId,
3141
+ locale: ctx.query.locale,
3142
+ data: { hubspotFormId: id }
3143
+ });
3144
+ ctx.body = { form: updated, hubspotFormId: id };
3145
+ } catch (err) {
3146
+ ctx.throw(400, err.message);
3147
+ }
3148
+ },
2900
3149
  async unpublish(ctx) {
2901
3150
  await documents().unpublish({
2902
3151
  documentId: ctx.params.documentId,
@@ -3663,6 +3912,7 @@ const routes = {
3663
3912
  adminRoute("GET", "/builder/forms/:documentId", "formsAdmin.findOne", [FORMS_ACTION]),
3664
3913
  adminRoute("PUT", "/builder/forms/:documentId", "formsAdmin.update", [FORMS_ACTION]),
3665
3914
  adminRoute("POST", "/builder/forms/:documentId/publish", "formsAdmin.publish", [FORMS_ACTION]),
3915
+ adminRoute("POST", "/builder/forms/:documentId/create-hubspot", "formsAdmin.createInHubspot", [FORMS_ACTION]),
3666
3916
  adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
3667
3917
  adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
3668
3918
  adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),