@strapi-community/plugin-better-auth-dashboard 1.0.0-beta.1 → 1.0.0-beta.2

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.
Files changed (45) hide show
  1. package/dist/admin/Avatar-CdT7eBCh.js +39 -0
  2. package/dist/admin/Avatar-zlMTAZh3.mjs +40 -0
  3. package/dist/admin/{Root-2FMy-qQg.mjs → Root-CgVBJuoM.mjs} +12 -4
  4. package/dist/admin/{Root-DDi0mtve.js → Root-CsiOnzlB.js} +12 -4
  5. package/dist/admin/cs-C_jSgeON.js +416 -0
  6. package/dist/admin/cs-wtnknvBC.mjs +416 -0
  7. package/dist/admin/de-Cg5HyoCY.js +416 -0
  8. package/dist/admin/de-DnnVdUxk.mjs +416 -0
  9. package/dist/admin/en-B3utEgMT.mjs +416 -0
  10. package/dist/admin/en-Cz1IJEvp.js +416 -0
  11. package/dist/admin/es-CGA0Tslt.js +416 -0
  12. package/dist/admin/es-RoCEriJL.mjs +416 -0
  13. package/dist/admin/fr-Ca74t21t.js +416 -0
  14. package/dist/admin/fr-CtmuSfiQ.mjs +416 -0
  15. package/dist/admin/{index-BHfYu0uA.js → index-BCBu-0nr.js} +65 -10
  16. package/dist/admin/{index-D9CiI8Yr.mjs → index-BI2WIsds.mjs} +73 -18
  17. package/dist/admin/index-CPSoAbZt.js +2321 -0
  18. package/dist/admin/index-D9ttdrT3.js +2716 -0
  19. package/dist/admin/index-DQdn5s8q.js +1184 -0
  20. package/dist/admin/index-DvfwvCj0.mjs +1182 -0
  21. package/dist/admin/index-VyHXoQ6p.mjs +2321 -0
  22. package/dist/admin/index-gFqHlMLP.mjs +2716 -0
  23. package/dist/admin/index.js +1 -1
  24. package/dist/admin/index.mjs +1 -1
  25. package/dist/admin/it-BAB9hDdp.mjs +416 -0
  26. package/dist/admin/it-dYT2_ERV.js +416 -0
  27. package/dist/admin/nl-CVqSbUU9.js +416 -0
  28. package/dist/admin/nl-CYv26PHv.mjs +416 -0
  29. package/dist/admin/pl-Bc0cS2mD.mjs +416 -0
  30. package/dist/admin/pl-HYF4BxRv.js +416 -0
  31. package/dist/admin/pt-DDUGyLtb.js +416 -0
  32. package/dist/admin/pt-DZyyK3EG.mjs +416 -0
  33. package/dist/admin/{useModelSchema-CqOTI7P1.js → useModelSchema-BogQah8f.js} +309 -287
  34. package/dist/admin/{useModelSchema-B2tmzlwQ.mjs → useModelSchema-C6bxNjW4.mjs} +320 -298
  35. package/dist/server/index.js +15 -15
  36. package/dist/server/index.mjs +15 -15
  37. package/package.json +4 -4
  38. package/dist/admin/Avatar-Cvk89aMx.js +0 -67
  39. package/dist/admin/Avatar-DNczJU0i.mjs +0 -66
  40. package/dist/admin/index-BQi_BUi6.mjs +0 -1968
  41. package/dist/admin/index-BV-4M_Fp.js +0 -1970
  42. package/dist/admin/index-CANDPUGD.mjs +0 -1215
  43. package/dist/admin/index-CqR8WpOa.js +0 -1217
  44. package/dist/admin/index-D6ktNZcj.js +0 -1685
  45. package/dist/admin/index-DszX3ibA.mjs +0 -1683
