strapi-plugin-hubspot 0.1.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.
- package/LICENSE +21 -0
- package/README.md +166 -0
- package/dist/_chunks/HubspotPropertyInput-B0HpXoqz.js +102 -0
- package/dist/_chunks/HubspotPropertyInput-CTZYZS_7.mjs +84 -0
- package/dist/_chunks/Settings-BgRpG_a6.js +158 -0
- package/dist/_chunks/Settings-Byupubny.mjs +140 -0
- package/dist/_chunks/index-CER4BKjS.mjs +42 -0
- package/dist/_chunks/index-DVRLoCa1.js +41 -0
- package/dist/admin/index.js +3 -0
- package/dist/admin/index.mjs +4 -0
- package/dist/admin/src/components/HubspotPropertyInput.d.ts +34 -0
- package/dist/admin/src/index.d.ts +30 -0
- package/dist/admin/src/pages/Settings.d.ts +3 -0
- package/dist/admin/src/pluginId.d.ts +1 -0
- package/dist/server/index.js +206 -0
- package/dist/server/index.mjs +207 -0
- package/dist/server/src/index.d.ts +83 -0
- package/dist/server/src/properties.d.ts +23 -0
- package/dist/server/src/settings.d.ts +28 -0
- package/package.json +82 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paul Lefizelier
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
# Strapi HubSpot
|
|
2
|
+
|
|
3
|
+
**Stop typing HubSpot property names from memory.** This plugin turns any CRM
|
|
4
|
+
property field in your content types into a searchable picker fed by your actual
|
|
5
|
+
portal, and refuses a bad mapping at save time instead of letting it fail
|
|
6
|
+
silently three weeks later.
|
|
7
|
+
|
|
8
|
+
## The problem it solves
|
|
9
|
+
|
|
10
|
+
If you build lead forms in Strapi and push them to HubSpot, somewhere in your
|
|
11
|
+
schema there is a field where an editor types a property name — `hs_role`,
|
|
12
|
+
`numberofemployees`, `jobtitle`. It is a free-text field, and nothing checks it.
|
|
13
|
+
|
|
14
|
+
That matters more than it looks, because of how the HubSpot API behaves:
|
|
15
|
+
|
|
16
|
+
> **A single unknown property makes HubSpot reject the entire upsert.**
|
|
17
|
+
|
|
18
|
+
So one typo doesn't cost you one answer. It costs you the whole lead. The
|
|
19
|
+
submission is accepted by your site, stored in Strapi, and never reaches the
|
|
20
|
+
CRM — with no error anyone will notice until someone asks why the pipeline is
|
|
21
|
+
empty.
|
|
22
|
+
|
|
23
|
+
The three ways this happens are all invisible in a text input:
|
|
24
|
+
|
|
25
|
+
| What you typed | What's wrong |
|
|
26
|
+
|---|---|
|
|
27
|
+
| `hs_rôle` | Doesn't exist — a typo, an autocorrect, a copy-paste |
|
|
28
|
+
| `role ` | Exists, but with a trailing space |
|
|
29
|
+
| `name` | Exists, but on **Company** — you mapped it to Contact |
|
|
30
|
+
|
|
31
|
+
This plugin makes all three impossible to save.
|
|
32
|
+
|
|
33
|
+
## What you get
|
|
34
|
+
|
|
35
|
+
### A property picker instead of a text field
|
|
36
|
+
|
|
37
|
+
Replace your property field's type with the `hubspot.property` custom field.
|
|
38
|
+
Editors then search the real, writable properties of your portal, each labelled
|
|
39
|
+
with the object it belongs to:
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
Contact · Rôle (hs_role)
|
|
43
|
+
Contact · Prénom (firstname)
|
|
44
|
+
Société · Nombre d'employés (numberofemployees)
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
Read-only properties — the ones HubSpot computes and always refuses to
|
|
48
|
+
accept — are filtered out, so the list only ever offers things that will
|
|
49
|
+
actually work.
|
|
50
|
+
|
|
51
|
+
### Validation on save
|
|
52
|
+
|
|
53
|
+
Point the plugin at the content types that carry mappings and it walks each
|
|
54
|
+
entry before it is written, at any depth — steps, repeatable components, dynamic
|
|
55
|
+
zones. An invalid mapping is refused with a message that says which property and
|
|
56
|
+
why:
|
|
57
|
+
|
|
58
|
+
> Mapping HubSpot invalide — « name » existe sur l'objet Société, pas sur Contact
|
|
59
|
+
|
|
60
|
+
### A settings screen
|
|
61
|
+
|
|
62
|
+
**Settings → HubSpot** holds the private app token. It is stored server-side and
|
|
63
|
+
never returned to the browser: the UI only receives whether a key exists, where
|
|
64
|
+
it comes from, and its last four characters. A "Test connection" button
|
|
65
|
+
round-trips to HubSpot and reports how many properties it can read.
|
|
66
|
+
|
|
67
|
+
## Install
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
npm install strapi-plugin-hubspot
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
Enable it in `config/plugins.ts`:
|
|
74
|
+
|
|
75
|
+
```ts
|
|
76
|
+
export default ({ env }) => ({
|
|
77
|
+
hubspot: {
|
|
78
|
+
enabled: true,
|
|
79
|
+
config: {
|
|
80
|
+
// Optional — the key can also be set from Settings → HubSpot.
|
|
81
|
+
apiKey: env("HUBSPOT_API_KEY", ""),
|
|
82
|
+
|
|
83
|
+
// Optional — entries whose mappings are validated on save.
|
|
84
|
+
validate: [
|
|
85
|
+
{
|
|
86
|
+
uid: "api::form.form",
|
|
87
|
+
objectField: "hsObject", // holds "contact" | "company"
|
|
88
|
+
propertyField: "hsProperty", // holds the property name
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
},
|
|
92
|
+
},
|
|
93
|
+
});
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Then point your property field at the custom field, in the component or content
|
|
97
|
+
type that holds it:
|
|
98
|
+
|
|
99
|
+
```json
|
|
100
|
+
{
|
|
101
|
+
"hsProperty": {
|
|
102
|
+
"type": "customField",
|
|
103
|
+
"customField": "plugin::hubspot.property"
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
Restart Strapi and hard-refresh the admin.
|
|
109
|
+
|
|
110
|
+
### The token
|
|
111
|
+
|
|
112
|
+
Create a **private app** in HubSpot with these scopes:
|
|
113
|
+
|
|
114
|
+
- `crm.schemas.contacts.read`
|
|
115
|
+
- `crm.schemas.companies.read`
|
|
116
|
+
|
|
117
|
+
The key is resolved in this order, first match wins:
|
|
118
|
+
|
|
119
|
+
1. saved from **Settings → HubSpot**
|
|
120
|
+
2. `config.apiKey` in `config/plugins.ts`
|
|
121
|
+
3. the `HUBSPOT_API_KEY` environment variable
|
|
122
|
+
|
|
123
|
+
## Migrating an existing field
|
|
124
|
+
|
|
125
|
+
The custom field is backed by a plain `string`. Switching an existing text field
|
|
126
|
+
to it needs **no migration**: every value already saved stays valid and
|
|
127
|
+
selectable, and uninstalling the plugin leaves readable data behind.
|
|
128
|
+
|
|
129
|
+
A stored value your portal doesn't recognise — typed before you installed this,
|
|
130
|
+
or since deleted in HubSpot — stays selected and is flagged *inconnue du portail*
|
|
131
|
+
rather than being silently dropped on the next save.
|
|
132
|
+
|
|
133
|
+
## How it degrades
|
|
134
|
+
|
|
135
|
+
A plugin that sits between your editors and their content has to fail quietly.
|
|
136
|
+
This one never blocks work:
|
|
137
|
+
|
|
138
|
+
| Situation | Behaviour |
|
|
139
|
+
|---|---|
|
|
140
|
+
| No API key configured | The field falls back to a plain text input, with a note explaining why |
|
|
141
|
+
| HubSpot unreachable | Saving proceeds; validation is skipped and a warning is logged |
|
|
142
|
+
| Property staged in HubSpot but not created yet | The picker accepts a typed value (`creatable`) |
|
|
143
|
+
| Plugin uninstalled | Values remain as strings — nothing to undo |
|
|
144
|
+
|
|
145
|
+
The property schema is cached for 10 minutes and de-duplicated across concurrent
|
|
146
|
+
requests, so a busy Content Manager doesn't hammer the HubSpot API. Saving a new
|
|
147
|
+
key drops the cache immediately.
|
|
148
|
+
|
|
149
|
+
## Admin API
|
|
150
|
+
|
|
151
|
+
All routes require an authenticated admin.
|
|
152
|
+
|
|
153
|
+
| Method | Path | Purpose |
|
|
154
|
+
|---|---|---|
|
|
155
|
+
| `GET` | `/hubspot/properties` | Writable properties of both objects. `?refresh=1` bypasses the cache |
|
|
156
|
+
| `GET` | `/hubspot/settings` | Whether a key exists, its source and hint — never the key |
|
|
157
|
+
| `PUT` | `/hubspot/settings` | Save a key (`{ apiKey }`) |
|
|
158
|
+
| `DELETE` | `/hubspot/settings` | Remove the stored key |
|
|
159
|
+
|
|
160
|
+
## Compatibility
|
|
161
|
+
|
|
162
|
+
Strapi **v5**. Node **>= 18**.
|
|
163
|
+
|
|
164
|
+
## License
|
|
165
|
+
|
|
166
|
+
MIT
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const designSystem = require("@strapi/design-system");
|
|
6
|
+
const admin = require("@strapi/strapi/admin");
|
|
7
|
+
const index = require("./index-DVRLoCa1.js");
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
11
|
+
if (e) {
|
|
12
|
+
for (const k in e) {
|
|
13
|
+
if (k !== "default") {
|
|
14
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: () => e[k]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
const React__namespace = /* @__PURE__ */ _interopNamespace(React);
|
|
26
|
+
const OBJECT_LABEL = {
|
|
27
|
+
contact: "Contact",
|
|
28
|
+
company: "Société"
|
|
29
|
+
};
|
|
30
|
+
const HubspotPropertyInput = React__namespace.forwardRef(
|
|
31
|
+
({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
32
|
+
const { get } = admin.useFetchClient();
|
|
33
|
+
const [properties, setProperties] = React__namespace.useState([]);
|
|
34
|
+
const [configured, setConfigured] = React__namespace.useState(null);
|
|
35
|
+
const [loadError, setLoadError] = React__namespace.useState("");
|
|
36
|
+
React__namespace.useEffect(() => {
|
|
37
|
+
let cancelled = false;
|
|
38
|
+
get(`/${index.PLUGIN_ID}/properties`).then(({ data }) => {
|
|
39
|
+
if (cancelled) return;
|
|
40
|
+
setConfigured(data.configured);
|
|
41
|
+
setProperties(data.properties ?? []);
|
|
42
|
+
}).catch((err) => {
|
|
43
|
+
if (cancelled) return;
|
|
44
|
+
setConfigured(false);
|
|
45
|
+
setLoadError(
|
|
46
|
+
err?.response?.data?.error?.message ?? "Propriétés HubSpot indisponibles"
|
|
47
|
+
);
|
|
48
|
+
});
|
|
49
|
+
return () => {
|
|
50
|
+
cancelled = true;
|
|
51
|
+
};
|
|
52
|
+
}, [get]);
|
|
53
|
+
const emit = (next) => onChange({ target: { name, value: next ?? "", type: "string" } });
|
|
54
|
+
const describe = () => {
|
|
55
|
+
if (loadError) return loadError;
|
|
56
|
+
if (configured === false) {
|
|
57
|
+
return "Aucune clé API HubSpot — saisie libre. Renseignez-la dans Réglages → HubSpot.";
|
|
58
|
+
}
|
|
59
|
+
return hint;
|
|
60
|
+
};
|
|
61
|
+
const options = React__namespace.useMemo(() => {
|
|
62
|
+
const known = properties.map((p) => ({
|
|
63
|
+
value: p.name,
|
|
64
|
+
label: `${OBJECT_LABEL[p.object]} · ${p.label} (${p.name})`
|
|
65
|
+
}));
|
|
66
|
+
if (value && !properties.some((p) => p.name === value)) {
|
|
67
|
+
known.unshift({ value, label: `${value} — inconnue du portail` });
|
|
68
|
+
}
|
|
69
|
+
return known;
|
|
70
|
+
}, [properties, value]);
|
|
71
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, error, hint: describe(), required, children: [
|
|
72
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { action: labelAction, children: label }),
|
|
73
|
+
configured === null ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { paddingTop: 2, paddingBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true, children: "Chargement des propriétés HubSpot…" }) }) : configured ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
74
|
+
designSystem.Combobox,
|
|
75
|
+
{
|
|
76
|
+
ref,
|
|
77
|
+
value: value || "",
|
|
78
|
+
onChange: emit,
|
|
79
|
+
onClear: () => emit(""),
|
|
80
|
+
disabled,
|
|
81
|
+
placeholder: "Rechercher une propriété…",
|
|
82
|
+
creatable: true,
|
|
83
|
+
onCreateOption: emit,
|
|
84
|
+
children: options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: o.value, children: o.label }, o.value))
|
|
85
|
+
}
|
|
86
|
+
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
87
|
+
designSystem.Field.Input,
|
|
88
|
+
{
|
|
89
|
+
ref,
|
|
90
|
+
value: value || "",
|
|
91
|
+
onChange: (e) => emit(e.target.value),
|
|
92
|
+
disabled,
|
|
93
|
+
placeholder: "ex. hs_role, numberofemployees"
|
|
94
|
+
}
|
|
95
|
+
),
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
97
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
98
|
+
] });
|
|
99
|
+
}
|
|
100
|
+
);
|
|
101
|
+
HubspotPropertyInput.displayName = "HubspotPropertyInput";
|
|
102
|
+
exports.default = HubspotPropertyInput;
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Field, Flex, Loader, Combobox, ComboboxOption } from "@strapi/design-system";
|
|
4
|
+
import { useFetchClient } from "@strapi/strapi/admin";
|
|
5
|
+
import { P as PLUGIN_ID } from "./index-CER4BKjS.mjs";
|
|
6
|
+
const OBJECT_LABEL = {
|
|
7
|
+
contact: "Contact",
|
|
8
|
+
company: "Société"
|
|
9
|
+
};
|
|
10
|
+
const HubspotPropertyInput = React.forwardRef(
|
|
11
|
+
({ name, value, onChange, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
12
|
+
const { get } = useFetchClient();
|
|
13
|
+
const [properties, setProperties] = React.useState([]);
|
|
14
|
+
const [configured, setConfigured] = React.useState(null);
|
|
15
|
+
const [loadError, setLoadError] = React.useState("");
|
|
16
|
+
React.useEffect(() => {
|
|
17
|
+
let cancelled = false;
|
|
18
|
+
get(`/${PLUGIN_ID}/properties`).then(({ data }) => {
|
|
19
|
+
if (cancelled) return;
|
|
20
|
+
setConfigured(data.configured);
|
|
21
|
+
setProperties(data.properties ?? []);
|
|
22
|
+
}).catch((err) => {
|
|
23
|
+
if (cancelled) return;
|
|
24
|
+
setConfigured(false);
|
|
25
|
+
setLoadError(
|
|
26
|
+
err?.response?.data?.error?.message ?? "Propriétés HubSpot indisponibles"
|
|
27
|
+
);
|
|
28
|
+
});
|
|
29
|
+
return () => {
|
|
30
|
+
cancelled = true;
|
|
31
|
+
};
|
|
32
|
+
}, [get]);
|
|
33
|
+
const emit = (next) => onChange({ target: { name, value: next ?? "", type: "string" } });
|
|
34
|
+
const describe = () => {
|
|
35
|
+
if (loadError) return loadError;
|
|
36
|
+
if (configured === false) {
|
|
37
|
+
return "Aucune clé API HubSpot — saisie libre. Renseignez-la dans Réglages → HubSpot.";
|
|
38
|
+
}
|
|
39
|
+
return hint;
|
|
40
|
+
};
|
|
41
|
+
const options = React.useMemo(() => {
|
|
42
|
+
const known = properties.map((p) => ({
|
|
43
|
+
value: p.name,
|
|
44
|
+
label: `${OBJECT_LABEL[p.object]} · ${p.label} (${p.name})`
|
|
45
|
+
}));
|
|
46
|
+
if (value && !properties.some((p) => p.name === value)) {
|
|
47
|
+
known.unshift({ value, label: `${value} — inconnue du portail` });
|
|
48
|
+
}
|
|
49
|
+
return known;
|
|
50
|
+
}, [properties, value]);
|
|
51
|
+
return /* @__PURE__ */ jsxs(Field.Root, { name, error, hint: describe(), required, children: [
|
|
52
|
+
/* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: label }),
|
|
53
|
+
configured === null ? /* @__PURE__ */ jsx(Flex, { paddingTop: 2, paddingBottom: 2, children: /* @__PURE__ */ jsx(Loader, { small: true, children: "Chargement des propriétés HubSpot…" }) }) : configured ? /* @__PURE__ */ jsx(
|
|
54
|
+
Combobox,
|
|
55
|
+
{
|
|
56
|
+
ref,
|
|
57
|
+
value: value || "",
|
|
58
|
+
onChange: emit,
|
|
59
|
+
onClear: () => emit(""),
|
|
60
|
+
disabled,
|
|
61
|
+
placeholder: "Rechercher une propriété…",
|
|
62
|
+
creatable: true,
|
|
63
|
+
onCreateOption: emit,
|
|
64
|
+
children: options.map((o) => /* @__PURE__ */ jsx(ComboboxOption, { value: o.value, children: o.label }, o.value))
|
|
65
|
+
}
|
|
66
|
+
) : /* @__PURE__ */ jsx(
|
|
67
|
+
Field.Input,
|
|
68
|
+
{
|
|
69
|
+
ref,
|
|
70
|
+
value: value || "",
|
|
71
|
+
onChange: (e) => emit(e.target.value),
|
|
72
|
+
disabled,
|
|
73
|
+
placeholder: "ex. hs_role, numberofemployees"
|
|
74
|
+
}
|
|
75
|
+
),
|
|
76
|
+
/* @__PURE__ */ jsx(Field.Hint, {}),
|
|
77
|
+
/* @__PURE__ */ jsx(Field.Error, {})
|
|
78
|
+
] });
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
HubspotPropertyInput.displayName = "HubspotPropertyInput";
|
|
82
|
+
export {
|
|
83
|
+
HubspotPropertyInput as default
|
|
84
|
+
};
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const React = require("react");
|
|
5
|
+
const designSystem = require("@strapi/design-system");
|
|
6
|
+
const admin = require("@strapi/strapi/admin");
|
|
7
|
+
const index = require("./index-DVRLoCa1.js");
|
|
8
|
+
function _interopNamespace(e) {
|
|
9
|
+
if (e && e.__esModule) return e;
|
|
10
|
+
const n = Object.create(null, { [Symbol.toStringTag]: { value: "Module" } });
|
|
11
|
+
if (e) {
|
|
12
|
+
for (const k in e) {
|
|
13
|
+
if (k !== "default") {
|
|
14
|
+
const d = Object.getOwnPropertyDescriptor(e, k);
|
|
15
|
+
Object.defineProperty(n, k, d.get ? d : {
|
|
16
|
+
enumerable: true,
|
|
17
|
+
get: () => e[k]
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
n.default = e;
|
|
23
|
+
return Object.freeze(n);
|
|
24
|
+
}
|
|
25
|
+
const React__namespace = /* @__PURE__ */ _interopNamespace(React);
|
|
26
|
+
const SOURCE_LABEL = {
|
|
27
|
+
settings: "saisie ici",
|
|
28
|
+
config: "config/plugins.ts",
|
|
29
|
+
env: "variable d'environnement"
|
|
30
|
+
};
|
|
31
|
+
const HubspotSettings = () => {
|
|
32
|
+
const { get, put, del } = admin.useFetchClient();
|
|
33
|
+
const [settings, setSettings] = React__namespace.useState(null);
|
|
34
|
+
const [apiKey, setApiKey] = React__namespace.useState("");
|
|
35
|
+
const [busy, setBusy] = React__namespace.useState(false);
|
|
36
|
+
const [feedback, setFeedback] = React__namespace.useState(
|
|
37
|
+
null
|
|
38
|
+
);
|
|
39
|
+
const load = React__namespace.useCallback(async () => {
|
|
40
|
+
const { data } = await get(`/${index.PLUGIN_ID}/settings`);
|
|
41
|
+
setSettings(data);
|
|
42
|
+
}, [get]);
|
|
43
|
+
React__namespace.useEffect(() => {
|
|
44
|
+
load().catch(() => setFeedback({ tone: "danger", text: "Réglages illisibles." }));
|
|
45
|
+
}, [load]);
|
|
46
|
+
const save = async () => {
|
|
47
|
+
setBusy(true);
|
|
48
|
+
setFeedback(null);
|
|
49
|
+
try {
|
|
50
|
+
const { data } = await put(`/${index.PLUGIN_ID}/settings`, { apiKey });
|
|
51
|
+
setSettings(data);
|
|
52
|
+
setApiKey("");
|
|
53
|
+
setFeedback({ tone: "success", text: "Clé enregistrée." });
|
|
54
|
+
} catch {
|
|
55
|
+
setFeedback({ tone: "danger", text: "Enregistrement impossible." });
|
|
56
|
+
} finally {
|
|
57
|
+
setBusy(false);
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
const remove = async () => {
|
|
61
|
+
setBusy(true);
|
|
62
|
+
setFeedback(null);
|
|
63
|
+
try {
|
|
64
|
+
const { data } = await del(`/${index.PLUGIN_ID}/settings`);
|
|
65
|
+
setSettings(data);
|
|
66
|
+
setApiKey("");
|
|
67
|
+
setFeedback({ tone: "success", text: "Clé supprimée." });
|
|
68
|
+
} catch {
|
|
69
|
+
setFeedback({ tone: "danger", text: "Suppression impossible." });
|
|
70
|
+
} finally {
|
|
71
|
+
setBusy(false);
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const test = async () => {
|
|
75
|
+
setBusy(true);
|
|
76
|
+
setFeedback(null);
|
|
77
|
+
try {
|
|
78
|
+
const { data } = await get(
|
|
79
|
+
`/${index.PLUGIN_ID}/properties?refresh=1`
|
|
80
|
+
);
|
|
81
|
+
setFeedback(
|
|
82
|
+
data.configured ? { tone: "success", text: `Connexion établie — ${data.properties.length} propriétés lisibles.` } : { tone: "danger", text: "Aucune clé configurée." }
|
|
83
|
+
);
|
|
84
|
+
} catch {
|
|
85
|
+
setFeedback({ tone: "danger", text: "HubSpot injoignable — clé invalide ou révoquée ?" });
|
|
86
|
+
} finally {
|
|
87
|
+
setBusy(false);
|
|
88
|
+
}
|
|
89
|
+
};
|
|
90
|
+
if (!settings) {
|
|
91
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true, children: "Chargement…" }) });
|
|
92
|
+
}
|
|
93
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
94
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
|
|
95
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "HubSpot" }),
|
|
96
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: "Le jeton d'application privée utilisé pour lire les propriétés du portail et valider les mappings de formulaires." })
|
|
97
|
+
] }),
|
|
98
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
|
|
99
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { active: settings.configured, children: settings.configured ? `Configurée ${settings.hint}` : "Non configurée" }),
|
|
100
|
+
settings.keySource ? /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
101
|
+
"source : ",
|
|
102
|
+
SOURCE_LABEL[settings.keySource]
|
|
103
|
+
] }) : null
|
|
104
|
+
] }),
|
|
105
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { maxWidth: "32rem", children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
106
|
+
designSystem.Field.Root,
|
|
107
|
+
{
|
|
108
|
+
name: "apiKey",
|
|
109
|
+
hint: "Saisir une clé ici remplace celles venant de config/plugins.ts et de HUBSPOT_API_KEY.",
|
|
110
|
+
children: [
|
|
111
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Jeton d'application privée" }),
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
113
|
+
designSystem.Field.Input,
|
|
114
|
+
{
|
|
115
|
+
type: "password",
|
|
116
|
+
value: apiKey,
|
|
117
|
+
autoComplete: "off",
|
|
118
|
+
placeholder: settings.configured ? "•••••••• (laisser vide pour conserver)" : "pat-eu1-…",
|
|
119
|
+
onChange: (e) => setApiKey(e.target.value)
|
|
120
|
+
}
|
|
121
|
+
),
|
|
122
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {})
|
|
123
|
+
]
|
|
124
|
+
}
|
|
125
|
+
) }),
|
|
126
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
|
|
127
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: save, loading: busy, disabled: !apiKey.trim(), children: "Enregistrer" }),
|
|
128
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: test, loading: busy, disabled: !settings.configured, children: "Tester la connexion" }),
|
|
129
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
130
|
+
designSystem.Button,
|
|
131
|
+
{
|
|
132
|
+
variant: "danger-light",
|
|
133
|
+
onClick: remove,
|
|
134
|
+
loading: busy,
|
|
135
|
+
disabled: settings.keySource !== "settings",
|
|
136
|
+
children: "Supprimer"
|
|
137
|
+
}
|
|
138
|
+
)
|
|
139
|
+
] }),
|
|
140
|
+
feedback ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
141
|
+
designSystem.Typography,
|
|
142
|
+
{
|
|
143
|
+
variant: "pi",
|
|
144
|
+
textColor: feedback.tone === "success" ? "success600" : "danger600",
|
|
145
|
+
children: feedback.text
|
|
146
|
+
}
|
|
147
|
+
) : null,
|
|
148
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
149
|
+
"Le jeton a besoin des portées ",
|
|
150
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: "crm.schemas.contacts.read" }),
|
|
151
|
+
" et",
|
|
152
|
+
" ",
|
|
153
|
+
/* @__PURE__ */ jsxRuntime.jsx("b", { children: "crm.schemas.companies.read" }),
|
|
154
|
+
" pour lister les propriétés. Il n'est jamais renvoyé au navigateur : seuls son existence, sa provenance et ses quatre derniers caractères le sont."
|
|
155
|
+
] }) })
|
|
156
|
+
] }) });
|
|
157
|
+
};
|
|
158
|
+
exports.default = HubspotSettings;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { Box, Loader, Flex, Typography, Badge, Field, Button } from "@strapi/design-system";
|
|
4
|
+
import { useFetchClient } from "@strapi/strapi/admin";
|
|
5
|
+
import { P as PLUGIN_ID } from "./index-CER4BKjS.mjs";
|
|
6
|
+
const SOURCE_LABEL = {
|
|
7
|
+
settings: "saisie ici",
|
|
8
|
+
config: "config/plugins.ts",
|
|
9
|
+
env: "variable d'environnement"
|
|
10
|
+
};
|
|
11
|
+
const HubspotSettings = () => {
|
|
12
|
+
const { get, put, del } = useFetchClient();
|
|
13
|
+
const [settings, setSettings] = React.useState(null);
|
|
14
|
+
const [apiKey, setApiKey] = React.useState("");
|
|
15
|
+
const [busy, setBusy] = React.useState(false);
|
|
16
|
+
const [feedback, setFeedback] = React.useState(
|
|
17
|
+
null
|
|
18
|
+
);
|
|
19
|
+
const load = React.useCallback(async () => {
|
|
20
|
+
const { data } = await get(`/${PLUGIN_ID}/settings`);
|
|
21
|
+
setSettings(data);
|
|
22
|
+
}, [get]);
|
|
23
|
+
React.useEffect(() => {
|
|
24
|
+
load().catch(() => setFeedback({ tone: "danger", text: "Réglages illisibles." }));
|
|
25
|
+
}, [load]);
|
|
26
|
+
const save = async () => {
|
|
27
|
+
setBusy(true);
|
|
28
|
+
setFeedback(null);
|
|
29
|
+
try {
|
|
30
|
+
const { data } = await put(`/${PLUGIN_ID}/settings`, { apiKey });
|
|
31
|
+
setSettings(data);
|
|
32
|
+
setApiKey("");
|
|
33
|
+
setFeedback({ tone: "success", text: "Clé enregistrée." });
|
|
34
|
+
} catch {
|
|
35
|
+
setFeedback({ tone: "danger", text: "Enregistrement impossible." });
|
|
36
|
+
} finally {
|
|
37
|
+
setBusy(false);
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
const remove = async () => {
|
|
41
|
+
setBusy(true);
|
|
42
|
+
setFeedback(null);
|
|
43
|
+
try {
|
|
44
|
+
const { data } = await del(`/${PLUGIN_ID}/settings`);
|
|
45
|
+
setSettings(data);
|
|
46
|
+
setApiKey("");
|
|
47
|
+
setFeedback({ tone: "success", text: "Clé supprimée." });
|
|
48
|
+
} catch {
|
|
49
|
+
setFeedback({ tone: "danger", text: "Suppression impossible." });
|
|
50
|
+
} finally {
|
|
51
|
+
setBusy(false);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
const test = async () => {
|
|
55
|
+
setBusy(true);
|
|
56
|
+
setFeedback(null);
|
|
57
|
+
try {
|
|
58
|
+
const { data } = await get(
|
|
59
|
+
`/${PLUGIN_ID}/properties?refresh=1`
|
|
60
|
+
);
|
|
61
|
+
setFeedback(
|
|
62
|
+
data.configured ? { tone: "success", text: `Connexion établie — ${data.properties.length} propriétés lisibles.` } : { tone: "danger", text: "Aucune clé configurée." }
|
|
63
|
+
);
|
|
64
|
+
} catch {
|
|
65
|
+
setFeedback({ tone: "danger", text: "HubSpot injoignable — clé invalide ou révoquée ?" });
|
|
66
|
+
} finally {
|
|
67
|
+
setBusy(false);
|
|
68
|
+
}
|
|
69
|
+
};
|
|
70
|
+
if (!settings) {
|
|
71
|
+
return /* @__PURE__ */ jsx(Box, { padding: 8, children: /* @__PURE__ */ jsx(Loader, { small: true, children: "Chargement…" }) });
|
|
72
|
+
}
|
|
73
|
+
return /* @__PURE__ */ jsx(Box, { padding: 8, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 6, children: [
|
|
74
|
+
/* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 2, children: [
|
|
75
|
+
/* @__PURE__ */ jsx(Typography, { variant: "alpha", children: "HubSpot" }),
|
|
76
|
+
/* @__PURE__ */ jsx(Typography, { variant: "epsilon", textColor: "neutral600", children: "Le jeton d'application privée utilisé pour lire les propriétés du portail et valider les mappings de formulaires." })
|
|
77
|
+
] }),
|
|
78
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, alignItems: "center", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(Badge, { active: settings.configured, children: settings.configured ? `Configurée ${settings.hint}` : "Non configurée" }),
|
|
80
|
+
settings.keySource ? /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
81
|
+
"source : ",
|
|
82
|
+
SOURCE_LABEL[settings.keySource]
|
|
83
|
+
] }) : null
|
|
84
|
+
] }),
|
|
85
|
+
/* @__PURE__ */ jsx(Box, { maxWidth: "32rem", children: /* @__PURE__ */ jsxs(
|
|
86
|
+
Field.Root,
|
|
87
|
+
{
|
|
88
|
+
name: "apiKey",
|
|
89
|
+
hint: "Saisir une clé ici remplace celles venant de config/plugins.ts et de HUBSPOT_API_KEY.",
|
|
90
|
+
children: [
|
|
91
|
+
/* @__PURE__ */ jsx(Field.Label, { children: "Jeton d'application privée" }),
|
|
92
|
+
/* @__PURE__ */ jsx(
|
|
93
|
+
Field.Input,
|
|
94
|
+
{
|
|
95
|
+
type: "password",
|
|
96
|
+
value: apiKey,
|
|
97
|
+
autoComplete: "off",
|
|
98
|
+
placeholder: settings.configured ? "•••••••• (laisser vide pour conserver)" : "pat-eu1-…",
|
|
99
|
+
onChange: (e) => setApiKey(e.target.value)
|
|
100
|
+
}
|
|
101
|
+
),
|
|
102
|
+
/* @__PURE__ */ jsx(Field.Hint, {})
|
|
103
|
+
]
|
|
104
|
+
}
|
|
105
|
+
) }),
|
|
106
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, children: [
|
|
107
|
+
/* @__PURE__ */ jsx(Button, { onClick: save, loading: busy, disabled: !apiKey.trim(), children: "Enregistrer" }),
|
|
108
|
+
/* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: test, loading: busy, disabled: !settings.configured, children: "Tester la connexion" }),
|
|
109
|
+
/* @__PURE__ */ jsx(
|
|
110
|
+
Button,
|
|
111
|
+
{
|
|
112
|
+
variant: "danger-light",
|
|
113
|
+
onClick: remove,
|
|
114
|
+
loading: busy,
|
|
115
|
+
disabled: settings.keySource !== "settings",
|
|
116
|
+
children: "Supprimer"
|
|
117
|
+
}
|
|
118
|
+
)
|
|
119
|
+
] }),
|
|
120
|
+
feedback ? /* @__PURE__ */ jsx(
|
|
121
|
+
Typography,
|
|
122
|
+
{
|
|
123
|
+
variant: "pi",
|
|
124
|
+
textColor: feedback.tone === "success" ? "success600" : "danger600",
|
|
125
|
+
children: feedback.text
|
|
126
|
+
}
|
|
127
|
+
) : null,
|
|
128
|
+
/* @__PURE__ */ jsx(Box, { paddingTop: 4, children: /* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral600", children: [
|
|
129
|
+
"Le jeton a besoin des portées ",
|
|
130
|
+
/* @__PURE__ */ jsx("b", { children: "crm.schemas.contacts.read" }),
|
|
131
|
+
" et",
|
|
132
|
+
" ",
|
|
133
|
+
/* @__PURE__ */ jsx("b", { children: "crm.schemas.companies.read" }),
|
|
134
|
+
" pour lister les propriétés. Il n'est jamais renvoyé au navigateur : seuls son existence, sa provenance et ses quatre derniers caractères le sont."
|
|
135
|
+
] }) })
|
|
136
|
+
] }) });
|
|
137
|
+
};
|
|
138
|
+
export {
|
|
139
|
+
HubspotSettings as default
|
|
140
|
+
};
|