strapi-plugin-hubspot 0.13.0 → 0.13.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ ## 0.13.1 — 2026-09-21
6
+
7
+ ### Added
8
+ - **GDPR proof on submissions.** `meta.consent` / `meta.consentedAt` sent by
9
+ the frontend are stored on the submission row and written on the HubSpot
10
+ timeline note. They are not CRM properties — an unknown HubSpot key would
11
+ fail the whole upsert. Rebuild HubSpot's native legal-consent block as a
12
+ required checkbox named `consent` (see README).
13
+
3
14
  ## 0.13.0 — 2026-08-31
4
15
 
5
16
  ### Added
package/README.md CHANGED
@@ -66,6 +66,33 @@ Every submission is stored in **HubSpot form submissions** (Content Manager),
66
66
  synced or not, with the CRM ids when the sync succeeded — the source of truth
67
67
  lives in your database, not in HubSpot's availability.
68
68
 
69
+ ### GDPR consent
70
+
71
+ HubSpot's native *legal consent* block is **not** a CRM field. Importing a
72
+ HubSpot form skips it (you'll see "rebuild it as a field") because this
73
+ builder maps answers onto Contact/Company properties, and an unknown property
74
+ makes HubSpot reject the whole upsert.
75
+
76
+ To connect consent so the CRM and Strapi both keep a proof:
77
+
78
+ 1. **In HubSpot** (Settings → Properties → Contact), create a checkbox
79
+ property, e.g. `rgpd_consent`. Optionally a datetime `rgpd_consented_at`.
80
+ 2. **In the form builder** (Admin → HubSpot Forms), last step: add a
81
+ **Checkbox**, name it `consent` (that name is what the site recognizes —
82
+ it will not inject a second box), mark it **required**.
83
+ 3. **Map it**: Object = Contact, property = `rgpd_consent`. The visitor's tick
84
+ then lands on the contact. Leave `consentedAt` unmapped; the frontend
85
+ sends it in `meta.consentedAt`, stored on the submission and on the
86
+ timeline note.
87
+ 4. **Publish** the form. The public submit pipeline already refuses a payload
88
+ without consent when the site is in front (Nuxt BFF). Direct calls to
89
+ `/api/hubspot/forms/:slug/submit` still accept a form that has no consent
90
+ field — add the checkbox so required-field validation covers them too.
91
+
92
+ The newsletter block on the site is **not** a HubSpot form: it upserts the
93
+ email as a contact and writes a timeline note with the consent timestamp.
94
+ No form to create in the builder for that one.
95
+
69
96
  ### Browsing submissions
70
97
 
71
98
  The **Submissions** button on the forms list (also reachable from a form's
@@ -1710,6 +1710,10 @@ function createFormsService(strapi, _opts = {}) {
1710
1710
  const path = opts.meta.originPath ? ` (${escapeHtml(String(opts.meta.originPath))})` : "";
1711
1711
  lines.push(`<strong>Sujet</strong> : ${escapeHtml(String(opts.meta.originLabel))}${path}`);
1712
1712
  }
1713
+ if (opts.meta.consent === true) {
1714
+ const when = typeof opts.meta.consentedAt === "string" && opts.meta.consentedAt ? ` (${escapeHtml(opts.meta.consentedAt)})` : "";
1715
+ lines.push(`<strong>Consentement RGPD</strong> : oui${when}`);
1716
+ }
1713
1717
  for (const record of opts.companies ?? []) {
1714
1718
  if (!record.name && !record.siret) continue;
1715
1719
  const bits = [record.name, record.siret ? `SIRET ${record.siret}` : ""].filter(Boolean);
@@ -2893,6 +2897,10 @@ const controllers = {
2893
2897
  const v = rawMeta[key];
2894
2898
  if (typeof v === "string" && v) meta[key] = v.slice(0, 500);
2895
2899
  }
2900
+ if (rawMeta.consent === true) meta.consent = true;
2901
+ if (typeof rawMeta.consentedAt === "string" && rawMeta.consentedAt.trim()) {
2902
+ meta.consentedAt = rawMeta.consentedAt.trim().slice(0, 40);
2903
+ }
2896
2904
  const outcome = await strapi.plugin("hubspot").service("forms").submit(entry, values, meta);
2897
2905
  if (!outcome.ok) {
2898
2906
  ctx.status = 422;
@@ -1709,6 +1709,10 @@ function createFormsService(strapi, _opts = {}) {
1709
1709
  const path = opts.meta.originPath ? ` (${escapeHtml(String(opts.meta.originPath))})` : "";
1710
1710
  lines.push(`<strong>Sujet</strong> : ${escapeHtml(String(opts.meta.originLabel))}${path}`);
1711
1711
  }
1712
+ if (opts.meta.consent === true) {
1713
+ const when = typeof opts.meta.consentedAt === "string" && opts.meta.consentedAt ? ` (${escapeHtml(opts.meta.consentedAt)})` : "";
1714
+ lines.push(`<strong>Consentement RGPD</strong> : oui${when}`);
1715
+ }
1712
1716
  for (const record of opts.companies ?? []) {
1713
1717
  if (!record.name && !record.siret) continue;
1714
1718
  const bits = [record.name, record.siret ? `SIRET ${record.siret}` : ""].filter(Boolean);
@@ -2892,6 +2896,10 @@ const controllers = {
2892
2896
  const v = rawMeta[key];
2893
2897
  if (typeof v === "string" && v) meta[key] = v.slice(0, 500);
2894
2898
  }
2899
+ if (rawMeta.consent === true) meta.consent = true;
2900
+ if (typeof rawMeta.consentedAt === "string" && rawMeta.consentedAt.trim()) {
2901
+ meta.consentedAt = rawMeta.consentedAt.trim().slice(0, 40);
2902
+ }
2895
2903
  const outcome = await strapi.plugin("hubspot").service("forms").submit(entry, values, meta);
2896
2904
  if (!outcome.ok) {
2897
2905
  ctx.status = 422;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "strapi-plugin-hubspot",
3
- "version": "0.13.0",
3
+ "version": "0.13.1",
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",