@@ -0,0 +1,1182 @@
1
+ import { jsx, jsxs } from "react/jsx-runtime";
2
+ import { Flex, Button, Loader, Main, Grid, Typography, Box, SingleSelect, SingleSelectOption, Divider, Badge } from "@strapi/design-system";
3
+ import { Page, useFetchClient, Layouts } from "@strapi/strapi/admin";
4
+ import { createContext, useContext, useState, useRef, useEffect } from "react";
5
+ import { useIntl } from "react-intl";
6
+ import { useQuery } from "react-query";
7
+ import styled, { useTheme } from "styled-components";
8
+ import { c as PERMISSIONS, P as PLUGIN_ID, g as getTranslation, d as client } from "./index-BI2WIsds.mjs";
9
+ import { A as Avatar } from "./Avatar-zlMTAZh3.mjs";
10
+ const SegmentedControlContext = createContext(null);
11
+ const Root = ({ value, onChange, label, children }) => /* @__PURE__ */ jsx(SegmentedControlContext.Provider, { value: { value, onChange }, children: /* @__PURE__ */ jsx(
12
+ Flex,
13
+ {
14
+ role: "group",
15
+ "aria-label": label,
16
+ gap: 1,
17
+ padding: 1,
18
+ background: "neutral100",
19
+ borderColor: "neutral200",
20
+ borderStyle: "solid",
21
+ borderWidth: "1px",
22
+ hasRadius: true,
23
+ children
24
+ }
25
+ ) });
26
+ const Item = ({ value, disabled, children }) => {
27
+ const ctx = useContext(SegmentedControlContext);
28
+ if (!ctx) throw new Error("SegmentedControl.Item must be used inside Root");
29
+ const active = ctx.value === value;
30
+ return /* @__PURE__ */ jsx(
31
+ Button,
32
+ {
33
+ size: "S",
34
+ variant: active ? "tertiary" : "ghost",
35
+ "aria-pressed": active,
36
+ disabled,
37
+ onClick: () => !active && ctx.onChange(value),
38
+ children
39
+ }
40
+ );
41
+ };
42
+ const SegmentedControl = { Root, Item };
43
+ const CARD = {
44
+ background: "neutral0",
45
+ hasRadius: true,
46
+ shadow: "tableShadow"
47
+ };
48
+ const TwoPanel = styled.div`
49
+ display: grid;
50
+ grid-template-columns: minmax(0, 1fr) 30rem;
51
+ gap: ${({ theme }) => theme.spaces[4]};
52
+ align-items: stretch;
53
+ @media (max-width: 900px) { grid-template-columns: 1fr; }
54
+ `;
55
+ const FeedSlot = styled.div`
56
+ position: relative;
57
+
58
+ & > * {
59
+ position: absolute;
60
+ inset: 0;
61
+ }
62
+
63
+ @media (max-width: 900px) {
64
+ min-height: 32rem;
65
+ }
66
+ `;
67
+ function SectionDivider({ label }) {
68
+ return /* @__PURE__ */ jsxs(Flex, { gap: 3, alignItems: "center", children: [
69
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: label }),
70
+ /* @__PURE__ */ jsx(Box, { flex: 1, children: /* @__PURE__ */ jsx(Divider, {}) })
71
+ ] });
72
+ }
73
+ function Empty({ children }) {
74
+ return /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 8, children: /* @__PURE__ */ jsx(Typography, { variant: "omega", textColor: "neutral500", children }) });
75
+ }
76
+ function Dot({ color }) {
77
+ return /* @__PURE__ */ jsx(
78
+ Box,
79
+ {
80
+ tag: "span",
81
+ background: color,
82
+ width: "0.8rem",
83
+ height: "0.8rem",
84
+ display: "inline-block",
85
+ shrink: 0,
86
+ style: { borderRadius: "50%" },
87
+ "aria-hidden": true
88
+ }
89
+ );
90
+ }
91
+ function TrendBadge({ pct }) {
92
+ const isPos = pct >= 0;
93
+ return /* @__PURE__ */ jsxs(Badge, { size: "S", variant: isPos ? "success" : "danger", children: [
94
+ isPos ? "↑" : "↓",
95
+ " ",
96
+ Math.abs(pct).toFixed(1),
97
+ "%"
98
+ ] });
99
+ }
100
+ function useCountUp(target, duration = 850) {
101
+ const [count, setCount] = useState(0);
102
+ const prevRef = useRef(0);
103
+ useEffect(() => {
104
+ let raf;
105
+ let start = 0;
106
+ const from = prevRef.current;
107
+ const diff = target - from;
108
+ const step = (ts) => {
109
+ if (!start) start = ts;
110
+ const progress = Math.min((ts - start) / duration, 1);
111
+ const eased = 1 - (1 - progress) ** 3;
112
+ setCount(Math.round(from + eased * diff));
113
+ if (progress < 1) raf = requestAnimationFrame(step);
114
+ else prevRef.current = target;
115
+ };
116
+ raf = requestAnimationFrame(step);
117
+ return () => cancelAnimationFrame(raf);
118
+ }, [target, duration]);
119
+ return count;
120
+ }
121
+ function Sparkline({
122
+ data,
123
+ color,
124
+ id
125
+ }) {
126
+ const W = 300;
127
+ const H = 44;
128
+ if (data.length < 2) return /* @__PURE__ */ jsx("svg", { width: "100%", height: H });
129
+ const min = Math.min(...data);
130
+ const max = Math.max(...data, min + 1);
131
+ const pts = data.map((v, i) => ({
132
+ x: i / (data.length - 1) * W,
133
+ y: H - 3 - (v - min) / (max - min) * (H - 8)
134
+ }));
135
+ const line = pts.reduce((a, p, i) => {
136
+ if (i === 0) return `M ${p.x} ${p.y}`;
137
+ const pr = pts[i - 1];
138
+ const cx = (pr.x + p.x) / 2;
139
+ return `${a} C ${cx} ${pr.y} ${cx} ${p.y} ${p.x} ${p.y}`;
140
+ }, "");
141
+ const area = `${line} L ${pts.at(-1).x} ${H} L 0 ${H} Z`;
142
+ const gid = `spk-${id}`;
143
+ return /* @__PURE__ */ jsxs(
144
+ "svg",
145
+ {
146
+ width: "100%",
147
+ height: H,
148
+ viewBox: `0 0 ${W} ${H}`,
149
+ preserveAspectRatio: "none",
150
+ "aria-hidden": true,
151
+ children: [
152
+ /* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs("linearGradient", { id: gid, x1: "0", y1: "0", x2: "0", y2: "1", children: [
153
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: color, stopOpacity: "0.32" }),
154
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: color, stopOpacity: "0" })
155
+ ] }) }),
156
+ /* @__PURE__ */ jsx("path", { d: area, fill: `url(#${gid})` }),
157
+ /* @__PURE__ */ jsx(
158
+ "path",
159
+ {
160
+ d: line,
161
+ stroke: color,
162
+ strokeWidth: "1.6",
163
+ fill: "none",
164
+ strokeLinecap: "round"
165
+ }
166
+ )
167
+ ]
168
+ }
169
+ );
170
+ }
171
+ const ALL_SERIES = [
172
+ {
173
+ key: "totalUsers",
174
+ color: "primary600",
175
+ label: {
176
+ id: getTranslation("overview.growth.series.total"),
177
+ defaultMessage: "Total"
178
+ }
179
+ },
180
+ {
181
+ key: "newUsers",
182
+ color: "success500",
183
+ label: {
184
+ id: getTranslation("overview.growth.series.new"),
185
+ defaultMessage: "New"
186
+ }
187
+ },
188
+ {
189
+ key: "activeUsers",
190
+ color: "warning500",
191
+ label: {
192
+ id: getTranslation("overview.growth.series.active"),
193
+ defaultMessage: "Active"
194
+ }
195
+ }
196
+ ];
197
+ function GrowthChart({
198
+ data,
199
+ hovered,
200
+ onHover,
201
+ activeSeries
202
+ }) {
203
+ const { colors } = useTheme();
204
+ const { formatMessage } = useIntl();
205
+ const W = 600;
206
+ const H = 200;
207
+ const PL = 40;
208
+ const PR = 12;
209
+ const PT = 12;
210
+ const PB = 28;
211
+ const CW = W - PL - PR;
212
+ const CH = H - PT - PB;
213
+ if (!data.length) {
214
+ return /* @__PURE__ */ jsx(Empty, { children: formatMessage({
215
+ id: getTranslation("overview.growth.empty"),
216
+ defaultMessage: "No growth data for this period"
217
+ }) });
218
+ }
219
+ const active = ALL_SERIES.filter((s) => activeSeries.has(s.key));
220
+ const allVals = data.flatMap((d) => active.map((s) => d[s.key]));
221
+ const maxV = Math.max(...allVals, 10);
222
+ const yMax = Math.ceil(maxV * 1.15);
223
+ const xp = (i) => PL + i / Math.max(data.length - 1, 1) * CW;
224
+ const yp = (v) => PT + (1 - v / yMax) * CH;
225
+ const smooth = (vals) => {
226
+ const pts = vals.map((v, i) => ({ x: xp(i), y: yp(v) }));
227
+ const path = pts.reduce((a, p, i) => {
228
+ if (i === 0) return `M ${p.x} ${p.y}`;
229
+ const pr = pts[i - 1];
230
+ const cx = (pr.x + p.x) / 2;
231
+ return `${a} C ${cx} ${pr.y} ${cx} ${p.y} ${p.x} ${p.y}`;
232
+ }, "");
233
+ const area = `${path} L ${pts.at(-1).x} ${PT + CH} L ${pts[0].x} ${PT + CH} Z`;
234
+ return { pts, path, area };
235
+ };
236
+ const lines = active.map((s) => ({
237
+ ...s,
238
+ hex: colors[s.color],
239
+ ...smooth(data.map((d) => d[s.key]))
240
+ }));
241
+ const yTicks = Array.from({ length: 5 }, (_, i) => {
242
+ const v = yMax * (4 - i) / 4;
243
+ return { v: Math.round(v), y: yp(v) };
244
+ });
245
+ const step = Math.max(1, Math.ceil(data.length / 8));
246
+ const segW = CW / Math.max(data.length - 1, 1);
247
+ return /* @__PURE__ */ jsxs(
248
+ "svg",
249
+ {
250
+ width: "100%",
251
+ viewBox: `0 0 ${W} ${H}`,
252
+ preserveAspectRatio: "xMidYMid meet",
253
+ style: { display: "block" },
254
+ "aria-label": formatMessage({
255
+ id: getTranslation("overview.growth.chartLabel"),
256
+ defaultMessage: "User growth chart"
257
+ }),
258
+ role: "img",
259
+ children: [
260
+ /* @__PURE__ */ jsx("defs", { children: lines.map((s) => /* @__PURE__ */ jsxs(
261
+ "linearGradient",
262
+ {
263
+ id: `ag-${s.key}`,
264
+ x1: "0",
265
+ y1: "0",
266
+ x2: "0",
267
+ y2: "1",
268
+ children: [
269
+ /* @__PURE__ */ jsx("stop", { offset: "0%", stopColor: s.hex, stopOpacity: "0.18" }),
270
+ /* @__PURE__ */ jsx("stop", { offset: "100%", stopColor: s.hex, stopOpacity: "0" })
271
+ ]
272
+ },
273
+ s.key
274
+ )) }),
275
+ yTicks.map((t) => /* @__PURE__ */ jsxs("g", { children: [
276
+ /* @__PURE__ */ jsx(
277
+ "line",
278
+ {
279
+ x1: PL,
280
+ y1: t.y,
281
+ x2: W - PR,
282
+ y2: t.y,
283
+ stroke: colors.neutral150,
284
+ strokeWidth: "1"
285
+ }
286
+ ),
287
+ /* @__PURE__ */ jsx(
288
+ "text",
289
+ {
290
+ x: PL - 6,
291
+ y: t.y + 4,
292
+ textAnchor: "end",
293
+ fill: colors.neutral500,
294
+ fontSize: "9",
295
+ children: t.v >= 1e3 ? `${(t.v / 1e3).toFixed(1)}k` : t.v
296
+ }
297
+ )
298
+ ] }, t.v)),
299
+ /* @__PURE__ */ jsx(
300
+ "line",
301
+ {
302
+ x1: PL,
303
+ y1: PT + CH,
304
+ x2: W - PR,
305
+ y2: PT + CH,
306
+ stroke: colors.neutral150,
307
+ strokeWidth: "1"
308
+ }
309
+ ),
310
+ lines.map((s) => /* @__PURE__ */ jsx("path", { d: s.area, fill: `url(#ag-${s.key})` }, `area-${s.key}`)),
311
+ lines.map((s) => /* @__PURE__ */ jsx(
312
+ "path",
313
+ {
314
+ d: s.path,
315
+ stroke: s.hex,
316
+ strokeWidth: "2",
317
+ fill: "none",
318
+ strokeLinecap: "round",
319
+ strokeLinejoin: "round"
320
+ },
321
+ `line-${s.key}`
322
+ )),
323
+ data.map((d, i) => {
324
+ if (i % step !== 0 && i !== data.length - 1) return null;
325
+ return /* @__PURE__ */ jsx(
326
+ "text",
327
+ {
328
+ x: xp(i),
329
+ y: H - 6,
330
+ textAnchor: "middle",
331
+ fill: colors.neutral500,
332
+ fontSize: "9",
333
+ children: d.label
334
+ },
335
+ i
336
+ );
337
+ }),
338
+ data.map((d, i) => {
339
+ const isHov = hovered === i;
340
+ return (
341
+ // biome-ignore lint/a11y/noStaticElementInteractions: SVG chart hover
342
+ /* @__PURE__ */ jsxs(
343
+ "g",
344
+ {
345
+ onMouseEnter: () => onHover(i),
346
+ onMouseLeave: () => onHover(null),
347
+ style: { cursor: "crosshair" },
348
+ children: [
349
+ /* @__PURE__ */ jsx(
350
+ "rect",
351
+ {
352
+ x: xp(i) - segW / 2,
353
+ y: PT,
354
+ width: segW,
355
+ height: CH,
356
+ fill: "transparent"
357
+ }
358
+ ),
359
+ isHov && /* @__PURE__ */ jsx(
360
+ "line",
361
+ {
362
+ x1: xp(i),
363
+ y1: PT,
364
+ x2: xp(i),
365
+ y2: PT + CH,
366
+ stroke: colors.neutral300,
367
+ strokeWidth: "1",
368
+ strokeDasharray: "3 3"
369
+ }
370
+ ),
371
+ lines.map((s) => /* @__PURE__ */ jsx(
372
+ "circle",
373
+ {
374
+ cx: xp(i),
375
+ cy: yp(d[s.key]),
376
+ r: isHov ? 4 : 2.5,
377
+ fill: s.hex,
378
+ stroke: colors.neutral0,
379
+ strokeWidth: isHov ? 2 : 1,
380
+ opacity: isHov ? 1 : 0.5
381
+ },
382
+ s.key
383
+ ))
384
+ ]
385
+ },
386
+ i
387
+ )
388
+ );
389
+ })
390
+ ]
391
+ }
392
+ );
393
+ }
394
+ function ProgressRing({
395
+ value,
396
+ max,
397
+ color,
398
+ size = 56
399
+ }) {
400
+ const { colors } = useTheme();
401
+ const r = (size - 9) / 2;
402
+ const circ = 2 * Math.PI * r;
403
+ const pct = Math.min(value / Math.max(max, 1), 1);
404
+ const offset = circ * (1 - pct);
405
+ return /* @__PURE__ */ jsxs(
406
+ "svg",
407
+ {
408
+ width: size,
409
+ height: size,
410
+ style: { flexShrink: 0 },
411
+ "aria-hidden": true,
412
+ children: [
413
+ /* @__PURE__ */ jsx(
414
+ "circle",
415
+ {
416
+ cx: size / 2,
417
+ cy: size / 2,
418
+ r,
419
+ fill: "none",
420
+ stroke: colors.neutral150,
421
+ strokeWidth: "7"
422
+ }
423
+ ),
424
+ /* @__PURE__ */ jsx(
425
+ "circle",
426
+ {
427
+ cx: size / 2,
428
+ cy: size / 2,
429
+ r,
430
+ fill: "none",
431
+ stroke: colors[color],
432
+ strokeWidth: "7",
433
+ strokeDasharray: `${circ} ${circ}`,
434
+ strokeDashoffset: offset,
435
+ strokeLinecap: "round",
436
+ transform: `rotate(-90 ${size / 2} ${size / 2})`,
437
+ style: {
438
+ transition: "stroke-dashoffset 0.9s cubic-bezier(0.4,0,0.2,1)"
439
+ }
440
+ }
441
+ )
442
+ ]
443
+ }
444
+ );
445
+ }
446
+ function relTime(date, formatMessage) {
447
+ const diff = Date.now() - new Date(date).getTime();
448
+ const mins = Math.floor(diff / 6e4);
449
+ if (mins < 1)
450
+ return formatMessage({
451
+ id: getTranslation("overview.relTime.justNow"),
452
+ defaultMessage: "just now"
453
+ });
454
+ if (mins < 60)
455
+ return formatMessage(
456
+ {
457
+ id: getTranslation("overview.relTime.minutes"),
458
+ defaultMessage: "{count}m"
459
+ },
460
+ { count: mins }
461
+ );
462
+ const hrs = Math.floor(mins / 60);
463
+ if (hrs < 24)
464
+ return formatMessage(
465
+ {
466
+ id: getTranslation("overview.relTime.hours"),
467
+ defaultMessage: "{count}h"
468
+ },
469
+ { count: hrs }
470
+ );
471
+ return formatMessage(
472
+ {
473
+ id: getTranslation("overview.relTime.days"),
474
+ defaultMessage: "{count}d"
475
+ },
476
+ { count: Math.floor(hrs / 24) }
477
+ );
478
+ }
479
+ function rateColor(r) {
480
+ return r >= 70 ? "success500" : r >= 40 ? "warning500" : "danger500";
481
+ }
482
+ function StatItem({
483
+ id,
484
+ label,
485
+ value,
486
+ pct,
487
+ sparkline,
488
+ color
489
+ }) {
490
+ const { colors } = useTheme();
491
+ const animated = useCountUp(value);
492
+ return /* @__PURE__ */ jsxs(
493
+ Flex,
494
+ {
495
+ ...CARD,
496
+ direction: "column",
497
+ alignItems: "stretch",
498
+ overflow: "hidden",
499
+ height: "100%",
500
+ children: [
501
+ /* @__PURE__ */ jsx(Box, { height: "0.2rem", background: color }),
502
+ /* @__PURE__ */ jsxs(
503
+ Flex,
504
+ {
505
+ direction: "column",
506
+ alignItems: "flex-start",
507
+ gap: 2,
508
+ paddingTop: 4,
509
+ paddingLeft: 5,
510
+ paddingRight: 5,
511
+ paddingBottom: sparkline && sparkline.length > 1 ? 2 : 5,
512
+ children: [
513
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: label }),
514
+ /* @__PURE__ */ jsx(Typography, { variant: "alpha", tag: "p", textColor: "neutral800", children: animated.toLocaleString() }),
515
+ pct !== void 0 && /* @__PURE__ */ jsx(TrendBadge, { pct })
516
+ ]
517
+ }
518
+ ),
519
+ sparkline && sparkline.length > 1 && /* @__PURE__ */ jsx(Box, { marginTop: "auto", height: "4.4rem", children: /* @__PURE__ */ jsx(Sparkline, { data: sparkline, color: colors[color], id }) })
520
+ ]
521
+ }
522
+ );
523
+ }
524
+ function OverviewPage() {
525
+ const { get } = useFetchClient();
526
+ const { formatMessage } = useIntl();
527
+ const failedMessage = formatMessage({
528
+ id: getTranslation("overview.common.failed"),
529
+ defaultMessage: "Failed"
530
+ });
531
+ const recentSignUpsMessage = {
532
+ id: getTranslation("overview.feed.recentSignUps"),
533
+ defaultMessage: "Recent Sign-ups"
534
+ };
535
+ const recentlyActiveMessage = {
536
+ id: getTranslation("overview.feed.recentlyActive"),
537
+ defaultMessage: "Recently Active"
538
+ };
539
+ const loadingMessage = formatMessage({
540
+ id: getTranslation("overview.common.loading"),
541
+ defaultMessage: "Loading…"
542
+ });
543
+ const [period, setPeriod] = useState(
544
+ "weekly"
545
+ );
546
+ const [feedMode, setFeedMode] = useState("signups");
547
+ const [hovIdx, setHovIdx] = useState(null);
548
+ const [rtnHov, setRtnHov] = useState(null);
549
+ const [activeSeries, setActiveSeries] = useState(
550
+ () => /* @__PURE__ */ new Set(["totalUsers", "newUsers", "activeUsers"])
551
+ );
552
+ const toggleSeries = (key) => {
553
+ setActiveSeries((prev) => {
554
+ const next = new Set(prev);
555
+ if (next.has(key) && next.size > 1) next.delete(key);
556
+ else next.add(key);
557
+ return next;
558
+ });
559
+ };
560
+ const statsQuery = useQuery({
561
+ queryKey: ["dash-user-stats"],
562
+ queryFn: async () => {
563
+ const r = await client.dash.userStats();
564
+ if (r.error) throw new Error(r.error.message ?? failedMessage);
565
+ return r.data;
566
+ }
567
+ });
568
+ const graphQuery = useQuery({
569
+ queryKey: ["dash-user-graph", period],
570
+ queryFn: async () => {
571
+ const r = await client.dash.userGraphData({ query: { period } });
572
+ if (r.error) throw new Error(r.error.message ?? failedMessage);
573
+ return r.data;
574
+ }
575
+ });
576
+ const retentionQuery = useQuery({
577
+ queryKey: ["dash-user-retention", period],
578
+ queryFn: async () => {
579
+ const r = await client.dash.userRetentionData({ query: { period } });
580
+ if (r.error) throw new Error(r.error.message ?? failedMessage);
581
+ return r.data;
582
+ }
583
+ });
584
+ const usersQuery = useQuery({
585
+ queryKey: ["dash-recent-users"],
586
+ queryFn: async () => {
587
+ const r = await client.dash.listUsers({
588
+ query: { limit: 12, offset: 0, sortBy: "createdAt", sortOrder: "desc" }
589
+ });
590
+ if (r.error) throw new Error(r.error.message ?? failedMessage);
591
+ return r.data;
592
+ }
593
+ });
594
+ const sessionsQuery = useQuery({
595
+ queryKey: ["dash-recent-sessions"],
596
+ queryFn: async () => {
597
+ const { data } = await get(
598
+ "/better-auth-dashboard/db?uid=plugin::better-auth.session&pagination[pageSize]=12&sort[0]=updatedAt:desc"
599
+ );
600
+ return data.results ?? [];
601
+ },
602
+ refetchInterval: 3e4
603
+ });
604
+ const orgsQuery = useQuery({
605
+ queryKey: ["dash-orgs-count"],
606
+ queryFn: async () => {
607
+ const r = await client.dash.listOrganizations({
608
+ query: { page: 1, limit: 1 }
609
+ });
610
+ if (r.error) throw new Error(r.error.message ?? failedMessage);
611
+ return r.data;
612
+ }
613
+ });
614
+ const sessionsRaw = sessionsQuery.data ?? [];
615
+ const activeUserIds = [];
616
+ const lastActiveByUserId = /* @__PURE__ */ new Map();
617
+ for (const s of sessionsRaw) {
618
+ const uid = String(s.userId);
619
+ if (!lastActiveByUserId.has(uid)) {
620
+ lastActiveByUserId.set(uid, s.updatedAt ?? s.createdAt);
621
+ activeUserIds.push(uid);
622
+ }
623
+ }
624
+ const activeUsersQuery = useQuery({
625
+ queryKey: ["dash-active-users", activeUserIds],
626
+ queryFn: async () => {
627
+ if (activeUserIds.length === 0) return [];
628
+ const params = new URLSearchParams({ uid: "plugin::better-auth.user" });
629
+ for (let i = 0; i < activeUserIds.length; i++) {
630
+ params.set(`filters[id][$in][${i}]`, activeUserIds[i]);
631
+ }
632
+ params.set("pagination[pageSize]", "12");
633
+ const { data } = await get(
634
+ `/better-auth-dashboard/db?${params}`
635
+ );
636
+ return data.results ?? [];
637
+ },
638
+ enabled: feedMode === "active" && activeUserIds.length > 0
639
+ });
640
+ if (statsQuery.isLoading) {
641
+ return /* @__PURE__ */ jsx(Flex, { justifyContent: "center", alignItems: "center", padding: 12, children: /* @__PURE__ */ jsx(Loader, { children: loadingMessage }) });
642
+ }
643
+ const stats = statsQuery.data;
644
+ if (!stats) return null;
645
+ const graphData = graphQuery.data?.data ?? [];
646
+ const rtnData = retentionQuery.data?.data ?? [];
647
+ const users = usersQuery.data?.users ?? [];
648
+ const orgCount = orgsQuery.data?.total ?? 0;
649
+ const activeUserMap = /* @__PURE__ */ new Map();
650
+ for (const u of activeUsersQuery.data ?? []) {
651
+ activeUserMap.set(String(u.id), u);
652
+ }
653
+ const sortedActiveUsers = activeUserIds.map((uid) => activeUserMap.get(uid)).filter((u) => u !== void 0);
654
+ const totalSpark = graphData.map((d) => d.totalUsers);
655
+ const newSpark = graphData.map((d) => d.newUsers);
656
+ const hovRow = hovIdx !== null ? graphData[hovIdx] : null;
657
+ const rtnHovRow = rtnHov !== null ? rtnData[rtnHov] : null;
658
+ const activeMax = Math.max(
659
+ stats.activeUsers.daily.active ?? 0,
660
+ stats.activeUsers.weekly.active ?? 0,
661
+ stats.activeUsers.monthly.active ?? 0,
662
+ 1
663
+ );
664
+ const renderFeedItem = (key, name, email, image, when) => /* @__PURE__ */ jsxs(
665
+ Box,
666
+ {
667
+ paddingTop: 2,
668
+ paddingBottom: 2,
669
+ paddingLeft: 4,
670
+ paddingRight: 4,
671
+ borderColor: "neutral150",
672
+ borderStyle: "solid",
673
+ borderWidth: "0 0 1px 0",
674
+ children: [
675
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 2, children: [
676
+ /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 2, minWidth: 0, children: [
677
+ /* @__PURE__ */ jsx(Avatar, { name, src: image, size: 20 }),
678
+ /* @__PURE__ */ jsx(
679
+ Typography,
680
+ {
681
+ variant: "omega",
682
+ fontWeight: "semiBold",
683
+ textColor: "neutral800",
684
+ ellipsis: true,
685
+ title: name,
686
+ children: name
687
+ }
688
+ )
689
+ ] }),
690
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral500", shrink: 0, children: relTime(when, formatMessage) })
691
+ ] }),
692
+ /* @__PURE__ */ jsx(
693
+ Typography,
694
+ {
695
+ variant: "pi",
696
+ textColor: "neutral600",
697
+ ellipsis: true,
698
+ tag: "div",
699
+ title: email,
700
+ children: email
701
+ }
702
+ )
703
+ ]
704
+ },
705
+ key
706
+ );
707
+ return /* @__PURE__ */ jsxs(Main, { "data-testid": "overview-page", children: [
708
+ /* @__PURE__ */ jsx(
709
+ Layouts.Header,
710
+ {
711
+ title: formatMessage({
712
+ id: getTranslation("overview.header.title"),
713
+ defaultMessage: "Overview"
714
+ }),
715
+ subtitle: (/* @__PURE__ */ new Date()).toLocaleDateString(void 0, {
716
+ weekday: "long",
717
+ year: "numeric",
718
+ month: "long",
719
+ day: "numeric"
720
+ }),
721
+ primaryAction: /* @__PURE__ */ jsxs(
722
+ SegmentedControl.Root,
723
+ {
724
+ label: formatMessage({
725
+ id: getTranslation("overview.period.label"),
726
+ defaultMessage: "Period"
727
+ }),
728
+ value: period,
729
+ onChange: (v) => setPeriod(v),
730
+ children: [
731
+ /* @__PURE__ */ jsx(SegmentedControl.Item, { value: "daily", children: formatMessage({
732
+ id: getTranslation("overview.period.daily"),
733
+ defaultMessage: "Daily"
734
+ }) }),
735
+ /* @__PURE__ */ jsx(SegmentedControl.Item, { value: "weekly", children: formatMessage({
736
+ id: getTranslation("overview.period.weekly"),
737
+ defaultMessage: "Weekly"
738
+ }) }),
739
+ /* @__PURE__ */ jsx(SegmentedControl.Item, { value: "monthly", children: formatMessage({
740
+ id: getTranslation("overview.period.monthly"),
741
+ defaultMessage: "Monthly"
742
+ }) })
743
+ ]
744
+ }
745
+ )
746
+ }
747
+ ),
748
+ /* @__PURE__ */ jsx(Layouts.Content, { children: /* @__PURE__ */ jsxs(
749
+ Flex,
750
+ {
751
+ direction: "column",
752
+ alignItems: "stretch",
753
+ gap: 6,
754
+ paddingBottom: 10,
755
+ children: [
756
+ /* @__PURE__ */ jsx(
757
+ SectionDivider,
758
+ {
759
+ label: formatMessage({
760
+ id: getTranslation("overview.sections.metrics"),
761
+ defaultMessage: "Metrics"
762
+ })
763
+ }
764
+ ),
765
+ /* @__PURE__ */ jsxs(
766
+ Grid.Root,
767
+ {
768
+ gap: 4,
769
+ style: {
770
+ gridTemplateColumns: "repeat(auto-fit, minmax(18rem, 1fr))"
771
+ },
772
+ children: [
773
+ /* @__PURE__ */ jsx(
774
+ StatItem,
775
+ {
776
+ id: "total",
777
+ label: formatMessage({
778
+ id: getTranslation("overview.stats.totalUsers"),
779
+ defaultMessage: "Total Users"
780
+ }),
781
+ value: stats.total ?? 0,
782
+ sparkline: totalSpark,
783
+ color: "primary600"
784
+ }
785
+ ),
786
+ /* @__PURE__ */ jsx(
787
+ StatItem,
788
+ {
789
+ id: "d-sig",
790
+ label: formatMessage({
791
+ id: getTranslation("overview.stats.dailySignUps"),
792
+ defaultMessage: "Daily Sign-ups"
793
+ }),
794
+ value: stats.daily.signUps ?? 0,
795
+ pct: stats.daily.percentage ?? void 0,
796
+ sparkline: newSpark,
797
+ color: "success500"
798
+ }
799
+ ),
800
+ /* @__PURE__ */ jsx(
801
+ StatItem,
802
+ {
803
+ id: "w-sig",
804
+ label: formatMessage({
805
+ id: getTranslation("overview.stats.weeklySignUps"),
806
+ defaultMessage: "Weekly Sign-ups"
807
+ }),
808
+ value: stats.weekly.signUps ?? 0,
809
+ pct: stats.weekly.percentage ?? void 0,
810
+ sparkline: newSpark,
811
+ color: "success500"
812
+ }
813
+ ),
814
+ /* @__PURE__ */ jsx(
815
+ StatItem,
816
+ {
817
+ id: "m-sig",
818
+ label: formatMessage({
819
+ id: getTranslation("overview.stats.monthlySignUps"),
820
+ defaultMessage: "Monthly Sign-ups"
821
+ }),
822
+ value: stats.monthly.signUps ?? 0,
823
+ pct: stats.monthly.percentage ?? void 0,
824
+ sparkline: newSpark,
825
+ color: "success500"
826
+ }
827
+ ),
828
+ /* @__PURE__ */ jsx(
829
+ StatItem,
830
+ {
831
+ id: "orgs",
832
+ label: formatMessage({
833
+ id: getTranslation("overview.stats.organizations"),
834
+ defaultMessage: "Organizations"
835
+ }),
836
+ value: orgCount,
837
+ color: "alternative600"
838
+ }
839
+ )
840
+ ]
841
+ }
842
+ ),
843
+ /* @__PURE__ */ jsx(
844
+ SectionDivider,
845
+ {
846
+ label: formatMessage({
847
+ id: getTranslation("overview.sections.growth"),
848
+ defaultMessage: "Growth"
849
+ })
850
+ }
851
+ ),
852
+ /* @__PURE__ */ jsxs(TwoPanel, { children: [
853
+ /* @__PURE__ */ jsxs(
854
+ Flex,
855
+ {
856
+ ...CARD,
857
+ direction: "column",
858
+ alignItems: "stretch",
859
+ gap: 3,
860
+ padding: 5,
861
+ children: [
862
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 3, children: [
863
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
864
+ id: getTranslation("overview.growth.title"),
865
+ defaultMessage: "User Growth"
866
+ }) }),
867
+ /* @__PURE__ */ jsx(Flex, { gap: 2, children: ALL_SERIES.map((s) => {
868
+ const on = activeSeries.has(s.key);
869
+ return /* @__PURE__ */ jsx(
870
+ Button,
871
+ {
872
+ size: "S",
873
+ variant: on ? "secondary" : "tertiary",
874
+ "aria-pressed": on,
875
+ startIcon: /* @__PURE__ */ jsx(Dot, { color: on ? s.color : "neutral300" }),
876
+ onClick: () => toggleSeries(s.key),
877
+ children: formatMessage(s.label)
878
+ },
879
+ s.key
880
+ );
881
+ }) })
882
+ ] }),
883
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", tag: "p", children: hovRow ? formatMessage(
884
+ {
885
+ id: getTranslation("overview.growth.hoverDetails"),
886
+ defaultMessage: "{label} · {total} total · +{new} new · {active} active"
887
+ },
888
+ {
889
+ label: hovRow.label,
890
+ total: hovRow.totalUsers.toLocaleString(),
891
+ new: hovRow.newUsers.toLocaleString(),
892
+ active: hovRow.activeUsers.toLocaleString()
893
+ }
894
+ ) : formatMessage({
895
+ id: getTranslation("overview.growth.hoverHint"),
896
+ defaultMessage: "Hover the chart to inspect a data point"
897
+ }) }),
898
+ graphQuery.isLoading ? /* @__PURE__ */ jsx(
899
+ Flex,
900
+ {
901
+ justifyContent: "center",
902
+ alignItems: "center",
903
+ minHeight: "17rem",
904
+ children: /* @__PURE__ */ jsx(Loader, { children: loadingMessage })
905
+ }
906
+ ) : /* @__PURE__ */ jsx(
907
+ GrowthChart,
908
+ {
909
+ data: graphData,
910
+ hovered: hovIdx,
911
+ onHover: setHovIdx,
912
+ activeSeries
913
+ }
914
+ )
915
+ ]
916
+ }
917
+ ),
918
+ /* @__PURE__ */ jsx(FeedSlot, { children: /* @__PURE__ */ jsxs(
919
+ Flex,
920
+ {
921
+ ...CARD,
922
+ direction: "column",
923
+ alignItems: "stretch",
924
+ overflow: "hidden",
925
+ children: [
926
+ /* @__PURE__ */ jsxs(
927
+ Flex,
928
+ {
929
+ justifyContent: "space-between",
930
+ gap: 2,
931
+ padding: 4,
932
+ borderColor: "neutral150",
933
+ borderStyle: "solid",
934
+ borderWidth: "0 0 1px 0",
935
+ children: [
936
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", fontWeight: "bold", children: feedMode === "signups" ? formatMessage(recentSignUpsMessage) : formatMessage(recentlyActiveMessage) }),
937
+ /* @__PURE__ */ jsx(Box, { width: "15rem", children: /* @__PURE__ */ jsxs(
938
+ SingleSelect,
939
+ {
940
+ size: "S",
941
+ "aria-label": formatMessage({
942
+ id: getTranslation("overview.feed.label"),
943
+ defaultMessage: "Feed"
944
+ }),
945
+ value: feedMode,
946
+ onChange: (v) => setFeedMode(v),
947
+ children: [
948
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "signups", children: formatMessage(recentSignUpsMessage) }),
949
+ /* @__PURE__ */ jsx(SingleSelectOption, { value: "active", children: formatMessage(recentlyActiveMessage) })
950
+ ]
951
+ }
952
+ ) })
953
+ ]
954
+ }
955
+ ),
956
+ /* @__PURE__ */ jsx(Box, { flex: 1, overflow: "auto", minHeight: 0, children: feedMode === "signups" ? usersQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { small: true, children: loadingMessage }) }) : users.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: formatMessage({
957
+ id: getTranslation("overview.feed.noUsers"),
958
+ defaultMessage: "No users yet"
959
+ }) }) : users.map(
960
+ (u) => renderFeedItem(
961
+ u.id,
962
+ u.name,
963
+ u.email,
964
+ u.image,
965
+ u.createdAt
966
+ )
967
+ ) : activeUsersQuery.isLoading || sessionsQuery.isLoading ? /* @__PURE__ */ jsx(Flex, { justifyContent: "center", padding: 4, children: /* @__PURE__ */ jsx(Loader, { small: true, children: loadingMessage }) }) : sortedActiveUsers.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: formatMessage({
968
+ id: getTranslation("overview.feed.noActivity"),
969
+ defaultMessage: "No recent activity"
970
+ }) }) : sortedActiveUsers.map(
971
+ (u) => renderFeedItem(
972
+ u.documentId,
973
+ u.name,
974
+ u.email,
975
+ u.image,
976
+ lastActiveByUserId.get(String(u.id)) ?? u.createdAt
977
+ )
978
+ ) })
979
+ ]
980
+ }
981
+ ) })
982
+ ] }),
983
+ /* @__PURE__ */ jsx(
984
+ SectionDivider,
985
+ {
986
+ label: formatMessage({
987
+ id: getTranslation("overview.sections.retentionActivity"),
988
+ defaultMessage: "Retention & Activity"
989
+ })
990
+ }
991
+ ),
992
+ /* @__PURE__ */ jsxs(TwoPanel, { children: [
993
+ /* @__PURE__ */ jsxs(
994
+ Flex,
995
+ {
996
+ ...CARD,
997
+ direction: "column",
998
+ alignItems: "stretch",
999
+ gap: 3,
1000
+ padding: 5,
1001
+ children: [
1002
+ /* @__PURE__ */ jsxs(Flex, { justifyContent: "space-between", gap: 3, children: [
1003
+ /* @__PURE__ */ jsx(Typography, { variant: "delta", tag: "h2", children: formatMessage({
1004
+ id: getTranslation("overview.retention.title"),
1005
+ defaultMessage: "Cohort Retention"
1006
+ }) }),
1007
+ /* @__PURE__ */ jsx(Flex, { gap: 3, children: [
1008
+ { color: "success500", label: "≥70%" },
1009
+ { color: "warning500", label: "40–70%" },
1010
+ { color: "danger500", label: "<40%" }
1011
+ ].map(({ color, label }) => /* @__PURE__ */ jsxs(Flex, { alignItems: "center", gap: 1, children: [
1012
+ /* @__PURE__ */ jsx(Dot, { color }),
1013
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", children: label })
1014
+ ] }, color)) })
1015
+ ] }),
1016
+ /* @__PURE__ */ jsx(Typography, { variant: "pi", textColor: "neutral600", tag: "p", children: rtnHovRow ? formatMessage(
1017
+ {
1018
+ id: getTranslation("overview.retention.hoverDetails"),
1019
+ defaultMessage: "Cohort {label} · {cohortSize} users · active {activeStart} – {activeEnd}"
1020
+ },
1021
+ {
1022
+ label: rtnHovRow.label,
1023
+ cohortSize: rtnHovRow.cohortSize.toLocaleString(),
1024
+ activeStart: rtnHovRow.activeStart,
1025
+ activeEnd: rtnHovRow.activeEnd
1026
+ }
1027
+ ) : formatMessage({
1028
+ id: getTranslation("overview.retention.hoverHint"),
1029
+ defaultMessage: "Hover a row to see cohort details"
1030
+ }) }),
1031
+ retentionQuery.isLoading ? /* @__PURE__ */ jsx(
1032
+ Flex,
1033
+ {
1034
+ justifyContent: "center",
1035
+ alignItems: "center",
1036
+ minHeight: "8rem",
1037
+ children: /* @__PURE__ */ jsx(Loader, { children: loadingMessage })
1038
+ }
1039
+ ) : rtnData.length === 0 ? /* @__PURE__ */ jsx(Empty, { children: formatMessage({
1040
+ id: getTranslation("overview.retention.empty"),
1041
+ defaultMessage: "No retention data for this period"
1042
+ }) }) : /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 1, children: rtnData.map((row, i) => {
1043
+ const color = rateColor(row.retentionRate);
1044
+ return /* @__PURE__ */ jsxs(
1045
+ Flex,
1046
+ {
1047
+ gap: 3,
1048
+ padding: 1,
1049
+ hasRadius: true,
1050
+ background: rtnHov === i ? "neutral100" : void 0,
1051
+ onMouseEnter: () => setRtnHov(i),
1052
+ onMouseLeave: () => setRtnHov(null),
1053
+ children: [
1054
+ /* @__PURE__ */ jsx(
1055
+ Typography,
1056
+ {
1057
+ variant: "pi",
1058
+ textColor: "neutral600",
1059
+ textAlign: "right",
1060
+ minWidth: "8rem",
1061
+ children: row.label
1062
+ }
1063
+ ),
1064
+ /* @__PURE__ */ jsx(
1065
+ Typography,
1066
+ {
1067
+ variant: "pi",
1068
+ textColor: "neutral500",
1069
+ textAlign: "right",
1070
+ minWidth: "5.2rem",
1071
+ children: row.cohortSize.toLocaleString()
1072
+ }
1073
+ ),
1074
+ /* @__PURE__ */ jsx(
1075
+ Box,
1076
+ {
1077
+ flex: 1,
1078
+ height: "1rem",
1079
+ background: "neutral150",
1080
+ hasRadius: true,
1081
+ overflow: "hidden",
1082
+ children: /* @__PURE__ */ jsx(
1083
+ Box,
1084
+ {
1085
+ height: "100%",
1086
+ background: color,
1087
+ hasRadius: true,
1088
+ style: {
1089
+ width: `${Math.max(row.retentionRate, 0.5)}%`,
1090
+ transition: "width 0.55s ease"
1091
+ }
1092
+ }
1093
+ )
1094
+ }
1095
+ ),
1096
+ /* @__PURE__ */ jsxs(
1097
+ Typography,
1098
+ {
1099
+ variant: "pi",
1100
+ fontWeight: "bold",
1101
+ textColor: color,
1102
+ textAlign: "right",
1103
+ minWidth: "3.8rem",
1104
+ children: [
1105
+ row.retentionRate.toFixed(1),
1106
+ "%"
1107
+ ]
1108
+ }
1109
+ )
1110
+ ]
1111
+ },
1112
+ row.n
1113
+ );
1114
+ }) })
1115
+ ]
1116
+ }
1117
+ ),
1118
+ /* @__PURE__ */ jsx(Flex, { direction: "column", alignItems: "stretch", gap: 4, children: [
1119
+ {
1120
+ label: formatMessage({
1121
+ id: getTranslation("overview.activeUsers.daily"),
1122
+ defaultMessage: "Daily Active"
1123
+ }),
1124
+ value: stats.activeUsers.daily.active,
1125
+ pct: stats.activeUsers.daily.percentage,
1126
+ color: "warning500"
1127
+ },
1128
+ {
1129
+ label: formatMessage({
1130
+ id: getTranslation("overview.activeUsers.weekly"),
1131
+ defaultMessage: "Weekly Active"
1132
+ }),
1133
+ value: stats.activeUsers.weekly.active,
1134
+ pct: stats.activeUsers.weekly.percentage,
1135
+ color: "success500"
1136
+ },
1137
+ {
1138
+ label: formatMessage({
1139
+ id: getTranslation("overview.activeUsers.monthly"),
1140
+ defaultMessage: "Monthly Active"
1141
+ }),
1142
+ value: stats.activeUsers.monthly.active,
1143
+ pct: stats.activeUsers.monthly.percentage,
1144
+ color: "primary600"
1145
+ }
1146
+ ].map(({ label, value, pct, color }) => /* @__PURE__ */ jsxs(Flex, { ...CARD, gap: 4, padding: 5, flex: 1, children: [
1147
+ /* @__PURE__ */ jsx(
1148
+ ProgressRing,
1149
+ {
1150
+ value: value ?? 0,
1151
+ max: activeMax,
1152
+ color,
1153
+ size: 56
1154
+ }
1155
+ ),
1156
+ /* @__PURE__ */ jsxs(Flex, { direction: "column", alignItems: "flex-start", gap: 1, children: [
1157
+ /* @__PURE__ */ jsx(Typography, { variant: "sigma", textColor: "neutral600", children: label }),
1158
+ /* @__PURE__ */ jsx(Typography, { variant: "beta", tag: "p", textColor: "neutral800", children: (value ?? 0).toLocaleString() }),
1159
+ pct != null && /* @__PURE__ */ jsx(TrendBadge, { pct })
1160
+ ] })
1161
+ ] }, label)) })
1162
+ ] })
1163
+ ]
1164
+ }
1165
+ ) })
1166
+ ] });
1167
+ }
1168
+ function ProtectedOverviewPage() {
1169
+ return /* @__PURE__ */ jsx(
1170
+ Page.Protect,
1171
+ {
1172
+ permissions: PERMISSIONS.overview.filter(
1173
+ (p) => p.action === `plugin::${PLUGIN_ID}.overview.read`
1174
+ ),
1175
+ children: /* @__PURE__ */ jsx(OverviewPage, {})
1176
+ }
1177
+ );
1178
+ }
1179
+ export {
1180
+ OverviewPage,
1181
+ ProtectedOverviewPage as default
1182
+ };