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