@opkod-france/strapi-plugin-component-usage 2.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,883 @@
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 reactIntl = require("react-intl");
6
+ const designSystem = require("@strapi/design-system");
7
+ const admin = require("@strapi/strapi/admin");
8
+ const index = require("./index-BrC4CJvo.js");
9
+ const icons = require("@strapi/icons");
10
+ const getTranslation = (id) => `${index.PLUGIN_ID}.${id}`;
11
+ const ComponentSearch = ({
12
+ searchQuery,
13
+ onSearchChange
14
+ }) => {
15
+ const { formatMessage } = reactIntl.useIntl();
16
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingLeft: 4, paddingRight: 4, paddingBottom: 3, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.SearchForm, { children: /* @__PURE__ */ jsxRuntime.jsx(
17
+ designSystem.Searchbar,
18
+ {
19
+ name: "searchbar",
20
+ onClear: () => onSearchChange(""),
21
+ value: searchQuery,
22
+ onChange: (e) => onSearchChange(e.target.value),
23
+ clearLabel: formatMessage({
24
+ id: getTranslation("ComponentSearch.clearLabel"),
25
+ defaultMessage: "Clear"
26
+ }),
27
+ placeholder: formatMessage({
28
+ id: getTranslation("ComponentSearch.placeholder"),
29
+ defaultMessage: "Search components..."
30
+ }),
31
+ children: formatMessage({
32
+ id: getTranslation("ComponentSearch.label"),
33
+ defaultMessage: "Search components"
34
+ })
35
+ }
36
+ ) }) });
37
+ };
38
+ const EmptyResults = () => {
39
+ const { formatMessage } = reactIntl.useIntl();
40
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(
41
+ designSystem.EmptyStateLayout,
42
+ {
43
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.WarningCircle, { width: "6rem", height: "6rem" }),
44
+ content: formatMessage({
45
+ id: getTranslation("EmptyResults.message"),
46
+ defaultMessage: "No components match your search"
47
+ })
48
+ }
49
+ ) });
50
+ };
51
+ const CategoryHeader = ({ category }) => {
52
+ return /* @__PURE__ */ jsxRuntime.jsx(
53
+ designSystem.Box,
54
+ {
55
+ paddingLeft: 4,
56
+ paddingRight: 4,
57
+ paddingTop: 2,
58
+ paddingBottom: 2,
59
+ background: "neutral100",
60
+ children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", textColor: "neutral600", children: category.toUpperCase() })
61
+ }
62
+ );
63
+ };
64
+ const getBadgeVariant = (count) => {
65
+ if (count === 0) return "secondary";
66
+ if (count < 5) return "success";
67
+ if (count < 10) return "warning";
68
+ return "danger";
69
+ };
70
+ const isComponentSelected = (selectedComponent, componentUid) => {
71
+ return selectedComponent?.uid === componentUid;
72
+ };
73
+ const ComponentListItem = ({
74
+ component,
75
+ selectedComponent,
76
+ onSelectComponent
77
+ }) => {
78
+ const isSelected = isComponentSelected(selectedComponent, component.uid);
79
+ return /* @__PURE__ */ jsxRuntime.jsx(
80
+ designSystem.Box,
81
+ {
82
+ paddingLeft: 4,
83
+ paddingRight: 4,
84
+ paddingTop: 3,
85
+ paddingBottom: 3,
86
+ background: isSelected ? "primary100" : "neutral0",
87
+ style: {
88
+ cursor: "pointer",
89
+ borderLeft: isSelected ? "3px solid #4945FF" : "3px solid transparent"
90
+ },
91
+ onClick: () => onSelectComponent(component),
92
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
93
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "start", children: [
94
+ /* @__PURE__ */ jsxRuntime.jsx(
95
+ designSystem.Typography,
96
+ {
97
+ fontWeight: "semiBold",
98
+ textColor: isSelected ? "primary600" : "neutral800",
99
+ children: component.displayName
100
+ }
101
+ ),
102
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: component.uid })
103
+ ] }),
104
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: getBadgeVariant(component.usageCount), children: component.usageCount })
105
+ ] })
106
+ }
107
+ );
108
+ };
109
+ const CategoryGroup = ({
110
+ category,
111
+ components,
112
+ selectedComponent,
113
+ onSelectComponent
114
+ }) => {
115
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 2, children: [
116
+ /* @__PURE__ */ jsxRuntime.jsx(CategoryHeader, { category }),
117
+ components.map((component) => /* @__PURE__ */ jsxRuntime.jsx(
118
+ ComponentListItem,
119
+ {
120
+ component,
121
+ selectedComponent,
122
+ onSelectComponent
123
+ },
124
+ component.uid
125
+ ))
126
+ ] });
127
+ };
128
+ const filterComponentsByQuery = (components, searchQuery) => {
129
+ if (!searchQuery) return components;
130
+ const lowerQuery = searchQuery.toLowerCase();
131
+ return components.filter(
132
+ (comp) => comp.displayName.toLowerCase().includes(lowerQuery) || comp.category.toLowerCase().includes(lowerQuery) || comp.uid.toLowerCase().includes(lowerQuery)
133
+ );
134
+ };
135
+ const groupComponentsByCategory = (components) => {
136
+ return components.reduce((acc, comp) => {
137
+ if (!acc[comp.category]) {
138
+ acc[comp.category] = [];
139
+ }
140
+ acc[comp.category].push(comp);
141
+ return acc;
142
+ }, {});
143
+ };
144
+ const getSortedCategories = (groupedComponents) => {
145
+ return Object.keys(groupedComponents).sort();
146
+ };
147
+ const ComponentList = ({
148
+ components,
149
+ selectedComponent,
150
+ onSelectComponent,
151
+ searchQuery,
152
+ onSearchChange
153
+ }) => {
154
+ const filteredComponents = react.useMemo(
155
+ () => filterComponentsByQuery(components, searchQuery),
156
+ [components, searchQuery]
157
+ );
158
+ const groupedComponents = react.useMemo(
159
+ () => groupComponentsByCategory(filteredComponents),
160
+ [filteredComponents]
161
+ );
162
+ const sortedCategories = react.useMemo(
163
+ () => getSortedCategories(groupedComponents),
164
+ [groupedComponents]
165
+ );
166
+ const hasNoResults = sortedCategories.length === 0;
167
+ return /* @__PURE__ */ jsxRuntime.jsxs(
168
+ designSystem.Box,
169
+ {
170
+ background: "neutral0",
171
+ hasRadius: true,
172
+ shadow: "tableShadow",
173
+ paddingTop: 4,
174
+ paddingBottom: 4,
175
+ overflow: "auto",
176
+ height: "calc(100vh - 200px)",
177
+ children: [
178
+ /* @__PURE__ */ jsxRuntime.jsx(
179
+ ComponentSearch,
180
+ {
181
+ searchQuery,
182
+ onSearchChange
183
+ }
184
+ ),
185
+ hasNoResults ? /* @__PURE__ */ jsxRuntime.jsx(EmptyResults, {}) : sortedCategories.map((category) => /* @__PURE__ */ jsxRuntime.jsx(
186
+ CategoryGroup,
187
+ {
188
+ category,
189
+ components: groupedComponents[category],
190
+ selectedComponent,
191
+ onSelectComponent
192
+ },
193
+ category
194
+ ))
195
+ ]
196
+ }
197
+ );
198
+ };
199
+ const UsageTable = ({ usage }) => {
200
+ if (!usage || usage.length === 0) {
201
+ return null;
202
+ }
203
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { background: "neutral0", hasRadius: true, shadow: "tableShadow", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Table, { colCount: 3, rowCount: usage.length, children: [
204
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Thead, { children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
205
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Content Type" }) }),
206
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Entry ID" }) }),
207
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Th, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "sigma", children: "Field Path" }) })
208
+ ] }) }),
209
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Tbody, { children: usage.map((usageItem, index2) => /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Tr, { children: [
210
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", children: usageItem.contentType }) }),
211
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: usageItem.entryId }) }),
212
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Td, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: usageItem.fieldPath }) })
213
+ ] }, `${usageItem.contentType}-${usageItem.entryId}-${index2}`)) })
214
+ ] }) });
215
+ };
216
+ const UsageSection = ({
217
+ component,
218
+ usage,
219
+ loadingUsage,
220
+ onRefresh
221
+ }) => {
222
+ const { formatMessage } = reactIntl.useIntl();
223
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, children: [
224
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", paddingBottom: 4, children: [
225
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", children: formatMessage({
226
+ id: getTranslation("UsageSection.title"),
227
+ defaultMessage: "Usage Details"
228
+ }) }),
229
+ component.usageCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(
230
+ designSystem.Button,
231
+ {
232
+ size: "S",
233
+ variant: "secondary",
234
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.ArrowClockwise, {}),
235
+ onClick: onRefresh,
236
+ loading: loadingUsage,
237
+ children: formatMessage({
238
+ id: getTranslation("UsageSection.refresh"),
239
+ defaultMessage: "Refresh"
240
+ })
241
+ }
242
+ )
243
+ ] }),
244
+ loadingUsage ? /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { small: true, children: formatMessage({
245
+ id: getTranslation("UsageSection.loading"),
246
+ defaultMessage: "Loading usage details..."
247
+ }) }) }) }) : component.usageCount === 0 ? /* @__PURE__ */ jsxRuntime.jsx(
248
+ designSystem.EmptyStateLayout,
249
+ {
250
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Information, { width: "64px", height: "64px" }),
251
+ content: formatMessage({
252
+ id: getTranslation("UsageSection.emptyState"),
253
+ defaultMessage: "This component is not used in any content"
254
+ })
255
+ }
256
+ ) : /* @__PURE__ */ jsxRuntime.jsx(UsageTable, { usage })
257
+ ] });
258
+ };
259
+ const DeleteDialog = ({
260
+ isOpen,
261
+ onClose,
262
+ onConfirm,
263
+ component,
264
+ isDeleting
265
+ }) => {
266
+ const { formatMessage } = reactIntl.useIntl();
267
+ if (!component || !isOpen) return null;
268
+ return /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Root, { open: isOpen, onOpenChange: (open) => {
269
+ if (!open) onClose();
270
+ }, children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Content, { children: [
271
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Header, { children: formatMessage({
272
+ id: getTranslation("DeleteDialog.title"),
273
+ defaultMessage: "Confirm Deletion"
274
+ }) }),
275
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Body, { icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}), children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", alignItems: "stretch", gap: 2, children: [
276
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { children: formatMessage(
277
+ {
278
+ id: getTranslation("DeleteDialog.message"),
279
+ defaultMessage: 'Are you sure you want to delete the component "{name}"?'
280
+ },
281
+ { name: component.displayName }
282
+ ) }),
283
+ component.usageCount > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 3, background: "danger100", hasRadius: true, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "danger700", children: formatMessage(
284
+ {
285
+ id: getTranslation("DeleteDialog.warning"),
286
+ defaultMessage: "Warning: This component is used in {count, plural, one {# place} other {# places}}. Deleting it may break your content types."
287
+ },
288
+ { count: component.usageCount }
289
+ ) }) }),
290
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { textColor: "neutral600", variant: "pi", children: formatMessage({
291
+ id: getTranslation("DeleteDialog.irreversible"),
292
+ defaultMessage: "This action cannot be undone."
293
+ }) })
294
+ ] }) }),
295
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Dialog.Footer, { children: [
296
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Cancel, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Button, { onClick: onClose, variant: "tertiary", children: formatMessage({
297
+ id: getTranslation("DeleteDialog.cancel"),
298
+ defaultMessage: "Cancel"
299
+ }) }) }),
300
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Dialog.Action, { children: /* @__PURE__ */ jsxRuntime.jsx(
301
+ designSystem.Button,
302
+ {
303
+ onClick: onConfirm,
304
+ variant: "danger",
305
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
306
+ loading: isDeleting,
307
+ children: formatMessage({
308
+ id: getTranslation("DeleteDialog.confirm"),
309
+ defaultMessage: "Delete Component"
310
+ })
311
+ }
312
+ ) })
313
+ ] })
314
+ ] }) });
315
+ };
316
+ const DependencyCard = ({
317
+ title,
318
+ subtitle,
319
+ category,
320
+ badges = [],
321
+ metadata = [],
322
+ onClick
323
+ }) => {
324
+ return /* @__PURE__ */ jsxRuntime.jsx(
325
+ designSystem.Box,
326
+ {
327
+ padding: 4,
328
+ background: "neutral0",
329
+ hasRadius: true,
330
+ borderColor: "neutral200",
331
+ borderStyle: "solid",
332
+ borderWidth: "1px",
333
+ style: {
334
+ cursor: onClick ? "pointer" : "default",
335
+ transition: "all 0.2s ease",
336
+ width: "100%"
337
+ },
338
+ onClick,
339
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { direction: "column", gap: 2, children: [
340
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "flex-start", children: [
341
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { style: { flex: 1 }, children: [
342
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { fontWeight: "semiBold", textColor: "neutral800", children: title }),
343
+ subtitle && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: subtitle })
344
+ ] }),
345
+ category && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: "secondary", children: category })
346
+ ] }),
347
+ badges.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { gap: 2, wrap: "wrap", children: badges.map((badge, index2) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { variant: badge.variant || "default", size: "S", children: badge.label }, index2)) }),
348
+ metadata.length > 0 && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 1, children: metadata.map((item, index2) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral500", children: item }, index2)) })
349
+ ] })
350
+ }
351
+ );
352
+ };
353
+ const DependencySection = ({
354
+ title,
355
+ description,
356
+ icon: Icon,
357
+ count,
358
+ items = [],
359
+ defaultExpanded = true,
360
+ accentColor = "primary600"
361
+ }) => {
362
+ const [isExpanded, setIsExpanded] = react.useState(defaultExpanded);
363
+ if (items.length === 0) {
364
+ return null;
365
+ }
366
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { paddingBottom: 4, children: [
367
+ /* @__PURE__ */ jsxRuntime.jsx(
368
+ designSystem.Box,
369
+ {
370
+ padding: 3,
371
+ background: "neutral100",
372
+ hasRadius: true,
373
+ style: { cursor: "pointer" },
374
+ onClick: () => setIsExpanded(!isExpanded),
375
+ children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "center", children: [
376
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 3, alignItems: "center", children: [
377
+ Icon && /* @__PURE__ */ jsxRuntime.jsx(
378
+ designSystem.Box,
379
+ {
380
+ background: accentColor,
381
+ padding: 2,
382
+ hasRadius: true,
383
+ style: {
384
+ display: "flex",
385
+ alignItems: "center",
386
+ justifyContent: "center"
387
+ },
388
+ children: /* @__PURE__ */ jsxRuntime.jsx(Icon, { width: "16px", height: "16px", fill: "white" })
389
+ }
390
+ ),
391
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
392
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { gap: 2, alignItems: "center", children: [
393
+ /* @__PURE__ */ jsxRuntime.jsx(
394
+ designSystem.Typography,
395
+ {
396
+ variant: "omega",
397
+ fontWeight: "bold",
398
+ textColor: "neutral800",
399
+ children: title
400
+ }
401
+ ),
402
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Badge, { children: count })
403
+ ] }),
404
+ description && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: description })
405
+ ] })
406
+ ] }),
407
+ /* @__PURE__ */ jsxRuntime.jsx(
408
+ designSystem.IconButton,
409
+ {
410
+ onClick: (e) => {
411
+ e.stopPropagation();
412
+ setIsExpanded(!isExpanded);
413
+ },
414
+ label: isExpanded ? "Collapse" : "Expand",
415
+ children: isExpanded ? /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronUp, {}) : /* @__PURE__ */ jsxRuntime.jsx(icons.ChevronDown, {})
416
+ }
417
+ )
418
+ ] })
419
+ }
420
+ ),
421
+ isExpanded && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 3, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Root, { gap: 3, children: items.map((item, index2) => /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 12, direction: "column", children: /* @__PURE__ */ jsxRuntime.jsx(DependencyCard, { ...item }) }, index2)) }) })
422
+ ] });
423
+ };
424
+ const ComponentRelationships = ({
425
+ relationships
426
+ }) => {
427
+ const { formatMessage } = reactIntl.useIntl();
428
+ if (!relationships) {
429
+ return null;
430
+ }
431
+ const { uses, usedIn } = relationships;
432
+ const hasUses = uses && uses.length > 0;
433
+ const hasUsedIn = usedIn && usedIn.length > 0;
434
+ const usesItems = (uses || []).map((use) => {
435
+ const badges = [];
436
+ if (use.repeatable) {
437
+ badges.push({
438
+ label: formatMessage({
439
+ id: getTranslation("ComponentRelationships.badge.repeatable"),
440
+ defaultMessage: "Repeatable"
441
+ }),
442
+ variant: "secondary"
443
+ });
444
+ }
445
+ if (use.isDynamicZone) {
446
+ badges.push({
447
+ label: formatMessage({
448
+ id: getTranslation("ComponentRelationships.badge.dynamicZone"),
449
+ defaultMessage: "Dynamic Zone"
450
+ }),
451
+ variant: "primary"
452
+ });
453
+ }
454
+ return {
455
+ title: use.componentUid,
456
+ subtitle: formatMessage(
457
+ {
458
+ id: getTranslation("ComponentRelationships.field"),
459
+ defaultMessage: "Field: {name}"
460
+ },
461
+ { name: use.attributeName }
462
+ ),
463
+ badges,
464
+ metadata: []
465
+ };
466
+ });
467
+ const usedInItems = (usedIn || []).map((parent) => {
468
+ const metadata = [];
469
+ if (parent.attributes && parent.attributes.length > 0) {
470
+ const fieldNames = parent.attributes.map((attr) => attr.name).join(", ");
471
+ metadata.push(
472
+ formatMessage(
473
+ {
474
+ id: getTranslation("ComponentRelationships.fields"),
475
+ defaultMessage: "Fields: {names}"
476
+ },
477
+ { names: fieldNames }
478
+ )
479
+ );
480
+ }
481
+ const badges = parent.attributes ? parent.attributes.map((attr) => {
482
+ const labels = [];
483
+ if (attr.repeatable)
484
+ labels.push(
485
+ formatMessage({
486
+ id: getTranslation(
487
+ "ComponentRelationships.badge.repeatable"
488
+ ),
489
+ defaultMessage: "Repeatable"
490
+ }).toLowerCase()
491
+ );
492
+ if (attr.isDynamicZone)
493
+ labels.push(
494
+ formatMessage({
495
+ id: getTranslation(
496
+ "ComponentRelationships.badge.dynamicZone"
497
+ ),
498
+ defaultMessage: "Dynamic Zone"
499
+ }).toLowerCase()
500
+ );
501
+ return labels.length > 0 ? { label: labels.join(", "), variant: "secondary" } : null;
502
+ }).filter(Boolean) : [];
503
+ return {
504
+ title: parent.displayName,
505
+ subtitle: parent.componentUid,
506
+ category: parent.category,
507
+ badges,
508
+ metadata
509
+ };
510
+ });
511
+ if (!hasUses && !hasUsedIn) {
512
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, paddingTop: 0, children: [
513
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 4, children: formatMessage({
514
+ id: getTranslation("ComponentRelationships.title"),
515
+ defaultMessage: "Component Dependencies"
516
+ }) }),
517
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 4, background: "neutral100", hasRadius: true, children: [
518
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", fontWeight: "semiBold", children: formatMessage({
519
+ id: getTranslation(
520
+ "ComponentRelationships.noDependencies.title"
521
+ ),
522
+ defaultMessage: "No Dependencies"
523
+ }) }),
524
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: formatMessage({
525
+ id: getTranslation(
526
+ "ComponentRelationships.noDependencies.message"
527
+ ),
528
+ defaultMessage: "This component is independent - it doesn't use other components and isn't used by other components."
529
+ }) })
530
+ ] })
531
+ ] });
532
+ }
533
+ return /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { padding: 6, paddingTop: 0, children: [
534
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "delta", paddingBottom: 4, children: formatMessage({
535
+ id: getTranslation("ComponentRelationships.title"),
536
+ defaultMessage: "Component Dependencies"
537
+ }) }),
538
+ /* @__PURE__ */ jsxRuntime.jsxs(
539
+ designSystem.Box,
540
+ {
541
+ padding: 4,
542
+ background: "neutral100",
543
+ hasRadius: true,
544
+ borderColor: "neutral200",
545
+ borderStyle: "solid",
546
+ borderWidth: "1px",
547
+ children: [
548
+ hasUses && /* @__PURE__ */ jsxRuntime.jsx(
549
+ DependencySection,
550
+ {
551
+ title: formatMessage({
552
+ id: getTranslation("ComponentRelationships.uses.title"),
553
+ defaultMessage: "Uses Components"
554
+ }),
555
+ description: formatMessage({
556
+ id: getTranslation("ComponentRelationships.uses.description"),
557
+ defaultMessage: "Components nested within this component"
558
+ }),
559
+ icon: icons.ArrowRight,
560
+ count: uses.length,
561
+ items: usesItems,
562
+ defaultExpanded: true,
563
+ accentColor: "success600"
564
+ }
565
+ ),
566
+ hasUsedIn && /* @__PURE__ */ jsxRuntime.jsx(
567
+ DependencySection,
568
+ {
569
+ title: formatMessage({
570
+ id: getTranslation("ComponentRelationships.usedIn.title"),
571
+ defaultMessage: "Used In Components"
572
+ }),
573
+ description: formatMessage({
574
+ id: getTranslation("ComponentRelationships.usedIn.description"),
575
+ defaultMessage: "Components that include this component"
576
+ }),
577
+ icon: icons.ArrowLeft,
578
+ count: usedIn.length,
579
+ items: usedInItems,
580
+ defaultExpanded: true,
581
+ accentColor: "primary600"
582
+ }
583
+ ),
584
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingTop: 4, paddingLeft: 3, paddingRight: 3, children: /* @__PURE__ */ jsxRuntime.jsxs(
585
+ designSystem.Box,
586
+ {
587
+ padding: 3,
588
+ background: "neutral0",
589
+ hasRadius: true,
590
+ style: { borderLeft: "3px solid #4945FF" },
591
+ children: [
592
+ /* @__PURE__ */ jsxRuntime.jsx(
593
+ designSystem.Typography,
594
+ {
595
+ variant: "omega",
596
+ fontWeight: "semiBold",
597
+ textColor: "neutral700",
598
+ children: formatMessage({
599
+ id: getTranslation("ComponentRelationships.summary.title"),
600
+ defaultMessage: "Dependency Summary"
601
+ })
602
+ }
603
+ ),
604
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Typography, { variant: "pi", textColor: "neutral600", children: [
605
+ hasUses && formatMessage(
606
+ {
607
+ id: getTranslation(
608
+ "ComponentRelationships.summary.uses"
609
+ ),
610
+ defaultMessage: "Uses {count, plural, one {# component} other {# components}}"
611
+ },
612
+ { count: uses.length }
613
+ ),
614
+ hasUses && hasUsedIn && " • ",
615
+ hasUsedIn && formatMessage(
616
+ {
617
+ id: getTranslation(
618
+ "ComponentRelationships.summary.usedBy"
619
+ ),
620
+ defaultMessage: "Used by {count, plural, one {# component} other {# components}}"
621
+ },
622
+ { count: usedIn.length }
623
+ )
624
+ ] })
625
+ ]
626
+ }
627
+ ) })
628
+ ]
629
+ }
630
+ )
631
+ ] });
632
+ };
633
+ const ComponentDetail = ({ component, onDelete, onRefresh }) => {
634
+ const { formatMessage } = reactIntl.useIntl();
635
+ const { get, del } = admin.useFetchClient();
636
+ const { toggleNotification } = admin.useNotification();
637
+ const [isDeleteDialogOpen, setIsDeleteDialogOpen] = react.useState(false);
638
+ const [isDeleting, setIsDeleting] = react.useState(false);
639
+ const [usage, setUsage] = react.useState(null);
640
+ const [loadingUsage, setLoadingUsage] = react.useState(false);
641
+ const [relationships, setRelationships] = react.useState(null);
642
+ const [loadingRelationships, setLoadingRelationships] = react.useState(false);
643
+ react.useEffect(() => {
644
+ if (component) {
645
+ if (component.usageCount > 0) {
646
+ fetchUsageDetails();
647
+ } else {
648
+ setUsage([]);
649
+ }
650
+ fetchRelationships();
651
+ }
652
+ }, [component?.uid]);
653
+ const fetchUsageDetails = async () => {
654
+ if (!component) return;
655
+ try {
656
+ setLoadingUsage(true);
657
+ const { data } = await get(
658
+ `/${index.PLUGIN_ID}/components/${encodeURIComponent(component.uid)}/usage`
659
+ );
660
+ setUsage(data.data || []);
661
+ } catch (err) {
662
+ console.error("Error fetching usage details:", err);
663
+ toggleNotification({
664
+ type: "warning",
665
+ message: "Failed to load usage details"
666
+ });
667
+ setUsage([]);
668
+ } finally {
669
+ setLoadingUsage(false);
670
+ }
671
+ };
672
+ const fetchRelationships = async () => {
673
+ if (!component) return;
674
+ try {
675
+ setLoadingRelationships(true);
676
+ const { data } = await get(
677
+ `/${index.PLUGIN_ID}/components/${encodeURIComponent(component.uid)}/relationships`
678
+ );
679
+ setRelationships(data.data || null);
680
+ } catch (err) {
681
+ console.error("Error fetching relationships:", err);
682
+ setRelationships(null);
683
+ } finally {
684
+ setLoadingRelationships(false);
685
+ }
686
+ };
687
+ const handleDelete = async () => {
688
+ try {
689
+ setIsDeleting(true);
690
+ await del(
691
+ `/${index.PLUGIN_ID}/components/${encodeURIComponent(component.uid)}`
692
+ );
693
+ toggleNotification({
694
+ type: "success",
695
+ message: `Component "${component.displayName}" deleted successfully`
696
+ });
697
+ setIsDeleteDialogOpen(false);
698
+ onDelete();
699
+ onRefresh();
700
+ } catch (err) {
701
+ console.error("Error deleting component:", err);
702
+ toggleNotification({
703
+ type: "warning",
704
+ message: err?.response?.data?.error?.message || "Failed to delete component. It may be in use."
705
+ });
706
+ } finally {
707
+ setIsDeleting(false);
708
+ }
709
+ };
710
+ if (!component) {
711
+ return /* @__PURE__ */ jsxRuntime.jsx(
712
+ designSystem.Box,
713
+ {
714
+ background: "neutral0",
715
+ hasRadius: true,
716
+ shadow: "tableShadow",
717
+ height: "calc(100vh - 200px)",
718
+ children: /* @__PURE__ */ jsxRuntime.jsx(
719
+ designSystem.EmptyStateLayout,
720
+ {
721
+ icon: /* @__PURE__ */ jsxRuntime.jsx(icons.Puzzle, { width: "10rem", height: "10rem" }),
722
+ content: formatMessage({
723
+ id: getTranslation("ComponentDetail.emptyState"),
724
+ defaultMessage: "Choose a component from the list to view its details and usage"
725
+ })
726
+ }
727
+ )
728
+ }
729
+ );
730
+ }
731
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
732
+ /* @__PURE__ */ jsxRuntime.jsxs(
733
+ designSystem.Box,
734
+ {
735
+ background: "neutral0",
736
+ hasRadius: true,
737
+ shadow: "tableShadow",
738
+ overflow: "auto",
739
+ height: "calc(100vh - 200px)",
740
+ children: [
741
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 6, borderColor: "neutral200", children: /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Flex, { justifyContent: "space-between", alignItems: "flex-start", children: [
742
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Box, { children: [
743
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "beta", tag: "h2", children: `${component.category}/${component.displayName}` }),
744
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Typography, { variant: "omega", textColor: "neutral600", children: component.uid })
745
+ ] }),
746
+ /* @__PURE__ */ jsxRuntime.jsx(
747
+ designSystem.Button,
748
+ {
749
+ variant: "danger-light",
750
+ startIcon: /* @__PURE__ */ jsxRuntime.jsx(icons.Trash, {}),
751
+ onClick: () => setIsDeleteDialogOpen(true),
752
+ children: formatMessage({
753
+ id: getTranslation("ComponentDetail.deleteButton"),
754
+ defaultMessage: "Delete Component"
755
+ })
756
+ }
757
+ )
758
+ ] }) }),
759
+ /* @__PURE__ */ jsxRuntime.jsx(
760
+ UsageSection,
761
+ {
762
+ component,
763
+ usage,
764
+ loadingUsage,
765
+ onRefresh: fetchUsageDetails
766
+ }
767
+ ),
768
+ !loadingRelationships && /* @__PURE__ */ jsxRuntime.jsx(ComponentRelationships, { relationships })
769
+ ]
770
+ }
771
+ ),
772
+ /* @__PURE__ */ jsxRuntime.jsx(
773
+ DeleteDialog,
774
+ {
775
+ isOpen: isDeleteDialogOpen,
776
+ onClose: () => setIsDeleteDialogOpen(false),
777
+ onConfirm: handleDelete,
778
+ component,
779
+ isDeleting
780
+ }
781
+ )
782
+ ] });
783
+ };
784
+ const HomePage = () => {
785
+ const { formatMessage } = reactIntl.useIntl();
786
+ const { get } = admin.useFetchClient();
787
+ const { toggleNotification } = admin.useNotification();
788
+ const [components, setComponents] = react.useState([]);
789
+ const [loading, setLoading] = react.useState(true);
790
+ const [error, setError] = react.useState(null);
791
+ const [selectedComponent, setSelectedComponent] = react.useState(null);
792
+ const [searchQuery, setSearchQuery] = react.useState("");
793
+ react.useEffect(() => {
794
+ fetchComponents();
795
+ }, []);
796
+ const fetchComponents = async () => {
797
+ try {
798
+ setLoading(true);
799
+ const { data } = await get(`/${index.PLUGIN_ID}/components`);
800
+ setComponents(data.data || []);
801
+ setError(null);
802
+ } catch (err) {
803
+ console.error("Error fetching components:", err);
804
+ setError(
805
+ formatMessage({
806
+ id: getTranslation("HomePage.error"),
807
+ defaultMessage: "Failed to load components. Please try again."
808
+ })
809
+ );
810
+ } finally {
811
+ setLoading(false);
812
+ }
813
+ };
814
+ const handleDeleteComplete = () => {
815
+ setSelectedComponent(null);
816
+ };
817
+ const calculateTotalUsages = () => {
818
+ return components.reduce((sum, c) => sum + c.usageCount, 0);
819
+ };
820
+ if (loading) {
821
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
822
+ /* @__PURE__ */ jsxRuntime.jsx(
823
+ admin.Layouts.Header,
824
+ {
825
+ title: formatMessage({
826
+ id: getTranslation("HomePage.title"),
827
+ defaultMessage: "Component Usage"
828
+ }),
829
+ subtitle: formatMessage({
830
+ id: getTranslation("plugin.description"),
831
+ defaultMessage: "View all components and their usage across content types"
832
+ })
833
+ }
834
+ ),
835
+ /* @__PURE__ */ jsxRuntime.jsx(admin.Layouts.Content, { children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 8, background: "neutral0", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Flex, { justifyContent: "center", children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Loader, { children: formatMessage({
836
+ id: getTranslation("HomePage.loading"),
837
+ defaultMessage: "Loading components..."
838
+ }) }) }) }) })
839
+ ] });
840
+ }
841
+ return /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Root, { children: [
842
+ /* @__PURE__ */ jsxRuntime.jsx(
843
+ admin.Layouts.Header,
844
+ {
845
+ title: formatMessage({
846
+ id: getTranslation("HomePage.title"),
847
+ defaultMessage: "Component Usage"
848
+ }),
849
+ subtitle: formatMessage(
850
+ {
851
+ id: getTranslation("HomePage.subtitle"),
852
+ defaultMessage: "{count, plural, =0 {No components} one {# component} other {# components}} • {usages, plural, =0 {no usage} one {# total usage} other {# total usages}}"
853
+ },
854
+ { count: components.length, usages: calculateTotalUsages() }
855
+ )
856
+ }
857
+ ),
858
+ /* @__PURE__ */ jsxRuntime.jsxs(admin.Layouts.Content, { children: [
859
+ error && /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { paddingBottom: 4, children: /* @__PURE__ */ jsxRuntime.jsx(designSystem.Box, { padding: 4, background: "danger100", hasRadius: true, children: error }) }),
860
+ /* @__PURE__ */ jsxRuntime.jsxs(designSystem.Grid.Root, { gap: 4, children: [
861
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 4, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsx(
862
+ ComponentList,
863
+ {
864
+ components,
865
+ selectedComponent,
866
+ onSelectComponent: setSelectedComponent,
867
+ searchQuery,
868
+ onSearchChange: setSearchQuery
869
+ }
870
+ ) }),
871
+ /* @__PURE__ */ jsxRuntime.jsx(designSystem.Grid.Item, { col: 8, direction: "column", alignItems: "stretch", children: /* @__PURE__ */ jsxRuntime.jsx(
872
+ ComponentDetail,
873
+ {
874
+ component: selectedComponent,
875
+ onDelete: handleDeleteComplete,
876
+ onRefresh: fetchComponents
877
+ }
878
+ ) })
879
+ ] })
880
+ ] })
881
+ ] });
882
+ };
883
+ exports.HomePage = HomePage;