strapi-plugin-hubspot 0.9.0 → 0.11.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/dist/_chunks/{Forms-BsKys-Hc.mjs → Forms-DadTbe9Q.mjs} +203 -16
  3. package/dist/_chunks/{Forms-JuzNfsbL.js → Forms-Dn2W6OS4.js} +202 -15
  4. package/dist/_chunks/{HubspotFormInput-CIvHS5rp.mjs → HubspotFormInput-NO59AtM_.mjs} +1 -1
  5. package/dist/_chunks/{HubspotFormInput-ph9pe6xY.js → HubspotFormInput-S_Z2t_DN.js} +1 -1
  6. package/dist/_chunks/{HubspotObjectInput-dAd-lMdD.js → HubspotObjectInput-BIgIa_Dm.js} +3 -3
  7. package/dist/_chunks/{HubspotObjectInput-BX1qYD2c.mjs → HubspotObjectInput-DAo3FAMz.mjs} +3 -3
  8. package/dist/_chunks/{HubspotPropertyInput-BM3ZaY8f.js → HubspotPropertyInput-B-piLcIy.js} +3 -3
  9. package/dist/_chunks/{HubspotPropertyInput-C2Z6472q.mjs → HubspotPropertyInput-KX1Bhcsl.mjs} +3 -3
  10. package/dist/_chunks/{Settings-Cdcjy9DA.js → Settings-DMu_QThB.js} +2 -2
  11. package/dist/_chunks/{Settings-DnwGetcc.mjs → Settings-Dz0PEFOv.mjs} +2 -2
  12. package/dist/_chunks/{en-Cqfair98.js → en-BDGqW_yS.js} +24 -1
  13. package/dist/_chunks/{en-CcQWnXra.mjs → en-CH5LSPkL.mjs} +24 -1
  14. package/dist/_chunks/{fr-CBRZ9q8U.mjs → fr-65-RaB3w.mjs} +24 -1
  15. package/dist/_chunks/{fr-D117-3Ta.js → fr-C82LMqUM.js} +24 -1
  16. package/dist/_chunks/{index-BbRtXnAL.js → index-B7Ru1fu5.js} +6 -6
  17. package/dist/_chunks/{index-LTaVoyWJ.mjs → index-DA31TY2n.mjs} +6 -6
  18. package/dist/_chunks/{objectLabels-DE2xGq7f.mjs → objectLabels-Bl7sO5pb.mjs} +1 -1
  19. package/dist/_chunks/{objectLabels-DU0Cagt_.js → objectLabels-FTh984_h.js} +1 -1
  20. package/dist/_chunks/{useHubspotSchema-BSKow0X3.mjs → useHubspotSchema-BlDilCHc.mjs} +1 -1
  21. package/dist/_chunks/{useHubspotSchema-DzBMAxv7.js → useHubspotSchema-C2_PuXEm.js} +1 -1
  22. package/dist/admin/index.js +1 -1
  23. package/dist/admin/index.mjs +1 -1
  24. package/dist/admin/src/builder/CompanyMapEditor.d.ts +14 -0
  25. package/dist/admin/src/builder/types.d.ts +14 -2
  26. package/dist/server/index.js +1029 -17
  27. package/dist/server/index.mjs +1029 -17
  28. package/dist/server/src/__tests__/company.test.d.ts +1 -0
  29. package/dist/server/src/company.d.ts +79 -0
  30. package/dist/server/src/conditions.d.ts +7 -1
  31. package/dist/server/src/content-types.d.ts +3 -0
  32. package/dist/server/src/data/naf.d.ts +1 -0
  33. package/dist/server/src/forms.d.ts +16 -0
  34. package/dist/server/src/index.d.ts +13 -0
  35. package/dist/shared/types.d.ts +51 -2
  36. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,5 +1,46 @@
1
1
  # Changelog
2
2
 
