@permitio/permit-strapi 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.
@@ -0,0 +1,1029 @@
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 admin = require("@strapi/strapi/admin");
6
+ const reactRouterDom = require("react-router-dom");
7
+ const designSystem = require("@strapi/design-system");
8
+ const icons = require("@strapi/icons");
9
+ const styled = require("styled-components");
10
+ const index = require("./index-C2zppPZ6.js");
11
+ const _interopDefault = (e) => e && e.__esModule ? e : { default: e };
12
+ const styled__default = /* @__PURE__ */ _interopDefault(styled);
13
+ const Logo$1 = styled__default.default.img`
14
+ width: 40px;
15
+ height: 40px;
16
+ border-radius: 6px;
17
+ `;
18
+ const SegButton = styled__default.default.button`
19
+ padding: 6px 16px;
20
+ font-size: 11px;
21
+ font-weight: 700;
22
+ letter-spacing: 0.05em;
23
+ text-transform: uppercase;
24
+ border: none;
25
+ cursor: pointer;
26
+ transition:
27
+ background 0.15s,
28
+ color 0.15s;
29
+ background: ${({ $active, $variant }) => $active ? $variant === "success" ? "#c6f0c2" : "#fce4e4" : "transparent"};
30
+ color: ${({ $active, $variant }) => $active ? $variant === "success" ? "#2f6846" : "#b72b1a" : "#8e8ea9"};
31
+ &:not(:last-child) {
32
+ border-right: 1px solid #dcdce4;
33
+ }
34
+ &:hover {
35
+ background: ${({ $active, $variant }) => $active ? $variant === "success" ? "#c6f0c2" : "#fce4e4" : "#f6f6f9"};
36
+ }
37
+ `;
38
+ const TextLink = styled__default.default.a`
39
+ color: inherit;
40
+ text-decoration: underline;
41
+ &:hover {
42
+ opacity: 0.8;
43
+ }
44
+ `;
45
+ const DEFAULT_PDP_URL$1 = "https://cloudpdp.api.permit.io";
46
+ const HomePage = ({ onDisconnect }) => {
47
+ const [config, setConfig] = react.useState(null);
48
+ const [contentTypes, setContentTypes] = react.useState([]);
49
+ const [protectedTypes, setProtectedTypes] = react.useState({});
50
+ const [savingResources, setSavingResources] = react.useState(false);
51
+ const [syncingUsers, setSyncingUsers] = react.useState(false);
52
+ const [syncResult, setSyncResult] = react.useState(null);
53
+ const [updateApiKey, setUpdateApiKey] = react.useState("");
54
+ const [updatePdpUrl, setUpdatePdpUrl] = react.useState("");
55
+ const [updateLoading, setUpdateLoading] = react.useState(false);
56
+ const [disconnectLoading, setDisconnectLoading] = react.useState(false);
57
+ const [disconnectConfirmed, setDisconnectConfirmed] = react.useState(false);
58
+ const [rebacConfig, setRebacConfig] = react.useState({});
59
+ const [instanceRoles, setInstanceRoles] = react.useState({});
60
+ const [savingRebac, setSavingRebac] = react.useState(false);
61
+ const [savingInstanceRoles, setSavingInstanceRoles] = react.useState({});
62
+ const [syncingInstances, setSyncingInstances] = react.useState({});
63
+ const [instanceSyncResults, setInstanceSyncResults] = react.useState({});
64
+ const [userFields, setUserFields] = react.useState([]);
65
+ const [userAttrMappings, setUserAttrMappings] = react.useState([]);
66
+ const [savingUserAttrs, setSavingUserAttrs] = react.useState(false);
67
+ const [resourceFieldsMap, setResourceFieldsMap] = react.useState({});
68
+ const [resourceAttrMappings, setResourceAttrMappings] = react.useState({});
69
+ const [savingResourceAttrs, setSavingResourceAttrs] = react.useState(false);
70
+ const { get, post, del } = admin.useFetchClient();
71
+ const { toggleNotification } = admin.useNotification();
72
+ react.useEffect(() => {
73
+ fetchConfig();
74
+ fetchContentTypes();
75
+ fetchAttributeMappings();
76
+ fetchRebacConfig();
77
+ }, []);
78
+ const fetchConfig = async () => {
79
+ try {
80
+ const { data } = await get("/permit-strapi/config");
81
+ setConfig(data);
82
+ setUpdatePdpUrl(data.pdpUrl);
83
+ } catch {
84
+ toggleNotification({ type: "danger", message: "Failed to load configuration" });
85
+ }
86
+ };
87
+ const fetchContentTypes = async () => {
88
+ try {
89
+ const [{ data: ctData }, { data: exData }] = await Promise.all([
90
+ get("/permit-strapi/content-types"),
91
+ get("/permit-strapi/excluded-resources")
92
+ ]);
93
+ setContentTypes(ctData.contentTypes);
94
+ console.log("[permit-strapi] content types fetched:", ctData.contentTypes);
95
+ const initial = {};
96
+ ctData.contentTypes.forEach((ct) => {
97
+ initial[ct.uid] = !exData.excludedResources.includes(ct.uid);
98
+ });
99
+ setProtectedTypes(initial);
100
+ for (const ct of ctData.contentTypes) {
101
+ fetchContentTypeFields(ct.uid);
102
+ }
103
+ } catch {
104
+ toggleNotification({ type: "danger", message: "Failed to load content types" });
105
+ }
106
+ };
107
+ const fetchContentTypeFields = async (uid) => {
108
+ try {
109
+ const encodedUid = encodeURIComponent(uid);
110
+ const { data } = await get(`/permit-strapi/content-type-fields/${encodedUid}`);
111
+ setResourceFieldsMap((prev) => ({ ...prev, [uid]: data.fields }));
112
+ } catch {
113
+ }
114
+ };
115
+ const fetchAttributeMappings = async () => {
116
+ try {
117
+ const [{ data: userFieldsData }, { data: userMappingsData }, { data: resourceMappingsData }] = await Promise.all([
118
+ get("/permit-strapi/user-fields"),
119
+ get("/permit-strapi/user-attribute-mappings"),
120
+ get("/permit-strapi/resource-attribute-mappings")
121
+ ]);
122
+ setUserFields(userFieldsData.fields);
123
+ setUserAttrMappings(userMappingsData.mappings);
124
+ setResourceAttrMappings(resourceMappingsData.mappings);
125
+ } catch {
126
+ }
127
+ };
128
+ const handleUpdateConfig = async () => {
129
+ if (!updateApiKey.trim()) return;
130
+ setUpdateLoading(true);
131
+ try {
132
+ await post("/permit-strapi/config", {
133
+ apiKey: updateApiKey.trim(),
134
+ pdpUrl: updatePdpUrl.trim() || DEFAULT_PDP_URL$1
135
+ });
136
+ toggleNotification({ type: "success", message: "Configuration updated successfully" });
137
+ fetchConfig();
138
+ setUpdateApiKey("");
139
+ } catch (error) {
140
+ const message = error?.response?.data?.error?.message || "Failed to update configuration";
141
+ toggleNotification({ type: "danger", message });
142
+ } finally {
143
+ setUpdateLoading(false);
144
+ }
145
+ };
146
+ const handleDisconnect = async () => {
147
+ setDisconnectLoading(true);
148
+ try {
149
+ await del("/permit-strapi/config");
150
+ toggleNotification({ type: "success", message: "Disconnected from Permit.io" });
151
+ setDisconnectConfirmed(false);
152
+ onDisconnect();
153
+ } catch {
154
+ toggleNotification({ type: "danger", message: "Failed to disconnect" });
155
+ } finally {
156
+ setDisconnectLoading(false);
157
+ }
158
+ };
159
+ const handleSyncAllUsers = async () => {
160
+ setSyncingUsers(true);
161
+ setSyncResult(null);
162
+ try {
163
+ const { data } = await post("/permit-strapi/sync-users", {});
164
+ setSyncResult(data);
165
+ toggleNotification({
166
+ type: "success",
167
+ message: `Synced ${data.synced} of ${data.total} users`
168
+ });
169
+ } catch {
170
+ toggleNotification({ type: "danger", message: "Failed to sync users" });
171
+ } finally {
172
+ setSyncingUsers(false);
173
+ }
174
+ };
175
+ const toggleProtection = (uid) => {
176
+ setProtectedTypes((prev) => ({ ...prev, [uid]: !prev[uid] }));
177
+ };
178
+ const handleSaveResources = async () => {
179
+ setSavingResources(true);
180
+ try {
181
+ const excludedResources = Object.entries(protectedTypes).filter(([, isProtected]) => !isProtected).map(([uid]) => uid);
182
+ await post("/permit-strapi/excluded-resources", { excludedResources });
183
+ toggleNotification({ type: "success", message: "Protected resources saved successfully" });
184
+ } catch {
185
+ toggleNotification({ type: "danger", message: "Failed to save protected resources" });
186
+ } finally {
187
+ setSavingResources(false);
188
+ }
189
+ };
190
+ const toggleUserAttribute = (fieldName) => {
191
+ setUserAttrMappings(
192
+ (prev) => prev.includes(fieldName) ? prev.filter((f) => f !== fieldName) : [...prev, fieldName]
193
+ );
194
+ };
195
+ const handleSaveUserAttributes = async () => {
196
+ setSavingUserAttrs(true);
197
+ try {
198
+ await post("/permit-strapi/user-attribute-mappings", { mappings: userAttrMappings });
199
+ toggleNotification({
200
+ type: "success",
201
+ message: "User attribute mappings saved. Resources re-synced."
202
+ });
203
+ } catch {
204
+ toggleNotification({ type: "danger", message: "Failed to save user attribute mappings" });
205
+ } finally {
206
+ setSavingUserAttrs(false);
207
+ }
208
+ };
209
+ const toggleResourceAttribute = (uid, fieldName) => {
210
+ setResourceAttrMappings((prev) => {
211
+ const current = prev[uid] || [];
212
+ const updated = current.includes(fieldName) ? current.filter((f) => f !== fieldName) : [...current, fieldName];
213
+ return { ...prev, [uid]: updated };
214
+ });
215
+ };
216
+ const handleSaveResourceAttributes = async () => {
217
+ setSavingResourceAttrs(true);
218
+ try {
219
+ await post("/permit-strapi/resource-attribute-mappings", { mappings: resourceAttrMappings });
220
+ toggleNotification({
221
+ type: "success",
222
+ message: "Resource attribute mappings saved. Resources re-synced."
223
+ });
224
+ } catch {
225
+ toggleNotification({ type: "danger", message: "Failed to save resource attribute mappings" });
226
+ } finally {
227
+ setSavingResourceAttrs(false);
228
+ }
229
+ };
230
+ const fetchRebacConfig = async () => {
231
+ try {
232
+ const { data } = await get("/permit-strapi/rebac-config");
233
+ setRebacConfig(data.config || {});
234
+ } catch {
235
+ }
236
+ };
237
+ const fetchInstanceRoles = async (uid) => {
238
+ try {
239
+ const encodedUid = encodeURIComponent(uid);
240
+ const { data } = await get(`/permit-strapi/instance-roles/${encodedUid}`);
241
+ setInstanceRoles((prev) => ({ ...prev, [uid]: data.roles || [] }));
242
+ } catch {
243
+ }
244
+ };
245
+ const toggleRebacEnabled = (uid) => {
246
+ setRebacConfig((prev) => {
247
+ const current = prev[uid] || { enabled: false, creatorRole: "owner" };
248
+ const updated = { ...current, enabled: !current.enabled };
249
+ if (updated.enabled && !instanceRoles[uid]) {
250
+ fetchInstanceRoles(uid);
251
+ }
252
+ return { ...prev, [uid]: updated };
253
+ });
254
+ };
255
+ const updateCreatorRole = (uid, value) => {
256
+ setRebacConfig((prev) => ({
257
+ ...prev,
258
+ [uid]: { ...prev[uid] || { enabled: false, creatorRole: "owner" }, creatorRole: value }
259
+ }));
260
+ };
261
+ const handleSaveRebacConfig = async () => {
262
+ setSavingRebac(true);
263
+ try {
264
+ await post("/permit-strapi/rebac-config", { config: rebacConfig });
265
+ toggleNotification({ type: "success", message: "ReBAC configuration saved" });
266
+ } catch {
267
+ toggleNotification({ type: "danger", message: "Failed to save ReBAC configuration" });
268
+ } finally {
269
+ setSavingRebac(false);
270
+ }
271
+ };
272
+ const addInstanceRole = (uid) => {
273
+ setInstanceRoles((prev) => ({
274
+ ...prev,
275
+ [uid]: [...prev[uid] || [], { key: "", name: "" }]
276
+ }));
277
+ };
278
+ const updateInstanceRole = (uid, index2, field, value) => {
279
+ setInstanceRoles((prev) => {
280
+ const roles = [...prev[uid] || []];
281
+ roles[index2] = { ...roles[index2], [field]: value };
282
+ return { ...prev, [uid]: roles };
283
+ });
284
+ };
285
+ const removeInstanceRole = (uid, index2) => {
286
+ setInstanceRoles((prev) => {
287
+ const roles = [...prev[uid] || []];
288
+ roles.splice(index2, 1);
289
+ return { ...prev, [uid]: roles };
290
+ });
291
+ };
292
+ const handleSaveInstanceRoles = async (uid) => {
293
+ setSavingInstanceRoles((prev) => ({ ...prev, [uid]: true }));
294
+ try {
295
+ const encodedUid = encodeURIComponent(uid);
296
+ const roles = instanceRoles[uid] || [];
297
+ await post(`/permit-strapi/instance-roles/${encodedUid}`, { roles });
298
+ toggleNotification({
299
+ type: "success",
300
+ message: "Instance roles saved and synced to Permit.io"
301
+ });
302
+ } catch {
303
+ toggleNotification({ type: "danger", message: "Failed to save instance roles" });
304
+ } finally {
305
+ setSavingInstanceRoles((prev) => ({ ...prev, [uid]: false }));
306
+ }
307
+ };
308
+ const handleSyncInstances = async (uid) => {
309
+ setSyncingInstances((prev) => ({ ...prev, [uid]: true }));
310
+ try {
311
+ const encodedUid = encodeURIComponent(uid);
312
+ const { data } = await post(`/permit-strapi/sync-instances/${encodedUid}`, {});
313
+ setInstanceSyncResults((prev) => ({ ...prev, [uid]: data }));
314
+ toggleNotification({
315
+ type: "success",
316
+ message: `Synced ${data.synced} of ${data.total} instances`
317
+ });
318
+ } catch {
319
+ toggleNotification({ type: "danger", message: "Failed to sync instances" });
320
+ } finally {
321
+ setSyncingInstances((prev) => ({ ...prev, [uid]: false }));
322
+ }
323
+ };
324
+ const protectedContentTypes = contentTypes.filter((ct) => protectedTypes[ct.uid] !== false);
325
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 10, children: [
326
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", hasRadius: true, padding: 6, marginBottom: 6, children: [
327
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
328
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
329
+ /* @__PURE__ */ jsxRuntime.jsx(Logo$1, { src: index.logo, alt: "Permit.io" }),
330
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "Permit.io" })
331
+ ] }),
332
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Status, { variant: "success", size: "S", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "success700", children: "Connected" }) })
333
+ ] }),
334
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
335
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingTop: 4, children: [
336
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 8, children: [
337
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
338
+ /* @__PURE__ */ jsxRuntime.jsx(
339
+ designSystem.Typography,
340
+ {
341
+ variant: "sigma",
342
+ textColor: "neutral500",
343
+ style: { textTransform: "uppercase", letterSpacing: "0.08em", fontSize: "10px" },
344
+ children: "API KEY"
345
+ }
346
+ ),
347
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: config?.apiKey })
348
+ ] }),
349
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, children: [
350
+ /* @__PURE__ */ jsxRuntime.jsx(
351
+ designSystem.Typography,
352
+ {
353
+ variant: "sigma",
354
+ textColor: "neutral500",
355
+ style: { textTransform: "uppercase", letterSpacing: "0.08em", fontSize: "10px" },
356
+ children: "PDP URL"
357
+ }
358
+ ),
359
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", style: { lineHeight: 1 }, children: [
360
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", style: { lineHeight: 1 }, children: config?.pdpUrl }),
361
+ /* @__PURE__ */ jsxRuntime.jsx(
362
+ "a",
363
+ {
364
+ href: "https://docs.permit.io",
365
+ target: "_blank",
366
+ rel: "noreferrer",
367
+ style: { display: "flex", alignItems: "center" },
368
+ children: /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, { width: "11px", height: "11px", fill: "neutral400" })
369
+ }
370
+ )
371
+ ] })
372
+ ] })
373
+ ] }),
374
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
375
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Root, { children: [
376
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Pencil, {}), size: "S", children: "Update Config" }) }),
377
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
378
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Update Configuration" }) }),
379
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 2, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
380
+ /* @__PURE__ */ jsxRuntime.jsxs(
381
+ designSystem.Field.Root,
382
+ {
383
+ name: "updateApiKey",
384
+ width: "100%",
385
+ hint: "Enter a new Permit.io API key to replace the current one",
386
+ children: [
387
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "New API Key" }),
388
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
389
+ /* @__PURE__ */ jsxRuntime.jsx(
390
+ designSystem.TextInput,
391
+ {
392
+ placeholder: "permit_key_...",
393
+ value: updateApiKey,
394
+ onChange: (e) => setUpdateApiKey(e.target.value),
395
+ type: "password"
396
+ }
397
+ )
398
+ ]
399
+ }
400
+ ),
401
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "updatePdpUrl", width: "100%", children: [
402
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "PDP UR" }),
403
+ /* @__PURE__ */ jsxRuntime.jsx(
404
+ designSystem.TextInput,
405
+ {
406
+ value: updatePdpUrl,
407
+ onChange: (e) => setUpdatePdpUrl(e.target.value)
408
+ }
409
+ )
410
+ ] }),
411
+ /* @__PURE__ */ jsxRuntime.jsxs(
412
+ designSystem.Flex,
413
+ {
414
+ gap: 2,
415
+ alignItems: "flex-start",
416
+ padding: 3,
417
+ style: { background: "#f0f4ff", borderRadius: "4px" },
418
+ children: [
419
+ /* @__PURE__ */ jsxRuntime.jsx(
420
+ icons.Information,
421
+ {
422
+ width: "14px",
423
+ height: "14px",
424
+ fill: "primary600",
425
+ style: { flexShrink: 0, marginTop: "2px" }
426
+ }
427
+ ),
428
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "primary600", children: [
429
+ "The Cloud PDP supports ",
430
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "RBAC only" }),
431
+ ". For ABAC or ReBAC policies, use a",
432
+ " ",
433
+ /* @__PURE__ */ jsxRuntime.jsx(
434
+ TextLink,
435
+ {
436
+ href: "https://docs.permit.io/how-to/deploy/deploy-to-production",
437
+ target: "_blank",
438
+ rel: "noreferrer",
439
+ children: "self-hosted PDP"
440
+ }
441
+ ),
442
+ "."
443
+ ] })
444
+ ]
445
+ }
446
+ )
447
+ ] }) }) }),
448
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
449
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}), children: "Cancel" }) }),
450
+ /* @__PURE__ */ jsxRuntime.jsx(
451
+ designSystem.Button,
452
+ {
453
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
454
+ onClick: handleUpdateConfig,
455
+ loading: updateLoading,
456
+ disabled: !updateApiKey.trim() || updateLoading,
457
+ children: "Save Changes"
458
+ }
459
+ )
460
+ ] })
461
+ ] })
462
+ ] }),
463
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Root, { children: [
464
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "danger-light", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), size: "S", children: "Disconnect" }) }),
465
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Content, { children: [
466
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Title, { children: "Disconnect from Permit.io" }) }),
467
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Body, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, children: [
468
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: "Are you sure you want to disconnect from Permit.io?" }),
469
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "danger600", children: "All API requests will immediately stop being checked for authorization. Your content types will be unprotected until you reconnect." }),
470
+ /* @__PURE__ */ jsxRuntime.jsx(
471
+ designSystem.Checkbox,
472
+ {
473
+ checked: disconnectConfirmed,
474
+ onCheckedChange: (val) => setDisconnectConfirmed(val),
475
+ children: "I understand the consequences of disconnecting"
476
+ }
477
+ )
478
+ ] }) }),
479
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Modal.Footer, { children: [
480
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Modal.Close, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "tertiary", startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Cross, {}), children: "Cancel" }) }),
481
+ /* @__PURE__ */ jsxRuntime.jsx(
482
+ designSystem.Button,
483
+ {
484
+ variant: "danger",
485
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
486
+ onClick: handleDisconnect,
487
+ loading: disconnectLoading,
488
+ disabled: !disconnectConfirmed || disconnectLoading,
489
+ children: "Yes, Disconnect"
490
+ }
491
+ )
492
+ ] })
493
+ ] })
494
+ ] })
495
+ ] })
496
+ ] })
497
+ ] }),
498
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", shadow: "tableShadow", hasRadius: true, padding: 6, marginBottom: 6, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", children: [
499
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
500
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "User Sync" }),
501
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Sync all existing Strapi users to Permit.io. New users are synced automatically on registration." }),
502
+ syncResult && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
503
+ "Last sync: ",
504
+ syncResult.synced,
505
+ " synced, ",
506
+ syncResult.failed,
507
+ " failed (total:",
508
+ " ",
509
+ syncResult.total,
510
+ ")"
511
+ ] })
512
+ ] }),
513
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { variant: "secondary", size: "S", onClick: handleSyncAllUsers, loading: syncingUsers, children: "Sync All Users" })
514
+ ] }) }),
515
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", hasRadius: true, padding: 6, marginBottom: 6, children: [
516
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, width: "100%", children: [
517
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "Protected Resources" }),
519
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Toggle which content types are protected by Permit.io authorization" })
520
+ ] }),
521
+ /* @__PURE__ */ jsxRuntime.jsx(
522
+ designSystem.Button,
523
+ {
524
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
525
+ size: "S",
526
+ onClick: handleSaveResources,
527
+ loading: savingResources,
528
+ children: "Save"
529
+ }
530
+ )
531
+ ] }),
532
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
533
+ contentTypes.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "No content types found. Create a collection type in the Content-Type Builder first." }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 0, paddingTop: 2, width: "100%", children: contentTypes.map((ct, index2) => {
534
+ const isProtected = protectedTypes[ct.uid] ?? true;
535
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { width: "100%", children: [
536
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", padding: 4, style: { width: "100%" }, children: [
537
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: 1, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: ct.displayName }) }),
538
+ /* @__PURE__ */ jsxRuntime.jsxs(
539
+ designSystem.Flex,
540
+ {
541
+ style: {
542
+ border: "1px solid #dcdce4",
543
+ borderRadius: "4px",
544
+ overflow: "hidden"
545
+ },
546
+ children: [
547
+ /* @__PURE__ */ jsxRuntime.jsx(
548
+ SegButton,
549
+ {
550
+ $active: !isProtected,
551
+ $variant: "danger",
552
+ onClick: () => isProtected && toggleProtection(ct.uid),
553
+ children: "Unprotected"
554
+ }
555
+ ),
556
+ /* @__PURE__ */ jsxRuntime.jsx(
557
+ SegButton,
558
+ {
559
+ $active: isProtected,
560
+ $variant: "success",
561
+ onClick: () => !isProtected && toggleProtection(ct.uid),
562
+ children: "Protected"
563
+ }
564
+ )
565
+ ]
566
+ }
567
+ )
568
+ ] }),
569
+ index2 < contentTypes.length - 1 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {})
570
+ ] }, ct.uid);
571
+ }) })
572
+ ] }),
573
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", hasRadius: true, padding: 6, children: [
574
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, paddingBottom: 4, alignItems: "flex-start", children: [
575
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "ABAC Attribute Mapping" }),
576
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Select which fields to pass as attributes for Attribute-Based Access Control. Requires a self-hosted PDP." })
577
+ ] }),
578
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Root, { children: [
579
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "user-attributes", children: [
580
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "User Attributes" }) }) }),
581
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: userFields.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No custom user fields found. Add fields to the User content type to use as ABAC attributes." }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, alignItems: "flex-start", children: [
582
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, wrap: "wrap", children: userFields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
583
+ designSystem.Checkbox,
584
+ {
585
+ checked: userAttrMappings.includes(field.name),
586
+ onCheckedChange: () => toggleUserAttribute(field.name),
587
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
588
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: field.name }),
589
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral400", children: [
590
+ "(",
591
+ field.type,
592
+ ")"
593
+ ] })
594
+ ] })
595
+ },
596
+ field.name
597
+ )) }),
598
+ /* @__PURE__ */ jsxRuntime.jsx(
599
+ designSystem.Button,
600
+ {
601
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
602
+ size: "S",
603
+ onClick: handleSaveUserAttributes,
604
+ loading: savingUserAttrs,
605
+ variant: "secondary",
606
+ children: "Save User Attributes"
607
+ }
608
+ )
609
+ ] }) }) })
610
+ ] }),
611
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: "resource-attributes", children: [
612
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Resource Attributes" }) }) }),
613
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: protectedContentTypes.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "No protected content types. Enable protection for a content type above to configure resource attributes." }) : /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 4, alignItems: "flex-start", children: [
614
+ protectedContentTypes.map((ct) => {
615
+ const fields = resourceFieldsMap[ct.uid] || [];
616
+ const selected = resourceAttrMappings[ct.uid] || [];
617
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
618
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: ct.displayName }),
619
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 2 }),
620
+ fields.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral400", children: "No mappable fields" }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 4, wrap: "wrap", paddingLeft: 2, children: fields.map((field) => /* @__PURE__ */ jsxRuntime.jsx(
621
+ designSystem.Checkbox,
622
+ {
623
+ checked: selected.includes(field.name),
624
+ onCheckedChange: () => toggleResourceAttribute(ct.uid, field.name),
625
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", children: [
626
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", children: field.name }),
627
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral400", children: [
628
+ "(",
629
+ field.type,
630
+ ")"
631
+ ] })
632
+ ] })
633
+ },
634
+ field.name
635
+ )) })
636
+ ] }, ct.uid);
637
+ }),
638
+ /* @__PURE__ */ jsxRuntime.jsx(
639
+ designSystem.Button,
640
+ {
641
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
642
+ size: "S",
643
+ onClick: handleSaveResourceAttributes,
644
+ loading: savingResourceAttrs,
645
+ variant: "secondary",
646
+ children: "Save Resource Attributes"
647
+ }
648
+ )
649
+ ] }) }) })
650
+ ] })
651
+ ] })
652
+ ] }),
653
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { background: "neutral0", shadow: "tableShadow", hasRadius: true, padding: 6, marginTop: 6, children: [
654
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, width: "100%", children: [
655
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
656
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: "ReBAC Configuration" }),
657
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: "Enable Relationship-Based Access Control per content type. Requires a self-hosted PDP." })
658
+ ] }),
659
+ /* @__PURE__ */ jsxRuntime.jsx(
660
+ designSystem.Button,
661
+ {
662
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
663
+ size: "S",
664
+ onClick: handleSaveRebacConfig,
665
+ loading: savingRebac,
666
+ children: "Save"
667
+ }
668
+ )
669
+ ] }),
670
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
671
+ protectedContentTypes.length === 0 ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 6, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral500", children: "No protected content types. Enable protection above first." }) }) : /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Root, { children: protectedContentTypes.map((ct) => {
672
+ const ctRebac = rebacConfig[ct.uid] || { enabled: false, creatorRole: "owner" };
673
+ const roles = instanceRoles[ct.uid] || [];
674
+ const syncResult2 = instanceSyncResults[ct.uid];
675
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Accordion.Item, { value: ct.uid, children: [
676
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Header, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Trigger, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
677
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: ct.displayName }),
678
+ ctRebac.enabled && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "success600", children: "(ReBAC enabled)" })
679
+ ] }) }) }),
680
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Accordion.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 5, alignItems: "flex-start", children: [
681
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { alignItems: "center", gap: 3, children: [
682
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "ReBAC Mode" }),
683
+ /* @__PURE__ */ jsxRuntime.jsxs(
684
+ designSystem.Flex,
685
+ {
686
+ style: {
687
+ border: "1px solid #dcdce4",
688
+ borderRadius: "4px",
689
+ overflow: "hidden"
690
+ },
691
+ children: [
692
+ /* @__PURE__ */ jsxRuntime.jsx(
693
+ SegButton,
694
+ {
695
+ $active: !ctRebac.enabled,
696
+ $variant: "danger",
697
+ onClick: () => ctRebac.enabled && toggleRebacEnabled(ct.uid),
698
+ children: "Disabled"
699
+ }
700
+ ),
701
+ /* @__PURE__ */ jsxRuntime.jsx(
702
+ SegButton,
703
+ {
704
+ $active: ctRebac.enabled,
705
+ $variant: "success",
706
+ onClick: () => !ctRebac.enabled && toggleRebacEnabled(ct.uid),
707
+ children: "Enabled"
708
+ }
709
+ )
710
+ ]
711
+ }
712
+ )
713
+ ] }),
714
+ ctRebac.enabled && /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
715
+ /* @__PURE__ */ jsxRuntime.jsxs(
716
+ designSystem.Field.Root,
717
+ {
718
+ name: `creator-role-${ct.uid}`,
719
+ width: "300px",
720
+ hint: "Role assigned to the user who creates a record",
721
+ children: [
722
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Creator Role" }),
723
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
724
+ /* @__PURE__ */ jsxRuntime.jsx(
725
+ designSystem.TextInput,
726
+ {
727
+ placeholder: "owner",
728
+ value: ctRebac.creatorRole,
729
+ onChange: (e) => updateCreatorRole(ct.uid, e.target.value)
730
+ }
731
+ )
732
+ ]
733
+ }
734
+ ),
735
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
736
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 3, alignItems: "flex-start", width: "100%", children: [
737
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Instance Roles" }),
738
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: "Define roles that can be assigned to users on specific records (e.g. owner, editor, viewer)" }),
739
+ roles.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 2, width: "100%", children: roles.map((role, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", width: "100%", children: [
740
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
741
+ designSystem.TextInput,
742
+ {
743
+ placeholder: "key (e.g. owner)",
744
+ value: role.key,
745
+ onChange: (e) => updateInstanceRole(ct.uid, index2, "key", e.target.value),
746
+ "aria-label": "Role key"
747
+ }
748
+ ) }),
749
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { flex: 1, children: /* @__PURE__ */ jsxRuntime.jsx(
750
+ designSystem.TextInput,
751
+ {
752
+ placeholder: "name (e.g. Owner)",
753
+ value: role.name,
754
+ onChange: (e) => updateInstanceRole(
755
+ ct.uid,
756
+ index2,
757
+ "name",
758
+ e.target.value
759
+ ),
760
+ "aria-label": "Role name"
761
+ }
762
+ ) }),
763
+ /* @__PURE__ */ jsxRuntime.jsx(
764
+ designSystem.Button,
765
+ {
766
+ variant: "danger-light",
767
+ size: "S",
768
+ onClick: () => removeInstanceRole(ct.uid, index2),
769
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
770
+ children: "Remove"
771
+ }
772
+ )
773
+ ] }, index2)) }),
774
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, children: [
775
+ /* @__PURE__ */ jsxRuntime.jsx(
776
+ designSystem.Button,
777
+ {
778
+ variant: "secondary",
779
+ size: "S",
780
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Plus, {}),
781
+ onClick: () => addInstanceRole(ct.uid),
782
+ children: "Add Role"
783
+ }
784
+ ),
785
+ /* @__PURE__ */ jsxRuntime.jsx(
786
+ designSystem.Button,
787
+ {
788
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
789
+ size: "S",
790
+ onClick: () => handleSaveInstanceRoles(ct.uid),
791
+ loading: savingInstanceRoles[ct.uid],
792
+ disabled: roles.length === 0,
793
+ children: "Save Roles"
794
+ }
795
+ )
796
+ ] })
797
+ ] }),
798
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Divider, {}),
799
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", width: "100%", children: [
800
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", children: [
801
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: "Sync Instances" }),
802
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: [
803
+ "Bulk sync all existing ",
804
+ ct.displayName,
805
+ " records to Permit.io as resource instances"
806
+ ] }),
807
+ syncResult2 && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral400", children: [
808
+ "Last sync: ",
809
+ syncResult2.synced,
810
+ " synced, ",
811
+ syncResult2.failed,
812
+ " ",
813
+ "failed (total: ",
814
+ syncResult2.total,
815
+ ")"
816
+ ] })
817
+ ] }),
818
+ /* @__PURE__ */ jsxRuntime.jsx(
819
+ designSystem.Button,
820
+ {
821
+ variant: "secondary",
822
+ size: "S",
823
+ onClick: () => handleSyncInstances(ct.uid),
824
+ loading: syncingInstances[ct.uid],
825
+ children: "Sync Instances"
826
+ }
827
+ )
828
+ ] })
829
+ ] })
830
+ ] }) }) })
831
+ ] }, ct.uid);
832
+ }) })
833
+ ] })
834
+ ] });
835
+ };
836
+ const Logo = styled__default.default.img`
837
+ width: 48px;
838
+ height: 48px;
839
+ border-radius: 8px;
840
+ `;
841
+ const StepLink = styled__default.default.a`
842
+ color: ${({ theme }) => theme.colors.primary600};
843
+ font-size: ${({ theme }) => theme.fontSizes[2]};
844
+ text-decoration: none;
845
+ &:hover { text-decoration: underline; }
846
+ `;
847
+ const StepNumber = styled__default.default(designSystem.Flex)`
848
+ width: 28px;
849
+ height: 28px;
850
+ border-radius: 50%;
851
+ background-color: ${({ theme }) => theme.colors.primary100};
852
+ flex-shrink: 0;
853
+ `;
854
+ const steps = [
855
+ {
856
+ number: 1,
857
+ title: "Create a Permit.io account",
858
+ description: "Sign up at permit.io and create a new workspace for your project.",
859
+ link: "https://app.permit.io",
860
+ linkText: "Go to Permit.io"
861
+ },
862
+ {
863
+ number: 2,
864
+ title: "Assign policies in Permit.io",
865
+ description: "Once connected, your Strapi content types and roles sync automatically. Head to the Permit.io dashboard to assign permissions per role.",
866
+ link: "https://docs.permit.io",
867
+ linkText: "Read the docs"
868
+ },
869
+ {
870
+ number: 3,
871
+ title: "Enter your API key below",
872
+ description: "Copy your API key from the Permit.io dashboard and paste it below to connect."
873
+ }
874
+ ];
875
+ const DEFAULT_PDP_URL = "https://cloudpdp.api.permit.io";
876
+ const GettingStarted = ({ onConnect }) => {
877
+ const [apiKey, setApiKey] = react.useState("");
878
+ const [pdpUrl, setPdpUrl] = react.useState(DEFAULT_PDP_URL);
879
+ const [errors, setErrors] = react.useState({});
880
+ const [loading, setLoading] = react.useState(false);
881
+ const { post } = admin.useFetchClient();
882
+ const { toggleNotification } = admin.useNotification();
883
+ const handleConnect = async () => {
884
+ if (!apiKey.trim()) {
885
+ setErrors({ apiKey: "API key is required" });
886
+ return;
887
+ }
888
+ setErrors({});
889
+ setLoading(true);
890
+ try {
891
+ await post("/permit-strapi/config", {
892
+ apiKey: apiKey.trim(),
893
+ pdpUrl: pdpUrl.trim() || DEFAULT_PDP_URL
894
+ });
895
+ toggleNotification({
896
+ type: "success",
897
+ message: "Successfully connected to Permit.io"
898
+ });
899
+ onConnect();
900
+ } catch (error) {
901
+ const message = error?.response?.data?.error?.message || "Failed to connect. Please check your API key.";
902
+ toggleNotification({
903
+ type: "danger",
904
+ message
905
+ });
906
+ } finally {
907
+ setLoading(false);
908
+ }
909
+ };
910
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 10, children: [
911
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 4, paddingBottom: 10, children: [
912
+ /* @__PURE__ */ jsxRuntime.jsx(Logo, { src: index.logo, alt: "Permit.io" }),
913
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "center", gap: 2, children: [
914
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "alpha", children: "Welcome to Permit Strapi" }),
915
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "epsilon", textColor: "neutral600", children: "Fine-grained authorization for your Strapi content types, powered by Permit.io" })
916
+ ] })
917
+ ] }),
918
+ /* @__PURE__ */ jsxRuntime.jsxs(
919
+ designSystem.Box,
920
+ {
921
+ background: "neutral0",
922
+ shadow: "tableShadow",
923
+ hasRadius: true,
924
+ padding: 8,
925
+ marginBottom: 6,
926
+ style: { maxWidth: 640, margin: "0 auto 24px auto" },
927
+ children: [
928
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 6, children: "Get started in 3 steps" }),
929
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { direction: "column", gap: 6, paddingTop: 4, children: steps.map((step) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 4, alignItems: "flex-start", children: [
930
+ /* @__PURE__ */ jsxRuntime.jsx(StepNumber, { justifyContent: "center", alignItems: "center", style: { marginTop: "2px", flexShrink: 0 }, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "primary600", children: step.number }) }),
931
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 1, alignItems: "flex-start", style: { flex: 1 }, children: [
932
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "bold", children: step.title }),
933
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: step.description }),
934
+ step.link && /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 1, alignItems: "center", paddingTop: 1, children: [
935
+ /* @__PURE__ */ jsxRuntime.jsx(StepLink, { href: step.link, target: "_blank", rel: "noreferrer", children: step.linkText }),
936
+ /* @__PURE__ */ jsxRuntime.jsx(icons.ExternalLink, { width: "12px", height: "12px", fill: "primary600" })
937
+ ] })
938
+ ] })
939
+ ] }, step.number)) }),
940
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 8, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "apiKey", error: errors.apiKey, children: [
941
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "Permit.io API Key" }),
942
+ /* @__PURE__ */ jsxRuntime.jsx(
943
+ designSystem.TextInput,
944
+ {
945
+ placeholder: "permit_key_...",
946
+ value: apiKey,
947
+ onChange: (e) => setApiKey(e.target.value),
948
+ type: "password"
949
+ }
950
+ ),
951
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Error, {})
952
+ ] }) }),
953
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingTop: 4, children: [
954
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Field.Root, { name: "pdpUrl", hint: "Leave as default to use Permit.io Cloud PDP. Change to your local PDP URL if self-hosting.", children: [
955
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Label, { children: "PDP URL" }),
956
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Field.Hint, {}),
957
+ /* @__PURE__ */ jsxRuntime.jsx(
958
+ designSystem.TextInput,
959
+ {
960
+ placeholder: DEFAULT_PDP_URL,
961
+ value: pdpUrl,
962
+ onChange: (e) => setPdpUrl(e.target.value)
963
+ }
964
+ )
965
+ ] }),
966
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(
967
+ designSystem.Flex,
968
+ {
969
+ gap: 2,
970
+ alignItems: "flex-start",
971
+ padding: 3,
972
+ style: { background: "#f0f4ff", borderRadius: "4px" },
973
+ children: [
974
+ /* @__PURE__ */ jsxRuntime.jsx(icons.Information, { width: "14px", height: "14px", fill: "primary600", style: { flexShrink: 0, marginTop: "2px" } }),
975
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "primary600", children: [
976
+ "The Cloud PDP supports ",
977
+ /* @__PURE__ */ jsxRuntime.jsx("strong", { children: "RBAC only" }),
978
+ ". If you plan to use ABAC or ReBAC policies, you must deploy a",
979
+ " ",
980
+ /* @__PURE__ */ jsxRuntime.jsx(
981
+ "a",
982
+ {
983
+ href: "https://docs.permit.io/how-to/deploy/deploy-to-production",
984
+ target: "_blank",
985
+ rel: "noreferrer",
986
+ style: { color: "inherit", textDecoration: "underline" },
987
+ children: "self-hosted PDP"
988
+ }
989
+ ),
990
+ "."
991
+ ] })
992
+ ]
993
+ }
994
+ ) })
995
+ ] }),
996
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { paddingTop: 6, justifyContent: "flex-end", children: /* @__PURE__ */ jsxRuntime.jsx(
997
+ designSystem.Button,
998
+ {
999
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Check, {}),
1000
+ onClick: handleConnect,
1001
+ disabled: !apiKey.trim() || loading,
1002
+ loading,
1003
+ size: "L",
1004
+ children: "Connect to Permit.io"
1005
+ }
1006
+ ) })
1007
+ ]
1008
+ }
1009
+ )
1010
+ ] });
1011
+ };
1012
+ const App = () => {
1013
+ const [isConfigured, setIsConfigured] = react.useState(null);
1014
+ const { get } = admin.useFetchClient();
1015
+ react.useEffect(() => {
1016
+ get("/permit-strapi/config").then(({ data }) => setIsConfigured(data.configured)).catch(() => setIsConfigured(false));
1017
+ }, []);
1018
+ if (isConfigured === null) {
1019
+ return /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Loading, {});
1020
+ }
1021
+ if (!isConfigured) {
1022
+ return /* @__PURE__ */ jsxRuntime.jsx(GettingStarted, { onConnect: () => setIsConfigured(true) });
1023
+ }
1024
+ return /* @__PURE__ */ jsxRuntime.jsxs(reactRouterDom.Routes, { children: [
1025
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { index: true, element: /* @__PURE__ */ jsxRuntime.jsx(HomePage, { onDisconnect: () => setIsConfigured(false) }) }),
1026
+ /* @__PURE__ */ jsxRuntime.jsx(reactRouterDom.Route, { path: "*", element: /* @__PURE__ */ jsxRuntime.jsx(admin.Page.Error, {}) })
1027
+ ] });
1028
+ };
1029
+ exports.App = App;