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

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