@complai/news-integration 1.0.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 +3 -0
- package/dist/admin/index.js +445 -0
- package/dist/admin/index.mjs +446 -0
- package/dist/server/index.js +191 -0
- package/dist/server/index.mjs +189 -0
- package/package.json +68 -0
package/README.md
ADDED
|
@@ -0,0 +1,445 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const react = require("react");
|
|
3
|
+
const jsxRuntime = require("react/jsx-runtime");
|
|
4
|
+
const designSystem = require("@strapi/design-system");
|
|
5
|
+
const admin = require("@strapi/strapi/admin");
|
|
6
|
+
const PLUGIN_ID = "news-integration";
|
|
7
|
+
const Initializer = ({ setPlugin }) => {
|
|
8
|
+
const ref = react.useRef(setPlugin);
|
|
9
|
+
react.useEffect(() => {
|
|
10
|
+
ref.current(PLUGIN_ID);
|
|
11
|
+
}, []);
|
|
12
|
+
return null;
|
|
13
|
+
};
|
|
14
|
+
const Checkbox = ({ checked, indeterminate, disabled: isDisabled }) => /* @__PURE__ */ jsxRuntime.jsxs(
|
|
15
|
+
designSystem.Box,
|
|
16
|
+
{
|
|
17
|
+
style: {
|
|
18
|
+
width: "18px",
|
|
19
|
+
height: "18px",
|
|
20
|
+
borderRadius: "4px",
|
|
21
|
+
border: checked || indeterminate ? "2px solid #4945ff" : "2px solid #dcdce4",
|
|
22
|
+
background: checked ? "#4945ff" : indeterminate ? "#4945ff" : "white",
|
|
23
|
+
display: "flex",
|
|
24
|
+
alignItems: "center",
|
|
25
|
+
justifyContent: "center",
|
|
26
|
+
flexShrink: 0,
|
|
27
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
28
|
+
opacity: isDisabled ? 0.5 : 1
|
|
29
|
+
},
|
|
30
|
+
children: [
|
|
31
|
+
checked && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "8", viewBox: "0 0 10 8", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 4L3.5 6.5L9 1", stroke: "white", strokeWidth: "2", strokeLinecap: "round" }) }),
|
|
32
|
+
indeterminate && !checked && /* @__PURE__ */ jsxRuntime.jsx("svg", { width: "10", height: "2", viewBox: "0 0 10 2", fill: "none", children: /* @__PURE__ */ jsxRuntime.jsx("path", { d: "M1 1H9", stroke: "white", strokeWidth: "2", strokeLinecap: "round" }) })
|
|
33
|
+
]
|
|
34
|
+
}
|
|
35
|
+
);
|
|
36
|
+
const OrganizationPickerInput = ({
|
|
37
|
+
name,
|
|
38
|
+
value,
|
|
39
|
+
onChange,
|
|
40
|
+
error,
|
|
41
|
+
required,
|
|
42
|
+
intlLabel,
|
|
43
|
+
labelAction,
|
|
44
|
+
disabled
|
|
45
|
+
}) => {
|
|
46
|
+
const [organizations, setOrganizations] = react.useState([]);
|
|
47
|
+
const [loading, setLoading] = react.useState(false);
|
|
48
|
+
const [fetchError, setFetchError] = react.useState(null);
|
|
49
|
+
const [isExpanded, setIsExpanded] = react.useState(true);
|
|
50
|
+
const [expandedGroups, setExpandedGroups] = react.useState({});
|
|
51
|
+
const { get } = admin.useFetchClient();
|
|
52
|
+
const selectedOrgs = react.useMemo(() => {
|
|
53
|
+
if (!value) return [];
|
|
54
|
+
if (typeof value === "string") {
|
|
55
|
+
try {
|
|
56
|
+
const parsed = JSON.parse(value);
|
|
57
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
58
|
+
} catch {
|
|
59
|
+
return [];
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return Array.isArray(value) ? value : [];
|
|
63
|
+
}, [value]);
|
|
64
|
+
const selectedOrgIds = react.useMemo(() => {
|
|
65
|
+
return new Set(selectedOrgs.map((org) => org.id));
|
|
66
|
+
}, [selectedOrgs]);
|
|
67
|
+
const computedGroups = react.useMemo(() => {
|
|
68
|
+
if (organizations.length === 0) return [];
|
|
69
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
70
|
+
const ungroupedOrgs = [];
|
|
71
|
+
organizations.forEach((org) => {
|
|
72
|
+
const tags = org.contentTags || [];
|
|
73
|
+
if (tags.length === 0) {
|
|
74
|
+
ungroupedOrgs.push({ id: org.id, name: org.displayName || org.name });
|
|
75
|
+
} else {
|
|
76
|
+
tags.forEach((tag) => {
|
|
77
|
+
if (!tagMap.has(tag)) {
|
|
78
|
+
tagMap.set(tag, []);
|
|
79
|
+
}
|
|
80
|
+
tagMap.get(tag).push({ id: org.id, name: org.displayName || org.name });
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
const groups = Array.from(tagMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([tag, orgs]) => ({
|
|
85
|
+
id: `tag-${tag}`,
|
|
86
|
+
name: tag,
|
|
87
|
+
description: "",
|
|
88
|
+
color: "#4945ff",
|
|
89
|
+
organizations: orgs,
|
|
90
|
+
organizationCount: orgs.length
|
|
91
|
+
}));
|
|
92
|
+
if (ungroupedOrgs.length > 0) {
|
|
93
|
+
groups.push({
|
|
94
|
+
id: "ungrouped",
|
|
95
|
+
name: "Ungrouped",
|
|
96
|
+
description: "Organizations without content tags",
|
|
97
|
+
color: "#8e8ea9",
|
|
98
|
+
organizations: ungroupedOrgs,
|
|
99
|
+
organizationCount: ungroupedOrgs.length
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
return groups;
|
|
103
|
+
}, [organizations]);
|
|
104
|
+
const fetchData = react.useCallback(async () => {
|
|
105
|
+
setLoading(true);
|
|
106
|
+
setFetchError(null);
|
|
107
|
+
try {
|
|
108
|
+
const orgsResponse = await get("/news-integration/organizations");
|
|
109
|
+
const orgsData = orgsResponse?.data || {};
|
|
110
|
+
const orgs = orgsData.organizations || [];
|
|
111
|
+
setOrganizations(orgs);
|
|
112
|
+
if (orgsData.error) {
|
|
113
|
+
setFetchError(orgsData.message || orgsData.error);
|
|
114
|
+
} else if (orgs.length === 0) {
|
|
115
|
+
setFetchError("No organizations found. Ensure you are logged in via Auth0 SSO.");
|
|
116
|
+
}
|
|
117
|
+
} catch (err) {
|
|
118
|
+
setFetchError(err?.message || "Failed to load data");
|
|
119
|
+
} finally {
|
|
120
|
+
setLoading(false);
|
|
121
|
+
}
|
|
122
|
+
}, [get]);
|
|
123
|
+
react.useEffect(() => {
|
|
124
|
+
if (isExpanded && organizations.length === 0 && !loading) {
|
|
125
|
+
fetchData();
|
|
126
|
+
}
|
|
127
|
+
}, [isExpanded, fetchData]);
|
|
128
|
+
const updateValue = (newSelection) => {
|
|
129
|
+
onChange({
|
|
130
|
+
target: {
|
|
131
|
+
name,
|
|
132
|
+
value: JSON.stringify(newSelection)
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
};
|
|
136
|
+
const handleToggle = (org) => {
|
|
137
|
+
const isSelected = selectedOrgIds.has(org.id);
|
|
138
|
+
let newSelection;
|
|
139
|
+
if (isSelected) {
|
|
140
|
+
newSelection = selectedOrgs.filter((s) => s.id !== org.id);
|
|
141
|
+
} else {
|
|
142
|
+
newSelection = [...selectedOrgs, { id: org.id, name: org.displayName || org.name }];
|
|
143
|
+
}
|
|
144
|
+
updateValue(newSelection);
|
|
145
|
+
};
|
|
146
|
+
const getGroupSelectionState = (group) => {
|
|
147
|
+
const groupOrgIds = (group.organizations || []).map((o) => o.id);
|
|
148
|
+
if (groupOrgIds.length === 0) return "none";
|
|
149
|
+
const selectedCount = groupOrgIds.filter((id) => selectedOrgIds.has(id)).length;
|
|
150
|
+
if (selectedCount === 0) return "none";
|
|
151
|
+
if (selectedCount === groupOrgIds.length) return "all";
|
|
152
|
+
return "partial";
|
|
153
|
+
};
|
|
154
|
+
const handleGroupToggle = (group) => {
|
|
155
|
+
const state = getGroupSelectionState(group);
|
|
156
|
+
const groupOrgIds = new Set((group.organizations || []).map((o) => o.id));
|
|
157
|
+
let newSelection;
|
|
158
|
+
if (state === "all") {
|
|
159
|
+
newSelection = selectedOrgs.filter((s) => !groupOrgIds.has(s.id));
|
|
160
|
+
} else {
|
|
161
|
+
const existingOrgIds = new Set(selectedOrgs.map((s) => s.id));
|
|
162
|
+
const orgsToAdd = (group.organizations || []).filter((o) => !existingOrgIds.has(o.id)).map((o) => ({ id: o.id, name: o.name }));
|
|
163
|
+
newSelection = [...selectedOrgs, ...orgsToAdd];
|
|
164
|
+
}
|
|
165
|
+
updateValue(newSelection);
|
|
166
|
+
};
|
|
167
|
+
const toggleGroupExpansion = (groupId) => {
|
|
168
|
+
setExpandedGroups((prev) => ({
|
|
169
|
+
...prev,
|
|
170
|
+
[groupId]: !prev[groupId]
|
|
171
|
+
}));
|
|
172
|
+
};
|
|
173
|
+
const handleSelectAll = () => {
|
|
174
|
+
const allOrgs = organizations.map((org) => ({
|
|
175
|
+
id: org.id,
|
|
176
|
+
name: org.displayName || org.name
|
|
177
|
+
}));
|
|
178
|
+
updateValue(allOrgs);
|
|
179
|
+
};
|
|
180
|
+
const handleClearAll = () => {
|
|
181
|
+
updateValue([]);
|
|
182
|
+
};
|
|
183
|
+
const isOrgSelected = (orgId) => selectedOrgIds.has(orgId);
|
|
184
|
+
const displayError = error && !error.includes("JSON") ? error : false;
|
|
185
|
+
return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Root, { name, error: displayError, required, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
|
|
186
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { action: labelAction, children: intlLabel?.defaultMessage || "Organizations" }),
|
|
187
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
188
|
+
designSystem.Box,
|
|
189
|
+
{
|
|
190
|
+
padding: 4,
|
|
191
|
+
background: "neutral0",
|
|
192
|
+
borderColor: error ? "danger600" : "neutral200",
|
|
193
|
+
hasRadius: true,
|
|
194
|
+
style: { border: "1px solid" },
|
|
195
|
+
children: [
|
|
196
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
|
|
197
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
198
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "18px" }, children: "🏢" }),
|
|
199
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
200
|
+
designSystem.Typography,
|
|
201
|
+
{
|
|
202
|
+
variant: "omega",
|
|
203
|
+
fontWeight: "bold",
|
|
204
|
+
textColor: selectedOrgs.length > 0 ? "success600" : "neutral600",
|
|
205
|
+
children: [
|
|
206
|
+
selectedOrgs.length,
|
|
207
|
+
" organization",
|
|
208
|
+
selectedOrgs.length !== 1 ? "s" : "",
|
|
209
|
+
" selected"
|
|
210
|
+
]
|
|
211
|
+
}
|
|
212
|
+
)
|
|
213
|
+
] }),
|
|
214
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
215
|
+
designSystem.Button,
|
|
216
|
+
{
|
|
217
|
+
variant: "ghost",
|
|
218
|
+
size: "S",
|
|
219
|
+
onClick: () => setIsExpanded(!isExpanded),
|
|
220
|
+
children: isExpanded ? "▲ Collapse" : "▼ Expand"
|
|
221
|
+
}
|
|
222
|
+
)
|
|
223
|
+
] }),
|
|
224
|
+
selectedOrgs.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { marginBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { wrap: "wrap", gap: 1, children: selectedOrgs.map((org) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
225
|
+
designSystem.Box,
|
|
226
|
+
{
|
|
227
|
+
padding: 1,
|
|
228
|
+
paddingLeft: 2,
|
|
229
|
+
paddingRight: 2,
|
|
230
|
+
background: "primary100",
|
|
231
|
+
hasRadius: true,
|
|
232
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "primary700", children: org.name })
|
|
233
|
+
},
|
|
234
|
+
org.id
|
|
235
|
+
)) }) }),
|
|
236
|
+
isExpanded && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
237
|
+
loading && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "center", padding: 4, children: [
|
|
238
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true }),
|
|
239
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", children: "Loading organizations..." }) })
|
|
240
|
+
] }),
|
|
241
|
+
fetchError && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 3, children: [
|
|
242
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "danger600", children: fetchError }),
|
|
243
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", size: "S", onClick: fetchData, children: "Retry" }) })
|
|
244
|
+
] }),
|
|
245
|
+
!loading && !fetchError && organizations.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
246
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, marginBottom: 3, children: [
|
|
247
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "S", onClick: handleSelectAll, disabled, children: "Select All" }),
|
|
248
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", size: "S", onClick: handleClearAll, disabled, children: "Clear All" })
|
|
249
|
+
] }),
|
|
250
|
+
computedGroups.length > 0 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { marginBottom: 4, children: [
|
|
251
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, marginBottom: 2, children: [
|
|
252
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { style: { fontSize: "16px" }, children: "🏷️" }),
|
|
253
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: "CONTENT TAG GROUPS" })
|
|
254
|
+
] }),
|
|
255
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, children: computedGroups.map((group) => {
|
|
256
|
+
const selectionState = getGroupSelectionState(group);
|
|
257
|
+
const isGroupExpanded = expandedGroups[group.id];
|
|
258
|
+
const orgCount = (group.organizations || []).length;
|
|
259
|
+
const selectedInGroup = (group.organizations || []).filter((o) => selectedOrgIds.has(o.id)).length;
|
|
260
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
261
|
+
designSystem.Box,
|
|
262
|
+
{
|
|
263
|
+
padding: 2,
|
|
264
|
+
paddingLeft: 3,
|
|
265
|
+
paddingRight: 3,
|
|
266
|
+
background: selectionState !== "none" ? "primary100" : "neutral100",
|
|
267
|
+
hasRadius: true,
|
|
268
|
+
width: "100%",
|
|
269
|
+
style: {
|
|
270
|
+
border: selectionState === "all" ? "2px solid #4945ff" : selectionState === "partial" ? "2px dashed #4945ff" : "2px solid transparent",
|
|
271
|
+
cursor: disabled ? "not-allowed" : "pointer"
|
|
272
|
+
},
|
|
273
|
+
onClick: () => !disabled && handleGroupToggle(group),
|
|
274
|
+
children: [
|
|
275
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
277
|
+
designSystem.Flex,
|
|
278
|
+
{
|
|
279
|
+
alignItems: "center",
|
|
280
|
+
gap: 2,
|
|
281
|
+
children: [
|
|
282
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
283
|
+
Checkbox,
|
|
284
|
+
{
|
|
285
|
+
checked: selectionState === "all",
|
|
286
|
+
indeterminate: selectionState === "partial",
|
|
287
|
+
disabled
|
|
288
|
+
}
|
|
289
|
+
),
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
|
|
291
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 2, children: [
|
|
292
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
293
|
+
designSystem.Box,
|
|
294
|
+
{
|
|
295
|
+
style: {
|
|
296
|
+
width: "12px",
|
|
297
|
+
height: "12px",
|
|
298
|
+
borderRadius: "3px",
|
|
299
|
+
background: group.color || "#4945ff"
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
),
|
|
303
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
304
|
+
designSystem.Typography,
|
|
305
|
+
{
|
|
306
|
+
variant: "omega",
|
|
307
|
+
fontWeight: "semiBold",
|
|
308
|
+
textColor: selectionState !== "none" ? "primary700" : "neutral800",
|
|
309
|
+
children: group.name
|
|
310
|
+
}
|
|
311
|
+
)
|
|
312
|
+
] }),
|
|
313
|
+
/* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
|
|
314
|
+
selectedInGroup,
|
|
315
|
+
"/",
|
|
316
|
+
orgCount,
|
|
317
|
+
" organizations",
|
|
318
|
+
group.description && ` • ${group.description}`
|
|
319
|
+
] })
|
|
320
|
+
] })
|
|
321
|
+
]
|
|
322
|
+
}
|
|
323
|
+
),
|
|
324
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
325
|
+
designSystem.Button,
|
|
326
|
+
{
|
|
327
|
+
variant: "ghost",
|
|
328
|
+
size: "S",
|
|
329
|
+
onClick: (e) => {
|
|
330
|
+
e.stopPropagation();
|
|
331
|
+
toggleGroupExpansion(group.id);
|
|
332
|
+
},
|
|
333
|
+
children: isGroupExpanded ? "▲" : "▼"
|
|
334
|
+
}
|
|
335
|
+
)
|
|
336
|
+
] }),
|
|
337
|
+
isGroupExpanded && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 4, paddingTop: 2, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: (group.organizations || []).map((org) => {
|
|
338
|
+
const isSelected = isOrgSelected(org.id);
|
|
339
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
340
|
+
designSystem.Box,
|
|
341
|
+
{
|
|
342
|
+
padding: 1,
|
|
343
|
+
paddingLeft: 2,
|
|
344
|
+
paddingRight: 2,
|
|
345
|
+
background: isSelected ? "primary100" : "neutral100",
|
|
346
|
+
hasRadius: true,
|
|
347
|
+
style: {
|
|
348
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
349
|
+
border: isSelected ? "1px solid #4945ff" : "1px solid transparent",
|
|
350
|
+
opacity: disabled ? 0.5 : 1
|
|
351
|
+
},
|
|
352
|
+
onClick: () => !disabled && handleToggle(org),
|
|
353
|
+
children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 1, children: [
|
|
354
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
355
|
+
Checkbox,
|
|
356
|
+
{
|
|
357
|
+
checked: isSelected,
|
|
358
|
+
disabled
|
|
359
|
+
}
|
|
360
|
+
),
|
|
361
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
362
|
+
designSystem.Typography,
|
|
363
|
+
{
|
|
364
|
+
variant: "pi",
|
|
365
|
+
textColor: isSelected ? "primary700" : "neutral600",
|
|
366
|
+
children: org.name
|
|
367
|
+
}
|
|
368
|
+
)
|
|
369
|
+
] })
|
|
370
|
+
},
|
|
371
|
+
org.id
|
|
372
|
+
);
|
|
373
|
+
}) }) })
|
|
374
|
+
]
|
|
375
|
+
},
|
|
376
|
+
group.id
|
|
377
|
+
);
|
|
378
|
+
}) }) })
|
|
379
|
+
] })
|
|
380
|
+
] }),
|
|
381
|
+
!loading && !fetchError && organizations.length === 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", onClick: fetchData, children: "Load Organizations" })
|
|
382
|
+
] })
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
),
|
|
386
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
|
|
387
|
+
/* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
|
|
388
|
+
] }) });
|
|
389
|
+
};
|
|
390
|
+
const OrganizationIcon = () => {
|
|
391
|
+
return react.createElement("span", { role: "img", "aria-label": "organizations" }, "🏢");
|
|
392
|
+
};
|
|
393
|
+
const index = {
|
|
394
|
+
register(app) {
|
|
395
|
+
app.customFields.register({
|
|
396
|
+
name: "organization-picker",
|
|
397
|
+
pluginId: PLUGIN_ID,
|
|
398
|
+
type: "text",
|
|
399
|
+
intlLabel: {
|
|
400
|
+
id: `${PLUGIN_ID}.organization-picker.label`,
|
|
401
|
+
defaultMessage: "Organization Picker"
|
|
402
|
+
},
|
|
403
|
+
intlDescription: {
|
|
404
|
+
id: `${PLUGIN_ID}.organization-picker.description`,
|
|
405
|
+
defaultMessage: "Select organizations for multi-tenant content"
|
|
406
|
+
},
|
|
407
|
+
icon: OrganizationIcon,
|
|
408
|
+
components: {
|
|
409
|
+
Input: async () => ({ default: OrganizationPickerInput })
|
|
410
|
+
},
|
|
411
|
+
options: {
|
|
412
|
+
base: [],
|
|
413
|
+
advanced: [
|
|
414
|
+
{
|
|
415
|
+
sectionTitle: {
|
|
416
|
+
id: "global.settings",
|
|
417
|
+
defaultMessage: "Settings"
|
|
418
|
+
},
|
|
419
|
+
items: [
|
|
420
|
+
{
|
|
421
|
+
name: "required",
|
|
422
|
+
type: "checkbox",
|
|
423
|
+
intlLabel: {
|
|
424
|
+
id: `${PLUGIN_ID}.organization-picker.required`,
|
|
425
|
+
defaultMessage: "Required field"
|
|
426
|
+
},
|
|
427
|
+
description: {
|
|
428
|
+
id: `${PLUGIN_ID}.organization-picker.required.description`,
|
|
429
|
+
defaultMessage: "You won't be able to create an entry if this field is empty"
|
|
430
|
+
}
|
|
431
|
+
}
|
|
432
|
+
]
|
|
433
|
+
}
|
|
434
|
+
]
|
|
435
|
+
}
|
|
436
|
+
});
|
|
437
|
+
app.registerPlugin({
|
|
438
|
+
id: PLUGIN_ID,
|
|
439
|
+
initializer: Initializer,
|
|
440
|
+
isReady: false,
|
|
441
|
+
name: PLUGIN_ID
|
|
442
|
+
});
|
|
443
|
+
}
|
|
444
|
+
};
|
|
445
|
+
module.exports = index;
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
import { useRef, useEffect, useState, useMemo, useCallback, createElement } from "react";
|
|
2
|
+
import { jsx, jsxs, Fragment } from "react/jsx-runtime";
|
|
3
|
+
import { Field, Flex, Box, Typography, Button, Loader } from "@strapi/design-system";
|
|
4
|
+
import { useFetchClient } from "@strapi/strapi/admin";
|
|
5
|
+
const PLUGIN_ID = "news-integration";
|
|
6
|
+
const Initializer = ({ setPlugin }) => {
|
|
7
|
+
const ref = useRef(setPlugin);
|
|
8
|
+
useEffect(() => {
|
|
9
|
+
ref.current(PLUGIN_ID);
|
|
10
|
+
}, []);
|
|
11
|
+
return null;
|
|
12
|
+
};
|
|
13
|
+
const Checkbox = ({ checked, indeterminate, disabled: isDisabled }) => /* @__PURE__ */ jsxs(
|
|
14
|
+
Box,
|
|
15
|
+
{
|
|
16
|
+
style: {
|
|
17
|
+
width: "18px",
|
|
18
|
+
height: "18px",
|
|
19
|
+
borderRadius: "4px",
|
|
20
|
+
border: checked || indeterminate ? "2px solid #4945ff" : "2px solid #dcdce4",
|
|
21
|
+
background: checked ? "#4945ff" : indeterminate ? "#4945ff" : "white",
|
|
22
|
+
display: "flex",
|
|
23
|
+
alignItems: "center",
|
|
24
|
+
justifyContent: "center",
|
|
25
|
+
flexShrink: 0,
|
|
26
|
+
cursor: isDisabled ? "not-allowed" : "pointer",
|
|
27
|
+
opacity: isDisabled ? 0.5 : 1
|
|
28
|
+
},
|
|
29
|
+
children: [
|
|
30
|
+
checked && /* @__PURE__ */ jsx("svg", { width: "10", height: "8", viewBox: "0 0 10 8", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M1 4L3.5 6.5L9 1", stroke: "white", strokeWidth: "2", strokeLinecap: "round" }) }),
|
|
31
|
+
indeterminate && !checked && /* @__PURE__ */ jsx("svg", { width: "10", height: "2", viewBox: "0 0 10 2", fill: "none", children: /* @__PURE__ */ jsx("path", { d: "M1 1H9", stroke: "white", strokeWidth: "2", strokeLinecap: "round" }) })
|
|
32
|
+
]
|
|
33
|
+
}
|
|
34
|
+
);
|
|
35
|
+
const OrganizationPickerInput = ({
|
|
36
|
+
name,
|
|
37
|
+
value,
|
|
38
|
+
onChange,
|
|
39
|
+
error,
|
|
40
|
+
required,
|
|
41
|
+
intlLabel,
|
|
42
|
+
labelAction,
|
|
43
|
+
disabled
|
|
44
|
+
}) => {
|
|
45
|
+
const [organizations, setOrganizations] = useState([]);
|
|
46
|
+
const [loading, setLoading] = useState(false);
|
|
47
|
+
const [fetchError, setFetchError] = useState(null);
|
|
48
|
+
const [isExpanded, setIsExpanded] = useState(true);
|
|
49
|
+
const [expandedGroups, setExpandedGroups] = useState({});
|
|
50
|
+
const { get } = useFetchClient();
|
|
51
|
+
const selectedOrgs = useMemo(() => {
|
|
52
|
+
if (!value) return [];
|
|
53
|
+
if (typeof value === "string") {
|
|
54
|
+
try {
|
|
55
|
+
const parsed = JSON.parse(value);
|
|
56
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
57
|
+
} catch {
|
|
58
|
+
return [];
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
return Array.isArray(value) ? value : [];
|
|
62
|
+
}, [value]);
|
|
63
|
+
const selectedOrgIds = useMemo(() => {
|
|
64
|
+
return new Set(selectedOrgs.map((org) => org.id));
|
|
65
|
+
}, [selectedOrgs]);
|
|
66
|
+
const computedGroups = useMemo(() => {
|
|
67
|
+
if (organizations.length === 0) return [];
|
|
68
|
+
const tagMap = /* @__PURE__ */ new Map();
|
|
69
|
+
const ungroupedOrgs = [];
|
|
70
|
+
organizations.forEach((org) => {
|
|
71
|
+
const tags = org.contentTags || [];
|
|
72
|
+
if (tags.length === 0) {
|
|
73
|
+
ungroupedOrgs.push({ id: org.id, name: org.displayName || org.name });
|
|
74
|
+
} else {
|
|
75
|
+
tags.forEach((tag) => {
|
|
76
|
+
if (!tagMap.has(tag)) {
|
|
77
|
+
tagMap.set(tag, []);
|
|
78
|
+
}
|
|
79
|
+
tagMap.get(tag).push({ id: org.id, name: org.displayName || org.name });
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
const groups = Array.from(tagMap.entries()).sort((a, b) => a[0].localeCompare(b[0])).map(([tag, orgs]) => ({
|
|
84
|
+
id: `tag-${tag}`,
|
|
85
|
+
name: tag,
|
|
86
|
+
description: "",
|
|
87
|
+
color: "#4945ff",
|
|
88
|
+
organizations: orgs,
|
|
89
|
+
organizationCount: orgs.length
|
|
90
|
+
}));
|
|
91
|
+
if (ungroupedOrgs.length > 0) {
|
|
92
|
+
groups.push({
|
|
93
|
+
id: "ungrouped",
|
|
94
|
+
name: "Ungrouped",
|
|
95
|
+
description: "Organizations without content tags",
|
|
96
|
+
color: "#8e8ea9",
|
|
97
|
+
organizations: ungroupedOrgs,
|
|
98
|
+
organizationCount: ungroupedOrgs.length
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
return groups;
|
|
102
|
+
}, [organizations]);
|
|
103
|
+
const fetchData = useCallback(async () => {
|
|
104
|
+
setLoading(true);
|
|
105
|
+
setFetchError(null);
|
|
106
|
+
try {
|
|
107
|
+
const orgsResponse = await get("/news-integration/organizations");
|
|
108
|
+
const orgsData = orgsResponse?.data || {};
|
|
109
|
+
const orgs = orgsData.organizations || [];
|
|
110
|
+
setOrganizations(orgs);
|
|
111
|
+
if (orgsData.error) {
|
|
112
|
+
setFetchError(orgsData.message || orgsData.error);
|
|
113
|
+
} else if (orgs.length === 0) {
|
|
114
|
+
setFetchError("No organizations found. Ensure you are logged in via Auth0 SSO.");
|
|
115
|
+
}
|
|
116
|
+
} catch (err) {
|
|
117
|
+
setFetchError(err?.message || "Failed to load data");
|
|
118
|
+
} finally {
|
|
119
|
+
setLoading(false);
|
|
120
|
+
}
|
|
121
|
+
}, [get]);
|
|
122
|
+
useEffect(() => {
|
|
123
|
+
if (isExpanded && organizations.length === 0 && !loading) {
|
|
124
|
+
fetchData();
|
|
125
|
+
}
|
|
126
|
+
}, [isExpanded, fetchData]);
|
|
127
|
+
const updateValue = (newSelection) => {
|
|
128
|
+
onChange({
|
|
129
|
+
target: {
|
|
130
|
+
name,
|
|
131
|
+
value: JSON.stringify(newSelection)
|
|
132
|
+
}
|
|
133
|
+
});
|
|
134
|
+
};
|
|
135
|
+
const handleToggle = (org) => {
|
|
136
|
+
const isSelected = selectedOrgIds.has(org.id);
|
|
137
|
+
let newSelection;
|
|
138
|
+
if (isSelected) {
|
|
139
|
+
newSelection = selectedOrgs.filter((s) => s.id !== org.id);
|
|
140
|
+
} else {
|
|
141
|
+
newSelection = [...selectedOrgs, { id: org.id, name: org.displayName || org.name }];
|
|
142
|
+
}
|
|
143
|
+
updateValue(newSelection);
|
|
144
|
+
};
|
|
145
|
+
const getGroupSelectionState = (group) => {
|
|
146
|
+
const groupOrgIds = (group.organizations || []).map((o) => o.id);
|
|
147
|
+
if (groupOrgIds.length === 0) return "none";
|
|
148
|
+
const selectedCount = groupOrgIds.filter((id) => selectedOrgIds.has(id)).length;
|
|
149
|
+
if (selectedCount === 0) return "none";
|
|
150
|
+
if (selectedCount === groupOrgIds.length) return "all";
|
|
151
|
+
return "partial";
|
|
152
|
+
};
|
|
153
|
+
const handleGroupToggle = (group) => {
|
|
154
|
+
const state = getGroupSelectionState(group);
|
|
155
|
+
const groupOrgIds = new Set((group.organizations || []).map((o) => o.id));
|
|
156
|
+
let newSelection;
|
|
157
|
+
if (state === "all") {
|
|
158
|
+
newSelection = selectedOrgs.filter((s) => !groupOrgIds.has(s.id));
|
|
159
|
+
} else {
|
|
160
|
+
const existingOrgIds = new Set(selectedOrgs.map((s) => s.id));
|
|
161
|
+
const orgsToAdd = (group.organizations || []).filter((o) => !existingOrgIds.has(o.id)).map((o) => ({ id: o.id, name: o.name }));
|
|
162
|
+
newSelection = [...selectedOrgs, ...orgsToAdd];
|
|
163
|
+
}
|
|
164
|
+
updateValue(newSelection);
|
|
165
|
+
};
|
|
166
|
+
const toggleGroupExpansion = (groupId) => {
|
|
167
|
+
setExpandedGroups((prev) => ({
|
|
168
|
+
...prev,
|
|
169
|
+
[groupId]: !prev[groupId]
|
|
170
|
+
}));
|
|
171
|
+
};
|
|
172
|
+
const handleSelectAll = () => {
|
|
173
|
+
const allOrgs = organizations.map((org) => ({
|
|
174
|
+
id: org.id,
|
|
175
|
+
name: org.displayName || org.name
|
|
176
|
+
}));
|
|
177
|
+
updateValue(allOrgs);
|
|
178
|
+
};
|
|
179
|
+
const handleClearAll = () => {
|
|
180
|
+
updateValue([]);
|
|
181
|
+
};
|
|
182
|
+
const isOrgSelected = (orgId) => selectedOrgIds.has(orgId);
|
|
183
|
+
const displayError = error && !error.includes("JSON") ? error : false;
|
|
184
|
+
return /* @__PURE__ */ jsx(Field.Root, { name, error: displayError, required, children: /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: [
|
|
185
|
+
/* @__PURE__ */ jsx(Field.Label, { action: labelAction, children: intlLabel?.defaultMessage || "Organizations" }),
|
|
186
|
+
/* @__PURE__ */ jsxs(
|
|
187
|
+
Box,
|
|
188
|
+
{
|
|
189
|
+
padding: 4,
|
|
190
|
+
background: "neutral0",
|
|
191
|
+
borderColor: error ? "danger600" : "neutral200",
|
|
192
|
+
hasRadius: true,
|
|
193
|
+
style: { border: "1px solid" },
|
|
194
|
+
children: [
|
|
195
|
+
/* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", marginBottom: 3, children: [
|
|
196
|
+
/* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
|
|
197
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "18px" }, children: "🏢" }),
|
|
198
|
+
/* @__PURE__ */ jsxs(
|
|
199
|
+
Typography,
|
|
200
|
+
{
|
|
201
|
+
variant: "omega",
|
|
202
|
+
fontWeight: "bold",
|
|
203
|
+
textColor: selectedOrgs.length > 0 ? "success600" : "neutral600",
|
|
204
|
+
children: [
|
|
205
|
+
selectedOrgs.length,
|
|
206
|
+
" organization",
|
|
207
|
+
selectedOrgs.length !== 1 ? "s" : "",
|
|
208
|
+
" selected"
|
|
209
|
+
]
|
|
210
|
+
}
|
|
211
|
+
)
|
|
212
|
+
] }),
|
|
213
|
+
/* @__PURE__ */ jsx(
|
|
214
|
+
Button,
|
|
215
|
+
{
|
|
216
|
+
variant: "ghost",
|
|
217
|
+
size: "S",
|
|
218
|
+
onClick: () => setIsExpanded(!isExpanded),
|
|
219
|
+
children: isExpanded ? "▲ Collapse" : "▼ Expand"
|
|
220
|
+
}
|
|
221
|
+
)
|
|
222
|
+
] }),
|
|
223
|
+
selectedOrgs.length > 0 && /* @__PURE__ */ jsx(Box, { marginBottom: 3, children: /* @__PURE__ */ jsx(Flex, { wrap: "wrap", gap: 1, children: selectedOrgs.map((org) => /* @__PURE__ */ jsx(
|
|
224
|
+
Box,
|
|
225
|
+
{
|
|
226
|
+
padding: 1,
|
|
227
|
+
paddingLeft: 2,
|
|
228
|
+
paddingRight: 2,
|
|
229
|
+
background: "primary100",
|
|
230
|
+
hasRadius: true,
|
|
231
|
+
children: /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "primary700", children: org.name })
|
|
232
|
+
},
|
|
233
|
+
org.id
|
|
234
|
+
)) }) }),
|
|
235
|
+
isExpanded && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
236
|
+
loading && /* @__PURE__ */ jsxs(Flex, { justifyContent: "center", padding: 4, children: [
|
|
237
|
+
/* @__PURE__ */ jsx(Loader, { small: true }),
|
|
238
|
+
/* @__PURE__ */ jsx(Box, { paddingLeft: 2, children: /* @__PURE__ */ jsx(Typography, { variant: "pi", children: "Loading organizations..." }) })
|
|
239
|
+
] }),
|
|
240
|
+
fetchError && /* @__PURE__ */ jsxs(Box, { marginBottom: 3, children: [
|
|
241
|
+
/* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "danger600", children: fetchError }),
|
|
242
|
+
/* @__PURE__ */ jsx(Box, { paddingTop: 2, children: /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "S", onClick: fetchData, children: "Retry" }) })
|
|
243
|
+
] }),
|
|
244
|
+
!loading && !fetchError && organizations.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
245
|
+
/* @__PURE__ */ jsxs(Flex, { gap: 2, marginBottom: 3, children: [
|
|
246
|
+
/* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "S", onClick: handleSelectAll, disabled, children: "Select All" }),
|
|
247
|
+
/* @__PURE__ */ jsx(Button, { variant: "tertiary", size: "S", onClick: handleClearAll, disabled, children: "Clear All" })
|
|
248
|
+
] }),
|
|
249
|
+
computedGroups.length > 0 && /* @__PURE__ */ jsxs(Box, { marginBottom: 4, children: [
|
|
250
|
+
/* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, marginBottom: 2, children: [
|
|
251
|
+
/* @__PURE__ */ jsx("span", { style: { fontSize: "16px" }, children: "🏷️" }),
|
|
252
|
+
/* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: "CONTENT TAG GROUPS" })
|
|
253
|
+
] }),
|
|
254
|
+
/* @__PURE__ */ jsx(Box, { style: { maxHeight: "300px", overflowY: "auto" }, children: /* @__PURE__ */ jsx(Flex, { direction: "column", gap: 2, children: computedGroups.map((group) => {
|
|
255
|
+
const selectionState = getGroupSelectionState(group);
|
|
256
|
+
const isGroupExpanded = expandedGroups[group.id];
|
|
257
|
+
const orgCount = (group.organizations || []).length;
|
|
258
|
+
const selectedInGroup = (group.organizations || []).filter((o) => selectedOrgIds.has(o.id)).length;
|
|
259
|
+
return /* @__PURE__ */ jsxs(
|
|
260
|
+
Box,
|
|
261
|
+
{
|
|
262
|
+
padding: 2,
|
|
263
|
+
paddingLeft: 3,
|
|
264
|
+
paddingRight: 3,
|
|
265
|
+
background: selectionState !== "none" ? "primary100" : "neutral100",
|
|
266
|
+
hasRadius: true,
|
|
267
|
+
width: "100%",
|
|
268
|
+
style: {
|
|
269
|
+
border: selectionState === "all" ? "2px solid #4945ff" : selectionState === "partial" ? "2px dashed #4945ff" : "2px solid transparent",
|
|
270
|
+
cursor: disabled ? "not-allowed" : "pointer"
|
|
271
|
+
},
|
|
272
|
+
onClick: () => !disabled && handleGroupToggle(group),
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", alignItems: "center", children: [
|
|
275
|
+
/* @__PURE__ */ jsxs(
|
|
276
|
+
Flex,
|
|
277
|
+
{
|
|
278
|
+
alignItems: "center",
|
|
279
|
+
gap: 2,
|
|
280
|
+
children: [
|
|
281
|
+
/* @__PURE__ */ jsx(
|
|
282
|
+
Checkbox,
|
|
283
|
+
{
|
|
284
|
+
checked: selectionState === "all",
|
|
285
|
+
indeterminate: selectionState === "partial",
|
|
286
|
+
disabled
|
|
287
|
+
}
|
|
288
|
+
),
|
|
289
|
+
/* @__PURE__ */ jsxs(Box, { children: [
|
|
290
|
+
/* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, children: [
|
|
291
|
+
/* @__PURE__ */ jsx(
|
|
292
|
+
Box,
|
|
293
|
+
{
|
|
294
|
+
style: {
|
|
295
|
+
width: "12px",
|
|
296
|
+
height: "12px",
|
|
297
|
+
borderRadius: "3px",
|
|
298
|
+
background: group.color || "#4945ff"
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
),
|
|
302
|
+
/* @__PURE__ */ jsx(
|
|
303
|
+
Typography,
|
|
304
|
+
{
|
|
305
|
+
variant: "omega",
|
|
306
|
+
fontWeight: "semiBold",
|
|
307
|
+
textColor: selectionState !== "none" ? "primary700" : "neutral800",
|
|
308
|
+
children: group.name
|
|
309
|
+
}
|
|
310
|
+
)
|
|
311
|
+
] }),
|
|
312
|
+
/* @__PURE__ */ jsxs(Typography, { variant: "pi", textColor: "neutral500", children: [
|
|
313
|
+
selectedInGroup,
|
|
314
|
+
"/",
|
|
315
|
+
orgCount,
|
|
316
|
+
" organizations",
|
|
317
|
+
group.description && ` • ${group.description}`
|
|
318
|
+
] })
|
|
319
|
+
] })
|
|
320
|
+
]
|
|
321
|
+
}
|
|
322
|
+
),
|
|
323
|
+
/* @__PURE__ */ jsx(
|
|
324
|
+
Button,
|
|
325
|
+
{
|
|
326
|
+
variant: "ghost",
|
|
327
|
+
size: "S",
|
|
328
|
+
onClick: (e) => {
|
|
329
|
+
e.stopPropagation();
|
|
330
|
+
toggleGroupExpansion(group.id);
|
|
331
|
+
},
|
|
332
|
+
children: isGroupExpanded ? "▲" : "▼"
|
|
333
|
+
}
|
|
334
|
+
)
|
|
335
|
+
] }),
|
|
336
|
+
isGroupExpanded && /* @__PURE__ */ jsx(Box, { paddingLeft: 4, paddingTop: 2, children: /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: (group.organizations || []).map((org) => {
|
|
337
|
+
const isSelected = isOrgSelected(org.id);
|
|
338
|
+
return /* @__PURE__ */ jsx(
|
|
339
|
+
Box,
|
|
340
|
+
{
|
|
341
|
+
padding: 1,
|
|
342
|
+
paddingLeft: 2,
|
|
343
|
+
paddingRight: 2,
|
|
344
|
+
background: isSelected ? "primary100" : "neutral100",
|
|
345
|
+
hasRadius: true,
|
|
346
|
+
style: {
|
|
347
|
+
cursor: disabled ? "not-allowed" : "pointer",
|
|
348
|
+
border: isSelected ? "1px solid #4945ff" : "1px solid transparent",
|
|
349
|
+
opacity: disabled ? 0.5 : 1
|
|
350
|
+
},
|
|
351
|
+
onClick: () => !disabled && handleToggle(org),
|
|
352
|
+
children: /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, children: [
|
|
353
|
+
/* @__PURE__ */ jsx(
|
|
354
|
+
Checkbox,
|
|
355
|
+
{
|
|
356
|
+
checked: isSelected,
|
|
357
|
+
disabled
|
|
358
|
+
}
|
|
359
|
+
),
|
|
360
|
+
/* @__PURE__ */ jsx(
|
|
361
|
+
Typography,
|
|
362
|
+
{
|
|
363
|
+
variant: "pi",
|
|
364
|
+
textColor: isSelected ? "primary700" : "neutral600",
|
|
365
|
+
children: org.name
|
|
366
|
+
}
|
|
367
|
+
)
|
|
368
|
+
] })
|
|
369
|
+
},
|
|
370
|
+
org.id
|
|
371
|
+
);
|
|
372
|
+
}) }) })
|
|
373
|
+
]
|
|
374
|
+
},
|
|
375
|
+
group.id
|
|
376
|
+
);
|
|
377
|
+
}) }) })
|
|
378
|
+
] })
|
|
379
|
+
] }),
|
|
380
|
+
!loading && !fetchError && organizations.length === 0 && /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: fetchData, children: "Load Organizations" })
|
|
381
|
+
] })
|
|
382
|
+
]
|
|
383
|
+
}
|
|
384
|
+
),
|
|
385
|
+
/* @__PURE__ */ jsx(Field.Hint, {}),
|
|
386
|
+
/* @__PURE__ */ jsx(Field.Error, {})
|
|
387
|
+
] }) });
|
|
388
|
+
};
|
|
389
|
+
const OrganizationIcon = () => {
|
|
390
|
+
return createElement("span", { role: "img", "aria-label": "organizations" }, "🏢");
|
|
391
|
+
};
|
|
392
|
+
const index = {
|
|
393
|
+
register(app) {
|
|
394
|
+
app.customFields.register({
|
|
395
|
+
name: "organization-picker",
|
|
396
|
+
pluginId: PLUGIN_ID,
|
|
397
|
+
type: "text",
|
|
398
|
+
intlLabel: {
|
|
399
|
+
id: `${PLUGIN_ID}.organization-picker.label`,
|
|
400
|
+
defaultMessage: "Organization Picker"
|
|
401
|
+
},
|
|
402
|
+
intlDescription: {
|
|
403
|
+
id: `${PLUGIN_ID}.organization-picker.description`,
|
|
404
|
+
defaultMessage: "Select organizations for multi-tenant content"
|
|
405
|
+
},
|
|
406
|
+
icon: OrganizationIcon,
|
|
407
|
+
components: {
|
|
408
|
+
Input: async () => ({ default: OrganizationPickerInput })
|
|
409
|
+
},
|
|
410
|
+
options: {
|
|
411
|
+
base: [],
|
|
412
|
+
advanced: [
|
|
413
|
+
{
|
|
414
|
+
sectionTitle: {
|
|
415
|
+
id: "global.settings",
|
|
416
|
+
defaultMessage: "Settings"
|
|
417
|
+
},
|
|
418
|
+
items: [
|
|
419
|
+
{
|
|
420
|
+
name: "required",
|
|
421
|
+
type: "checkbox",
|
|
422
|
+
intlLabel: {
|
|
423
|
+
id: `${PLUGIN_ID}.organization-picker.required`,
|
|
424
|
+
defaultMessage: "Required field"
|
|
425
|
+
},
|
|
426
|
+
description: {
|
|
427
|
+
id: `${PLUGIN_ID}.organization-picker.required.description`,
|
|
428
|
+
defaultMessage: "You won't be able to create an entry if this field is empty"
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
]
|
|
432
|
+
}
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
});
|
|
436
|
+
app.registerPlugin({
|
|
437
|
+
id: PLUGIN_ID,
|
|
438
|
+
initializer: Initializer,
|
|
439
|
+
isReady: false,
|
|
440
|
+
name: PLUGIN_ID
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
};
|
|
444
|
+
export {
|
|
445
|
+
index as default
|
|
446
|
+
};
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
const https = require("https");
|
|
3
|
+
const http = require("http");
|
|
4
|
+
const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
|
|
5
|
+
const https__default = /* @__PURE__ */ _interopDefault(https);
|
|
6
|
+
const http__default = /* @__PURE__ */ _interopDefault(http);
|
|
7
|
+
const register = ({ strapi: strapi2 }) => {
|
|
8
|
+
strapi2.customFields.register({
|
|
9
|
+
name: "organization-picker",
|
|
10
|
+
plugin: "news-integration",
|
|
11
|
+
type: "text",
|
|
12
|
+
inputSize: {
|
|
13
|
+
default: 12,
|
|
14
|
+
isResizable: false
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
};
|
|
18
|
+
const config = {
|
|
19
|
+
default: {
|
|
20
|
+
apiBase: process.env.STRAPI_ADMIN_API_BASE || "https://localhost:4200"
|
|
21
|
+
},
|
|
22
|
+
validator() {
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
function makeRequest(url, options = {}) {
|
|
26
|
+
return new Promise((resolve, reject) => {
|
|
27
|
+
const parsedUrl = new URL(url);
|
|
28
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
29
|
+
const client = isHttps ? https__default.default : http__default.default;
|
|
30
|
+
const requestOptions = {
|
|
31
|
+
hostname: parsedUrl.hostname,
|
|
32
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
33
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
34
|
+
method: options.method || "GET",
|
|
35
|
+
headers: options.headers || {},
|
|
36
|
+
// Ignore SSL certificate errors for localhost in development
|
|
37
|
+
rejectUnauthorized: !(parsedUrl.hostname === "localhost" || parsedUrl.hostname === "127.0.0.1")
|
|
38
|
+
};
|
|
39
|
+
const req = client.request(requestOptions, (res) => {
|
|
40
|
+
let data = "";
|
|
41
|
+
res.on("data", (chunk) => {
|
|
42
|
+
data += chunk;
|
|
43
|
+
});
|
|
44
|
+
res.on("end", () => {
|
|
45
|
+
resolve({
|
|
46
|
+
ok: Boolean(res.statusCode && res.statusCode >= 200 && res.statusCode < 300),
|
|
47
|
+
status: res.statusCode || 500,
|
|
48
|
+
statusText: res.statusMessage || "Unknown error",
|
|
49
|
+
text: () => Promise.resolve(data),
|
|
50
|
+
json: () => Promise.resolve(JSON.parse(data))
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
req.on("error", (error) => {
|
|
55
|
+
reject(error);
|
|
56
|
+
});
|
|
57
|
+
if (options.body) {
|
|
58
|
+
req.write(options.body);
|
|
59
|
+
}
|
|
60
|
+
req.end();
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
const TOKEN_TTL_MS = 8 * 60 * 60 * 1e3;
|
|
64
|
+
function getAuth0Context(ctx) {
|
|
65
|
+
const adminUser = ctx.state?.user;
|
|
66
|
+
const email = adminUser?.email;
|
|
67
|
+
if (!email) {
|
|
68
|
+
return { idToken: null, accessToken: null, email: null };
|
|
69
|
+
}
|
|
70
|
+
let idToken = null;
|
|
71
|
+
let accessToken = null;
|
|
72
|
+
const store = strapi.auth0TokenStore;
|
|
73
|
+
if (store) {
|
|
74
|
+
const entry = store.get(email.toLowerCase());
|
|
75
|
+
if (entry && Date.now() - entry.timestamp < TOKEN_TTL_MS) {
|
|
76
|
+
idToken = entry.idToken;
|
|
77
|
+
accessToken = entry.accessToken;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return { idToken, accessToken, email };
|
|
81
|
+
}
|
|
82
|
+
const newsIntegration = {
|
|
83
|
+
/**
|
|
84
|
+
* Get plugin configuration (for admin UI status display)
|
|
85
|
+
*/
|
|
86
|
+
getConfig(ctx) {
|
|
87
|
+
const config2 = strapi.config.get("plugin::news-integration");
|
|
88
|
+
ctx.body = {
|
|
89
|
+
apiBase: config2?.apiBase || ""
|
|
90
|
+
};
|
|
91
|
+
},
|
|
92
|
+
/**
|
|
93
|
+
* Fetch user's organizations from the ComplAi API.
|
|
94
|
+
*
|
|
95
|
+
* Uses the Auth0 access token (preferred) or ID token from the SSO session.
|
|
96
|
+
* The access token is a JWT when the Grant config includes an audience,
|
|
97
|
+
* which is required for api-auth0 to accept the request.
|
|
98
|
+
*/
|
|
99
|
+
async getOrganizations(ctx) {
|
|
100
|
+
const config2 = strapi.config.get("plugin::news-integration") || {};
|
|
101
|
+
const auth0Context = getAuth0Context(ctx);
|
|
102
|
+
const { accessToken, idToken, email: userEmail } = auth0Context;
|
|
103
|
+
const bearerToken = accessToken || idToken;
|
|
104
|
+
if (!bearerToken) {
|
|
105
|
+
strapi.log.warn("news-integration: No Auth0 token — ensure you are logged in via Auth0 SSO");
|
|
106
|
+
ctx.body = {
|
|
107
|
+
organizations: [],
|
|
108
|
+
error: "No Auth0 token available",
|
|
109
|
+
message: "Please ensure you are logged in via Auth0 SSO. Try logging out and back in."
|
|
110
|
+
};
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
const apiBase = config2.apiBase || "https://localhost:4200";
|
|
115
|
+
const profileEndpoint = `${apiBase}/api/v2/me`;
|
|
116
|
+
strapi.log.debug(`news-integration: Fetching organizations for ${userEmail} (using ${accessToken ? "access_token" : "id_token"})`);
|
|
117
|
+
const response = await makeRequest(profileEndpoint, {
|
|
118
|
+
method: "GET",
|
|
119
|
+
headers: {
|
|
120
|
+
"Content-Type": "application/json",
|
|
121
|
+
"Authorization": `Bearer ${bearerToken}`
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
if (!response.ok) {
|
|
125
|
+
const errorText = await response.text();
|
|
126
|
+
strapi.log.error(`news-integration: API error ${response.status}: ${errorText.substring(0, 200)}`);
|
|
127
|
+
ctx.body = {
|
|
128
|
+
organizations: [],
|
|
129
|
+
error: "Failed to fetch user profile",
|
|
130
|
+
message: `API returned ${response.status}: ${response.statusText}`
|
|
131
|
+
};
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
const data = await response.json();
|
|
135
|
+
const organizations = (data.organizations || []).map((org) => ({
|
|
136
|
+
id: org.id,
|
|
137
|
+
name: org.displayName || org.name || org.id,
|
|
138
|
+
displayName: org.displayName || org.name || org.id,
|
|
139
|
+
plan: org.plan || null,
|
|
140
|
+
role: org.role || null,
|
|
141
|
+
contentTags: Array.isArray(org.contentTags) ? org.contentTags : []
|
|
142
|
+
}));
|
|
143
|
+
strapi.log.debug(`news-integration: Found ${organizations.length} organizations for ${userEmail}`);
|
|
144
|
+
ctx.body = {
|
|
145
|
+
organizations,
|
|
146
|
+
userEmail: data.email || userEmail,
|
|
147
|
+
userId: data.userId || data.sub
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
strapi.log.error(`news-integration: Error fetching organizations: ${error.message}`);
|
|
151
|
+
ctx.body = {
|
|
152
|
+
organizations: [],
|
|
153
|
+
error: "Internal error",
|
|
154
|
+
message: error.message
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
};
|
|
159
|
+
const controllers = {
|
|
160
|
+
newsIntegration
|
|
161
|
+
};
|
|
162
|
+
const routes = [
|
|
163
|
+
{
|
|
164
|
+
method: "GET",
|
|
165
|
+
path: "/config",
|
|
166
|
+
handler: "newsIntegration.getConfig",
|
|
167
|
+
config: {
|
|
168
|
+
policies: [],
|
|
169
|
+
auth: false
|
|
170
|
+
}
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
method: "GET",
|
|
174
|
+
path: "/organizations",
|
|
175
|
+
handler: "newsIntegration.getOrganizations",
|
|
176
|
+
config: {
|
|
177
|
+
policies: [],
|
|
178
|
+
// Require Strapi admin authentication
|
|
179
|
+
auth: {
|
|
180
|
+
strategy: "admin"
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
];
|
|
185
|
+
const index = {
|
|
186
|
+
register,
|
|
187
|
+
config,
|
|
188
|
+
controllers,
|
|
189
|
+
routes
|
|
190
|
+
};
|
|
191
|
+
module.exports = index;
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
import https from "https";
|
|
2
|
+
import http from "http";
|
|
3
|
+
const register = ({ strapi: strapi2 }) => {
|
|
4
|
+
strapi2.customFields.register({
|
|
5
|
+
name: "organization-picker",
|
|
6
|
+
plugin: "news-integration",
|
|
7
|
+
type: "text",
|
|
8
|
+
inputSize: {
|
|
9
|
+
default: 12,
|
|
10
|
+
isResizable: false
|
|
11
|
+
}
|
|
12
|
+
});
|
|
13
|
+
};
|
|
14
|
+
const config = {
|
|
15
|
+
default: {
|
|
16
|
+
apiBase: process.env.STRAPI_ADMIN_API_BASE || "https://localhost:4200"
|
|
17
|
+
},
|
|
18
|
+
validator() {
|
|
19
|
+
}
|
|
20
|
+
};
|
|
21
|
+
function makeRequest(url, options = {}) {
|
|
22
|
+
return new Promise((resolve, reject) => {
|
|
23
|
+
const parsedUrl = new URL(url);
|
|
24
|
+
const isHttps = parsedUrl.protocol === "https:";
|
|
25
|
+
const client = isHttps ? https : http;
|
|
26
|
+
const requestOptions = {
|
|
27
|
+
hostname: parsedUrl.hostname,
|
|
28
|
+
port: parsedUrl.port || (isHttps ? 443 : 80),
|
|
29
|
+
path: parsedUrl.pathname + parsedUrl.search,
|
|
30
|
+
method: options.method || "GET",
|
|
31
|
+
headers: options.headers || {},
|
|
32
|
+
// Ignore SSL certificate errors for localhost in development
|
|
33
|
+
rejectUnauthorized: !(parsedUrl.hostname === "localhost" || parsedUrl.hostname === "127.0.0.1")
|
|
34
|
+
};
|
|
35
|
+
const req = client.request(requestOptions, (res) => {
|
|
36
|
+
let data = "";
|
|
37
|
+
res.on("data", (chunk) => {
|
|
38
|
+
data += chunk;
|
|
39
|
+
});
|
|
40
|
+
res.on("end", () => {
|
|
41
|
+
resolve({
|
|
42
|
+
ok: Boolean(res.statusCode && res.statusCode >= 200 && res.statusCode < 300),
|
|
43
|
+
status: res.statusCode || 500,
|
|
44
|
+
statusText: res.statusMessage || "Unknown error",
|
|
45
|
+
text: () => Promise.resolve(data),
|
|
46
|
+
json: () => Promise.resolve(JSON.parse(data))
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
});
|
|
50
|
+
req.on("error", (error) => {
|
|
51
|
+
reject(error);
|
|
52
|
+
});
|
|
53
|
+
if (options.body) {
|
|
54
|
+
req.write(options.body);
|
|
55
|
+
}
|
|
56
|
+
req.end();
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const TOKEN_TTL_MS = 8 * 60 * 60 * 1e3;
|
|
60
|
+
function getAuth0Context(ctx) {
|
|
61
|
+
const adminUser = ctx.state?.user;
|
|
62
|
+
const email = adminUser?.email;
|
|
63
|
+
if (!email) {
|
|
64
|
+
return { idToken: null, accessToken: null, email: null };
|
|
65
|
+
}
|
|
66
|
+
let idToken = null;
|
|
67
|
+
let accessToken = null;
|
|
68
|
+
const store = strapi.auth0TokenStore;
|
|
69
|
+
if (store) {
|
|
70
|
+
const entry = store.get(email.toLowerCase());
|
|
71
|
+
if (entry && Date.now() - entry.timestamp < TOKEN_TTL_MS) {
|
|
72
|
+
idToken = entry.idToken;
|
|
73
|
+
accessToken = entry.accessToken;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
return { idToken, accessToken, email };
|
|
77
|
+
}
|
|
78
|
+
const newsIntegration = {
|
|
79
|
+
/**
|
|
80
|
+
* Get plugin configuration (for admin UI status display)
|
|
81
|
+
*/
|
|
82
|
+
getConfig(ctx) {
|
|
83
|
+
const config2 = strapi.config.get("plugin::news-integration");
|
|
84
|
+
ctx.body = {
|
|
85
|
+
apiBase: config2?.apiBase || ""
|
|
86
|
+
};
|
|
87
|
+
},
|
|
88
|
+
/**
|
|
89
|
+
* Fetch user's organizations from the ComplAi API.
|
|
90
|
+
*
|
|
91
|
+
* Uses the Auth0 access token (preferred) or ID token from the SSO session.
|
|
92
|
+
* The access token is a JWT when the Grant config includes an audience,
|
|
93
|
+
* which is required for api-auth0 to accept the request.
|
|
94
|
+
*/
|
|
95
|
+
async getOrganizations(ctx) {
|
|
96
|
+
const config2 = strapi.config.get("plugin::news-integration") || {};
|
|
97
|
+
const auth0Context = getAuth0Context(ctx);
|
|
98
|
+
const { accessToken, idToken, email: userEmail } = auth0Context;
|
|
99
|
+
const bearerToken = accessToken || idToken;
|
|
100
|
+
if (!bearerToken) {
|
|
101
|
+
strapi.log.warn("news-integration: No Auth0 token — ensure you are logged in via Auth0 SSO");
|
|
102
|
+
ctx.body = {
|
|
103
|
+
organizations: [],
|
|
104
|
+
error: "No Auth0 token available",
|
|
105
|
+
message: "Please ensure you are logged in via Auth0 SSO. Try logging out and back in."
|
|
106
|
+
};
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
const apiBase = config2.apiBase || "https://localhost:4200";
|
|
111
|
+
const profileEndpoint = `${apiBase}/api/v2/me`;
|
|
112
|
+
strapi.log.debug(`news-integration: Fetching organizations for ${userEmail} (using ${accessToken ? "access_token" : "id_token"})`);
|
|
113
|
+
const response = await makeRequest(profileEndpoint, {
|
|
114
|
+
method: "GET",
|
|
115
|
+
headers: {
|
|
116
|
+
"Content-Type": "application/json",
|
|
117
|
+
"Authorization": `Bearer ${bearerToken}`
|
|
118
|
+
}
|
|
119
|
+
});
|
|
120
|
+
if (!response.ok) {
|
|
121
|
+
const errorText = await response.text();
|
|
122
|
+
strapi.log.error(`news-integration: API error ${response.status}: ${errorText.substring(0, 200)}`);
|
|
123
|
+
ctx.body = {
|
|
124
|
+
organizations: [],
|
|
125
|
+
error: "Failed to fetch user profile",
|
|
126
|
+
message: `API returned ${response.status}: ${response.statusText}`
|
|
127
|
+
};
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
const data = await response.json();
|
|
131
|
+
const organizations = (data.organizations || []).map((org) => ({
|
|
132
|
+
id: org.id,
|
|
133
|
+
name: org.displayName || org.name || org.id,
|
|
134
|
+
displayName: org.displayName || org.name || org.id,
|
|
135
|
+
plan: org.plan || null,
|
|
136
|
+
role: org.role || null,
|
|
137
|
+
contentTags: Array.isArray(org.contentTags) ? org.contentTags : []
|
|
138
|
+
}));
|
|
139
|
+
strapi.log.debug(`news-integration: Found ${organizations.length} organizations for ${userEmail}`);
|
|
140
|
+
ctx.body = {
|
|
141
|
+
organizations,
|
|
142
|
+
userEmail: data.email || userEmail,
|
|
143
|
+
userId: data.userId || data.sub
|
|
144
|
+
};
|
|
145
|
+
} catch (error) {
|
|
146
|
+
strapi.log.error(`news-integration: Error fetching organizations: ${error.message}`);
|
|
147
|
+
ctx.body = {
|
|
148
|
+
organizations: [],
|
|
149
|
+
error: "Internal error",
|
|
150
|
+
message: error.message
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
const controllers = {
|
|
156
|
+
newsIntegration
|
|
157
|
+
};
|
|
158
|
+
const routes = [
|
|
159
|
+
{
|
|
160
|
+
method: "GET",
|
|
161
|
+
path: "/config",
|
|
162
|
+
handler: "newsIntegration.getConfig",
|
|
163
|
+
config: {
|
|
164
|
+
policies: [],
|
|
165
|
+
auth: false
|
|
166
|
+
}
|
|
167
|
+
},
|
|
168
|
+
{
|
|
169
|
+
method: "GET",
|
|
170
|
+
path: "/organizations",
|
|
171
|
+
handler: "newsIntegration.getOrganizations",
|
|
172
|
+
config: {
|
|
173
|
+
policies: [],
|
|
174
|
+
// Require Strapi admin authentication
|
|
175
|
+
auth: {
|
|
176
|
+
strategy: "admin"
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
];
|
|
181
|
+
const index = {
|
|
182
|
+
register,
|
|
183
|
+
config,
|
|
184
|
+
controllers,
|
|
185
|
+
routes
|
|
186
|
+
};
|
|
187
|
+
export {
|
|
188
|
+
index as default
|
|
189
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
{
|
|
2
|
+
"version": "1.0.0",
|
|
3
|
+
"keywords": [],
|
|
4
|
+
"type": "commonjs",
|
|
5
|
+
"exports": {
|
|
6
|
+
"./package.json": "./package.json",
|
|
7
|
+
"./strapi-admin": {
|
|
8
|
+
"types": "./dist/admin/src/index.d.ts",
|
|
9
|
+
"source": "./admin/src/index.ts",
|
|
10
|
+
"import": "./dist/admin/index.mjs",
|
|
11
|
+
"require": "./dist/admin/index.js",
|
|
12
|
+
"default": "./dist/admin/index.js"
|
|
13
|
+
},
|
|
14
|
+
"./strapi-server": {
|
|
15
|
+
"types": "./dist/server/src/index.d.ts",
|
|
16
|
+
"source": "./server/src/index.ts",
|
|
17
|
+
"import": "./dist/server/index.mjs",
|
|
18
|
+
"require": "./dist/server/index.js",
|
|
19
|
+
"default": "./dist/server/index.js"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"files": [
|
|
23
|
+
"dist"
|
|
24
|
+
],
|
|
25
|
+
"scripts": {
|
|
26
|
+
"build": "strapi-plugin build",
|
|
27
|
+
"watch": "strapi-plugin watch",
|
|
28
|
+
"watch:link": "strapi-plugin watch:link",
|
|
29
|
+
"verify": "strapi-plugin verify",
|
|
30
|
+
"test:ts:front": "run -T tsc -p admin/tsconfig.json",
|
|
31
|
+
"test:ts:back": "run -T tsc -p server/tsconfig.json"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@strapi/design-system": "^2.0.0-rc.30",
|
|
36
|
+
"@strapi/icons": "^2.0.0-rc.30",
|
|
37
|
+
"@strapi/sdk-plugin": "^5.4.0",
|
|
38
|
+
"@strapi/strapi": "^5.36.1",
|
|
39
|
+
"@strapi/typescript-utils": "^5.36.1",
|
|
40
|
+
"@types/node": "^22.10.2",
|
|
41
|
+
"@types/react": "^19.2.14",
|
|
42
|
+
"@types/react-dom": "^19.2.3",
|
|
43
|
+
"prettier": "^3.8.1",
|
|
44
|
+
"react": "^18.3.1",
|
|
45
|
+
"react-dom": "^18.3.1",
|
|
46
|
+
"react-router-dom": "^6.30.3",
|
|
47
|
+
"styled-components": "^6.3.11",
|
|
48
|
+
"typescript": "^5.9.3"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@strapi/design-system": "^2.0.0-rc.30",
|
|
52
|
+
"@strapi/icons": "^2.0.0-rc.30",
|
|
53
|
+
"@strapi/sdk-plugin": "^5.4.0",
|
|
54
|
+
"@strapi/strapi": "^5.36.1",
|
|
55
|
+
"react": "^18.3.1",
|
|
56
|
+
"react-dom": "^18.3.1",
|
|
57
|
+
"react-router-dom": "^6.30.3",
|
|
58
|
+
"styled-components": "^6.3.11"
|
|
59
|
+
},
|
|
60
|
+
"strapi": {
|
|
61
|
+
"kind": "plugin",
|
|
62
|
+
"name": "news-integration",
|
|
63
|
+
"displayName": "Complai API integration",
|
|
64
|
+
"description": "Integration plugin for Auth0 token acquisition and company API calls"
|
|
65
|
+
},
|
|
66
|
+
"name": "@complai/news-integration",
|
|
67
|
+
"description": "Integration plugin for Auth0 token acquisition and company API calls"
|
|
68
|
+
}
|