3
+ ## 0.11.0 — 2026-08-29
4
+
5
+ ### Added — the `company` field (INSEE/SIRENE)
6
+ - New field type **`company`**: the visitor picks their company from the
7
+ French SIRENE registry (Recherche d'entreprises API — no key), siège and
8
+ matching établissements offered separately, closed ones filtered. Free text
9
+ stays allowed: an unfindable company submits its typed name alone.
10
+ - **The server is the authority**: the browser only nominates a SIRET; at
11
+ submission the plugin re-resolves it against SIRENE and maps fresh data. If
12
+ the API is down, the browser's snapshot is used and flagged unresolved.
13
+ - **Per-datum HubSpot mapping** (`companyMap`, server-side only — stripped
14
+ from the public API): legal name, SIRET, SIREN, address, postal code, city,
15
+ is-headquarters, NAF code + label (embedded NAF rev. 2 table), INSEE
16
+ headcount range — each with its own object + property picker in the builder.
17
+ - **Company dedup order**: mapped SIRET property → corporate email domain →
18
+ bare creation with the INSEE data. A personal email with a resolved SIRET
19
+ still gets its Company — the exact case the field exists for.
20
+ - Rotating **placeholder examples** (`placeholderExamples`, served publicly),
21
+ edited one per line in the builder.
22
+ - Public route `GET /api/hubspot/company-search?q=` (bounded, TTL-cached,
23
+ 4s timeout) for the frontend autocomplete; timeline note gains a company
24
+ line; submissions store the resolved records (`companies` attribute).
25
+ - Structural guard: a field name colliding with a company field's companion
26
+ keys (`<name>__siret`, `<name>__company`) is refused; `companyMap` entries
27
+ are validated against the portal schema like any mapping.
28
+
29
+ ## 0.10.0 — 2026-08-29
30
+
31
+ ### Added — HubSpot-parity condition operators
32
+ - `in` / `notIn` ("is any of" / "is none of"): one rule matching several
33
+ values — a multi-select over the referenced field's options in the editor
34
+ (comma-separated input when the field has none). Closes the gap that
35
+ previously required stacking OR'd `eq` rules.
36
+ - `notContains`, `startsWith`, `endsWith` — case-insensitive, like `contains`.
37
+ - Structural validation flags an `in`/`notIn` rule with no values, like the
38
+ other comparators; switching operator clears the stale value shape.
39
+
40
+ Frontends mirroring the engine client-side must add the five operators (the
41
+ typed `Operator` union in `strapi-plugin-hubspot/types` is the reference —
42
+ the server remains the authority at submission either way).
43
+
3
44
  ## 0.9.0 — 2026-08-29
4
45
 
5
46
  ### Added
@@ -2,14 +2,37 @@ import { jsx, jsxs, Fragment } from "react/jsx-runtime";
2
2
  import { useParams, useSearchParams, Link, useNavigate, Routes, Route } from "react-router-dom";
3
3
  import * as React from "react";
4
4
  import { u as useIntl } from "./index-CEh8vkxY.mjs";
5
- import { Typography, Flex, Box, SingleSelect, SingleSelectOption, TextInput, IconButton, Button, Field, Checkbox, Divider, Combobox, ComboboxOption, Loader, Badge, Textarea, Table, Thead, Tr, Th, Tbody, Td, Modal } from "@strapi/design-system";
5
+ import { Typography, Flex, Box, SingleSelect, SingleSelectOption, MultiSelect, MultiSelectOption, TextInput, IconButton, Button, Combobox, ComboboxOption, Field, Textarea, Checkbox, Divider, Loader, Badge, Table, Thead, Tr, Th, Tbody, Td, Modal } from "@strapi/design-system";
6
6
  import { Trash, Plus, ArrowClockwise, ArrowLeft, ArrowUp, ArrowDown, List, Duplicate, Download } from "@strapi/icons";
7
7
  import { useFetchClient } from "@strapi/strapi/admin";
8
- import { g as getTranslation, P as PLUGIN_ID } from "./index-LTaVoyWJ.mjs";
9
- import { o as objectLabel } from "./objectLabels-DE2xGq7f.mjs";
10
- import { u as useHubspotSchema } from "./useHubspotSchema-BSKow0X3.mjs";
11
- const OPERATORS = ["eq", "neq", "contains", "empty", "notEmpty", "gt", "lt"];
12
- const NEEDS_VALUE = /* @__PURE__ */ new Set(["eq", "neq", "contains", "gt", "lt"]);
8
+ import { g as getTranslation, P as PLUGIN_ID } from "./index-DA31TY2n.mjs";
9
+ import { o as objectLabel } from "./objectLabels-Bl7sO5pb.mjs";
10
+ import { u as useHubspotSchema } from "./useHubspotSchema-BlDilCHc.mjs";
11
+ const OPERATORS = [
12
+ "eq",
13
+ "neq",
14
+ "in",
15
+ "notIn",
16
+ "contains",
17
+ "notContains",
18
+ "startsWith",
19
+ "endsWith",
20
+ "empty",
21
+ "notEmpty",
22
+ "gt",
23
+ "lt"
24
+ ];
25
+ const NEEDS_VALUE = /* @__PURE__ */ new Set([
26
+ "eq",
27
+ "neq",
28
+ "contains",
29
+ "notContains",
30
+ "startsWith",
31
+ "endsWith",
32
+ "gt",
33
+ "lt"
34
+ ]);
35
+ const NEEDS_VALUES = /* @__PURE__ */ new Set(["in", "notIn"]);
13
36
  const ConditionEditor = ({ condition, candidates, onChange }) => {
14
37
  const { formatMessage } = useIntl();
15
38
  const t = (id, defaultMessage) => formatMessage({ id: getTranslation(id), defaultMessage });
@@ -20,7 +43,12 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
20
43
  empty: t("condition.op.empty", "is empty"),
21
44
  notEmpty: t("condition.op.notEmpty", "is filled"),
22
45
  gt: t("condition.op.gt", "is greater than"),
23
- lt: t("condition.op.lt", "is less than")
46
+ lt: t("condition.op.lt", "is less than"),
47
+ in: t("condition.op.in", "is any of"),
48
+ notIn: t("condition.op.notIn", "is none of"),
49
+ notContains: t("condition.op.notContains", "doesn't contain"),
50
+ startsWith: t("condition.op.startsWith", "starts with"),
51
+ endsWith: t("condition.op.endsWith", "ends with")
24
52
  };
25
53
  const rules = condition?.rules ?? [];
26
54
  const logic = condition?.logic ?? "and";
@@ -54,10 +82,31 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
54
82
  {
55
83
  "aria-label": t("condition.operator", "Operator"),
56
84
  value: rule.operator,
57
- onChange: (v) => updateRule(index, { operator: v }),
85
+ onChange: (v) => updateRule(index, { operator: v, value: "", values: [] }),
58
86
  children: OPERATORS.map((op) => /* @__PURE__ */ jsx(SingleSelectOption, { value: op, children: opLabel[op] }, op))
59
87
  }
60
88
  ) }),
