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.
- package/CHANGELOG.md +27 -0
- package/dist/_chunks/{Forms-DyV6KyiO.mjs → Forms-CUNHDvhI.mjs} +52 -6
- package/dist/_chunks/{Forms-uUN4XjhW.js → Forms-CylJ0nD3.js} +52 -6
- package/dist/_chunks/{HubspotFormInput-BkWat_wu.js → HubspotFormInput-D2H2u9fZ.js} +1 -1
- package/dist/_chunks/{HubspotFormInput-DEXPX-6j.mjs → HubspotFormInput-Dppgr_rd.mjs} +1 -1
- package/dist/_chunks/{HubspotObjectInput-CFDYW-TM.js → HubspotObjectInput-BjRCaqXr.js} +3 -3
- package/dist/_chunks/{HubspotObjectInput-DL8fYK2x.mjs → HubspotObjectInput-CWFddNNw.mjs} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-CFMkbQeT.js → HubspotPropertyInput-BlDarkcj.js} +3 -3
- package/dist/_chunks/{HubspotPropertyInput-WU5FMJj3.mjs → HubspotPropertyInput-TGG9ozTa.mjs} +3 -3
- package/dist/_chunks/{Settings-B_hhdpHa.js → Settings-DOx7YSqA.js} +2 -2
- package/dist/_chunks/{Settings-CiXEps_C.mjs → Settings-tct49GYU.mjs} +2 -2
- package/dist/_chunks/{index-BeBn7bJa.mjs → index-Au3W85GX.mjs} +5 -5
- package/dist/_chunks/{index-CG_WQzSa.js → index-DhGVA3ZL.js} +5 -5
- package/dist/_chunks/{objectLabels-CS_0QCNz.mjs → objectLabels-3rnJ0WUc.mjs} +1 -1
- package/dist/_chunks/{objectLabels-BGq-Ypgn.js → objectLabels-Bx7x8fFO.js} +1 -1
- package/dist/_chunks/{useHubspotSchema-CP7LRdzO.mjs → useHubspotSchema-BlVXfwju.mjs} +1 -1
- package/dist/_chunks/{useHubspotSchema-BZP2qkAu.js → useHubspotSchema-DrGeidXC.js} +1 -1
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/builder/types.d.ts +2 -1
- package/dist/server/index.js +293 -43
- package/dist/server/index.mjs +293 -43
- package/dist/server/src/conditions.d.ts +1 -0
- package/dist/server/src/formSync.d.ts +20 -0
- package/dist/server/src/formsAdmin.d.ts +1 -0
- package/dist/server/src/hsforms.d.ts +35 -7
- package/dist/server/src/importHubspot.d.ts +2 -1
- package/dist/server/src/index.d.ts +1 -0
- package/package.json +1 -1
package/dist/server/index.mjs
CHANGED
|
@@ -1599,6 +1599,48 @@ function missingMappedFields(definition, existingNames) {
|
|
|
1599
1599
|
const existing = new Set(existingNames);
|
|
1600
1600
|
return mappedContactFields(definition).filter((field) => !existing.has(field.property));
|
|
1601
1601
|
}
|
|
1602
|
+
const OBJECT_TYPE_BY_NAME = {
|
|
1603
|
+
contact: "0-1",
|
|
1604
|
+
company: "0-2"
|
|
1605
|
+
};
|
|
1606
|
+
function collectedHubspotKeys(definition) {
|
|
1607
|
+
const keys = /* @__PURE__ */ new Set();
|
|
1608
|
+
const add = (object, property) => {
|
|
1609
|
+
const typeId = OBJECT_TYPE_BY_NAME[object] || object;
|
|
1610
|
+
if (property) keys.add(`${typeId}:${property}`);
|
|
1611
|
+
};
|
|
1612
|
+
for (const step of definition.steps ?? []) {
|
|
1613
|
+
for (const fld of step.fields ?? []) {
|
|
1614
|
+
if (fld.type === "company") {
|
|
1615
|
+
const map = fld.companyMap ?? {};
|
|
1616
|
+
for (const slot of Object.values(map)) {
|
|
1617
|
+
if (slot?.property?.trim()) add(slot.object?.trim() || "company", slot.property.trim());
|
|
1618
|
+
}
|
|
1619
|
+
continue;
|
|
1620
|
+
}
|
|
1621
|
+
const mapping = fld.hubspot ?? {};
|
|
1622
|
+
const object = mapping.object?.trim() || "contact";
|
|
1623
|
+
const property = mapping.property?.trim() || fld.name;
|
|
1624
|
+
if (property) add(object, property);
|
|
1625
|
+
}
|
|
1626
|
+
}
|
|
1627
|
+
return keys;
|
|
1628
|
+
}
|
|
1629
|
+
function missingRequiredHubspotFields(definition, required) {
|
|
1630
|
+
const collected = collectedHubspotKeys(definition);
|
|
1631
|
+
const missing = [];
|
|
1632
|
+
for (const field of required) {
|
|
1633
|
+
if (!field.name) continue;
|
|
1634
|
+
const objectTypeId = field.objectTypeId?.trim() || "0-1";
|
|
1635
|
+
if (collected.has(`${objectTypeId}:${field.name}`)) continue;
|
|
1636
|
+
missing.push({
|
|
1637
|
+
name: field.name,
|
|
1638
|
+
objectTypeId,
|
|
1639
|
+
object: objectTypeId === "0-2" ? "company" : "contact"
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
return missing;
|
|
1643
|
+
}
|
|
1602
1644
|
function hsFieldType(type, options) {
|
|
1603
1645
|
switch (type) {
|
|
1604
1646
|
case "email":
|
|
@@ -1649,6 +1691,74 @@ function appendFieldGroups(existing, extra) {
|
|
|
1649
1691
|
}))
|
|
1650
1692
|
];
|
|
1651
1693
|
}
|
|
1694
|
+
function conditionToHubspot(condition, parentId) {
|
|
1695
|
+
const rules = (condition.rules ?? []).filter((rule) => rule.field === parentId);
|
|
1696
|
+
if (!rules.length || rules.length !== (condition.rules ?? []).length) return null;
|
|
1697
|
+
const op = rules[0]?.operator;
|
|
1698
|
+
if (rules.some((rule) => rule.operator !== op)) return null;
|
|
1699
|
+
const values = rules.map((rule) => rule.value).filter((value) => Boolean(value));
|
|
1700
|
+
switch (op) {
|
|
1701
|
+
case "eq":
|
|
1702
|
+
return { operator: values.length > 1 ? "SET_ANY" : "EQ", values };
|
|
1703
|
+
case "neq":
|
|
1704
|
+
return { operator: values.length > 1 ? "NOT_SET_ANY" : "NEQ", values };
|
|
1705
|
+
case "contains":
|
|
1706
|
+
return { operator: "CONTAINS", values };
|
|
1707
|
+
case "gt":
|
|
1708
|
+
return { operator: "GT", values };
|
|
1709
|
+
case "lt":
|
|
1710
|
+
return { operator: "LT", values };
|
|
1711
|
+
case "notEmpty":
|
|
1712
|
+
return { operator: "SET", values: [] };
|
|
1713
|
+
case "empty":
|
|
1714
|
+
return { operator: "NOT_SET", values: [] };
|
|
1715
|
+
default:
|
|
1716
|
+
return null;
|
|
1717
|
+
}
|
|
1718
|
+
}
|
|
1719
|
+
function applyVisibleIfToFieldGroups(groups, definition) {
|
|
1720
|
+
const byId = /* @__PURE__ */ new Map();
|
|
1721
|
+
for (const step of definition.steps ?? []) {
|
|
1722
|
+
for (const fld of step.fields ?? []) {
|
|
1723
|
+
const mapping = fld.hubspot ?? {};
|
|
1724
|
+
byId.set(fld.id, { name: fld.name, property: mapping.property?.trim() || fld.name });
|
|
1725
|
+
}
|
|
1726
|
+
}
|
|
1727
|
+
const moves = /* @__PURE__ */ new Map();
|
|
1728
|
+
for (const step of definition.steps ?? []) {
|
|
1729
|
+
for (const fld of step.fields ?? []) {
|
|
1730
|
+
const visibleIf = fld.visibleIf;
|
|
1731
|
+
const parentId = visibleIf?.rules?.[0]?.field;
|
|
1732
|
+
if (!visibleIf || !parentId) continue;
|
|
1733
|
+
const converted = conditionToHubspot(visibleIf, parentId);
|
|
1734
|
+
const parent = byId.get(parentId);
|
|
1735
|
+
const mapping = fld.hubspot ?? {};
|
|
1736
|
+
const property = mapping.property?.trim() || fld.name;
|
|
1737
|
+
if (!converted || !parent) continue;
|
|
1738
|
+
moves.set(property, { parent: parent.property, ...converted });
|
|
1739
|
+
}
|
|
1740
|
+
}
|
|
1741
|
+
if (!moves.size) return groups;
|
|
1742
|
+
const cloned = JSON.parse(JSON.stringify(groups));
|
|
1743
|
+
const flat = [];
|
|
1744
|
+
for (const group of cloned) {
|
|
1745
|
+
(group.fields ?? []).forEach((field, index2) => flat.push({ group, index: index2, field }));
|
|
1746
|
+
}
|
|
1747
|
+
for (const [property, move] of moves) {
|
|
1748
|
+
const child = flat.find((item) => item.field.name === property);
|
|
1749
|
+
const parent = flat.find((item) => item.field.name === move.parent);
|
|
1750
|
+
if (!child || !parent || child === parent) continue;
|
|
1751
|
+
parent.field.dependentFields = [
|
|
1752
|
+
...parent.field.dependentFields ?? [],
|
|
1753
|
+
{
|
|
1754
|
+
dependentCondition: { operator: move.operator, values: move.values },
|
|
1755
|
+
field: child.field
|
|
1756
|
+
}
|
|
1757
|
+
];
|
|
1758
|
+
child.group.fields?.splice(child.index, 1);
|
|
1759
|
+
}
|
|
1760
|
+
return cloned;
|
|
1761
|
+
}
|
|
1652
1762
|
const HS_BASE$2 = "https://api.hubapi.com";
|
|
1653
1763
|
const OBJECT_BY_TYPE_ID = {
|
|
1654
1764
|
"0-1": "contact",
|
|
@@ -1730,7 +1840,7 @@ function convertField(raw, ctx) {
|
|
|
1730
1840
|
helpText: raw.description || void 0,
|
|
1731
1841
|
...options.length ? { options } : {},
|
|
1732
1842
|
hubspot: { object, property: name },
|
|
1733
|
-
...ctx.parentCondition ? { visibleIf: ctx.parentCondition } : {}
|
|
1843
|
+
...ctx.parentCondition ? { visibleIf: ctx.parentCondition, visibleIfSource: "hubspot" } : {}
|
|
1734
1844
|
};
|
|
1735
1845
|
const out = [field];
|
|
1736
1846
|
for (const dependent of raw.dependentFields ?? []) {
|
|
@@ -1753,6 +1863,12 @@ function convertHubspotForm(raw) {
|
|
|
1753
1863
|
const usedNames = /* @__PURE__ */ new Set();
|
|
1754
1864
|
const fields = [];
|
|
1755
1865
|
for (const group of raw.fieldGroups ?? []) {
|
|
1866
|
+
if (group.groupType === "progressive" || group.groupType === "queued") {
|
|
1867
|
+
skipped.push({
|
|
1868
|
+
code: "progressive",
|
|
1869
|
+
detail: group.groupType
|
|
1870
|
+
});
|
|
1871
|
+
}
|
|
1756
1872
|
if (group.richText && !(group.fields ?? []).length) {
|
|
1757
1873
|
skipped.push({ code: "rich-text", detail: group.richText.slice(0, 80) });
|
|
1758
1874
|
continue;
|
|
@@ -1824,7 +1940,10 @@ function mergeHubspotImport(current, incoming) {
|
|
|
1824
1940
|
existing.helpText = next.helpText;
|
|
1825
1941
|
if (next.options) existing.options = next.options;
|
|
1826
1942
|
else delete existing.options;
|
|
1827
|
-
if (
|
|
1943
|
+
if (next.visibleIf && (existing.visibleIfSource === "hubspot" || !existing.visibleIf)) {
|
|
1944
|
+
existing.visibleIf = next.visibleIf;
|
|
1945
|
+
if (existing.visibleIfSource !== "editor") existing.visibleIfSource = "hubspot";
|
|
1946
|
+
}
|
|
1828
1947
|
updated.push(key);
|
|
1829
1948
|
continue;
|
|
1830
1949
|
}
|
|
@@ -1922,10 +2041,6 @@ function formsSubmitUrl(portalId, formGuid, region = "eu1") {
|
|
|
1922
2041
|
function toHsFields(props) {
|
|
1923
2042
|
return Object.entries(props).filter(([, value]) => value !== "").map(([name, value]) => ({ name, value }));
|
|
1924
2043
|
}
|
|
1925
|
-
function filterToFormFields(fields, allowed) {
|
|
1926
|
-
if (!allowed) return fields;
|
|
1927
|
-
return fields.filter((field) => allowed.has(field.name));
|
|
1928
|
-
}
|
|
1929
2044
|
function formFieldNames(raw) {
|
|
1930
2045
|
const names = [];
|
|
1931
2046
|
for (const group of raw.fieldGroups ?? []) {
|
|
@@ -1935,20 +2050,42 @@ function formFieldNames(raw) {
|
|
|
1935
2050
|
}
|
|
1936
2051
|
return names;
|
|
1937
2052
|
}
|
|
1938
|
-
|
|
1939
|
-
|
|
2053
|
+
const OBJECT_BAG = {
|
|
2054
|
+
"0-1": "contact",
|
|
2055
|
+
"0-2": "company"
|
|
2056
|
+
};
|
|
2057
|
+
function formShapeFields(raw) {
|
|
2058
|
+
const fields = [];
|
|
1940
2059
|
for (const group of raw.fieldGroups ?? []) {
|
|
1941
2060
|
for (const field of group.fields ?? []) {
|
|
1942
|
-
if (field.name
|
|
2061
|
+
if (!field.name) continue;
|
|
2062
|
+
fields.push({
|
|
2063
|
+
name: field.name,
|
|
2064
|
+
objectTypeId: field.objectTypeId?.trim() || "0-1",
|
|
2065
|
+
required: Boolean(field.required)
|
|
2066
|
+
});
|
|
1943
2067
|
}
|
|
1944
2068
|
}
|
|
1945
|
-
return
|
|
2069
|
+
return fields;
|
|
2070
|
+
}
|
|
2071
|
+
function fieldsForHubspotForm(bags, shape, extra = {}) {
|
|
2072
|
+
const contact = { ...bags.contact ?? {}, ...extra };
|
|
2073
|
+
if (!shape?.fields.length) {
|
|
2074
|
+
return toHsFields(contact).map((field) => ({ ...field, objectTypeId: "0-1" }));
|
|
2075
|
+
}
|
|
2076
|
+
const out = [];
|
|
2077
|
+
for (const field of shape.fields) {
|
|
2078
|
+
const bagName = OBJECT_BAG[field.objectTypeId] || "contact";
|
|
2079
|
+
const value = bags[bagName]?.[field.name] ?? contact[field.name];
|
|
2080
|
+
if (!value) continue;
|
|
2081
|
+
out.push({ name: field.name, value, objectTypeId: field.objectTypeId || "0-1" });
|
|
2082
|
+
}
|
|
2083
|
+
return out;
|
|
1946
2084
|
}
|
|
1947
2085
|
function parseFormShape(raw) {
|
|
1948
2086
|
const legal = raw.legalConsentOptions;
|
|
1949
|
-
const
|
|
1950
|
-
|
|
1951
|
-
);
|
|
2087
|
+
const legalType = typeof legal?.type === "string" ? legal.type : void 0;
|
|
2088
|
+
const hasLegalConsent = Boolean(legalType && legalType !== "none");
|
|
1952
2089
|
const communications = [];
|
|
1953
2090
|
for (const box of legal?.communicationsCheckboxes ?? []) {
|
|
1954
2091
|
const id = Number(box.subscriptionTypeId ?? box.communicationTypeId);
|
|
@@ -1958,12 +2095,21 @@ function parseFormShape(raw) {
|
|
|
1958
2095
|
text: String(box.label || box.text || "").trim() || "I agree to receive communications."
|
|
1959
2096
|
});
|
|
1960
2097
|
}
|
|
2098
|
+
const subscriptionTypeIds = (legal?.subscriptionTypeIds ?? []).map((id) => Number(id)).filter((id) => Number.isFinite(id) && id > 0);
|
|
1961
2099
|
const consentToProcessText = legal?.consentToProcessText?.trim();
|
|
2100
|
+
const privacyText = legal?.privacyText?.trim();
|
|
2101
|
+
const lawfulRaw = String(legal?.lawfulBasis || "").toUpperCase();
|
|
2102
|
+
const fields = formShapeFields(raw);
|
|
1962
2103
|
return {
|
|
1963
|
-
names: new Set(
|
|
1964
|
-
required: new Set(
|
|
2104
|
+
names: new Set(fields.map((field) => field.name)),
|
|
2105
|
+
required: new Set(fields.filter((field) => field.required).map((field) => field.name)),
|
|
2106
|
+
fields,
|
|
1965
2107
|
hasLegalConsent,
|
|
1966
|
-
|
|
2108
|
+
...legalType ? { legalType } : {},
|
|
2109
|
+
lawfulBasis: lawfulRaw.includes("CUSTOMER") ? "CUSTOMER" : "LEAD",
|
|
2110
|
+
subscriptionTypeIds,
|
|
2111
|
+
...privacyText ? { privacyText } : {},
|
|
2112
|
+
captcha: raw.configuration?.recaptchaEnabled === true || raw.configuration?.captchaEnabled === true,
|
|
1967
2113
|
...consentToProcessText ? { consentToProcessText } : {},
|
|
1968
2114
|
communications
|
|
1969
2115
|
};
|
|
@@ -1975,9 +2121,22 @@ function canRetryEmailOnly(required) {
|
|
|
1975
2121
|
function buildLegalConsent(shape, given, fallbackText) {
|
|
1976
2122
|
if (!given) return void 0;
|
|
1977
2123
|
if (shape && !shape.hasLegalConsent) return void 0;
|
|
2124
|
+
const fallback = fallbackText?.trim() || DEFAULT_CONSENT_TEXT;
|
|
2125
|
+
if (shape?.legalType === "legitimate_interest") {
|
|
2126
|
+
const subscriptionTypeId = shape.subscriptionTypeIds[0];
|
|
2127
|
+
if (!subscriptionTypeId) return void 0;
|
|
2128
|
+
return {
|
|
2129
|
+
legitimateInterest: {
|
|
2130
|
+
value: true,
|
|
2131
|
+
subscriptionTypeId,
|
|
2132
|
+
legalBasis: shape.lawfulBasis || "LEAD",
|
|
2133
|
+
text: shape.privacyText?.trim() || fallback
|
|
2134
|
+
}
|
|
2135
|
+
};
|
|
2136
|
+
}
|
|
1978
2137
|
const consent = {
|
|
1979
2138
|
consentToProcess: true,
|
|
1980
|
-
text: shape?.consentToProcessText?.trim() ||
|
|
2139
|
+
text: shape?.consentToProcessText?.trim() || fallback
|
|
1981
2140
|
};
|
|
1982
2141
|
if (shape?.communications.length) {
|
|
1983
2142
|
consent.communications = shape.communications.map((item) => ({
|
|
@@ -2105,13 +2264,62 @@ async function submitMarketingForm(input) {
|
|
|
2105
2264
|
async function syncFieldsToHubspotForm(apiKey, formGuid, definition) {
|
|
2106
2265
|
const raw = await fetchHubspotForm(apiKey, formGuid);
|
|
2107
2266
|
const extra = missingMappedFields(definition, formFieldNames(raw));
|
|
2108
|
-
|
|
2109
|
-
|
|
2110
|
-
|
|
2111
|
-
|
|
2267
|
+
const fieldGroups = applyVisibleIfToFieldGroups(
|
|
2268
|
+
appendFieldGroups(raw.fieldGroups ?? [], extra),
|
|
2269
|
+
definition
|
|
2270
|
+
);
|
|
2271
|
+
if (!extra.length && JSON.stringify(fieldGroups) === JSON.stringify(appendFieldGroups(raw.fieldGroups ?? [], []))) {
|
|
2272
|
+
return [];
|
|
2273
|
+
}
|
|
2274
|
+
await patchHubspotForm(apiKey, formGuid, { fieldGroups });
|
|
2112
2275
|
clearFormShapeCache(formGuid);
|
|
2113
2276
|
return extra.map((field) => field.property);
|
|
2114
2277
|
}
|
|
2278
|
+
async function createMarketingForm(apiKey, name, definition) {
|
|
2279
|
+
const fields = mappedContactFields(definition).map((field) => toHsFormField(field));
|
|
2280
|
+
if (!fields.length) {
|
|
2281
|
+
throw new Error("Add at least one mapped contact field before creating a HubSpot form");
|
|
2282
|
+
}
|
|
2283
|
+
const res = await fetch("https://api.hubapi.com/marketing/v3/forms", {
|
|
2284
|
+
method: "POST",
|
|
2285
|
+
headers: {
|
|
2286
|
+
Authorization: `Bearer ${apiKey}`,
|
|
2287
|
+
"Content-Type": "application/json"
|
|
2288
|
+
},
|
|
2289
|
+
body: JSON.stringify({
|
|
2290
|
+
name,
|
|
2291
|
+
formType: "hubspot",
|
|
2292
|
+
fieldGroups: applyVisibleIfToFieldGroups(
|
|
2293
|
+
fields.map((field) => ({
|
|
2294
|
+
groupType: "default_group",
|
|
2295
|
+
richTextType: "text",
|
|
2296
|
+
fields: [field]
|
|
2297
|
+
})),
|
|
2298
|
+
definition
|
|
2299
|
+
),
|
|
2300
|
+
configuration: {
|
|
2301
|
+
language: "fr",
|
|
2302
|
+
recaptchaEnabled: false,
|
|
2303
|
+
createNewContactForNewEmail: true,
|
|
2304
|
+
prePopulateKnownValues: true,
|
|
2305
|
+
notifyContactOwner: false,
|
|
2306
|
+
notifyRecipients: [],
|
|
2307
|
+
editable: true,
|
|
2308
|
+
archivable: true,
|
|
2309
|
+
cloneable: true,
|
|
2310
|
+
allowLinkToResetKnownValues: false,
|
|
2311
|
+
postSubmitAction: { type: "thank_you", value: "Merci." }
|
|
2312
|
+
},
|
|
2313
|
+
displayOptions: { submitButtonText: "Envoyer", style: { submitButtonText: "Envoyer" } },
|
|
2314
|
+
legalConsentOptions: { type: "none" }
|
|
2315
|
+
})
|
|
2316
|
+
});
|
|
2317
|
+
const body = await res.json().catch(() => ({}));
|
|
2318
|
+
if (!res.ok || !body.id) {
|
|
2319
|
+
throw new Error(body.message || `HubSpot ${res.status}`);
|
|
2320
|
+
}
|
|
2321
|
+
return body.id;
|
|
2322
|
+
}
|
|
2115
2323
|
function publicForm(entry) {
|
|
2116
2324
|
return {
|
|
2117
2325
|
slug: entry.slug,
|
|
@@ -2125,7 +2333,9 @@ function publicForm(entry) {
|
|
|
2125
2333
|
steps: (entry.definition?.steps ?? []).map((step) => ({
|
|
2126
2334
|
...step,
|
|
2127
2335
|
// The CRM mappings are the server's business — never the browser's.
|
|
2128
|
-
fields: (step.fields ?? []).map(
|
|
2336
|
+
fields: (step.fields ?? []).map(
|
|
2337
|
+
({ hubspot: _hubspot, companyMap: _companyMap, visibleIfSource: _source, ...fld }) => fld
|
|
2338
|
+
)
|
|
2129
2339
|
}))
|
|
2130
2340
|
};
|
|
2131
2341
|
}
|
|
@@ -2499,12 +2709,16 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
2499
2709
|
if (tryFormsApi && portalId && formGuid) {
|
|
2500
2710
|
try {
|
|
2501
2711
|
const shape = await loadFormShape(apiKey, formGuid);
|
|
2502
|
-
const fields =
|
|
2503
|
-
|
|
2504
|
-
|
|
2712
|
+
const fields = fieldsForHubspotForm(
|
|
2713
|
+
{
|
|
2714
|
+
contact: contactProps,
|
|
2715
|
+
company: stringProps(partition.accepted.company ?? {})
|
|
2716
|
+
},
|
|
2717
|
+
shape,
|
|
2718
|
+
{ email }
|
|
2505
2719
|
);
|
|
2506
2720
|
if (fields.length && !fields.some((f) => f.name === "email")) {
|
|
2507
|
-
fields.unshift({ name: "email", value: email });
|
|
2721
|
+
fields.unshift({ name: "email", value: email, objectTypeId: "0-1" });
|
|
2508
2722
|
}
|
|
2509
2723
|
sentFieldNames = fields.map((f) => f.name);
|
|
2510
2724
|
const submitted = await submitMarketingForm({
|
|
@@ -2512,7 +2726,7 @@ function createFormsService(strapi, _opts = {}) {
|
|
|
2512
2726
|
portalId,
|
|
2513
2727
|
formGuid,
|
|
2514
2728
|
region,
|
|
2515
|
-
fields: fields.length ? fields : [{ name: "email", value: email }],
|
|
2729
|
+
fields: fields.length ? fields : [{ name: "email", value: email, objectTypeId: "0-1" }],
|
|
2516
2730
|
context: {
|
|
2517
2731
|
hutk: sanitizeHutk(meta.hutk),
|
|
2518
2732
|
pageUri: typeof meta.pageUrl === "string" ? meta.pageUrl : void 0,
|
|
@@ -2865,6 +3079,22 @@ function createFormsAdminController(strapi) {
|
|
|
2865
3079
|
ctx.body = { errors: structural, problems: mappings };
|
|
2866
3080
|
return;
|
|
2867
3081
|
}
|
|
3082
|
+
const { apiKey } = await resolveApiKey(strapi);
|
|
3083
|
+
const account = await resolveAccount(strapi);
|
|
3084
|
+
const ownGuid = typeof entry.hubspotFormId === "string" ? entry.hubspotFormId.trim() : "";
|
|
3085
|
+
const formGuid = isFormGuid(ownGuid) ? ownGuid : isFormGuid(account.defaultFormId) ? account.defaultFormId.trim() : "";
|
|
3086
|
+
if (apiKey && formGuid) {
|
|
3087
|
+
const shape = await loadFormShape(apiKey, formGuid);
|
|
3088
|
+
const missingHubspot = shape ? missingRequiredHubspotFields(
|
|
3089
|
+
entry.definition ?? EMPTY_DEFINITION,
|
|
3090
|
+
[...shape.fields.filter((field) => field.required)]
|
|
3091
|
+
) : [];
|
|
3092
|
+
if (missingHubspot.length) {
|
|
3093
|
+
ctx.status = 400;
|
|
3094
|
+
ctx.body = { errors: structural, problems: mappings, missingHubspot };
|
|
3095
|
+
return;
|
|
3096
|
+
}
|
|
3097
|
+
}
|
|
2868
3098
|
await documents().publish({
|
|
2869
3099
|
documentId: ctx.params.documentId,
|
|
2870
3100
|
locale: ctx.query.locale
|
|
@@ -2872,22 +3102,16 @@ function createFormsAdminController(strapi) {
|
|
|
2872
3102
|
const policy = await resolvePolicy(strapi);
|
|
2873
3103
|
let addedFields = [];
|
|
2874
3104
|
let syncWarning;
|
|
2875
|
-
if (policy.syncFieldsOnPublish) {
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2882
|
-
|
|
2883
|
-
|
|
2884
|
-
|
|
2885
|
-
entry.definition ?? EMPTY_DEFINITION
|
|
2886
|
-
);
|
|
2887
|
-
} catch (err) {
|
|
2888
|
-
syncWarning = err.message;
|
|
2889
|
-
strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
|
|
2890
|
-
}
|
|
3105
|
+
if (policy.syncFieldsOnPublish && apiKey && formGuid) {
|
|
3106
|
+
try {
|
|
3107
|
+
addedFields = await syncFieldsToHubspotForm(
|
|
3108
|
+
apiKey,
|
|
3109
|
+
formGuid,
|
|
3110
|
+
entry.definition ?? EMPTY_DEFINITION
|
|
3111
|
+
);
|
|
3112
|
+
} catch (err) {
|
|
3113
|
+
syncWarning = err.message;
|
|
3114
|
+
strapi.log.warn(`[hubspot] field sync on publish failed — ${syncWarning}`);
|
|
2891
3115
|
}
|
|
2892
3116
|
}
|
|
2893
3117
|
ctx.body = {
|
|
@@ -2896,6 +3120,31 @@ function createFormsAdminController(strapi) {
|
|
|
2896
3120
|
...syncWarning ? { syncWarning } : {}
|
|
2897
3121
|
};
|
|
2898
3122
|
},
|
|
3123
|
+
async createInHubspot(ctx) {
|
|
3124
|
+
const entry = await documents().findOne({
|
|
3125
|
+
documentId: ctx.params.documentId,
|
|
3126
|
+
locale: ctx.query.locale,
|
|
3127
|
+
status: "draft"
|
|
3128
|
+
});
|
|
3129
|
+
if (!entry) ctx.throw(404, "Form not found");
|
|
3130
|
+
const { apiKey } = await resolveApiKey(strapi);
|
|
3131
|
+
if (!apiKey) ctx.throw(400, "HubSpot API key is missing");
|
|
3132
|
+
try {
|
|
3133
|
+
const id = await createMarketingForm(
|
|
3134
|
+
apiKey,
|
|
3135
|
+
entry.name || entry.slug || "Formulaire",
|
|
3136
|
+
entry.definition ?? EMPTY_DEFINITION
|
|
3137
|
+
);
|
|
3138
|
+
const updated = await documents().update({
|
|
3139
|
+
documentId: ctx.params.documentId,
|
|
3140
|
+
locale: ctx.query.locale,
|
|
3141
|
+
data: { hubspotFormId: id }
|
|
3142
|
+
});
|
|
3143
|
+
ctx.body = { form: updated, hubspotFormId: id };
|
|
3144
|
+
} catch (err) {
|
|
3145
|
+
ctx.throw(400, err.message);
|
|
3146
|
+
}
|
|
3147
|
+
},
|
|
2899
3148
|
async unpublish(ctx) {
|
|
2900
3149
|
await documents().unpublish({
|
|
2901
3150
|
documentId: ctx.params.documentId,
|
|
@@ -3662,6 +3911,7 @@ const routes = {
|
|
|
3662
3911
|
adminRoute("GET", "/builder/forms/:documentId", "formsAdmin.findOne", [FORMS_ACTION]),
|
|
3663
3912
|
adminRoute("PUT", "/builder/forms/:documentId", "formsAdmin.update", [FORMS_ACTION]),
|
|
3664
3913
|
adminRoute("POST", "/builder/forms/:documentId/publish", "formsAdmin.publish", [FORMS_ACTION]),
|
|
3914
|
+
adminRoute("POST", "/builder/forms/:documentId/create-hubspot", "formsAdmin.createInHubspot", [FORMS_ACTION]),
|
|
3665
3915
|
adminRoute("POST", "/builder/forms/:documentId/unpublish", "formsAdmin.unpublish", [FORMS_ACTION]),
|
|
3666
3916
|
adminRoute("DELETE", "/builder/forms/:documentId", "formsAdmin.remove", [FORMS_ACTION]),
|
|
3667
3917
|
adminRoute("POST", "/builder/forms/:documentId/duplicate", "formsAdmin.duplicate", [FORMS_ACTION]),
|
|
@@ -26,6 +26,21 @@ export interface MappedFormField {
|
|
|
26
26
|
export declare function leftoverContactProps(contact: Record<string, string>, sentFieldNames: Iterable<string>): Record<string, string>;
|
|
27
27
|
export declare function mappedContactFields(definition: FormDefinition): MappedFormField[];
|
|
28
28
|
export declare function missingMappedFields(definition: FormDefinition, existingNames: Iterable<string>): MappedFormField[];
|
|
29
|
+
export interface MissingHubspotRequired {
|
|
30
|
+
name: string;
|
|
31
|
+
objectTypeId: string;
|
|
32
|
+
object: string;
|
|
33
|
+
}
|
|
34
|
+
/** Properties the Strapi form can actually send (`objectTypeId:property`). */
|
|
35
|
+
export declare function collectedHubspotKeys(definition: FormDefinition): Set<string>;
|
|
36
|
+
/**
|
|
37
|
+
* Required marketing-form fields the Strapi form never collects. HubSpot
|
|
38
|
+
* rejects the whole conversion when one of these is absent.
|
|
39
|
+
*/
|
|
40
|
+
export declare function missingRequiredHubspotFields(definition: FormDefinition, required: {
|
|
41
|
+
name: string;
|
|
42
|
+
objectTypeId?: string;
|
|
43
|
+
}[]): MissingHubspotRequired[];
|
|
29
44
|
export declare function hsFieldType(type: string, options?: unknown[]): string;
|
|
30
45
|
export declare function toHsFormField(field: MappedFormField): Record<string, unknown>;
|
|
31
46
|
export interface RawFieldGroupLike {
|
|
@@ -35,3 +50,8 @@ export interface RawFieldGroupLike {
|
|
|
35
50
|
fields?: unknown[];
|
|
36
51
|
}
|
|
37
52
|
export declare function appendFieldGroups(existing: RawFieldGroupLike[], extra: MappedFormField[]): Record<string, unknown>[];
|
|
53
|
+
/**
|
|
54
|
+
* Nest Strapi `visibleIf` fields under their HubSpot parent as dependentFields.
|
|
55
|
+
* Conditions edited in Strapi are included; unsupported multi-parent rules stay flat.
|
|
56
|
+
*/
|
|
57
|
+
export declare function applyVisibleIfToFieldGroups(groups: Record<string, unknown>[], definition: FormDefinition): Record<string, unknown>[];
|
|
@@ -26,6 +26,7 @@ export declare function createFormsAdminController(strapi: Core.Strapi): {
|
|
|
26
26
|
create(ctx: Ctx): Promise<void>;
|
|
27
27
|
update(ctx: Ctx): Promise<void>;
|
|
28
28
|
publish(ctx: Ctx): Promise<void>;
|
|
29
|
+
createInHubspot(ctx: Ctx): Promise<void>;
|
|
29
30
|
unpublish(ctx: Ctx): Promise<void>;
|
|
30
31
|
/**
|
|
31
32
|
* Feeds the `hubspot.form-picker` custom field: every form of the builder
|
|
@@ -23,13 +23,9 @@ export declare function toHsFields(props: Record<string, string>): {
|
|
|
23
23
|
name: string;
|
|
24
24
|
value: string;
|
|
25
25
|
}[];
|
|
26
|
-
export declare function filterToFormFields
|
|
26
|
+
export declare function filterToFormFields<T extends {
|
|
27
27
|
name: string;
|
|
28
|
-
|
|
29
|
-
}[], allowed: Set<string> | null): {
|
|
30
|
-
name: string;
|
|
31
|
-
value: string;
|
|
32
|
-
}[];
|
|
28
|
+
}>(fields: T[], allowed: Set<string> | null): T[];
|
|
33
29
|
export declare function formFieldNames(raw: {
|
|
34
30
|
fieldGroups?: {
|
|
35
31
|
fields?: {
|
|
@@ -50,26 +46,56 @@ export interface CommunicationConsent {
|
|
|
50
46
|
subscriptionTypeId: number;
|
|
51
47
|
text: string;
|
|
52
48
|
}
|
|
49
|
+
export interface FormShapeField {
|
|
50
|
+
name: string;
|
|
51
|
+
objectTypeId: string;
|
|
52
|
+
required: boolean;
|
|
53
|
+
}
|
|
53
54
|
export interface FormShape {
|
|
54
55
|
names: Set<string>;
|
|
55
56
|
required: Set<string>;
|
|
57
|
+
fields: FormShapeField[];
|
|
56
58
|
hasLegalConsent: boolean;
|
|
59
|
+
legalType?: string;
|
|
60
|
+
lawfulBasis?: "LEAD" | "CUSTOMER";
|
|
61
|
+
subscriptionTypeIds: number[];
|
|
62
|
+
privacyText?: string;
|
|
57
63
|
captcha: boolean;
|
|
58
64
|
consentToProcessText?: string;
|
|
59
65
|
communications: CommunicationConsent[];
|
|
60
66
|
}
|
|
67
|
+
export declare function formShapeFields(raw: {
|
|
68
|
+
fieldGroups?: {
|
|
69
|
+
fields?: {
|
|
70
|
+
name?: string;
|
|
71
|
+
required?: boolean;
|
|
72
|
+
objectTypeId?: string;
|
|
73
|
+
}[];
|
|
74
|
+
}[];
|
|
75
|
+
}): FormShapeField[];
|
|
76
|
+
/** Values HubSpot declared on the marketing form, with the right objectTypeId. */
|
|
77
|
+
export declare function fieldsForHubspotForm(bags: Record<string, Record<string, string>>, shape: FormShape | null | undefined, extra?: Record<string, string>): {
|
|
78
|
+
name: string;
|
|
79
|
+
value: string;
|
|
80
|
+
objectTypeId: string;
|
|
81
|
+
}[];
|
|
61
82
|
export declare function parseFormShape(raw: {
|
|
62
83
|
fieldGroups?: {
|
|
63
84
|
fields?: {
|
|
64
85
|
name?: string;
|
|
65
86
|
required?: boolean;
|
|
87
|
+
objectTypeId?: string;
|
|
66
88
|
}[];
|
|
67
89
|
}[];
|
|
68
90
|
configuration?: {
|
|
69
91
|
captchaEnabled?: boolean;
|
|
92
|
+
recaptchaEnabled?: boolean;
|
|
70
93
|
};
|
|
71
94
|
legalConsentOptions?: {
|
|
72
95
|
type?: string;
|
|
96
|
+
lawfulBasis?: string;
|
|
97
|
+
privacyText?: string;
|
|
98
|
+
subscriptionTypeIds?: (number | string)[];
|
|
73
99
|
consentToProcessText?: string;
|
|
74
100
|
communicationsCheckboxes?: {
|
|
75
101
|
label?: string;
|
|
@@ -81,7 +107,7 @@ export declare function parseFormShape(raw: {
|
|
|
81
107
|
}): FormShape;
|
|
82
108
|
/** Email-only retry is only safe when HubSpot doesn't require other fields. */
|
|
83
109
|
export declare function canRetryEmailOnly(required: Iterable<string> | undefined): boolean;
|
|
84
|
-
export declare function buildLegalConsent(shape: Pick<FormShape, "hasLegalConsent" | "communications" | "consentToProcessText"> | null | undefined, given: boolean, fallbackText?: string): Record<string, unknown> | undefined;
|
|
110
|
+
export declare function buildLegalConsent(shape: Pick<FormShape, "hasLegalConsent" | "legalType" | "lawfulBasis" | "subscriptionTypeIds" | "privacyText" | "communications" | "consentToProcessText"> | null | undefined, given: boolean, fallbackText?: string): Record<string, unknown> | undefined;
|
|
85
111
|
export interface FormSubmitContext {
|
|
86
112
|
hutk?: string;
|
|
87
113
|
pageUri?: string;
|
|
@@ -127,3 +153,5 @@ export declare function submitMarketingForm(input: FormSubmitInput): Promise<{
|
|
|
127
153
|
* that already exist. Returns the property names that were added.
|
|
128
154
|
*/
|
|
129
155
|
export declare function syncFieldsToHubspotForm(apiKey: string, formGuid: string, definition: FormDefinition): Promise<string[]>;
|
|
156
|
+
/** Create a marketing form from the Strapi definition and return its GUID. */
|
|
157
|
+
export declare function createMarketingForm(apiKey: string, name: string, definition: FormDefinition): Promise<string>;
|
|
@@ -21,7 +21,7 @@ export interface HubspotFormSummary {
|
|
|
21
21
|
}
|
|
22
22
|
/** Something the converter could not express in the builder's model. */
|
|
23
23
|
export interface SkippedItem {
|
|
24
|
-
code: "field-type" | "hidden-field" | "object" | "duplicate" | "condition" | "rich-text" | "legal-consent";
|
|
24
|
+
code: "field-type" | "hidden-field" | "object" | "duplicate" | "condition" | "rich-text" | "legal-consent" | "progressive";
|
|
25
25
|
/** Field label or name, when the item is a field. */
|
|
26
26
|
label?: string;
|
|
27
27
|
/** Raw detail (the fieldType, the operator…) for the report. */
|
|
@@ -58,6 +58,7 @@ interface RawField {
|
|
|
58
58
|
dependentFields?: RawDependent[];
|
|
59
59
|
}
|
|
60
60
|
interface RawFieldGroup {
|
|
61
|
+
groupType?: string;
|
|
61
62
|
richText?: string;
|
|
62
63
|
fields?: RawField[];
|
|
63
64
|
}
|
|
@@ -274,6 +274,7 @@ declare const _default: {
|
|
|
274
274
|
create(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
275
275
|
update(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
276
276
|
publish(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
277
|
+
createInHubspot(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
277
278
|
unpublish(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
278
279
|
options(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
|
279
280
|
listSources(ctx: import("./formsAdmin").Ctx): Promise<void>;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-hubspot",
|
|
3
|
-
"version": "0.16.
|
|
3
|
+
"version": "0.16.8",
|
|
4
4
|
"description": "The form builder for HubSpot in Strapi — multi-step forms with conditional fields mapped to real CRM properties, a public submit pipeline with retries, and save-time mapping validation.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|