strapi-plugin-hubspot 0.3.0 → 0.4.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/README.md +121 -2
- package/dist/_chunks/HubspotObjectInput-DAq_W1rA.js +91 -0
- package/dist/_chunks/HubspotObjectInput-G-A_pL2j.mjs +73 -0
- package/dist/_chunks/HubspotPropertyInput-B-Jg0x68.js +186 -0
- package/dist/_chunks/HubspotPropertyInput-Cv77QiS6.mjs +168 -0
- package/dist/_chunks/Settings-58edRL5D.mjs +1293 -0
- package/dist/_chunks/Settings-CryiUgSe.js +1311 -0
- package/dist/_chunks/en-CnQbjHs-.js +81 -0
- package/dist/_chunks/en-DHQZuRsj.mjs +81 -0
- package/dist/_chunks/fr-Bv8zsNX5.js +81 -0
- package/dist/_chunks/fr-Dxgil7RP.mjs +81 -0
- package/dist/_chunks/index-BudHTwSw.mjs +137 -0
- package/dist/_chunks/index-Tlw1p93d.js +136 -0
- package/dist/_chunks/objectLabels-CJTKZ4vT.mjs +425 -0
- package/dist/_chunks/objectLabels-GRmYGCIn.js +442 -0
- package/dist/admin/index.js +1 -1
- package/dist/admin/index.mjs +1 -1
- package/dist/admin/src/components/AuditSection.d.ts +5 -0
- package/dist/admin/src/components/FailuresSection.d.ts +11 -0
- package/dist/admin/src/components/HubspotObjectInput.d.ts +28 -0
- package/dist/admin/src/components/HubspotPropertyInput.d.ts +1 -0
- package/dist/admin/src/getTranslation.d.ts +3 -0
- package/dist/admin/src/index.d.ts +6 -0
- package/dist/admin/src/objectLabels.d.ts +2 -1
- package/dist/server/index.js +417 -69
- package/dist/server/index.mjs +417 -69
- package/dist/server/src/__tests__/audit.test.d.ts +1 -0
- package/dist/server/src/__tests__/checkMapping.test.d.ts +1 -0
- package/dist/server/src/__tests__/loadSchema.test.d.ts +1 -0
- package/dist/server/src/__tests__/submit.test.d.ts +1 -0
- package/dist/server/src/__tests__/validation.test.d.ts +1 -0
- package/dist/server/src/audit.d.ts +60 -0
- package/dist/server/src/content-types.d.ts +56 -0
- package/dist/server/src/index.d.ts +104 -28
- package/dist/server/src/properties.d.ts +7 -2
- package/dist/server/src/submit.d.ts +35 -0
- package/dist/server/src/validation.d.ts +37 -0
- package/package.json +5 -2
- package/dist/_chunks/HubspotPropertyInput-C3KbDHYP.mjs +0 -106
- package/dist/_chunks/HubspotPropertyInput-C9PpYYgj.js +0 -124
- package/dist/_chunks/Settings-DGJ_mmlw.mjs +0 -140
- package/dist/_chunks/Settings-Lj49S5fO.js +0 -158
- package/dist/_chunks/index-Bq1tS5h6.js +0 -71
- package/dist/_chunks/index-lx7um1Yx.mjs +0 -72
|
@@ -1,30 +1,5 @@
|
|
|
1
1
|
import type { Core } from "@strapi/strapi";
|
|
2
|
-
|
|
3
|
-
* Plugin configuration (config/plugins.ts of the host app):
|
|
4
|
-
*
|
|
5
|
-
* hubspot: {
|
|
6
|
-
* enabled: true,
|
|
7
|
-
* config: {
|
|
8
|
-
* apiKey: env("HUBSPOT_API_KEY"), // optional — can be set from the admin UI
|
|
9
|
-
* validate: [ // optional — entries whose mappings are checked on save
|
|
10
|
-
* { uid: "api::form.form", objectField: "hsObject", propertyField: "hsProperty" },
|
|
11
|
-
* ],
|
|
12
|
-
* },
|
|
13
|
-
* }
|
|
14
|
-
*
|
|
15
|
-
* The API key set from the admin UI takes precedence over both.
|
|
16
|
-
*/
|
|
17
|
-
interface ValidateTarget {
|
|
18
|
-
uid: string;
|
|
19
|
-
objectField: string;
|
|
20
|
-
propertyField: string;
|
|
21
|
-
/**
|
|
22
|
-
* Repeatable holding the values a field can submit (default `options`), each
|
|
23
|
-
* `{ value?, label? }`. Checked against enumeration properties: a select whose
|
|
24
|
-
* choices drifted from HubSpot fails exactly like an unknown property.
|
|
25
|
-
*/
|
|
26
|
-
optionsField?: string;
|
|
27
|
-
}
|
|
2
|
+
import { type ValidateTarget } from "./validation";
|
|
28
3
|
declare const _default: {
|
|
29
4
|
config: {
|
|
30
5
|
default: {
|
|
@@ -36,6 +11,68 @@ declare const _default: {
|
|
|
36
11
|
validate?: unknown;
|
|
37
12
|
}): void;
|
|
38
13
|
};
|
|
14
|
+
contentTypes: {
|
|
15
|
+
failure: {
|
|
16
|
+
/**
|
|
17
|
+
* Plugin configuration (config/plugins.ts of the host app):
|
|
18
|
+
*
|
|
19
|
+
* hubspot: {
|
|
20
|
+
* enabled: true,
|
|
21
|
+
* config: {
|
|
22
|
+
* apiKey: env("HUBSPOT_API_KEY"), // optional — can be set from the admin UI
|
|
23
|
+
* validate: [ // optional — entries whose mappings are checked on save
|
|
24
|
+
* { uid: "api::form.form", objectField: "hsObject", propertyField: "hsProperty" },
|
|
25
|
+
* ],
|
|
26
|
+
* },
|
|
27
|
+
* }
|
|
28
|
+
*
|
|
29
|
+
* The API key set from the admin UI takes precedence over both.
|
|
30
|
+
*/
|
|
31
|
+
/** RBAC action gating the settings screen and its routes. */
|
|
32
|
+
schema: {
|
|
33
|
+
kind: string;
|
|
34
|
+
collectionName: string;
|
|
35
|
+
info: {
|
|
36
|
+
singularName: string;
|
|
37
|
+
pluralName: string;
|
|
38
|
+
displayName: string;
|
|
39
|
+
description: string;
|
|
40
|
+
};
|
|
41
|
+
options: {
|
|
42
|
+
draftAndPublish: boolean;
|
|
43
|
+
};
|
|
44
|
+
pluginOptions: {
|
|
45
|
+
"content-manager": {
|
|
46
|
+
visible: boolean;
|
|
47
|
+
};
|
|
48
|
+
"content-type-builder": {
|
|
49
|
+
visible: boolean;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
attributes: {
|
|
53
|
+
object: {
|
|
54
|
+
type: string;
|
|
55
|
+
required: boolean;
|
|
56
|
+
};
|
|
57
|
+
idProperty: {
|
|
58
|
+
type: string;
|
|
59
|
+
required: boolean;
|
|
60
|
+
};
|
|
61
|
+
properties: {
|
|
62
|
+
type: string;
|
|
63
|
+
required: boolean;
|
|
64
|
+
};
|
|
65
|
+
error: {
|
|
66
|
+
type: string;
|
|
67
|
+
};
|
|
68
|
+
attempts: {
|
|
69
|
+
type: string;
|
|
70
|
+
default: number;
|
|
71
|
+
};
|
|
72
|
+
};
|
|
73
|
+
};
|
|
74
|
+
};
|
|
75
|
+
};
|
|
39
76
|
controllers: {
|
|
40
77
|
properties: ({ strapi }: {
|
|
41
78
|
strapi: Core.Strapi;
|
|
@@ -48,6 +85,24 @@ declare const _default: {
|
|
|
48
85
|
throw: (s: number, m: string) => never;
|
|
49
86
|
}): Promise<void>;
|
|
50
87
|
};
|
|
88
|
+
audit: ({ strapi }: {
|
|
89
|
+
strapi: Core.Strapi;
|
|
90
|
+
}) => {
|
|
91
|
+
run(ctx: {
|
|
92
|
+
body: unknown;
|
|
93
|
+
throw: (s: number, m: string) => never;
|
|
94
|
+
}): Promise<void>;
|
|
95
|
+
};
|
|
96
|
+
failures: ({ strapi }: {
|
|
97
|
+
strapi: Core.Strapi;
|
|
98
|
+
}) => {
|
|
99
|
+
list(ctx: {
|
|
100
|
+
body: unknown;
|
|
101
|
+
}): Promise<void>;
|
|
102
|
+
retry(ctx: {
|
|
103
|
+
body: unknown;
|
|
104
|
+
}): Promise<void>;
|
|
105
|
+
};
|
|
51
106
|
settings: ({ strapi }: {
|
|
52
107
|
strapi: Core.Strapi;
|
|
53
108
|
}) => {
|
|
@@ -75,16 +130,37 @@ declare const _default: {
|
|
|
75
130
|
path: string;
|
|
76
131
|
handler: string;
|
|
77
132
|
config: {
|
|
78
|
-
policies: string
|
|
133
|
+
policies: (string | {
|
|
134
|
+
name: string;
|
|
135
|
+
config: {
|
|
136
|
+
actions: string[];
|
|
137
|
+
};
|
|
138
|
+
})[];
|
|
79
139
|
};
|
|
80
140
|
}[];
|
|
81
141
|
};
|
|
82
142
|
};
|
|
143
|
+
services: {
|
|
144
|
+
submit: ({ strapi }: {
|
|
145
|
+
strapi: Core.Strapi;
|
|
146
|
+
}) => {
|
|
147
|
+
upsert: (input: import("./submit").UpsertInput, { queueOnFailure }?: {
|
|
148
|
+
queueOnFailure?: boolean;
|
|
149
|
+
}) => Promise<import("./submit").UpsertResult>;
|
|
150
|
+
retryFailures: ({ limit }?: {
|
|
151
|
+
limit?: number;
|
|
152
|
+
}) => Promise<{
|
|
153
|
+
retried: number;
|
|
154
|
+
succeeded: number;
|
|
155
|
+
failed: number;
|
|
156
|
+
}>;
|
|
157
|
+
};
|
|
158
|
+
};
|
|
83
159
|
register({ strapi }: {
|
|
84
160
|
strapi: Core.Strapi;
|
|
85
161
|
}): void;
|
|
86
162
|
bootstrap({ strapi }: {
|
|
87
163
|
strapi: Core.Strapi;
|
|
88
|
-
}): void
|
|
164
|
+
}): Promise<void>;
|
|
89
165
|
};
|
|
90
166
|
export default _default;
|
|
@@ -7,6 +7,11 @@ export interface HsObjectDef {
|
|
|
7
7
|
}
|
|
8
8
|
/** Objects HubSpot ships with; anything else is declared in the plugin config. */
|
|
9
9
|
export declare const STANDARD_OBJECTS: HsObjectDef[];
|
|
10
|
+
export interface HsPropertyOption {
|
|
11
|
+
value: string;
|
|
12
|
+
/** Human label, when HubSpot has one — used when importing options into Strapi. */
|
|
13
|
+
label?: string;
|
|
14
|
+
}
|
|
10
15
|
export interface HsProperty {
|
|
11
16
|
name: string;
|
|
12
17
|
label: string;
|
|
@@ -14,8 +19,8 @@ export interface HsProperty {
|
|
|
14
19
|
object: string;
|
|
15
20
|
type?: string;
|
|
16
21
|
/** Allowed values, for enumeration properties. */
|
|
17
|
-
options:
|
|
18
|
-
/** HubSpot's own grouping, shown to help editors locate a property. */
|
|
22
|
+
options: HsPropertyOption[];
|
|
23
|
+
/** HubSpot's own grouping (display label), shown to help editors locate a property. */
|
|
19
24
|
group?: string;
|
|
20
25
|
}
|
|
21
26
|
export interface Schema {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { Core } from "@strapi/strapi";
|
|
2
|
+
import { type Problem } from "./properties";
|
|
3
|
+
export declare const FAILURE_UID = "plugin::hubspot.failure";
|
|
4
|
+
export interface UpsertInput {
|
|
5
|
+
/** Target object (`contact`, `company`, a custom type id…). */
|
|
6
|
+
object: string;
|
|
7
|
+
/** Unique property used to find-or-create — `email` for contacts. */
|
|
8
|
+
idProperty: string;
|
|
9
|
+
/** Property name → value. Values are coerced to strings; empty ones are dropped. */
|
|
10
|
+
properties: Record<string, unknown>;
|
|
11
|
+
}
|
|
12
|
+
export interface UpsertResult {
|
|
13
|
+
ok: boolean;
|
|
14
|
+
/** HubSpot record id, on success. */
|
|
15
|
+
id?: string;
|
|
16
|
+
/** Pre-validation refusals — the payload never left the server. */
|
|
17
|
+
problems?: Problem[];
|
|
18
|
+
/** True when the payload was parked for `retryFailures()`. */
|
|
19
|
+
queued?: boolean;
|
|
20
|
+
error?: string;
|
|
21
|
+
}
|
|
22
|
+
export declare function createSubmitService(strapi: Core.Strapi, { sleep }?: {
|
|
23
|
+
sleep?: (ms: number) => Promise<void>;
|
|
24
|
+
}): {
|
|
25
|
+
upsert: (input: UpsertInput, { queueOnFailure }?: {
|
|
26
|
+
queueOnFailure?: boolean;
|
|
27
|
+
}) => Promise<UpsertResult>;
|
|
28
|
+
retryFailures: ({ limit }?: {
|
|
29
|
+
limit?: number;
|
|
30
|
+
}) => Promise<{
|
|
31
|
+
retried: number;
|
|
32
|
+
succeeded: number;
|
|
33
|
+
failed: number;
|
|
34
|
+
}>;
|
|
35
|
+
};
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { Core } from "@strapi/strapi";
|
|
2
|
+
import { type Mapping } from "./properties";
|
|
3
|
+
/** An entry type whose HubSpot mappings are checked before every write. */
|
|
4
|
+
export interface ValidateTarget {
|
|
5
|
+
uid: string;
|
|
6
|
+
objectField: string;
|
|
7
|
+
propertyField: string;
|
|
8
|
+
/**
|
|
9
|
+
* Repeatable holding the values a field can submit (default `options`), each
|
|
10
|
+
* `{ value?, label? }`. Checked against enumeration properties: a select whose
|
|
11
|
+
* choices drifted from HubSpot fails exactly like an unknown property.
|
|
12
|
+
*/
|
|
13
|
+
optionsField?: string;
|
|
14
|
+
/**
|
|
15
|
+
* When `false`, an `unknown` property is logged and let through instead of
|
|
16
|
+
* blocking the save — the workflow where a property is staged in HubSpot but
|
|
17
|
+
* not created yet. The other codes (`wrong-object`, `whitespace`,
|
|
18
|
+
* `bad-option`) always block: no legitimate workflow produces them.
|
|
19
|
+
* Defaults to `true`.
|
|
20
|
+
*/
|
|
21
|
+
strict?: boolean;
|
|
22
|
+
}
|
|
23
|
+
/** Every `{ object, property }` pair found anywhere in an entry, at any depth. */
|
|
24
|
+
export declare function collectMappings(node: unknown, target: ValidateTarget, found?: Mapping[]): Mapping[];
|
|
25
|
+
interface DocumentContext {
|
|
26
|
+
action: string;
|
|
27
|
+
uid: string;
|
|
28
|
+
params?: unknown;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* The document-service middleware validating one target's mappings on write.
|
|
32
|
+
*
|
|
33
|
+
* Extracted from `bootstrap` so the strict/non-strict behaviour can be tested
|
|
34
|
+
* against a minimal strapi mock instead of a running instance.
|
|
35
|
+
*/
|
|
36
|
+
export declare function makeValidationMiddleware(strapi: Core.Strapi, target: ValidateTarget): (context: DocumentContext, next: () => any) => Promise<any>;
|
|
37
|
+
export {};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "strapi-plugin-hubspot",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.4.0",
|
|
4
4
|
"description": "HubSpot integration for Strapi — pick CRM properties from a searchable list filtered by object, and catch bad mappings before they reach the API.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"strapi",
|
|
@@ -44,6 +44,8 @@
|
|
|
44
44
|
"build": "strapi-plugin build",
|
|
45
45
|
"watch": "strapi-plugin watch",
|
|
46
46
|
"verify": "strapi-plugin verify",
|
|
47
|
+
"test": "vitest run",
|
|
48
|
+
"test:watch": "vitest",
|
|
47
49
|
"prepublishOnly": "npm run build"
|
|
48
50
|
},
|
|
49
51
|
"devDependencies": {
|
|
@@ -59,7 +61,8 @@
|
|
|
59
61
|
"react-dom": "^18.3.1",
|
|
60
62
|
"react-router-dom": "^6.26.0",
|
|
61
63
|
"styled-components": "^6.1.11",
|
|
62
|
-
"typescript": "^5.5.4"
|
|
64
|
+
"typescript": "^5.5.4",
|
|
65
|
+
"vitest": "^3.2.4"
|
|
63
66
|
},
|
|
64
67
|
"peerDependencies": {
|
|
65
68
|
"@strapi/design-system": "^2.0.0-rc.29",
|
|
@@ -1,106 +0,0 @@
|
|
|
1
|
-
import { jsxs, jsx } from "react/jsx-runtime";
|
|
2
|
-
import * as React from "react";
|
|
3
|
-
import { Field, Flex, Loader, Combobox, ComboboxOption, Link } from "@strapi/design-system";
|
|
4
|
-
import { useFetchClient, useField } from "@strapi/strapi/admin";
|
|
5
|
-
import { P as PLUGIN_ID } from "./index-lx7um1Yx.mjs";
|
|
6
|
-
const LABELS = {
|
|
7
|
-
contact: "Contact",
|
|
8
|
-
company: "Société",
|
|
9
|
-
deal: "Transaction",
|
|
10
|
-
ticket: "Ticket",
|
|
11
|
-
product: "Produit",
|
|
12
|
-
line_item: "Ligne de commande",
|
|
13
|
-
quote: "Devis"
|
|
14
|
-
};
|
|
15
|
-
function objectLabel(object) {
|
|
16
|
-
return LABELS[object] ?? object;
|
|
17
|
-
}
|
|
18
|
-
const HubspotPropertyInput = React.forwardRef(
|
|
19
|
-
({ name, value, onChange, attribute, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
20
|
-
const { get } = useFetchClient();
|
|
21
|
-
const [schema, setSchema] = React.useState(null);
|
|
22
|
-
const [loadError, setLoadError] = React.useState("");
|
|
23
|
-
const objectFieldName = attribute?.options?.objectField || "hsObject";
|
|
24
|
-
const siblingPath = React.useMemo(
|
|
25
|
-
() => name.replace(/[^.]+$/, objectFieldName),
|
|
26
|
-
[name, objectFieldName]
|
|
27
|
-
);
|
|
28
|
-
const siblingField = useField(siblingPath);
|
|
29
|
-
const selectedObject = siblingField?.value;
|
|
30
|
-
React.useEffect(() => {
|
|
31
|
-
let cancelled = false;
|
|
32
|
-
get(`/${PLUGIN_ID}/properties`).then(({ data }) => {
|
|
33
|
-
if (!cancelled) setSchema(data);
|
|
34
|
-
}).catch((err) => {
|
|
35
|
-
if (cancelled) return;
|
|
36
|
-
setSchema({ configured: false, properties: [], objects: [], unavailable: [] });
|
|
37
|
-
setLoadError(err?.response?.data?.error?.message ?? "Propriétés HubSpot indisponibles");
|
|
38
|
-
});
|
|
39
|
-
return () => {
|
|
40
|
-
cancelled = true;
|
|
41
|
-
};
|
|
42
|
-
}, [get]);
|
|
43
|
-
const emit = (next) => onChange({ target: { name, value: next ?? "", type: "string" } });
|
|
44
|
-
const options = React.useMemo(() => {
|
|
45
|
-
const all = schema?.properties ?? [];
|
|
46
|
-
const scoped = selectedObject ? all.filter((p) => p.object === selectedObject) : all;
|
|
47
|
-
const shown = scoped.map((p) => ({
|
|
48
|
-
value: p.name,
|
|
49
|
-
// The object prefix is redundant once filtered, and noisy.
|
|
50
|
-
label: selectedObject ? `${p.label} (${p.name})` : `${objectLabel(p.object)} · ${p.label} (${p.name})`
|
|
51
|
-
}));
|
|
52
|
-
if (value && !scoped.some((p) => p.name === value)) {
|
|
53
|
-
const elsewhere = all.find((p) => p.name === value);
|
|
54
|
-
shown.unshift({
|
|
55
|
-
value,
|
|
56
|
-
label: elsewhere ? `${value} — appartient à ${objectLabel(elsewhere.object)}` : `${value} — inconnue du portail`
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
return shown;
|
|
60
|
-
}, [schema, selectedObject, value]);
|
|
61
|
-
const describe = () => {
|
|
62
|
-
if (loadError) return loadError;
|
|
63
|
-
if (schema && !schema.configured) {
|
|
64
|
-
return "Aucune clé API HubSpot — saisie libre. Renseignez-la dans Réglages → HubSpot.";
|
|
65
|
-
}
|
|
66
|
-
if (selectedObject && schema?.unavailable.some((u) => u.object === selectedObject)) {
|
|
67
|
-
return `Propriétés de « ${objectLabel(selectedObject)} » illisibles — portée manquante sur le jeton.`;
|
|
68
|
-
}
|
|
69
|
-
return hint;
|
|
70
|
-
};
|
|
71
|
-
const crmLink = schema?.portalId && value && schema.properties.some((p) => p.name === value) ? `https://${schema.uiDomain || "app.hubspot.com"}/property-settings/${schema.portalId}/properties?search=${encodeURIComponent(value)}` : null;
|
|
72
|
-
return /* @__PURE__ */ jsxs(Field.Root, { name, error, hint: describe(), required, children: [
|
|
73
|
-
/* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: label }),
|
|
74
|
-
!schema ? /* @__PURE__ */ jsx(Flex, { paddingTop: 2, paddingBottom: 2, children: /* @__PURE__ */ jsx(Loader, { small: true, children: "Chargement des propriétés HubSpot…" }) }) : schema.configured ? /* @__PURE__ */ jsx(
|
|
75
|
-
Combobox,
|
|
76
|
-
{
|
|
77
|
-
ref,
|
|
78
|
-
value: value || "",
|
|
79
|
-
onChange: emit,
|
|
80
|
-
onClear: () => emit(""),
|
|
81
|
-
disabled,
|
|
82
|
-
placeholder: "Rechercher une propriété…",
|
|
83
|
-
creatable: true,
|
|
84
|
-
onCreateOption: emit,
|
|
85
|
-
children: options.map((o) => /* @__PURE__ */ jsx(ComboboxOption, { value: o.value, children: o.label }, o.value))
|
|
86
|
-
}
|
|
87
|
-
) : /* @__PURE__ */ jsx(
|
|
88
|
-
Field.Input,
|
|
89
|
-
{
|
|
90
|
-
ref,
|
|
91
|
-
value: value || "",
|
|
92
|
-
onChange: (e) => emit(e.target.value),
|
|
93
|
-
disabled,
|
|
94
|
-
placeholder: "ex. hs_role, numberofemployees"
|
|
95
|
-
}
|
|
96
|
-
),
|
|
97
|
-
crmLink ? /* @__PURE__ */ jsx(Flex, { paddingTop: 1, children: /* @__PURE__ */ jsx(Link, { href: crmLink, isExternal: true, children: "Voir dans HubSpot" }) }) : null,
|
|
98
|
-
/* @__PURE__ */ jsx(Field.Hint, {}),
|
|
99
|
-
/* @__PURE__ */ jsx(Field.Error, {})
|
|
100
|
-
] });
|
|
101
|
-
}
|
|
102
|
-
);
|
|
103
|
-
HubspotPropertyInput.displayName = "HubspotPropertyInput";
|
|
104
|
-
export {
|
|
105
|
-
HubspotPropertyInput as default
|
|
106
|
-
};
|
|
@@ -1,124 +0,0 @@
|
|
|
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-Bq1tS5h6.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 LABELS = {
|
|
27
|
-
contact: "Contact",
|
|
28
|
-
company: "Société",
|
|
29
|
-
deal: "Transaction",
|
|
30
|
-
ticket: "Ticket",
|
|
31
|
-
product: "Produit",
|
|
32
|
-
line_item: "Ligne de commande",
|
|
33
|
-
quote: "Devis"
|
|
34
|
-
};
|
|
35
|
-
function objectLabel(object) {
|
|
36
|
-
return LABELS[object] ?? object;
|
|
37
|
-
}
|
|
38
|
-
const HubspotPropertyInput = React__namespace.forwardRef(
|
|
39
|
-
({ name, value, onChange, attribute, disabled, error, required, label, hint, labelAction }, ref) => {
|
|
40
|
-
const { get } = admin.useFetchClient();
|
|
41
|
-
const [schema, setSchema] = React__namespace.useState(null);
|
|
42
|
-
const [loadError, setLoadError] = React__namespace.useState("");
|
|
43
|
-
const objectFieldName = attribute?.options?.objectField || "hsObject";
|
|
44
|
-
const siblingPath = React__namespace.useMemo(
|
|
45
|
-
() => name.replace(/[^.]+$/, objectFieldName),
|
|
46
|
-
[name, objectFieldName]
|
|
47
|
-
);
|
|
48
|
-
const siblingField = admin.useField(siblingPath);
|
|
49
|
-
const selectedObject = siblingField?.value;
|
|
50
|
-
React__namespace.useEffect(() => {
|
|
51
|
-
let cancelled = false;
|
|
52
|
-
get(`/${index.PLUGIN_ID}/properties`).then(({ data }) => {
|
|
53
|
-
if (!cancelled) setSchema(data);
|
|
54
|
-
}).catch((err) => {
|
|
55
|
-
if (cancelled) return;
|
|
56
|
-
setSchema({ configured: false, properties: [], objects: [], unavailable: [] });
|
|
57
|
-
setLoadError(err?.response?.data?.error?.message ?? "Propriétés HubSpot indisponibles");
|
|
58
|
-
});
|
|
59
|
-
return () => {
|
|
60
|
-
cancelled = true;
|
|
61
|
-
};
|
|
62
|
-
}, [get]);
|
|
63
|
-
const emit = (next) => onChange({ target: { name, value: next ?? "", type: "string" } });
|
|
64
|
-
const options = React__namespace.useMemo(() => {
|
|
65
|
-
const all = schema?.properties ?? [];
|
|
66
|
-
const scoped = selectedObject ? all.filter((p) => p.object === selectedObject) : all;
|
|
67
|
-
const shown = scoped.map((p) => ({
|
|
68
|
-
value: p.name,
|
|
69
|
-
// The object prefix is redundant once filtered, and noisy.
|
|
70
|
-
label: selectedObject ? `${p.label} (${p.name})` : `${objectLabel(p.object)} · ${p.label} (${p.name})`
|
|
71
|
-
}));
|
|
72
|
-
if (value && !scoped.some((p) => p.name === value)) {
|
|
73
|
-
const elsewhere = all.find((p) => p.name === value);
|
|
74
|
-
shown.unshift({
|
|
75
|
-
value,
|
|
76
|
-
label: elsewhere ? `${value} — appartient à ${objectLabel(elsewhere.object)}` : `${value} — inconnue du portail`
|
|
77
|
-
});
|
|
78
|
-
}
|
|
79
|
-
return shown;
|
|
80
|
-
}, [schema, selectedObject, value]);
|
|
81
|
-
const describe = () => {
|
|
82
|
-
if (loadError) return loadError;
|
|
83
|
-
if (schema && !schema.configured) {
|
|
84
|
-
return "Aucune clé API HubSpot — saisie libre. Renseignez-la dans Réglages → HubSpot.";
|
|
85
|
-
}
|
|
86
|
-
if (selectedObject && schema?.unavailable.some((u) => u.object === selectedObject)) {
|
|
87
|
-
return `Propriétés de « ${objectLabel(selectedObject)} » illisibles — portée manquante sur le jeton.`;
|
|
88
|
-
}
|
|
89
|
-
return hint;
|
|
90
|
-
};
|
|
91
|
-
const crmLink = schema?.portalId && value && schema.properties.some((p) => p.name === value) ? `https://${schema.uiDomain || "app.hubspot.com"}/property-settings/${schema.portalId}/properties?search=${encodeURIComponent(value)}` : null;
|
|
92
|
-
return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name, error, hint: describe(), required, children: [
|
|
93
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { action: labelAction, children: label }),
|
|
94
|
-
!schema ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { paddingTop: 2, paddingBottom: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true, children: "Chargement des propriétés HubSpot…" }) }) : schema.configured ? /* @__PURE__ */ jsxRuntime.jsx(
|
|
95
|
-
designSystem.Combobox,
|
|
96
|
-
{
|
|
97
|
-
ref,
|
|
98
|
-
value: value || "",
|
|
99
|
-
onChange: emit,
|
|
100
|
-
onClear: () => emit(""),
|
|
101
|
-
disabled,
|
|
102
|
-
placeholder: "Rechercher une propriété…",
|
|
103
|
-
creatable: true,
|
|
104
|
-
onCreateOption: emit,
|
|
105
|
-
children: options.map((o) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.ComboboxOption, { value: o.value, children: o.label }, o.value))
|
|
106
|
-
}
|
|
107
|
-
) : /* @__PURE__ */ jsxRuntime.jsx(
|
|
108
|
-
designSystem.Field.Input,
|
|
109
|
-
{
|
|
110
|
-
ref,
|
|
111
|
-
value: value || "",
|
|
112
|
-
onChange: (e) => emit(e.target.value),
|
|
113
|
-
disabled,
|
|
114
|
-
placeholder: "ex. hs_role, numberofemployees"
|
|
115
|
-
}
|
|
116
|
-
),
|
|
117
|
-
crmLink ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { paddingTop: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Link, { href: crmLink, isExternal: true, children: "Voir dans HubSpot" }) }) : null,
|
|
118
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
119
|
-
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
120
|
-
] });
|
|
121
|
-
}
|
|
122
|
-
);
|
|
123
|
-
HubspotPropertyInput.displayName = "HubspotPropertyInput";
|
|
124
|
-
exports.default = HubspotPropertyInput;
|
|
@@ -1,140 +0,0 @@
|
|
|
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-lx7um1Yx.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
|
-
};
|