89
+ NEEDS_VALUES.has(rule.operator) && /* @__PURE__ */ jsx(Box, { flex: "1", minWidth: "140px", children: targetOptions.length ? /* @__PURE__ */ jsx(
90
+ MultiSelect,
91
+ {
92
+ "aria-label": t("condition.values", "Values"),
93
+ value: rule.values ?? [],
94
+ withTags: true,
95
+ onChange: (values) => updateRule(index, { values }),
96
+ children: targetOptions.map((o) => /* @__PURE__ */ jsx(MultiSelectOption, { value: o.value, children: o.label || o.value }, o.value))
97
+ }
98
+ ) : /* @__PURE__ */ jsx(
99
+ TextInput,
100
+ {
101
+ "aria-label": t("condition.values", "Values"),
102
+ placeholder: t("condition.values-placeholder", "value1, value2…"),
103
+ defaultValue: (rule.values ?? []).join(", "),
104
+ onBlur: (e) => updateRule(index, {
105
+ values: e.target.value.split(",").map((v) => v.trim()).filter(Boolean)
106
+ })
107
+ },
108
+ `${index}-${rule.operator}`
109
+ ) }),
61
110
  NEEDS_VALUE.has(rule.operator) && /* @__PURE__ */ jsx(Box, { flex: "1", minWidth: "110px", children: targetOptions.length ? /* @__PURE__ */ jsx(
62
111
  SingleSelect,
63
112
  {
@@ -110,7 +159,20 @@ const FIELD_TYPES = [
110
159
  "textarea",
111
160
  "select",
112
161
  "checkbox",
113
- "radio"
162
+ "radio",
163
+ "company"
164
+ ];
165
+ const COMPANY_DATA = [
166
+ "name",
167
+ "siret",
168
+ "siren",
169
+ "address",
170
+ "zip",
171
+ "city",
172
+ "headquarters",
173
+ "naf",
174
+ "nafLabel",
175
+ "headcount"
114
176
  ];
115
177
  let counter = 0;
116
178
  const makeId = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter += 1).toString(36)}${Math.random().toString(36).slice(2, 6)}`;
@@ -125,6 +187,96 @@ function fieldsBefore(definition, beforeId) {
125
187
  }
126
188
  return out;
127
189
  }
190
+ const CompanyMapEditor = ({ map, onChange }) => {
191
+ const intl = useIntl();
192
+ const { formatMessage } = intl;
193
+ const { schema } = useHubspotSchema();
194
+ const t = (id, defaultMessage) => formatMessage({ id: getTranslation(id), defaultMessage });
195
+ const datumLabel = {
196
+ name: t("company.datum.name", "Legal name"),
197
+ siret: t("company.datum.siret", "SIRET"),
198
+ siren: t("company.datum.siren", "SIREN"),
199
+ address: t("company.datum.address", "Address"),
200
+ zip: t("company.datum.zip", "Postal code"),
201
+ city: t("company.datum.city", "City"),
202
+ headquarters: t("company.datum.headquarters", "Is headquarters"),
203
+ naf: t("company.datum.naf", "NAF code"),
204
+ nafLabel: t("company.datum.nafLabel", "NAF label"),
205
+ headcount: t("company.datum.headcount", "Headcount range")
206
+ };
207
+ const mapped = COMPANY_DATA.filter((d) => map[d]);
208
+ const unmapped = COMPANY_DATA.filter((d) => !map[d]);
209
+ const patch = (datum, entry) => {
210
+ const next = { ...map };
211
+ if (entry) next[datum] = entry;
212
+ else delete next[datum];
213
+ onChange(next);
214
+ };
215
+ return /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
216
+ mapped.map((datum) => {
217
+ const entry = map[datum] ?? {};
218
+ const object = entry.object?.trim() || "company";
219
+ const properties = (schema?.properties ?? []).filter((p) => p.object === object);
220
+ return /* @__PURE__ */ jsxs(Box, { background: "neutral100", hasRadius: true, padding: 2, children: [
221
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 1, children: [
222
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: datumLabel[datum] }),
223
+ /* @__PURE__ */ jsx(
224
+ IconButton,
225
+ {
226
+ label: t("company.remove-datum", "Stop sending this datum"),
227
+ onClick: () => patch(datum, void 0),
228
+ children: /* @__PURE__ */ jsx(Trash, {})
229
+ }
230
+ )
231
+ ] }),
232
+ /* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
233
+ /* @__PURE__ */ jsx(Box, { width: "130px", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsx(
234
+ SingleSelect,
235
+ {
236
+ "aria-label": t("panel.object", "Object"),
237
+ value: object,
238
+ onChange: (v) => (
239
+ // A property never survives an object switch.
240
+ patch(datum, { object: String(v), property: void 0 })
241
+ ),
242
+ children: (schema?.objects ?? ["contact", "company"]).map((name) => /* @__PURE__ */ jsx(SingleSelectOption, { value: name, children: objectLabel(intl, name) }, name))
243
+ }
244
+ ) }),
245
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsx(
246
+ Combobox,
247
+ {
248
+ "aria-label": t("panel.property", "Property"),
249
+ value: entry.property ?? "",
250
+ onChange: (v) => patch(datum, { object, property: v || void 0 }),
251
+ onClear: () => patch(datum, { object, property: void 0 }),
252
+ placeholder: t("panel.property-placeholder", "Search a property…"),
253
+ children: properties.map((p) => /* @__PURE__ */ jsxs(ComboboxOption, { value: p.name, children: [
254
+ p.group ? `${p.group} · ` : "",
255
+ p.label,
256
+ " (",
257
+ p.name,
258
+ ")"
259
+ ] }, p.name))
260
+ }
261
+ ) })
262
+ ] })
263
+ ] }, datum);
264
+ }),
265
+ unmapped.length > 0 && /* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
266
+ /* @__PURE__ */ jsx(Plus, { "aria-hidden": true, width: 12, height: 12 }),
267
+ /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsx(
268
+ SingleSelect,
269
+ {
270
+ "aria-label": t("company.add-datum", "Send another datum"),
271
+ placeholder: t("company.add-datum", "Send another datum"),
272
+ value: "",
273
+ onChange: (v) => patch(v, { object: "company", property: void 0 }),
274
+ children: unmapped.map((datum) => /* @__PURE__ */ jsx(SingleSelectOption, { value: datum, children: datumLabel[datum] }, datum))
275
+ }
276
+ ) })
277
+ ] })
278
+ ] });
279
+ };
128
280
  const slugifyName = (label) => label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
129
281
  const FieldPanel = ({ field, candidates, problem, onChange }) => {
130
282
  const intl = useIntl();
@@ -135,6 +287,13 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
135
287
  const properties = (schema?.properties ?? []).filter((p) => p.object === object);
136
288
  const selected = properties.find((p) => p.name === field.hubspot?.property);
137
289
  const isChoice = field.type === "select" || field.type === "radio";
290
+ const isCompany = field.type === "company";
291
+ const DEFAULT_COMPANY_MAP = {
292
+ name: { object: "company", property: "name" },
293
+ address: { object: "company", property: "address" },
294
+ zip: { object: "company", property: "zip" },
295
+ city: { object: "company", property: "city" }
296
+ };
138
297
  const setHubspot = (patch) => onChange({ hubspot: { ...field.hubspot, ...patch } });
139
298
  const importOptions = () => {
140
299
  if (!selected?.options?.length) return;
@@ -175,15 +334,30 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
175
334
  SingleSelect,
176
335
  {
177
336
  value: field.type,
178
- onChange: (v) => onChange({ type: v }),
337
+ onChange: (v) => onChange({
338
+ type: v,
339
+ ...v === "company" && !field.companyMap ? { companyMap: DEFAULT_COMPANY_MAP } : {}
340
+ }),
179
341
  children: FIELD_TYPES.map((type) => /* @__PURE__ */ jsx(SingleSelectOption, { value: type, children: type }, type))
180
342
  }
181
343
  )
182
344
  ] }),
183
345
  /* @__PURE__ */ jsxs(Flex, { gap: 4, children: [
184
346
  /* @__PURE__ */ jsx(Box, { flex: "1", children: /* @__PURE__ */ jsxs(Field.Root, { children: [
185
- /* @__PURE__ */ jsx(Field.Label, { children: t("panel.placeholder", "Placeholder") }),
186
- /* @__PURE__ */ jsx(
347
+ /* @__PURE__ */ jsx(Field.Label, { children: isCompany ? t("company.examples", "Placeholder examples (one per line)") : t("panel.placeholder", "Placeholder") }),
348
+ isCompany ? /* @__PURE__ */ jsx(
349
+ Textarea,
350
+ {
351
+ value: (field.placeholderExamples ?? []).join("\n"),
352
+ placeholder: t(
353
+ "company.examples-placeholder",
354
+ "Ex. : un établissement d'un grand groupe\nEx. : le siège de votre PME"
355
+ ),
356
+ onChange: (e) => onChange({
357
+ placeholderExamples: e.target.value.split("\n").map((line) => line.trim()).filter(Boolean)
358
+ })
359
+ }
360
+ ) : /* @__PURE__ */ jsx(
187
361
  TextInput,
188
362
  {
189
363
  value: field.placeholder ?? "",
@@ -318,7 +492,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
318
492
  /* @__PURE__ */ jsx(Divider, {}),
319
493
  /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
320
494
  /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
321
- /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: t("panel.hubspot", "HubSpot") }),
495
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: isCompany ? t("company.section", "Company data (INSEE → HubSpot)") : t("panel.hubspot", "HubSpot") }),
322
496
  /* @__PURE__ */ jsx(
323
497
  IconButton,
324
498
  {
@@ -329,7 +503,20 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
329
503
  }
330
504
  )
331
505
  ] }),
332
- /* @__PURE__ */ jsxs(Field.Root, { children: [
506
+ isCompany && /* @__PURE__ */ jsxs(Fragment, { children: [
507
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t(
508
+ "company.section-hint",
509
+ "The visitor picks their company from the SIRENE registry; the server re-resolves the SIRET and sends each datum below to its property."
510
+ ) }),
511
+ /* @__PURE__ */ jsx(
512
+ CompanyMapEditor,
513
+ {
514
+ map: field.companyMap ?? {},
515
+ onChange: (companyMap) => onChange({ companyMap })
516
+ }
517
+ )
518
+ ] }),
519
+ !isCompany && /* @__PURE__ */ jsxs(Field.Root, { children: [
333
520
  /* @__PURE__ */ jsx(Field.Label, { children: t("panel.object", "Object") }),
334
521
  /* @__PURE__ */ jsx(
335
522
  SingleSelect,
@@ -342,7 +529,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
342
529
  }
343
530
  )
344
531
  ] }),
345
- /* @__PURE__ */ jsxs(Field.Root, { children: [
532
+ !isCompany && /* @__PURE__ */ jsxs(Field.Root, { children: [
346
533
  /* @__PURE__ */ jsx(Field.Label, { children: t("panel.property", "Property") }),
347
534
  /* @__PURE__ */ jsx(
348
535
  Combobox,
@@ -365,7 +552,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
365
552
  }) }),
366
553
  !field.hubspot?.property && /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: t("panel.property-hint", "Without a property, the answer is sent under the payload key.") })
367
554
  ] }),
368
- deepLink && /* @__PURE__ */ jsx("a", { href: deepLink, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "primary600", children: t("panel.view-in-hubspot", "View in HubSpot ↗") }) })
555
+ !isCompany && deepLink && /* @__PURE__ */ jsx("a", { href: deepLink, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "primary600", children: t("panel.view-in-hubspot", "View in HubSpot ↗") }) })
369
556
  ] }),
370
557
  /* @__PURE__ */ jsx(Divider, {}),
371
558
  /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
@@ -7,9 +7,9 @@ const index = require("./index-DkTxsEqL.js");
7
7
  const designSystem = require("@strapi/design-system");
8
8
  const icons = require("@strapi/icons");
9
9
  const admin = require("@strapi/strapi/admin");
10
- const index$1 = require("./index-BbRtXnAL.js");
11
- const objectLabels = require("./objectLabels-DU0Cagt_.js");
12
- const useHubspotSchema = require("./useHubspotSchema-DzBMAxv7.js");
10
+ const index$1 = require("./index-B7Ru1fu5.js");
11
+ const objectLabels = require("./objectLabels-FTh984_h.js");
12
+ const useHubspotSchema = require("./useHubspotSchema-C2_PuXEm.js");
13
13
  function _interopNamespace(e) {
14
14
  if (e && e.__esModule) return e;
15
15
  const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
@@ -28,8 +28,31 @@ function _interopNamespace(e) {
28
28
  return Object.freeze(n);
29
29
  }
30
30
  const React__namespace = /* @__PURE__ */ _interopNamespace(React);
31
- const OPERATORS = ["eq", "neq", "contains", "empty", "notEmpty", "gt", "lt"];
32
- const NEEDS_VALUE = /* @__PURE__ */ new Set(["eq", "neq", "contains", "gt", "lt"]);
31
+ const OPERATORS = [
32
+ "eq",
33
+ "neq",
34
+ "in",
35
+ "notIn",
36
+ "contains",
37
+ "notContains",
38
+ "startsWith",
39
+ "endsWith",
40
+ "empty",
41
+ "notEmpty",
42
+ "gt",
43
+ "lt"
44
+ ];
45
+ const NEEDS_VALUE = /* @__PURE__ */ new Set([
46
+ "eq",
47
+ "neq",
48
+ "contains",
49
+ "notContains",
50
+ "startsWith",
51
+ "endsWith",
52
+ "gt",
53
+ "lt"
54
+ ]);
55
+ const NEEDS_VALUES = /* @__PURE__ */ new Set(["in", "notIn"]);
33
56
  const ConditionEditor = ({ condition, candidates, onChange }) => {
34
57
  const { formatMessage } = index.useIntl();
35
58
  const t = (id, defaultMessage) => formatMessage({ id: index$1.getTranslation(id), defaultMessage });
@@ -40,7 +63,12 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
40
63
  empty: t("condition.op.empty", "is empty"),
41
64
  notEmpty: t("condition.op.notEmpty", "is filled"),
42
65
  gt: t("condition.op.gt", "is greater than"),
43
- lt: t("condition.op.lt", "is less than")
66
+ lt: t("condition.op.lt", "is less than"),
67
+ in: t("condition.op.in", "is any of"),
68
+ notIn: t("condition.op.notIn", "is none of"),
69
+ notContains: t("condition.op.notContains", "doesn't contain"),
70
+ startsWith: t("condition.op.startsWith", "starts with"),
71
+ endsWith: t("condition.op.endsWith", "ends with")
44
72
  };
45
73
  const rules = condition?.rules ?? [];
46
74
  const logic = condition?.logic ?? "and";
@@ -74,10 +102,31 @@ const ConditionEditor = ({ condition, candidates, onChange }) => {
74
102
  {
75
103
  "aria-label": t("condition.operator", "Operator"),
76
104
  value: rule.operator,
77
- onChange: (v) => updateRule(index2, { operator: v }),
105
+ onChange: (v) => updateRule(index2, { operator: v, value: "", values: [] }),
78
106
  children: OPERATORS.map((op) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: op, children: opLabel[op] }, op))
79
107
  }
80
108
  ) }),
109
+ NEEDS_VALUES.has(rule.operator) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", minWidth: "140px", children: targetOptions.length ? /* @__PURE__ */ jsxRuntime.jsx(
110
+ designSystem.MultiSelect,
111
+ {
112
+ "aria-label": t("condition.values", "Values"),
113
+ value: rule.values ?? [],
114
+ withTags: true,
115
+ onChange: (values) => updateRule(index2, { values }),
116
+ children: targetOptions.map((o) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.MultiSelectOption, { value: o.value, children: o.label || o.value }, o.value))
117
+ }
118
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
119
+ designSystem.TextInput,
120
+ {
121
+ "aria-label": t("condition.values", "Values"),
122
+ placeholder: t("condition.values-placeholder", "value1, value2…"),
123
+ defaultValue: (rule.values ?? []).join(", "),
124
+ onBlur: (e) => updateRule(index2, {
125
+ values: e.target.value.split(",").map((v) => v.trim()).filter(Boolean)
126
+ })
127
+ },
128
+ `${index2}-${rule.operator}`
129
+ ) }),
81
130
  NEEDS_VALUE.has(rule.operator) && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", minWidth: "110px", children: targetOptions.length ? /* @__PURE__ */ jsxRuntime.jsx(
82
131
  designSystem.SingleSelect,
83
132
  {
@@ -130,7 +179,20 @@ const FIELD_TYPES = [
130
179
  "textarea",
131
180
  "select",
132
181
  "checkbox",
133
- "radio"
182
+ "radio",
183
+ "company"
184
+ ];
185
+ const COMPANY_DATA = [
186
+ "name",
187
+ "siret",
188
+ "siren",
189
+ "address",
190
+ "zip",
191
+ "city",
192
+ "headquarters",
193
+ "naf",
194
+ "nafLabel",
195
+ "headcount"
134
196
  ];
135
197
  let counter = 0;
136
198
  const makeId = (prefix) => `${prefix}_${Date.now().toString(36)}${(counter += 1).toString(36)}${Math.random().toString(36).slice(2, 6)}`;
@@ -145,6 +207,96 @@ function fieldsBefore(definition, beforeId) {
145
207
  }
146
208
  return out;
147
209
  }
210
+ const CompanyMapEditor = ({ map, onChange }) => {
211
+ const intl = index.useIntl();
212
+ const { formatMessage } = intl;
213
+ const { schema } = useHubspotSchema.useHubspotSchema();
214
+ const t = (id, defaultMessage) => formatMessage({ id: index$1.getTranslation(id), defaultMessage });
215
+ const datumLabel = {
216
+ name: t("company.datum.name", "Legal name"),
217
+ siret: t("company.datum.siret", "SIRET"),
218
+ siren: t("company.datum.siren", "SIREN"),
219
+ address: t("company.datum.address", "Address"),
220
+ zip: t("company.datum.zip", "Postal code"),
221
+ city: t("company.datum.city", "City"),
222
+ headquarters: t("company.datum.headquarters", "Is headquarters"),
223
+ naf: t("company.datum.naf", "NAF code"),
224
+ nafLabel: t("company.datum.nafLabel", "NAF label"),
225
+ headcount: t("company.datum.headcount", "Headcount range")
226
+ };
227
+ const mapped = COMPANY_DATA.filter((d) => map[d]);
228
+ const unmapped = COMPANY_DATA.filter((d) => !map[d]);
229
+ const patch = (datum, entry) => {
230
+ const next = { ...map };
231
+ if (entry) next[datum] = entry;
232
+ else delete next[datum];
233
+ onChange(next);
234
+ };
235
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
236
+ mapped.map((datum) => {
237
+ const entry = map[datum] ?? {};
238
+ const object = entry.object?.trim() || "company";
239
+ const properties = (schema?.properties ?? []).filter((p) => p.object === object);
240
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral100", hasRadius: true, padding: 2, children: [
241
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 1, children: [
242
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: datumLabel[datum] }),
243
+ /* @__PURE__ */ jsxRuntime.jsx(
244
+ designSystem.IconButton,
245
+ {
246
+ label: t("company.remove-datum", "Stop sending this datum"),
247
+ onClick: () => patch(datum, void 0),
248
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {})
249
+ }
250
+ )
251
+ ] }),
252
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
253
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { width: "130px", style: { flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(
254
+ designSystem.SingleSelect,
255
+ {
256
+ "aria-label": t("panel.object", "Object"),
257
+ value: object,
258
+ onChange: (v) => (
259
+ // A property never survives an object switch.
260
+ patch(datum, { object: String(v), property: void 0 })
261
+ ),
262
+ children: (schema?.objects ?? ["contact", "company"]).map((name) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: name, children: objectLabels.objectLabel(intl, name) }, name))
263
+ }
264
+ ) }),
265
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsx(
266
+ designSystem.Combobox,
267
+ {
268
+ "aria-label": t("panel.property", "Property"),
269
+ value: entry.property ?? "",
270
+ onChange: (v) => patch(datum, { object, property: v || void 0 }),
271
+ onClear: () => patch(datum, { object, property: void 0 }),
272
+ placeholder: t("panel.property-placeholder", "Search a property…"),
273
+ children: properties.map((p) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.ComboboxOption, { value: p.name, children: [
274
+ p.group ? `${p.group} · ` : "",
275
+ p.label,
276
+ " (",
277
+ p.name,
278
+ ")"
279
+ ] }, p.name))
280
+ }
281
+ ) })
282
+ ] })
283
+ ] }, datum);
284
+ }),
285
+ unmapped.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
286
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, { "aria-hidden": true, width: 12, height: 12 }),
287
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsx(
288
+ designSystem.SingleSelect,
289
+ {
290
+ "aria-label": t("company.add-datum", "Send another datum"),
291
+ placeholder: t("company.add-datum", "Send another datum"),
292
+ value: "",
293
+ onChange: (v) => patch(v, { object: "company", property: void 0 }),
294
+ children: unmapped.map((datum) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: datum, children: datumLabel[datum] }, datum))
295
+ }
296
+ ) })
297
+ ] })
298
+ ] });
299
+ };
148
300
  const slugifyName = (label) => label.normalize("NFD").replace(/[\u0300-\u036f]/g, "").toLowerCase().replace(/[^a-z0-9]+/g, "_").replace(/^_+|_+$/g, "");
149
301
  const FieldPanel = ({ field, candidates, problem, onChange }) => {
150
302
  const intl = index.useIntl();
@@ -155,6 +307,13 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
155
307
  const properties = (schema?.properties ?? []).filter((p) => p.object === object);
156
308
  const selected = properties.find((p) => p.name === field.hubspot?.property);
157
309
  const isChoice = field.type === "select" || field.type === "radio";
310
+ const isCompany = field.type === "company";
311
+ const DEFAULT_COMPANY_MAP = {
312
+ name: { object: "company", property: "name" },
313
+ address: { object: "company", property: "address" },
314
+ zip: { object: "company", property: "zip" },
315
+ city: { object: "company", property: "city" }
316
+ };
158
317
  const setHubspot = (patch) => onChange({ hubspot: { ...field.hubspot, ...patch } });
159
318
  const importOptions = () => {
160
319
  if (!selected?.options?.length) return;
@@ -195,15 +354,30 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
195
354
  designSystem.SingleSelect,
196
355
  {
197
356
  value: field.type,
198
- onChange: (v) => onChange({ type: v }),
357
+ onChange: (v) => onChange({
358
+ type: v,
359
+ ...v === "company" && !field.companyMap ? { companyMap: DEFAULT_COMPANY_MAP } : {}
360
+ }),
199
361
  children: FIELD_TYPES.map((type) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.SingleSelectOption, { value: type, children: type }, type))
200
362
  }
201
363
  )
202
364
  ] }),
203
365
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, children: [
204
366
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: "1", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
205
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.placeholder", "Placeholder") }),
206
- /* @__PURE__ */ jsxRuntime.jsx(
367
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: isCompany ? t("company.examples", "Placeholder examples (one per line)") : t("panel.placeholder", "Placeholder") }),
368
+ isCompany ? /* @__PURE__ */ jsxRuntime.jsx(
369
+ designSystem.Textarea,
370
+ {
371
+ value: (field.placeholderExamples ?? []).join("\n"),
372
+ placeholder: t(
373
+ "company.examples-placeholder",
374
+ "Ex. : un établissement d'un grand groupe\nEx. : le siège de votre PME"
375
+ ),
376
+ onChange: (e) => onChange({
377
+ placeholderExamples: e.target.value.split("\n").map((line) => line.trim()).filter(Boolean)
378
+ })
379
+ }
380
+ ) : /* @__PURE__ */ jsxRuntime.jsx(
207
381
  designSystem.TextInput,
208
382
  {
209
383
  value: field.placeholder ?? "",
@@ -338,7 +512,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
338
512
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
339
513
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
340
514
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
341
- /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: t("panel.hubspot", "HubSpot") }),
515
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: isCompany ? t("company.section", "Company data (INSEE → HubSpot)") : t("panel.hubspot", "HubSpot") }),
342
516
  /* @__PURE__ */ jsxRuntime.jsx(
343
517
  designSystem.IconButton,
344
518
  {
@@ -349,7 +523,20 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
349
523
  }
350
524
  )
351
525
  ] }),
352
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
526
+ isCompany && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
527
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t(
528
+ "company.section-hint",
529
+ "The visitor picks their company from the SIRENE registry; the server re-resolves the SIRET and sends each datum below to its property."
530
+ ) }),
531
+ /* @__PURE__ */ jsxRuntime.jsx(
532
+ CompanyMapEditor,
533
+ {
534
+ map: field.companyMap ?? {},
535
+ onChange: (companyMap) => onChange({ companyMap })
536
+ }
537
+ )
538
+ ] }),
539
+ !isCompany && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
353
540
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.object", "Object") }),
354
541
  /* @__PURE__ */ jsxRuntime.jsx(
355
542
  designSystem.SingleSelect,
@@ -362,7 +549,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
362
549
  }
363
550
  )
364
551
  ] }),
365
- /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
552
+ !isCompany && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { children: [
366
553
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: t("panel.property", "Property") }),
367
554
  /* @__PURE__ */ jsxRuntime.jsx(
368
555
  designSystem.Combobox,
@@ -385,7 +572,7 @@ const FieldPanel = ({ field, candidates, problem, onChange }) => {
385
572
  }) }),
386
573
  !field.hubspot?.property && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: t("panel.property-hint", "Without a property, the answer is sent under the payload key.") })
387
574
  ] }),
388
- deepLink && /* @__PURE__ */ jsxRuntime.jsx("a", { href: deepLink, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "primary600", children: t("panel.view-in-hubspot", "View in HubSpot ↗") }) })
575
+ !isCompany && deepLink && /* @__PURE__ */ jsxRuntime.jsx("a", { href: deepLink, target: "_blank", rel: "noreferrer", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "primary600", children: t("panel.view-in-hubspot", "View in HubSpot ↗") }) })
389
576
  ] }),
390
577
  /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
391
578
  /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [