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