@fonderie/react-admin-screens 1.0.2 → 1.1.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.
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/screens/AdminShell.tsx
2
2
  import { ConfigEditorScreen, ConfigListScreen } from "@fonderie/react-config-admin-screens";
3
3
  import { TemplateEditorScreen, TemplateListScreen } from "@fonderie/react-courier-admin-screens";
4
- import { useState as useState5 } from "react";
4
+ import { useEffect, useState as useState6 } from "react";
5
5
 
6
6
  // src/styles.ts
7
7
  var t = {
@@ -23,6 +23,31 @@ var t = {
23
23
  trackingDisplay: "var(--fonderie-tracking-display,-0.05em)",
24
24
  shadowCard: "var(--fonderie-shadow-card,0 2px 3px 0 rgba(0,0,0,.05))"
25
25
  };
26
+ var tint = (token, pct) => `color-mix(in srgb, ${token} ${pct}%, transparent)`;
27
+ var toneColor = {
28
+ ok: t.accentStrong,
29
+ bad: t.danger,
30
+ // Amber on white fails contrast; mixing in the text colour darkens it in
31
+ // light mode and lightens it in dark, from the same two tokens.
32
+ warn: `color-mix(in srgb, ${t.warning} 70%, ${t.text})`,
33
+ neutral: t.muted,
34
+ info: t.text
35
+ };
36
+ var control = {
37
+ display: "inline-flex",
38
+ alignItems: "center",
39
+ justifyContent: "center",
40
+ gap: 6,
41
+ height: 32,
42
+ boxSizing: "border-box",
43
+ borderRadius: 6,
44
+ padding: "0 12px",
45
+ fontSize: 13,
46
+ fontWeight: 500,
47
+ fontFamily: "inherit",
48
+ lineHeight: 1,
49
+ whiteSpace: "nowrap"
50
+ };
26
51
  var styles = {
27
52
  shell: {
28
53
  display: "flex",
@@ -31,155 +56,578 @@ var styles = {
31
56
  color: t.text,
32
57
  background: t.bg
33
58
  },
34
- // The sidebar sits on the raised surface against the page canvas, which is
35
- // what separates the two without a heavy rule between them.
59
+ // Sticky, full height, scrolls on its own: the nav never scrolls away with
60
+ // a long table, and the footer (session controls) stays in reach.
36
61
  nav: {
37
- width: 220,
38
- borderRight: `1px solid ${t.border}`,
39
- padding: 16,
62
+ width: 248,
40
63
  flexShrink: 0,
41
- background: t.surface
64
+ boxSizing: "border-box",
65
+ borderRight: `1px solid ${t.border}`,
66
+ background: t.surface,
67
+ position: "sticky",
68
+ top: 0,
69
+ height: "100vh",
70
+ display: "flex",
71
+ flexDirection: "column"
72
+ },
73
+ navBrand: {
74
+ display: "flex",
75
+ alignItems: "center",
76
+ gap: 10,
77
+ padding: "18px 16px 14px",
78
+ borderBottom: `1px solid ${t.borderLight}`
79
+ },
80
+ navMark: {
81
+ width: 28,
82
+ height: 28,
83
+ borderRadius: 7,
84
+ background: t.text,
85
+ color: t.surface,
86
+ display: "grid",
87
+ placeItems: "center",
88
+ flexShrink: 0
89
+ },
90
+ navBrandTitle: {
91
+ fontSize: 14,
92
+ fontWeight: 600,
93
+ letterSpacing: t.trackingDisplay,
94
+ lineHeight: 1.2,
95
+ overflow: "hidden",
96
+ textOverflow: "ellipsis",
97
+ whiteSpace: "nowrap"
42
98
  },
99
+ navScroll: { flex: 1, overflowY: "auto", padding: "8px 10px 16px" },
43
100
  navGroup: {
44
101
  fontSize: 11,
45
102
  textTransform: "uppercase",
46
103
  letterSpacing: 0.6,
47
104
  color: t.muted,
48
- margin: "16px 0 6px",
105
+ margin: "16px 8px 4px",
49
106
  fontWeight: 600
50
107
  },
51
108
  navItem: {
52
- display: "block",
109
+ display: "flex",
110
+ alignItems: "center",
111
+ gap: 10,
53
112
  width: "100%",
54
113
  textAlign: "left",
55
114
  background: "none",
56
115
  border: "none",
57
- borderRadius: t.radius,
58
- padding: "6px 8px",
116
+ borderRadius: 6,
117
+ padding: "7px 8px",
59
118
  cursor: "pointer",
60
- fontSize: 14,
61
- color: t.text,
62
- fontFamily: "inherit"
119
+ fontSize: 13.5,
120
+ fontWeight: 500,
121
+ color: t.muted,
122
+ fontFamily: "inherit",
123
+ position: "relative"
124
+ },
125
+ navItemActive: { background: tint(t.accent, 12), color: t.text, fontWeight: 600 },
126
+ navIconActive: { color: t.accentStrong },
127
+ navFooter: {
128
+ borderTop: `1px solid ${t.borderLight}`,
129
+ padding: 12,
130
+ display: "flex",
131
+ flexDirection: "column",
132
+ gap: 10
133
+ },
134
+ // Phone layout: a top bar with the menu button replaces the sidebar, which
135
+ // opens as a drawer over the content.
136
+ topbar: {
137
+ position: "sticky",
138
+ top: 0,
139
+ zIndex: 20,
140
+ display: "flex",
141
+ alignItems: "center",
142
+ gap: 10,
143
+ padding: "10px 16px",
144
+ background: t.surface,
145
+ borderBottom: `1px solid ${t.border}`
146
+ },
147
+ drawer: {
148
+ position: "fixed",
149
+ inset: 0,
150
+ zIndex: 30,
151
+ display: "flex"
152
+ },
153
+ scrim: { flex: 1, background: "rgba(0,0,0,.35)" },
154
+ main: { flex: 1, minWidth: 0 },
155
+ container: { padding: "32px 40px 64px", maxWidth: 1160, boxSizing: "border-box" },
156
+ // ── page header ──
157
+ header: {
158
+ display: "flex",
159
+ alignItems: "flex-start",
160
+ justifyContent: "space-between",
161
+ gap: 16,
162
+ marginBottom: 24,
163
+ flexWrap: "wrap"
63
164
  },
64
- navItemActive: { background: t.surfaceAlt, fontWeight: 600, color: t.accentStrong },
65
- main: { flex: 1, padding: 24, maxWidth: 960 },
66
- container: { padding: 24, maxWidth: 960 },
67
165
  title: {
68
- fontSize: 20,
166
+ fontSize: 22,
69
167
  fontWeight: 600,
70
- marginTop: 0,
71
- marginBottom: 12,
72
- letterSpacing: t.trackingDisplay
168
+ margin: 0,
169
+ letterSpacing: t.trackingDisplay,
170
+ lineHeight: 1.25
73
171
  },
172
+ lead: { color: t.muted, fontSize: 13.5, margin: "4px 0 0", lineHeight: 1.5 },
173
+ actions: { display: "flex", gap: 8, alignItems: "center", flexWrap: "wrap" },
74
174
  subtitle: {
75
175
  fontSize: 15,
76
176
  fontWeight: 600,
77
- marginTop: 24,
78
- marginBottom: 8,
79
- letterSpacing: t.trackingDisplay
177
+ marginTop: 32,
178
+ marginBottom: 10,
179
+ letterSpacing: t.trackingDisplay,
180
+ display: "flex",
181
+ alignItems: "center",
182
+ gap: 8
183
+ },
184
+ // ── feedback ──
185
+ status: { padding: "24px 0", color: t.muted, fontSize: 13.5 },
186
+ error: {
187
+ color: t.danger,
188
+ background: tint(t.danger, 8),
189
+ border: `1px solid ${tint(t.danger, 25)}`,
190
+ borderRadius: t.radiusLg,
191
+ padding: "10px 14px",
192
+ marginBottom: 16,
193
+ fontSize: 13.5
194
+ },
195
+ notice: {
196
+ color: t.text,
197
+ background: tint(t.warning, 10),
198
+ border: `1px solid ${tint(t.warning, 35)}`,
199
+ borderRadius: t.radiusLg,
200
+ padding: "10px 14px",
201
+ marginBottom: 16,
202
+ fontSize: 13.5
80
203
  },
81
- status: { padding: 12, color: t.muted },
82
- error: { color: t.danger, marginBottom: 12, fontSize: 14 },
83
204
  ok: { color: t.accentStrong, fontWeight: 600 },
84
205
  bad: { color: t.danger, fontWeight: 600 },
85
206
  advice: { color: t.warning },
86
207
  muted: { color: t.muted, fontSize: 13 },
87
- mono: { fontFamily: t.mono, fontSize: 13 },
88
- table: { width: "100%", borderCollapse: "collapse", fontSize: 14 },
208
+ mono: { fontFamily: t.mono, fontSize: 12.5 },
209
+ code: {
210
+ fontFamily: t.mono,
211
+ fontSize: 12,
212
+ background: t.surfaceAlt,
213
+ border: `1px solid ${t.borderLight}`,
214
+ borderRadius: 4,
215
+ padding: "1px 5px"
216
+ },
217
+ // ── surfaces ──
218
+ // Tables and lists are cards themselves, so every screen gets the framed,
219
+ // rounded look without wrapping each one. The last row's rule is removed in
220
+ // the shell stylesheet (a pseudo-selector); embedded without the shell it
221
+ // simply doubles against the frame.
222
+ table: {
223
+ width: "100%",
224
+ borderCollapse: "separate",
225
+ borderSpacing: 0,
226
+ fontSize: 13.5,
227
+ background: t.surface,
228
+ border: `1px solid ${t.border}`,
229
+ borderRadius: t.radiusLg,
230
+ overflow: "hidden",
231
+ boxShadow: t.shadowCard
232
+ },
89
233
  th: {
90
234
  textAlign: "left",
91
235
  borderBottom: `1px solid ${t.border}`,
92
- padding: "6px 8px",
236
+ padding: "9px 14px",
237
+ fontWeight: 600,
238
+ fontSize: 11.5,
239
+ textTransform: "uppercase",
240
+ letterSpacing: 0.4,
241
+ color: t.muted,
242
+ background: t.surfaceAlt,
243
+ whiteSpace: "nowrap"
244
+ },
245
+ td: {
246
+ borderBottom: `1px solid ${t.borderLight}`,
247
+ padding: "11px 14px",
248
+ verticalAlign: "top"
249
+ },
250
+ list: {
251
+ listStyle: "none",
252
+ padding: 0,
253
+ margin: 0,
254
+ background: t.surface,
255
+ border: `1px solid ${t.border}`,
256
+ borderRadius: t.radiusLg,
257
+ overflow: "hidden",
258
+ boxShadow: t.shadowCard
259
+ },
260
+ row: { borderBottom: `1px solid ${t.borderLight}`, padding: "12px 16px" },
261
+ card: {
262
+ background: t.surface,
263
+ border: `1px solid ${t.border}`,
264
+ borderRadius: t.radiusLg,
265
+ padding: 16,
266
+ boxShadow: t.shadowCard
267
+ },
268
+ grid: {
269
+ display: "grid",
270
+ gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))",
271
+ gap: 12,
272
+ marginBottom: 24
273
+ },
274
+ statLabel: { fontSize: 12, color: t.muted, fontWeight: 500 },
275
+ statValue: {
276
+ fontSize: 26,
93
277
  fontWeight: 600,
94
- color: t.muted
278
+ letterSpacing: t.trackingDisplay,
279
+ lineHeight: 1.2,
280
+ marginTop: 6
281
+ },
282
+ statHint: { fontSize: 12, color: t.muted, marginTop: 2 },
283
+ empty: {
284
+ background: t.surface,
285
+ border: `1px dashed ${t.border}`,
286
+ borderRadius: t.radiusLg,
287
+ padding: "40px 24px",
288
+ textAlign: "center",
289
+ color: t.muted,
290
+ fontSize: 13.5
291
+ },
292
+ emptyTitle: { color: t.text, fontWeight: 600, fontSize: 14.5, margin: "10px 0 4px" },
293
+ dl: { display: "grid", gridTemplateColumns: "160px 1fr", margin: 0 },
294
+ dt: {
295
+ color: t.muted,
296
+ fontSize: 13,
297
+ padding: "10px 16px",
298
+ borderBottom: `1px solid ${t.borderLight}`
299
+ },
300
+ dd: {
301
+ margin: 0,
302
+ padding: "10px 16px",
303
+ borderBottom: `1px solid ${t.borderLight}`,
304
+ fontSize: 13.5,
305
+ minWidth: 0
95
306
  },
96
- td: { borderBottom: `1px solid ${t.borderLight}`, padding: "6px 8px", verticalAlign: "top" },
97
- list: { listStyle: "none", padding: 0, margin: 0 },
98
- row: { borderBottom: `1px solid ${t.borderLight}`, padding: "8px 0" },
99
307
  badge: {
100
- display: "inline-block",
308
+ display: "inline-flex",
309
+ alignItems: "center",
310
+ gap: 5,
101
311
  borderRadius: 999,
102
- padding: "1px 8px",
103
- fontSize: 12,
312
+ padding: "2px 8px",
313
+ fontSize: 11.5,
314
+ fontWeight: 500,
315
+ lineHeight: 1.5,
104
316
  background: t.surfaceAlt,
105
317
  border: `1px solid ${t.border}`,
106
- color: t.muted
318
+ color: t.muted,
319
+ whiteSpace: "nowrap"
107
320
  },
321
+ pill: {
322
+ display: "inline-flex",
323
+ alignItems: "center",
324
+ gap: 6,
325
+ borderRadius: 999,
326
+ padding: "2px 9px 2px 8px",
327
+ fontSize: 12,
328
+ fontWeight: 600,
329
+ lineHeight: 1.5,
330
+ whiteSpace: "nowrap",
331
+ verticalAlign: "baseline"
332
+ },
333
+ dot: { width: 6, height: 6, borderRadius: 999, flexShrink: 0, background: "currentColor" },
334
+ // ── controls ──
108
335
  button: {
336
+ ...control,
109
337
  background: t.surface,
110
338
  border: `1px solid ${t.border}`,
111
- borderRadius: t.radius,
112
- padding: "4px 10px",
113
- cursor: "pointer",
114
- fontSize: 13,
115
339
  color: t.text,
116
- fontFamily: "inherit",
340
+ cursor: "pointer",
117
341
  boxShadow: t.shadowCard
118
342
  },
119
- toolbar: { display: "flex", gap: 8, alignItems: "center", marginBottom: 12 },
120
- // Surfaces the screens compose themselves, kept here so a card looks the
121
- // same on every page rather than being re-invented per screen.
122
- card: {
343
+ buttonPrimary: {
344
+ ...control,
345
+ background: t.text,
346
+ border: `1px solid ${t.text}`,
347
+ color: t.surface,
348
+ cursor: "pointer"
349
+ },
350
+ buttonDanger: {
351
+ ...control,
123
352
  background: t.surface,
353
+ border: `1px solid ${tint(t.danger, 40)}`,
354
+ color: t.danger,
355
+ cursor: "pointer"
356
+ },
357
+ buttonGhost: {
358
+ ...control,
359
+ background: "none",
360
+ border: "1px solid transparent",
361
+ color: t.muted,
362
+ cursor: "pointer",
363
+ padding: "0 8px"
364
+ },
365
+ link: {
366
+ background: "none",
367
+ border: "none",
368
+ padding: 0,
369
+ color: t.text,
370
+ fontFamily: "inherit",
371
+ fontSize: "inherit",
372
+ fontWeight: 500,
373
+ cursor: "pointer",
374
+ textAlign: "left"
375
+ },
376
+ input: {
377
+ height: 32,
378
+ boxSizing: "border-box",
124
379
  border: `1px solid ${t.border}`,
125
- borderRadius: t.radiusLg,
126
- padding: 16,
127
- boxShadow: t.shadowCard
380
+ borderRadius: 6,
381
+ padding: "0 10px",
382
+ fontSize: 13.5,
383
+ fontFamily: "inherit",
384
+ background: t.surface,
385
+ color: t.text,
386
+ minWidth: 0
128
387
  },
388
+ toolbar: { display: "flex", gap: 8, alignItems: "center", marginBottom: 16, flexWrap: "wrap" },
129
389
  accent: { color: t.accent }
130
390
  };
131
391
 
392
+ // src/icons.ts
393
+ var ICONS = {
394
+ attention: ["M22 12h-4l-3 9L9 3l-3 9H2"],
395
+ modules: [
396
+ "M21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16z",
397
+ "M3.3 7 12 12l8.7-5",
398
+ "M12 22V12"
399
+ ],
400
+ environment: [
401
+ "M4 21v-7",
402
+ "M4 10V3",
403
+ "M12 21v-9",
404
+ "M12 8V3",
405
+ "M20 21v-5",
406
+ "M20 12V3",
407
+ "M1 14h6",
408
+ "M9 8h6",
409
+ "M17 16h6"
410
+ ],
411
+ doctor: [
412
+ "M22 12h-2.48a2 2 0 0 0-1.93 1.46l-2.35 8.36a.25.25 0 0 1-.48 0L9.24 2.18a.25.25 0 0 0-.48 0l-2.35 8.36A2 2 0 0 1 4.49 12H2"
413
+ ],
414
+ routes: [
415
+ "M6 3v12",
416
+ "M18 9a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
417
+ "M6 21a3 3 0 1 0 0-6 3 3 0 0 0 0 6z",
418
+ "M18 9a9 9 0 0 1-9 9"
419
+ ],
420
+ users: [
421
+ "M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2",
422
+ "M9 11a4 4 0 1 0 0-8 4 4 0 0 0 0 8z",
423
+ "M22 21v-2a4 4 0 0 0-3-3.87",
424
+ "M16 3.13a4 4 0 0 1 0 7.75"
425
+ ],
426
+ catalog: [
427
+ "M12.586 2.586A2 2 0 0 0 11.172 2H4a2 2 0 0 0-2 2v7.172a2 2 0 0 0 .586 1.414l8.704 8.704a2.426 2.426 0 0 0 3.42 0l6.58-6.58a2.426 2.426 0 0 0 0-3.42z",
428
+ "M7.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"
429
+ ],
430
+ subscriber: ["M2 7h20v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2z", "M2 11h20", "M6 15h4"],
431
+ settings: [
432
+ "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.39a2 2 0 0 0-.73-2.73l-.15-.08a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.38a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z",
433
+ "M12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6z"
434
+ ],
435
+ templates: [
436
+ "M22 7l-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7",
437
+ "M4 4h16a2 2 0 0 1 2 2v12a2 2 0 0 1-2 2H4a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2z"
438
+ ],
439
+ audit: ["M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z", "M9 12l2 2 4-4"],
440
+ log: ["M12 8v4l3 3", "M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5"],
441
+ tokens: [
442
+ "M2.586 17.414A2 2 0 0 0 2 18.828V21a1 1 0 0 0 1 1h3a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h1a1 1 0 0 0 1-1v-1a1 1 0 0 1 1-1h.172a2 2 0 0 0 1.414-.586l.814-.814a6.5 6.5 0 1 0-4-4z",
443
+ "M16.5 8a.5.5 0 1 0 0-1 .5.5 0 0 0 0 1z"
444
+ ],
445
+ migrations: [
446
+ "M12 22c4.97 0 9-1.79 9-4V6",
447
+ "M3 6v12c0 2.21 4.03 4 9 4",
448
+ "M12 10c4.97 0 9-1.79 9-4s-4.03-4-9-4-9 1.79-9 4 4.03 4 9 4z",
449
+ "M3 12c0 2.21 4.03 4 9 4s9-1.79 9-4"
450
+ ],
451
+ menu: ["M4 6h16", "M4 12h16", "M4 18h16"],
452
+ close: ["M18 6 6 18", "m6 6 12 12"],
453
+ refresh: [
454
+ "M3 12a9 9 0 0 1 9-9 9.75 9.75 0 0 1 6.74 2.74L21 8",
455
+ "M21 3v5h-5",
456
+ "M21 12a9 9 0 0 1-9 9 9.75 9.75 0 0 1-6.74-2.74L3 16",
457
+ "M8 16H3v5"
458
+ ],
459
+ check: ["M20 6 9 17l-5-5"],
460
+ alert: [
461
+ "M12 9v4",
462
+ "M12 17h.01",
463
+ "M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"
464
+ ],
465
+ search: ["M11 19a8 8 0 1 0 0-16 8 8 0 0 0 0 16z", "m21 21-4.3-4.3"],
466
+ back: ["m12 19-7-7 7-7", "M19 12H5"],
467
+ logout: ["M9 21H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h4", "m16 17 5-5-5-5", "M21 12H9"],
468
+ lock: [
469
+ "M5 11h14a2 2 0 0 1 2 2v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-7a2 2 0 0 1 2-2z",
470
+ "M7 11V7a5 5 0 0 1 10 0v4"
471
+ ],
472
+ plus: ["M12 5v14", "M5 12h14"],
473
+ mark: [
474
+ "M4 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
475
+ "M15 3h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1V4a1 1 0 0 1 1-1z",
476
+ "M15 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1h-5a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1z",
477
+ "M4 14h5a1 1 0 0 1 1 1v5a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1v-5a1 1 0 0 1 1-1z"
478
+ ]
479
+ };
480
+
481
+ // src/ui.tsx
482
+ import { jsx, jsxs } from "react/jsx-runtime";
483
+ function Icon({
484
+ name,
485
+ size = 16,
486
+ style
487
+ }) {
488
+ return /* @__PURE__ */ jsx(
489
+ "svg",
490
+ {
491
+ width: size,
492
+ height: size,
493
+ viewBox: "0 0 24 24",
494
+ fill: "none",
495
+ stroke: "currentColor",
496
+ strokeWidth: 2,
497
+ strokeLinecap: "round",
498
+ strokeLinejoin: "round",
499
+ "aria-hidden": "true",
500
+ style: { flexShrink: 0, ...style },
501
+ children: ICONS[name].map((d) => /* @__PURE__ */ jsx("path", { d }, d))
502
+ }
503
+ );
504
+ }
505
+ function PageHeader({
506
+ title,
507
+ lead,
508
+ actions
509
+ }) {
510
+ return /* @__PURE__ */ jsxs("div", { style: styles.header, children: [
511
+ /* @__PURE__ */ jsxs("div", { style: { minWidth: 0 }, children: [
512
+ /* @__PURE__ */ jsx("h1", { style: styles.title, children: title }),
513
+ lead ? /* @__PURE__ */ jsx("p", { style: styles.lead, children: lead }) : null
514
+ ] }),
515
+ actions ? /* @__PURE__ */ jsx("div", { style: styles.actions, children: actions }) : null
516
+ ] });
517
+ }
518
+ function Pill({
519
+ tone,
520
+ children,
521
+ dot = true
522
+ }) {
523
+ const color = toneColor[tone];
524
+ return /* @__PURE__ */ jsxs("span", { style: { ...styles.pill, color, background: tint(color, tone === "neutral" ? 10 : 13) }, children: [
525
+ dot ? /* @__PURE__ */ jsx("span", { style: styles.dot }) : null,
526
+ children
527
+ ] });
528
+ }
529
+ function Stat({
530
+ label,
531
+ value,
532
+ hint,
533
+ tone
534
+ }) {
535
+ return /* @__PURE__ */ jsxs("div", { style: styles.card, children: [
536
+ /* @__PURE__ */ jsx("div", { style: styles.statLabel, children: label }),
537
+ /* @__PURE__ */ jsx("div", { style: { ...styles.statValue, ...tone ? { color: toneColor[tone] } : {} }, children: value }),
538
+ hint ? /* @__PURE__ */ jsx("div", { style: styles.statHint, children: hint }) : null
539
+ ] });
540
+ }
541
+ function Empty({
542
+ icon = "check",
543
+ title,
544
+ children
545
+ }) {
546
+ return /* @__PURE__ */ jsxs("div", { style: styles.empty, children: [
547
+ /* @__PURE__ */ jsx(Icon, { name: icon, size: 22 }),
548
+ /* @__PURE__ */ jsx("div", { style: styles.emptyTitle, children: title }),
549
+ children ? /* @__PURE__ */ jsx("div", { children }) : null
550
+ ] });
551
+ }
552
+ function RefreshButton({
553
+ onClick,
554
+ busy,
555
+ label = "Refresh"
556
+ }) {
557
+ return /* @__PURE__ */ jsxs("button", { type: "button", style: styles.button, onClick, disabled: busy, children: [
558
+ /* @__PURE__ */ jsx(Icon, { name: "refresh", size: 14 }),
559
+ busy ? "Working\u2026" : label
560
+ ] });
561
+ }
562
+ function MethodChip({ method }) {
563
+ const tone = method === "GET" ? "ok" : method === "DELETE" ? "bad" : method === "POST" ? "info" : "warn";
564
+ const color = toneColor[tone];
565
+ return /* @__PURE__ */ jsx(
566
+ "span",
567
+ {
568
+ style: {
569
+ ...styles.mono,
570
+ fontSize: 11,
571
+ fontWeight: 700,
572
+ color,
573
+ background: tint(color, 12),
574
+ borderRadius: 4,
575
+ padding: "2px 6px",
576
+ display: "inline-block",
577
+ minWidth: 44,
578
+ textAlign: "center"
579
+ },
580
+ children: method
581
+ }
582
+ );
583
+ }
584
+
132
585
  // src/screens/AdminLogScreen.tsx
133
586
  import { useAdminLog } from "@fonderie/react-admin";
134
- import { Fragment, jsx, jsxs } from "react/jsx-runtime";
587
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
135
588
  function AdminLogScreen({ client, pageSize = 50 }) {
136
589
  const { entries, hasMore, isLoading, error, refresh, loadMore } = useAdminLog(client, {
137
590
  limit: pageSize
138
591
  });
139
- return /* @__PURE__ */ jsxs("div", { style: styles.container, children: [
140
- /* @__PURE__ */ jsxs("div", { style: styles.toolbar, children: [
141
- /* @__PURE__ */ jsx("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Admin log" }),
142
- /* @__PURE__ */ jsx(
143
- "button",
144
- {
145
- type: "button",
146
- style: styles.button,
147
- onClick: () => void refresh(),
148
- disabled: isLoading,
149
- children: "Refresh"
150
- }
151
- )
152
- ] }),
153
- error ? /* @__PURE__ */ jsx("p", { style: styles.error, role: "alert", children: error.status === 404 ? "The admin log is off \u2014 give AdminModule a store." : error.explanation }) : /* @__PURE__ */ jsxs(Fragment, { children: [
154
- /* @__PURE__ */ jsxs("table", { style: styles.table, children: [
155
- /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsxs("tr", { children: [
156
- /* @__PURE__ */ jsx("th", { style: styles.th, children: "When" }),
157
- /* @__PURE__ */ jsx("th", { style: styles.th, children: "Actor" }),
158
- /* @__PURE__ */ jsx("th", { style: styles.th, children: "Request" }),
159
- /* @__PURE__ */ jsx("th", { style: styles.th, children: "Status" }),
160
- /* @__PURE__ */ jsx("th", { style: styles.th, children: "Module" })
592
+ return /* @__PURE__ */ jsxs2("div", { style: styles.container, children: [
593
+ /* @__PURE__ */ jsx2(
594
+ PageHeader,
595
+ {
596
+ title: "Admin log",
597
+ lead: "Every request made through this surface, newest first \u2014 refused ones included.",
598
+ actions: /* @__PURE__ */ jsx2(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
599
+ }
600
+ ),
601
+ error ? /* @__PURE__ */ jsx2("p", { style: styles.error, role: "alert", children: error.status === 404 ? "The admin log is off \u2014 give AdminModule a store." : error.explanation }) : entries.length === 0 && !isLoading ? /* @__PURE__ */ jsx2(Empty, { icon: "log", title: "No requests yet" }) : /* @__PURE__ */ jsxs2(Fragment, { children: [
602
+ /* @__PURE__ */ jsxs2("table", { style: styles.table, children: [
603
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
604
+ /* @__PURE__ */ jsx2("th", { style: styles.th, children: "When" }),
605
+ /* @__PURE__ */ jsx2("th", { style: styles.th, children: "Actor" }),
606
+ /* @__PURE__ */ jsx2("th", { style: styles.th, children: "Request" }),
607
+ /* @__PURE__ */ jsx2("th", { style: styles.th, children: "Status" }),
608
+ /* @__PURE__ */ jsx2("th", { style: styles.th, children: "Module" })
161
609
  ] }) }),
162
- /* @__PURE__ */ jsx("tbody", { children: entries.map((e) => /* @__PURE__ */ jsxs("tr", { children: [
163
- /* @__PURE__ */ jsx("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.at).toLocaleString() }),
164
- /* @__PURE__ */ jsx("td", { style: styles.td, children: e.actor }),
165
- /* @__PURE__ */ jsxs("td", { style: { ...styles.td, ...styles.mono }, children: [
166
- e.method,
610
+ /* @__PURE__ */ jsx2("tbody", { children: entries.map((e) => /* @__PURE__ */ jsxs2("tr", { children: [
611
+ /* @__PURE__ */ jsx2("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(e.at).toLocaleString() }),
612
+ /* @__PURE__ */ jsx2("td", { style: styles.td, children: e.actor }),
613
+ /* @__PURE__ */ jsxs2("td", { style: styles.td, children: [
614
+ /* @__PURE__ */ jsx2(MethodChip, { method: e.method }),
167
615
  " ",
168
- e.path
616
+ /* @__PURE__ */ jsx2("span", { style: { ...styles.mono, wordBreak: "break-all" }, children: e.path })
169
617
  ] }),
170
- /* @__PURE__ */ jsxs("td", { style: styles.td, children: [
171
- /* @__PURE__ */ jsx("span", { style: e.status >= 400 ? styles.bad : styles.ok, children: e.status }),
618
+ /* @__PURE__ */ jsxs2("td", { style: { ...styles.td, whiteSpace: "nowrap" }, children: [
619
+ /* @__PURE__ */ jsx2(Pill, { tone: e.status >= 500 ? "bad" : e.status >= 400 ? "warn" : "ok", children: e.status }),
172
620
  " ",
173
- /* @__PURE__ */ jsxs("span", { style: styles.muted, children: [
621
+ /* @__PURE__ */ jsxs2("span", { style: styles.muted, children: [
174
622
  e.durationMs,
175
623
  " ms"
176
624
  ] })
177
625
  ] }),
178
- /* @__PURE__ */ jsx("td", { style: { ...styles.td, ...styles.muted }, children: e.module })
626
+ /* @__PURE__ */ jsx2("td", { style: { ...styles.td, ...styles.muted }, children: e.module })
179
627
  ] }, e.id)) })
180
628
  ] }),
181
- isLoading ? /* @__PURE__ */ jsx("p", { style: styles.status, children: "Loading\u2026" }) : null,
182
- hasMore && !isLoading ? /* @__PURE__ */ jsx(
629
+ isLoading ? /* @__PURE__ */ jsx2("p", { style: styles.status, children: "Loading\u2026" }) : null,
630
+ hasMore && !isLoading ? /* @__PURE__ */ jsx2(
183
631
  "button",
184
632
  {
185
633
  type: "button",
@@ -193,180 +641,373 @@ function AdminLogScreen({ client, pageSize = 50 }) {
193
641
  }
194
642
 
195
643
  // src/screens/AttentionScreen.tsx
196
- import { useAttention } from "@fonderie/react-admin";
197
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
644
+ import { useAttention, useManifest } from "@fonderie/react-admin";
645
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
198
646
  function AttentionScreen({ client }) {
199
647
  const { attention, isLoading, error, refresh } = useAttention(client);
200
- return /* @__PURE__ */ jsxs2("div", { style: styles.container, children: [
201
- /* @__PURE__ */ jsxs2("div", { style: styles.toolbar, children: [
202
- /* @__PURE__ */ jsx2("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Attention" }),
203
- /* @__PURE__ */ jsx2(
204
- "button",
648
+ const { manifest } = useManifest(client);
649
+ const errors = attention?.items.filter((i) => i.severity === "error") ?? [];
650
+ const advice = attention?.items.filter((i) => i.severity !== "error") ?? [];
651
+ const ready = manifest?.modules.filter((m) => m.readiness.ok).length ?? 0;
652
+ const total = manifest?.modules.length ?? 0;
653
+ return /* @__PURE__ */ jsxs3("div", { style: styles.container, children: [
654
+ /* @__PURE__ */ jsx3(
655
+ PageHeader,
656
+ {
657
+ title: "Attention",
658
+ lead: attention ? `What needs you on this deployment \xB7 checked ${new Date(attention.generatedAt).toLocaleTimeString()}` : "What needs you on this deployment",
659
+ actions: /* @__PURE__ */ jsx3(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
660
+ }
661
+ ),
662
+ error ? /* @__PURE__ */ jsx3("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
663
+ /* @__PURE__ */ jsxs3("div", { style: styles.grid, children: [
664
+ /* @__PURE__ */ jsx3(
665
+ Stat,
205
666
  {
206
- type: "button",
207
- style: styles.button,
208
- onClick: () => void refresh(),
209
- disabled: isLoading,
210
- children: "Refresh"
667
+ label: "Needs action",
668
+ value: attention ? errors.length : "\u2014",
669
+ tone: errors.length ? "bad" : attention ? "ok" : void 0,
670
+ hint: "errors to fix"
671
+ }
672
+ ),
673
+ /* @__PURE__ */ jsx3(
674
+ Stat,
675
+ {
676
+ label: "Advice",
677
+ value: attention ? advice.length : "\u2014",
678
+ tone: advice.length ? "warn" : void 0,
679
+ hint: "worth a look"
680
+ }
681
+ ),
682
+ /* @__PURE__ */ jsx3(
683
+ Stat,
684
+ {
685
+ label: "Modules ready",
686
+ value: manifest ? `${ready}/${total}` : "\u2014",
687
+ tone: manifest && ready < total ? "warn" : manifest ? "ok" : void 0,
688
+ hint: manifest ? `admin ${manifest.admin.version}` : void 0
689
+ }
690
+ ),
691
+ /* @__PURE__ */ jsx3(
692
+ Stat,
693
+ {
694
+ label: "Routes",
695
+ value: manifest ? manifest.routes.length : "\u2014",
696
+ hint: manifest ? manifest.env : void 0
211
697
  }
212
698
  )
213
699
  ] }),
214
- isLoading && !attention ? /* @__PURE__ */ jsx2("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ jsx2("p", { style: styles.error, role: "alert", children: error.explanation }) : attention?.items.length === 0 ? /* @__PURE__ */ jsxs2("p", { style: styles.ok, children: [
215
- "Nothing needs you. Checked ",
700
+ isLoading && !attention ? /* @__PURE__ */ jsx3("p", { style: styles.status, children: "Running the checks\u2026" }) : attention && attention.items.length === 0 ? /* @__PURE__ */ jsxs3(Empty, { title: "Nothing needs you", children: [
701
+ "Every check passes. Checked ",
216
702
  new Date(attention.generatedAt).toLocaleString(),
217
703
  "."
218
- ] }) : /* @__PURE__ */ jsx2("ul", { style: styles.list, children: attention?.items.map((item) => /* @__PURE__ */ jsxs2("li", { style: styles.row, children: [
219
- /* @__PURE__ */ jsx2("span", { style: item.severity === "error" ? styles.bad : styles.advice, children: item.severity === "error" ? "error" : "advice" }),
220
- " ",
221
- /* @__PURE__ */ jsx2("span", { style: styles.mono, children: item.source }),
222
- " \u2014 ",
223
- item.message
224
- ] }, `${item.source}:${item.message}`)) })
704
+ ] }) : /* @__PURE__ */ jsx3("ul", { style: styles.list, children: [...errors, ...advice].map((item) => /* @__PURE__ */ jsxs3(
705
+ "li",
706
+ {
707
+ style: { ...styles.row, display: "flex", gap: 12, alignItems: "flex-start" },
708
+ children: [
709
+ /* @__PURE__ */ jsx3(
710
+ Icon,
711
+ {
712
+ name: "alert",
713
+ size: 16,
714
+ style: {
715
+ marginTop: 3,
716
+ color: item.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-warning,#f5a623)"
717
+ }
718
+ }
719
+ ),
720
+ /* @__PURE__ */ jsxs3("div", { style: { minWidth: 0, flex: 1 }, children: [
721
+ /* @__PURE__ */ jsxs3(
722
+ "div",
723
+ {
724
+ style: {
725
+ display: "flex",
726
+ gap: 8,
727
+ alignItems: "center",
728
+ flexWrap: "wrap",
729
+ marginBottom: 2
730
+ },
731
+ children: [
732
+ /* @__PURE__ */ jsx3(Pill, { tone: item.severity === "error" ? "bad" : "warn", children: item.severity === "error" ? "error" : "advice" }),
733
+ /* @__PURE__ */ jsx3("span", { style: styles.mono, children: item.source })
734
+ ]
735
+ }
736
+ ),
737
+ /* @__PURE__ */ jsx3("div", { style: { lineHeight: 1.55 }, children: item.message })
738
+ ] })
739
+ ]
740
+ },
741
+ `${item.source}:${item.message}`
742
+ )) })
225
743
  ] });
226
744
  }
227
745
 
228
746
  // src/screens/EnvironmentScreen.tsx
229
747
  import { useAdminEnvironment } from "@fonderie/react-admin";
230
- import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
748
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
231
749
  function EnvironmentScreen({ client }) {
232
750
  const { report, isLoading, error } = useAdminEnvironment(client);
233
- return /* @__PURE__ */ jsxs3("div", { style: styles.container, children: [
234
- /* @__PURE__ */ jsx3("h1", { style: styles.title, children: "Configuration" }),
235
- isLoading ? /* @__PURE__ */ jsx3("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx3("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ jsxs3(Fragment2, { children: [
236
- /* @__PURE__ */ jsx3("h2", { style: styles.subtitle, children: "Readiness" }),
237
- /* @__PURE__ */ jsx3("ul", { style: styles.list, children: report.modules.map((m) => /* @__PURE__ */ jsxs3("li", { style: styles.row, children: [
238
- /* @__PURE__ */ jsx3("span", { style: styles.mono, children: m.name }),
239
- " ",
240
- m.problems.length === 0 ? /* @__PURE__ */ jsx3("span", { style: styles.ok, children: "ok" }) : m.problems.map((p) => /* @__PURE__ */ jsx3(
751
+ const missing = report?.env.filter((e) => !e.set).length ?? 0;
752
+ return /* @__PURE__ */ jsxs4("div", { style: styles.container, children: [
753
+ /* @__PURE__ */ jsx4(
754
+ PageHeader,
755
+ {
756
+ title: "Environment",
757
+ lead: "Whether each module is configured and each variable the app reads is set. Values are never shown.",
758
+ actions: report && report.env.length > 0 ? /* @__PURE__ */ jsx4(Pill, { tone: missing ? "bad" : "ok", children: missing ? `${missing} missing` : "all set" }) : null
759
+ }
760
+ ),
761
+ isLoading ? /* @__PURE__ */ jsx4("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx4("p", { style: styles.error, role: "alert", children: error.explanation }) : report ? /* @__PURE__ */ jsxs4(Fragment2, { children: [
762
+ /* @__PURE__ */ jsx4("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Variables" }),
763
+ report.env.length === 0 ? /* @__PURE__ */ jsx4("p", { style: styles.muted, children: "No variables declared \u2014 pass `env` to AdminModule." }) : /* @__PURE__ */ jsx4(
764
+ "div",
765
+ {
766
+ style: {
767
+ ...styles.grid,
768
+ gridTemplateColumns: "repeat(auto-fill, minmax(260px, 1fr))",
769
+ gap: 8
770
+ },
771
+ children: report.env.map((e) => /* @__PURE__ */ jsxs4(
772
+ "div",
773
+ {
774
+ style: {
775
+ ...styles.card,
776
+ padding: "10px 12px",
777
+ display: "flex",
778
+ alignItems: "center",
779
+ justifyContent: "space-between",
780
+ gap: 8
781
+ },
782
+ children: [
783
+ /* @__PURE__ */ jsx4("span", { style: { ...styles.mono, wordBreak: "break-all" }, children: e.name }),
784
+ /* @__PURE__ */ jsx4(Pill, { tone: e.set ? "ok" : "bad", children: e.set ? "set" : "missing" })
785
+ ]
786
+ },
787
+ e.name
788
+ ))
789
+ }
790
+ ),
791
+ /* @__PURE__ */ jsx4("h2", { style: styles.subtitle, children: "Module readiness" }),
792
+ /* @__PURE__ */ jsx4("ul", { style: styles.list, children: report.modules.map((m) => /* @__PURE__ */ jsxs4("li", { style: styles.row, children: [
793
+ /* @__PURE__ */ jsxs4(
241
794
  "div",
242
795
  {
243
- style: p.severity === "error" ? styles.bad : styles.advice,
796
+ style: {
797
+ display: "flex",
798
+ justifyContent: "space-between",
799
+ gap: 12,
800
+ alignItems: "center"
801
+ },
802
+ children: [
803
+ /* @__PURE__ */ jsx4("span", { style: { ...styles.mono, fontWeight: 600 }, children: m.name }),
804
+ /* @__PURE__ */ jsx4(
805
+ Pill,
806
+ {
807
+ tone: m.problems.length === 0 ? "ok" : m.problems.some((p) => p.severity === "error") ? "bad" : "warn",
808
+ children: m.problems.length === 0 ? "ready" : `${m.problems.length} problem${m.problems.length === 1 ? "" : "s"}`
809
+ }
810
+ )
811
+ ]
812
+ }
813
+ ),
814
+ m.problems.map((p) => /* @__PURE__ */ jsx4(
815
+ "div",
816
+ {
817
+ style: {
818
+ marginTop: 6,
819
+ fontSize: 13,
820
+ color: p.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-text-muted,#5c5c5c)"
821
+ },
244
822
  children: p.message
245
823
  },
246
824
  p.message
247
825
  ))
248
- ] }, m.name)) }),
249
- /* @__PURE__ */ jsx3("h2", { style: styles.subtitle, children: "Environment" }),
250
- report.env.length === 0 ? /* @__PURE__ */ jsx3("p", { style: styles.muted, children: "No variables declared \u2014 pass `env` to AdminModule." }) : /* @__PURE__ */ jsx3("ul", { style: styles.list, children: report.env.map((e) => /* @__PURE__ */ jsxs3("li", { style: styles.row, children: [
251
- /* @__PURE__ */ jsx3("span", { style: styles.mono, children: e.name }),
252
- " ",
253
- e.set ? /* @__PURE__ */ jsx3("span", { style: styles.ok, children: "set" }) : /* @__PURE__ */ jsx3("span", { style: styles.bad, children: "missing" })
254
- ] }, e.name)) })
826
+ ] }, m.name)) })
255
827
  ] }) : null
256
828
  ] });
257
829
  }
258
830
 
259
831
  // src/screens/DoctorScreen.tsx
260
832
  import { useDoctor } from "@fonderie/react-admin";
261
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
833
+ import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
262
834
  function DoctorScreen({ client }) {
263
835
  const { report, isLoading, error, refresh } = useDoctor(client);
264
- return /* @__PURE__ */ jsxs4("div", { style: styles.container, children: [
265
- /* @__PURE__ */ jsxs4("div", { style: styles.toolbar, children: [
266
- /* @__PURE__ */ jsx4("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Doctor" }),
267
- /* @__PURE__ */ jsx4(
268
- "button",
269
- {
270
- type: "button",
271
- style: styles.button,
272
- onClick: () => void refresh(),
273
- disabled: isLoading,
274
- children: "Run again"
275
- }
276
- ),
277
- report && !isLoading ? /* @__PURE__ */ jsx4("span", { style: report.ok ? styles.ok : styles.bad, children: report.ok ? "all checks pass" : "a check failed" }) : null
278
- ] }),
279
- isLoading && !report ? /* @__PURE__ */ jsx4("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ jsx4("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsx4("ul", { style: styles.list, children: report?.checks.map((c) => /* @__PURE__ */ jsxs4("li", { style: styles.row, children: [
280
- /* @__PURE__ */ jsx4("span", { style: c.skipped ? styles.muted : c.ok ? styles.ok : styles.bad, children: c.skipped ? "skipped" : c.ok ? "ok" : "failed" }),
281
- " ",
282
- /* @__PURE__ */ jsx4("span", { style: styles.mono, children: c.name }),
283
- " ",
284
- /* @__PURE__ */ jsxs4("span", { style: styles.muted, children: [
285
- c.module,
286
- " \xB7 ",
287
- c.durationMs,
288
- " ms"
289
- ] }),
290
- c.skipped ? /* @__PURE__ */ jsx4("div", { style: styles.muted, children: c.skipped }) : null,
291
- c.findings.map((f) => /* @__PURE__ */ jsx4("div", { style: c.ok ? styles.advice : styles.bad, children: f }, f))
292
- ] }, c.name)) })
836
+ return /* @__PURE__ */ jsxs5("div", { style: styles.container, children: [
837
+ /* @__PURE__ */ jsx5(
838
+ PageHeader,
839
+ {
840
+ title: "Doctor",
841
+ lead: "Every reconciliation check across the installed modules, run on demand.",
842
+ actions: /* @__PURE__ */ jsxs5(Fragment3, { children: [
843
+ report && !isLoading ? /* @__PURE__ */ jsx5(Pill, { tone: report.ok ? "ok" : "bad", children: report.ok ? "all checks pass" : "a check failed" }) : null,
844
+ /* @__PURE__ */ jsx5(RefreshButton, { onClick: () => void refresh(), busy: isLoading, label: "Run again" })
845
+ ] })
846
+ }
847
+ ),
848
+ isLoading && !report ? /* @__PURE__ */ jsx5("p", { style: styles.status, children: "Running the checks\u2026" }) : error ? /* @__PURE__ */ jsx5("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsxs5("table", { style: styles.table, children: [
849
+ /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
850
+ /* @__PURE__ */ jsx5("th", { style: { ...styles.th, width: 110 }, children: "Result" }),
851
+ /* @__PURE__ */ jsx5("th", { style: styles.th, children: "Check" }),
852
+ /* @__PURE__ */ jsx5("th", { style: { ...styles.th, textAlign: "right", width: 90 }, children: "Time" })
853
+ ] }) }),
854
+ /* @__PURE__ */ jsx5("tbody", { children: report?.checks.map((c) => /* @__PURE__ */ jsxs5("tr", { children: [
855
+ /* @__PURE__ */ jsx5("td", { style: styles.td, children: /* @__PURE__ */ jsx5(
856
+ Pill,
857
+ {
858
+ tone: c.skipped ? "neutral" : c.ok ? c.findings.length ? "warn" : "ok" : "bad",
859
+ children: c.skipped ? "skipped" : c.ok ? c.findings.length ? "advice" : "ok" : "failed"
860
+ }
861
+ ) }),
862
+ /* @__PURE__ */ jsxs5("td", { style: styles.td, children: [
863
+ /* @__PURE__ */ jsxs5("div", { children: [
864
+ /* @__PURE__ */ jsx5("span", { style: { ...styles.mono, fontWeight: 600 }, children: c.name }),
865
+ " ",
866
+ /* @__PURE__ */ jsx5("span", { style: styles.muted, children: c.module })
867
+ ] }),
868
+ c.skipped ? /* @__PURE__ */ jsx5("div", { style: { ...styles.muted, marginTop: 4 }, children: c.skipped }) : null,
869
+ c.findings.map((f) => /* @__PURE__ */ jsx5(
870
+ "div",
871
+ {
872
+ style: {
873
+ marginTop: 4,
874
+ fontSize: 13,
875
+ lineHeight: 1.55,
876
+ color: c.ok ? "var(--fonderie-text,#171717)" : "var(--fonderie-danger,#e00)"
877
+ },
878
+ children: f
879
+ },
880
+ f
881
+ ))
882
+ ] }),
883
+ /* @__PURE__ */ jsxs5(
884
+ "td",
885
+ {
886
+ style: {
887
+ ...styles.td,
888
+ ...styles.muted,
889
+ textAlign: "right",
890
+ whiteSpace: "nowrap"
891
+ },
892
+ children: [
893
+ c.durationMs,
894
+ " ms"
895
+ ]
896
+ }
897
+ )
898
+ ] }, c.name)) })
899
+ ] })
293
900
  ] });
294
901
  }
295
902
 
296
903
  // src/screens/ModulesScreen.tsx
297
- import { useManifest } from "@fonderie/react-admin";
298
- import { Fragment as Fragment3, jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
904
+ import { useManifest as useManifest2 } from "@fonderie/react-admin";
905
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
299
906
  function ModulesScreen({ client }) {
300
- const { manifest, isLoading, error } = useManifest(client);
301
- return /* @__PURE__ */ jsxs5("div", { style: styles.container, children: [
302
- /* @__PURE__ */ jsx5("h1", { style: styles.title, children: "Modules" }),
303
- isLoading ? /* @__PURE__ */ jsx5("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx5("p", { style: styles.error, role: "alert", children: error.explanation }) : manifest ? /* @__PURE__ */ jsxs5(Fragment3, { children: [
304
- /* @__PURE__ */ jsxs5("p", { style: styles.muted, children: [
305
- manifest.env,
306
- " \xB7 admin ",
307
- manifest.admin.version,
308
- " \xB7 admin log",
309
- " ",
310
- manifest.admin.log ? "on" : "off",
311
- " \xB7 ",
312
- manifest.routes.length,
313
- " routes"
314
- ] }),
315
- /* @__PURE__ */ jsxs5("table", { style: styles.table, children: [
316
- /* @__PURE__ */ jsx5("thead", { children: /* @__PURE__ */ jsxs5("tr", { children: [
317
- /* @__PURE__ */ jsx5("th", { style: styles.th, children: "Module" }),
318
- /* @__PURE__ */ jsx5("th", { style: styles.th, children: "Version" }),
319
- /* @__PURE__ */ jsx5("th", { style: styles.th, children: "Readiness" }),
320
- /* @__PURE__ */ jsx5("th", { style: styles.th, children: "Describes admin" })
321
- ] }) }),
322
- /* @__PURE__ */ jsx5("tbody", { children: manifest.modules.map((m) => /* @__PURE__ */ jsxs5("tr", { children: [
323
- /* @__PURE__ */ jsx5("td", { style: { ...styles.td, ...styles.mono }, children: m.name }),
324
- /* @__PURE__ */ jsx5("td", { style: styles.td, children: m.version ?? /* @__PURE__ */ jsx5("span", { style: styles.muted, children: "not reported" }) }),
325
- /* @__PURE__ */ jsxs5("td", { style: styles.td, children: [
326
- m.readiness.ok ? /* @__PURE__ */ jsx5("span", { style: styles.ok, children: "ok" }) : /* @__PURE__ */ jsx5("span", { style: styles.bad, children: "error" }),
327
- m.readiness.problems.map((p) => /* @__PURE__ */ jsx5(
328
- "div",
329
- {
330
- style: p.severity === "error" ? styles.bad : styles.advice,
331
- children: p.message
907
+ const { manifest, isLoading, error } = useManifest2(client);
908
+ return /* @__PURE__ */ jsxs6("div", { style: styles.container, children: [
909
+ /* @__PURE__ */ jsx6(
910
+ PageHeader,
911
+ {
912
+ title: "Modules",
913
+ lead: manifest ? `${manifest.env} \xB7 admin ${manifest.admin.version} \xB7 admin log ${manifest.admin.log ? "on" : "off"} \xB7 ${manifest.routes.length} routes` : "Every installed module, its version and whether it is ready."
914
+ }
915
+ ),
916
+ isLoading ? /* @__PURE__ */ jsx6("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx6("p", { style: styles.error, role: "alert", children: error.explanation }) : manifest ? /* @__PURE__ */ jsxs6("table", { style: styles.table, children: [
917
+ /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { children: [
918
+ /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Module" }),
919
+ /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Version" }),
920
+ /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Readiness" }),
921
+ /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Admin" })
922
+ ] }) }),
923
+ /* @__PURE__ */ jsx6("tbody", { children: manifest.modules.map((m) => /* @__PURE__ */ jsxs6("tr", { children: [
924
+ /* @__PURE__ */ jsx6("td", { style: { ...styles.td, ...styles.mono, fontWeight: 600 }, children: m.name }),
925
+ /* @__PURE__ */ jsx6("td", { style: { ...styles.td, ...styles.mono }, children: m.version ?? /* @__PURE__ */ jsx6("span", { style: styles.muted, children: "not reported" }) }),
926
+ /* @__PURE__ */ jsxs6("td", { style: styles.td, children: [
927
+ /* @__PURE__ */ jsx6(Pill, { tone: m.readiness.ok ? "ok" : "bad", children: m.readiness.ok ? "ready" : "error" }),
928
+ m.readiness.problems.map((p) => /* @__PURE__ */ jsx6(
929
+ "div",
930
+ {
931
+ style: {
932
+ marginTop: 6,
933
+ fontSize: 13,
934
+ color: p.severity === "error" ? "var(--fonderie-danger,#e00)" : "var(--fonderie-text-muted,#5c5c5c)"
332
935
  },
333
- p.message
334
- ))
335
- ] }),
336
- /* @__PURE__ */ jsx5("td", { style: styles.td, children: m.describesAdmin ? "yes" : /* @__PURE__ */ jsx5("span", { style: styles.muted, children: "no" }) })
337
- ] }, m.name)) })
338
- ] })
936
+ children: p.message
937
+ },
938
+ p.message
939
+ ))
940
+ ] }),
941
+ /* @__PURE__ */ jsx6("td", { style: styles.td, children: m.describesAdmin ? /* @__PURE__ */ jsx6("span", { style: styles.badge, children: "describes admin" }) : /* @__PURE__ */ jsx6("span", { style: styles.muted, children: "\u2014" }) })
942
+ ] }, m.name)) })
339
943
  ] }) : null
340
944
  ] });
341
945
  }
342
946
 
343
947
  // src/screens/RoutesScreen.tsx
344
948
  import { useAdminRoutes } from "@fonderie/react-admin";
345
- import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
949
+ import { useState } from "react";
950
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
346
951
  function RoutesScreen({ client }) {
347
952
  const { report, isLoading, error } = useAdminRoutes(client);
348
- return /* @__PURE__ */ jsxs6("div", { style: styles.container, children: [
349
- /* @__PURE__ */ jsx6("h1", { style: styles.title, children: "Routes" }),
350
- isLoading ? /* @__PURE__ */ jsx6("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx6("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsxs6("table", { style: styles.table, children: [
351
- /* @__PURE__ */ jsx6("thead", { children: /* @__PURE__ */ jsxs6("tr", { children: [
352
- /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Method" }),
353
- /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Path" }),
354
- /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Guard" }),
355
- /* @__PURE__ */ jsx6("th", { style: styles.th, children: "Module" })
953
+ const [q, setQ] = useState("");
954
+ const needle = q.trim().toLowerCase();
955
+ const routes = (report?.routes ?? []).filter(
956
+ (r) => !needle || `${r.method} ${r.path} ${r.module ?? "application"} ${r.guard}`.toLowerCase().includes(needle)
957
+ );
958
+ return /* @__PURE__ */ jsxs7("div", { style: styles.container, children: [
959
+ /* @__PURE__ */ jsx7(
960
+ PageHeader,
961
+ {
962
+ title: "Routes",
963
+ lead: report ? `${report.routes.length} routes exposed by this deployment, with the guard in front of each.` : "Every exposed route, with its guard.",
964
+ actions: /* @__PURE__ */ jsx7(
965
+ "input",
966
+ {
967
+ type: "search",
968
+ value: q,
969
+ onChange: (e) => setQ(e.target.value),
970
+ placeholder: "Filter by path, method, module\u2026",
971
+ style: { ...styles.input, width: 280 },
972
+ "aria-label": "Filter routes"
973
+ }
974
+ )
975
+ }
976
+ ),
977
+ isLoading ? /* @__PURE__ */ jsx7("p", { style: styles.status, children: "Loading\u2026" }) : error ? /* @__PURE__ */ jsx7("p", { style: styles.error, role: "alert", children: error.explanation }) : /* @__PURE__ */ jsxs7("table", { style: styles.table, children: [
978
+ /* @__PURE__ */ jsx7("thead", { children: /* @__PURE__ */ jsxs7("tr", { children: [
979
+ /* @__PURE__ */ jsx7("th", { style: { ...styles.th, width: 80 }, children: "Method" }),
980
+ /* @__PURE__ */ jsx7("th", { style: styles.th, children: "Path" }),
981
+ /* @__PURE__ */ jsx7("th", { style: styles.th, children: "Guard" }),
982
+ /* @__PURE__ */ jsx7("th", { style: styles.th, children: "Module" })
356
983
  ] }) }),
357
- /* @__PURE__ */ jsx6("tbody", { children: report?.routes.map((r) => /* @__PURE__ */ jsxs6("tr", { children: [
358
- /* @__PURE__ */ jsx6("td", { style: { ...styles.td, ...styles.mono }, children: r.method }),
359
- /* @__PURE__ */ jsx6("td", { style: { ...styles.td, ...styles.mono }, children: r.path }),
360
- /* @__PURE__ */ jsx6("td", { style: styles.td, children: /* @__PURE__ */ jsx6("span", { style: styles.badge, children: r.guard }) }),
361
- /* @__PURE__ */ jsx6("td", { style: { ...styles.td, ...styles.muted }, children: r.module ?? "application" })
362
- ] }, `${r.method} ${r.path}`)) })
984
+ /* @__PURE__ */ jsxs7("tbody", { children: [
985
+ routes.map((r) => /* @__PURE__ */ jsxs7("tr", { children: [
986
+ /* @__PURE__ */ jsx7("td", { style: styles.td, children: /* @__PURE__ */ jsx7(MethodChip, { method: r.method }) }),
987
+ /* @__PURE__ */ jsx7("td", { style: { ...styles.td, ...styles.mono, wordBreak: "break-all" }, children: r.path }),
988
+ /* @__PURE__ */ jsx7("td", { style: styles.td, children: /* @__PURE__ */ jsx7(
989
+ Pill,
990
+ {
991
+ tone: r.guard === "admin" ? "ok" : r.guard === "probe" ? "neutral" : "info",
992
+ dot: false,
993
+ children: r.guard
994
+ }
995
+ ) }),
996
+ /* @__PURE__ */ jsx7("td", { style: { ...styles.td, ...styles.muted }, children: r.module ?? "application" })
997
+ ] }, `${r.method} ${r.path}`)),
998
+ routes.length === 0 ? /* @__PURE__ */ jsx7("tr", { children: /* @__PURE__ */ jsxs7("td", { style: { ...styles.td, ...styles.muted }, colSpan: 4, children: [
999
+ "No route matches \u201C",
1000
+ q,
1001
+ "\u201D."
1002
+ ] }) }) : null
1003
+ ] })
363
1004
  ] })
364
1005
  ] });
365
1006
  }
366
1007
 
367
1008
  // src/screens/MigrationsScreen.tsx
368
1009
  import { useAdminMigrations } from "@fonderie/react-admin";
369
- import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
1010
+ import { jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
370
1011
  function why(m, everApplied) {
371
1012
  if (m.pending.length === 0) return null;
372
1013
  if (m.blockedBy) return `Apply "${m.blockedBy}" first \u2014 it runs before this one and is behind.`;
@@ -377,36 +1018,40 @@ function why(m, everApplied) {
377
1018
  function MigrationsScreen({ client }) {
378
1019
  const { report, isLoading, error, refresh, apply } = useAdminMigrations(client);
379
1020
  const behind = report?.modules.filter((m) => m.pending.length > 0) ?? [];
380
- return /* @__PURE__ */ jsxs7("div", { style: styles.container, children: [
381
- /* @__PURE__ */ jsx7("h1", { style: styles.title, children: "Migrations" }),
382
- error ? /* @__PURE__ */ jsx7("p", { style: styles.error, role: "alert", children: error.status === 403 ? "Applying migrations needs a token with the write scope." : error.explanation }) : null,
383
- /* @__PURE__ */ jsxs7("p", { children: [
384
- /* @__PURE__ */ jsx7("button", { type: "button", style: styles.button, onClick: () => void refresh(), disabled: isLoading, children: isLoading ? "Checking\u2026" : "Refresh" }),
385
- " ",
386
- report && !report.everApplied ? /* @__PURE__ */ jsx7("span", { style: styles.advice, children: "This database has never been migrated \u2014 treating it as a first install, so nothing is held back." }) : null
387
- ] }),
388
- report && behind.length === 0 ? /* @__PURE__ */ jsx7("p", { style: styles.ok, children: "Every module is up to date." }) : null,
1021
+ return /* @__PURE__ */ jsxs8("div", { style: styles.container, children: [
1022
+ /* @__PURE__ */ jsx8(
1023
+ PageHeader,
1024
+ {
1025
+ title: "Migrations",
1026
+ lead: "Schema changes each module ships, and whether this database has them.",
1027
+ actions: /* @__PURE__ */ jsx8(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1028
+ }
1029
+ ),
1030
+ error ? /* @__PURE__ */ jsx8("p", { style: styles.error, role: "alert", children: error.status === 403 ? "Applying migrations needs a token with the write scope." : error.explanation }) : null,
1031
+ report && !report.everApplied ? /* @__PURE__ */ jsx8("p", { style: styles.notice, children: "This database has never been migrated \u2014 treating it as a first install, so nothing is held back." }) : null,
1032
+ report && behind.length === 0 ? /* @__PURE__ */ jsx8(Empty, { icon: "migrations", title: "Every module is up to date", children: "No pending migrations." }) : null,
389
1033
  behind.map((m) => {
390
1034
  const blocked = why(m, report?.everApplied ?? false);
391
1035
  const files = m.pending.map((p) => p.file);
392
- return /* @__PURE__ */ jsxs7("section", { style: { marginBottom: 24 }, children: [
393
- /* @__PURE__ */ jsxs7("h2", { style: styles.subtitle, children: [
394
- m.name,
395
- " \u2014 ",
396
- m.pending.length,
397
- " pending"
1036
+ return /* @__PURE__ */ jsxs8("section", { style: { ...styles.card, marginBottom: 16 }, children: [
1037
+ /* @__PURE__ */ jsxs8("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: [
1038
+ /* @__PURE__ */ jsx8("span", { style: styles.mono, children: m.name }),
1039
+ /* @__PURE__ */ jsxs8(Pill, { tone: "warn", children: [
1040
+ m.pending.length,
1041
+ " pending"
1042
+ ] })
398
1043
  ] }),
399
- /* @__PURE__ */ jsx7("ul", { children: m.pending.map((p) => /* @__PURE__ */ jsxs7("li", { children: [
400
- /* @__PURE__ */ jsx7("code", { children: p.file }),
1044
+ /* @__PURE__ */ jsx8("ul", { style: { margin: "0 0 12px", paddingLeft: 18, lineHeight: 1.9 }, children: m.pending.map((p) => /* @__PURE__ */ jsxs8("li", { children: [
1045
+ /* @__PURE__ */ jsx8("code", { style: styles.code, children: p.file }),
401
1046
  " ",
402
- p.impact === "destructive" ? /* @__PURE__ */ jsx7("span", { style: styles.bad, children: "destructive" }) : /* @__PURE__ */ jsx7("span", { style: styles.advice, children: "additive" }),
403
- p.destructive.length > 0 ? /* @__PURE__ */ jsx7("ul", { children: p.destructive.map((s) => /* @__PURE__ */ jsx7("li", { children: /* @__PURE__ */ jsx7("code", { children: s.slice(0, 120) }) }, s)) }) : null
1047
+ p.impact === "destructive" ? /* @__PURE__ */ jsx8(Pill, { tone: "bad", children: "destructive" }) : /* @__PURE__ */ jsx8(Pill, { tone: "neutral", children: "additive" }),
1048
+ p.destructive.length > 0 ? /* @__PURE__ */ jsx8("ul", { children: p.destructive.map((s) => /* @__PURE__ */ jsx8("li", { children: /* @__PURE__ */ jsx8("code", { children: s.slice(0, 120) }) }, s)) }) : null
404
1049
  ] }, p.file)) }),
405
- blocked ? /* @__PURE__ */ jsx7("p", { style: styles.advice, children: blocked }) : /* @__PURE__ */ jsxs7(
1050
+ blocked ? /* @__PURE__ */ jsx8("p", { style: { ...styles.notice, marginBottom: 0 }, children: blocked }) : /* @__PURE__ */ jsxs8(
406
1051
  "button",
407
1052
  {
408
1053
  type: "button",
409
- style: styles.button,
1054
+ style: styles.buttonPrimary,
410
1055
  disabled: isLoading || !m.appliable,
411
1056
  onClick: () => {
412
1057
  if (window.confirm(
@@ -430,35 +1075,72 @@ function MigrationsScreen({ client }) {
430
1075
 
431
1076
  // src/screens/TokensScreen.tsx
432
1077
  import { useAdminTokens } from "@fonderie/react-admin";
433
- import { useState } from "react";
434
- import { Fragment as Fragment4, jsx as jsx8, jsxs as jsxs8 } from "react/jsx-runtime";
1078
+ import { useState as useState2 } from "react";
1079
+ import { Fragment as Fragment4, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
435
1080
  var ALL = ["read", "write", "secrets"];
436
1081
  function TokensScreen({ client }) {
437
1082
  const { report, isLoading, error, issue, revoke } = useAdminTokens(client);
438
- const [name, setName] = useState("");
439
- const [scopes, setScopes] = useState(["read"]);
440
- const [days, setDays] = useState("");
441
- const [minted, setMinted] = useState(null);
1083
+ const [name, setName] = useState2("");
1084
+ const [scopes, setScopes] = useState2(["read"]);
1085
+ const [days, setDays] = useState2("");
1086
+ const [minted, setMinted] = useState2(null);
442
1087
  const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
443
- return /* @__PURE__ */ jsxs8("div", { style: styles.container, children: [
444
- /* @__PURE__ */ jsx8("h1", { style: styles.title, children: "Access" }),
445
- error ? /* @__PURE__ */ jsx8("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
446
- /* @__PURE__ */ jsx8("h2", { style: styles.subtitle, children: "Root token" }),
447
- report ? /* @__PURE__ */ jsxs8("p", { children: [
448
- report.admin.ok ? /* @__PURE__ */ jsx8("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ jsx8("span", { style: styles.bad, children: "weak" }),
449
- report.admin.problems.map((p) => /* @__PURE__ */ jsx8("div", { style: styles.bad, children: p.message }, p.message))
1088
+ return /* @__PURE__ */ jsxs9("div", { style: styles.container, children: [
1089
+ /* @__PURE__ */ jsx9(
1090
+ PageHeader,
1091
+ {
1092
+ title: "Access",
1093
+ lead: "Who can open this surface: the root token's strength, scoped tokens you have issued, and any legacy per-module tokens."
1094
+ }
1095
+ ),
1096
+ error ? /* @__PURE__ */ jsx9("p", { style: styles.error, role: "alert", children: error.status === 401 ? "Issuing and revoking need the root token (the one in your deployment config)." : error.explanation }) : null,
1097
+ /* @__PURE__ */ jsx9("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Root token" }),
1098
+ report ? /* @__PURE__ */ jsxs9("div", { style: { ...styles.card, display: "flex", gap: 12, alignItems: "flex-start" }, children: [
1099
+ /* @__PURE__ */ jsx9(Icon, { name: "lock", size: 18, style: { marginTop: 2 } }),
1100
+ /* @__PURE__ */ jsxs9("div", { children: [
1101
+ /* @__PURE__ */ jsx9(Pill, { tone: report.admin.ok ? "ok" : "bad", children: report.admin.ok ? "strong" : "weak" }),
1102
+ report.admin.problems.map((p) => /* @__PURE__ */ jsx9(
1103
+ "div",
1104
+ {
1105
+ style: { marginTop: 6, color: "var(--fonderie-danger,#e00)", fontSize: 13 },
1106
+ children: p.message
1107
+ },
1108
+ p.message
1109
+ ))
1110
+ ] })
450
1111
  ] }) : null,
451
- /* @__PURE__ */ jsx8("h2", { style: styles.subtitle, children: "Issued tokens" }),
452
- report?.issued === null ? /* @__PURE__ */ jsx8("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ jsxs8(Fragment4, { children: [
453
- minted ? /* @__PURE__ */ jsxs8("p", { style: styles.ok, children: [
454
- "Copy this now \u2014 it is never shown again.",
455
- " ",
456
- /* @__PURE__ */ jsx8("span", { style: styles.mono, children: minted.token }),
457
- " (",
458
- minted.name,
459
- ")"
460
- ] }) : null,
461
- /* @__PURE__ */ jsxs8(
1112
+ /* @__PURE__ */ jsx9("h2", { style: styles.subtitle, children: "Issued tokens" }),
1113
+ report?.issued === null ? /* @__PURE__ */ jsx9("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ jsxs9(Fragment4, { children: [
1114
+ minted ? /* @__PURE__ */ jsxs9(
1115
+ "div",
1116
+ {
1117
+ style: {
1118
+ ...styles.notice,
1119
+ background: "color-mix(in srgb, var(--fonderie-accent,#00d294) 10%, transparent)",
1120
+ borderColor: "color-mix(in srgb, var(--fonderie-accent,#00d294) 35%, transparent)"
1121
+ },
1122
+ children: [
1123
+ /* @__PURE__ */ jsx9("strong", { children: "Copy this now \u2014 it is never shown again." }),
1124
+ " (",
1125
+ minted.name,
1126
+ ")",
1127
+ /* @__PURE__ */ jsx9(
1128
+ "div",
1129
+ {
1130
+ style: {
1131
+ ...styles.code,
1132
+ display: "block",
1133
+ marginTop: 8,
1134
+ padding: "8px 10px",
1135
+ wordBreak: "break-all"
1136
+ },
1137
+ children: minted.token
1138
+ }
1139
+ )
1140
+ ]
1141
+ }
1142
+ ) : null,
1143
+ /* @__PURE__ */ jsxs9(
462
1144
  "form",
463
1145
  {
464
1146
  style: styles.toolbar,
@@ -477,70 +1159,90 @@ function TokensScreen({ client }) {
477
1159
  });
478
1160
  },
479
1161
  children: [
480
- /* @__PURE__ */ jsx8(
1162
+ /* @__PURE__ */ jsx9(
481
1163
  "input",
482
1164
  {
483
1165
  value: name,
484
1166
  onChange: (e) => setName(e.target.value),
485
1167
  placeholder: "name",
486
- style: { ...styles.button, cursor: "text" },
1168
+ style: styles.input,
487
1169
  "aria-label": "Name"
488
1170
  }
489
1171
  ),
490
- ALL.map((s) => /* @__PURE__ */ jsxs8("label", { style: { ...styles.badge, cursor: "pointer" }, children: [
491
- /* @__PURE__ */ jsx8("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
492
- " ",
1172
+ ALL.map((s) => /* @__PURE__ */ jsxs9(
1173
+ "label",
1174
+ {
1175
+ style: {
1176
+ ...styles.badge,
1177
+ cursor: "pointer",
1178
+ height: 32,
1179
+ boxSizing: "border-box",
1180
+ padding: "0 10px",
1181
+ fontSize: 13
1182
+ },
1183
+ children: [
1184
+ /* @__PURE__ */ jsx9("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
1185
+ " ",
1186
+ s
1187
+ ]
1188
+ },
493
1189
  s
494
- ] }, s)),
495
- /* @__PURE__ */ jsx8(
1190
+ )),
1191
+ /* @__PURE__ */ jsx9(
496
1192
  "input",
497
1193
  {
498
1194
  value: days,
499
1195
  onChange: (e) => setDays(e.target.value),
500
1196
  placeholder: "days (optional)",
501
- style: { ...styles.button, cursor: "text", width: 140 },
1197
+ style: { ...styles.input, width: 140 },
502
1198
  "aria-label": "Days"
503
1199
  }
504
1200
  ),
505
- /* @__PURE__ */ jsx8(
1201
+ /* @__PURE__ */ jsxs9(
506
1202
  "button",
507
1203
  {
508
1204
  type: "submit",
509
- style: styles.button,
1205
+ style: styles.buttonPrimary,
510
1206
  disabled: !name.trim() || scopes.length === 0 || isLoading,
511
- children: "Issue"
1207
+ children: [
1208
+ /* @__PURE__ */ jsx9(Icon, { name: "plus", size: 14 }),
1209
+ "Issue token"
1210
+ ]
512
1211
  }
513
1212
  )
514
1213
  ]
515
1214
  }
516
1215
  ),
517
- report?.issued && report.issued.length > 0 ? /* @__PURE__ */ jsxs8("table", { style: styles.table, children: [
518
- /* @__PURE__ */ jsx8("thead", { children: /* @__PURE__ */ jsxs8("tr", { children: [
519
- /* @__PURE__ */ jsx8("th", { style: styles.th, children: "Name" }),
520
- /* @__PURE__ */ jsx8("th", { style: styles.th, children: "Scopes" }),
521
- /* @__PURE__ */ jsx8("th", { style: styles.th, children: "Created" }),
522
- /* @__PURE__ */ jsx8("th", { style: styles.th, children: "Expires" }),
523
- /* @__PURE__ */ jsx8("th", { style: styles.th, children: "Last used" }),
524
- /* @__PURE__ */ jsx8("th", { style: styles.th })
1216
+ report?.issued && report.issued.length > 0 ? /* @__PURE__ */ jsxs9("table", { style: styles.table, children: [
1217
+ /* @__PURE__ */ jsx9("thead", { children: /* @__PURE__ */ jsxs9("tr", { children: [
1218
+ /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Name" }),
1219
+ /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Scopes" }),
1220
+ /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Created" }),
1221
+ /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Expires" }),
1222
+ /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Last used" }),
1223
+ /* @__PURE__ */ jsx9("th", { style: styles.th })
525
1224
  ] }) }),
526
- /* @__PURE__ */ jsx8("tbody", { children: report.issued.map((t2) => /* @__PURE__ */ jsxs8("tr", { children: [
527
- /* @__PURE__ */ jsxs8("td", { style: styles.td, children: [
1225
+ /* @__PURE__ */ jsx9("tbody", { children: report.issued.map((t2) => /* @__PURE__ */ jsxs9("tr", { children: [
1226
+ /* @__PURE__ */ jsxs9("td", { style: styles.td, children: [
528
1227
  t2.name,
529
- t2.revokedAt ? /* @__PURE__ */ jsx8("span", { style: styles.badge, children: " revoked" }) : null
1228
+ t2.revokedAt ? /* @__PURE__ */ jsxs9(Fragment4, { children: [
1229
+ " ",
1230
+ /* @__PURE__ */ jsx9(Pill, { tone: "neutral", children: "revoked" })
1231
+ ] }) : null
530
1232
  ] }),
531
- /* @__PURE__ */ jsx8("td", { style: { ...styles.td, ...styles.mono }, children: t2.scopes.join(", ") }),
532
- /* @__PURE__ */ jsxs8("td", { style: { ...styles.td, ...styles.muted }, children: [
1233
+ /* @__PURE__ */ jsx9("td", { style: { ...styles.td, ...styles.mono }, children: t2.scopes.join(", ") }),
1234
+ /* @__PURE__ */ jsxs9("td", { style: { ...styles.td, ...styles.muted }, children: [
533
1235
  new Date(t2.createdAt).toLocaleDateString(),
534
1236
  " \xB7 ",
535
1237
  t2.createdBy
536
1238
  ] }),
537
- /* @__PURE__ */ jsx8("td", { style: { ...styles.td, ...styles.muted }, children: t2.expiresAt ? new Date(t2.expiresAt).toLocaleDateString() : "never" }),
538
- /* @__PURE__ */ jsx8("td", { style: { ...styles.td, ...styles.muted }, children: t2.lastUsedAt ? new Date(t2.lastUsedAt).toLocaleString() : "never" }),
539
- /* @__PURE__ */ jsx8("td", { style: styles.td, children: t2.revokedAt ? null : /* @__PURE__ */ jsx8(
1239
+ /* @__PURE__ */ jsx9("td", { style: { ...styles.td, ...styles.muted }, children: t2.expiresAt ? new Date(t2.expiresAt).toLocaleDateString() : "never" }),
1240
+ /* @__PURE__ */ jsx9("td", { style: { ...styles.td, ...styles.muted }, children: t2.lastUsedAt ? new Date(t2.lastUsedAt).toLocaleString() : "never" }),
1241
+ /* @__PURE__ */ jsx9("td", { style: styles.td, children: t2.revokedAt ? null : /* @__PURE__ */ jsx9(
540
1242
  "button",
541
1243
  {
542
1244
  type: "button",
543
- style: styles.button,
1245
+ style: styles.buttonDanger,
544
1246
  onClick: () => {
545
1247
  if (window.confirm(
546
1248
  `Revoke "${t2.name}"? Anything using it stops working immediately.`
@@ -552,13 +1254,13 @@ function TokensScreen({ client }) {
552
1254
  }
553
1255
  ) })
554
1256
  ] }, t2.id)) })
555
- ] }) : /* @__PURE__ */ jsx8("p", { style: styles.muted, children: "None issued." })
1257
+ ] }) : /* @__PURE__ */ jsx9(Empty, { icon: "tokens", title: "No scoped tokens yet", children: "Issue a read-only token for a dashboard or a teammate instead of sharing the root." })
556
1258
  ] }),
557
- /* @__PURE__ */ jsx8("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
558
- report && report.legacy.length === 0 ? /* @__PURE__ */ jsx8("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ jsx8("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ jsxs8("li", { style: styles.row, children: [
559
- /* @__PURE__ */ jsx8("span", { style: styles.mono, children: l.module }),
1259
+ /* @__PURE__ */ jsx9("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
1260
+ report && report.legacy.length === 0 ? /* @__PURE__ */ jsx9("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ jsx9("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ jsxs9("li", { style: styles.row, children: [
1261
+ /* @__PURE__ */ jsx9("span", { style: styles.mono, children: l.module }),
560
1262
  " ",
561
- /* @__PURE__ */ jsx8("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
1263
+ /* @__PURE__ */ jsx9("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
562
1264
  ] }, l.module)) })
563
1265
  ] });
564
1266
  }
@@ -571,11 +1273,11 @@ import {
571
1273
  useAdminUserSessions,
572
1274
  useAdminUsers
573
1275
  } from "@fonderie/react-admin";
574
- import { useState as useState2 } from "react";
575
- import { Fragment as Fragment5, jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
1276
+ import { useState as useState3 } from "react";
1277
+ import { Fragment as Fragment5, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
576
1278
  function UsersScreen({ client, pageSize = 50 }) {
577
- const [input, setInput] = useState2("");
578
- const [selected, setSelected] = useState2(null);
1279
+ const [input, setInput] = useState3("");
1280
+ const [selected, setSelected] = useState3(null);
579
1281
  const list = useAdminUsers(client, { limit: pageSize });
580
1282
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = useAdminUser(
581
1283
  client,
@@ -584,14 +1286,20 @@ function UsersScreen({ client, pageSize = 50 }) {
584
1286
  const userId = user?.id ?? null;
585
1287
  const sessions = useAdminUserSessions(client, userId);
586
1288
  const history = useAdminLoginHistory(client, userId, { limit: 20 });
587
- const yesNo = (v) => v ? /* @__PURE__ */ jsx9("span", { style: styles.ok, children: "yes" }) : /* @__PURE__ */ jsx9("span", { style: styles.muted, children: "no" });
1289
+ const yesNo = (v) => v ? /* @__PURE__ */ jsx10(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ jsx10(Pill, { tone: "neutral", children: "no" });
588
1290
  const clear = () => {
589
1291
  setSelected(null);
590
1292
  setInput("");
591
1293
  };
592
- return /* @__PURE__ */ jsxs9("div", { style: styles.container, children: [
593
- /* @__PURE__ */ jsx9("h1", { style: styles.title, children: "Users" }),
594
- /* @__PURE__ */ jsxs9(
1294
+ return /* @__PURE__ */ jsxs10("div", { style: styles.container, children: [
1295
+ /* @__PURE__ */ jsx10(
1296
+ PageHeader,
1297
+ {
1298
+ title: "Users",
1299
+ lead: "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1300
+ }
1301
+ ),
1302
+ /* @__PURE__ */ jsxs10(
595
1303
  "form",
596
1304
  {
597
1305
  style: styles.toolbar,
@@ -601,19 +1309,25 @@ function UsersScreen({ client, pageSize = 50 }) {
601
1309
  setSelected(email ? { email } : null);
602
1310
  },
603
1311
  children: [
604
- /* @__PURE__ */ jsx9(
1312
+ /* @__PURE__ */ jsx10(
605
1313
  "input",
606
1314
  {
607
1315
  type: "email",
608
1316
  value: input,
609
1317
  onChange: (e) => setInput(e.target.value),
610
1318
  placeholder: "email address",
611
- style: { ...styles.button, cursor: "text", minWidth: 280 },
1319
+ style: { ...styles.input, minWidth: 280 },
612
1320
  "aria-label": "Email"
613
1321
  }
614
1322
  ),
615
- /* @__PURE__ */ jsx9("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" }),
616
- selected ? /* @__PURE__ */ jsx9("button", { type: "button", style: styles.button, onClick: clear, children: "\u2190 All users" }) : /* @__PURE__ */ jsx9(
1323
+ /* @__PURE__ */ jsxs10("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1324
+ /* @__PURE__ */ jsx10(Icon, { name: "search", size: 14 }),
1325
+ "Look up"
1326
+ ] }),
1327
+ selected ? /* @__PURE__ */ jsxs10("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1328
+ /* @__PURE__ */ jsx10(Icon, { name: "back", size: 14 }),
1329
+ "All users"
1330
+ ] }) : /* @__PURE__ */ jsx10(
617
1331
  "button",
618
1332
  {
619
1333
  type: "button",
@@ -626,37 +1340,37 @@ function UsersScreen({ client, pageSize = 50 }) {
626
1340
  ]
627
1341
  }
628
1342
  ),
629
- error ? /* @__PURE__ */ jsx9("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
630
- !selected ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
631
- list.error ? /* @__PURE__ */ jsx9("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
632
- list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ jsx9("p", { style: styles.muted, children: "No users yet." }) : /* @__PURE__ */ jsxs9("table", { style: styles.table, children: [
633
- /* @__PURE__ */ jsx9("thead", { children: /* @__PURE__ */ jsxs9("tr", { children: [
634
- /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Email" }),
635
- /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Name" }),
636
- /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Created" }),
637
- /* @__PURE__ */ jsx9("th", { style: styles.th, children: "Status" })
1343
+ error ? /* @__PURE__ */ jsx10("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1344
+ !selected ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
1345
+ list.error ? /* @__PURE__ */ jsx10("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1346
+ list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ jsx10(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ jsxs10("table", { style: styles.table, children: [
1347
+ /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsxs10("tr", { children: [
1348
+ /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Email" }),
1349
+ /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Name" }),
1350
+ /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Created" }),
1351
+ /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Status" })
638
1352
  ] }) }),
639
- /* @__PURE__ */ jsx9("tbody", { children: list.users.map((u) => /* @__PURE__ */ jsxs9("tr", { children: [
640
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: /* @__PURE__ */ jsx9(
1353
+ /* @__PURE__ */ jsx10("tbody", { children: list.users.map((u) => /* @__PURE__ */ jsxs10("tr", { children: [
1354
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: /* @__PURE__ */ jsx10(
641
1355
  "button",
642
1356
  {
643
1357
  type: "button",
644
- style: { ...styles.navItem, padding: 0 },
1358
+ style: styles.link,
645
1359
  onClick: () => setSelected({ id: u.id }),
646
1360
  children: u.email
647
1361
  }
648
1362
  ) }),
649
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ jsx9("span", { style: styles.muted, children: "\u2014" }) }),
650
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
651
- /* @__PURE__ */ jsxs9("td", { style: styles.td, children: [
652
- u.suspended ? /* @__PURE__ */ jsx9("span", { style: styles.badge, children: "suspended" }) : null,
653
- u.deletedAt ? /* @__PURE__ */ jsx9("span", { style: styles.badge, children: "deleted" }) : null,
654
- !u.suspended && !u.deletedAt ? /* @__PURE__ */ jsx9("span", { style: styles.muted, children: "active" }) : null
1363
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ jsx10("span", { style: styles.muted, children: "\u2014" }) }),
1364
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1365
+ /* @__PURE__ */ jsxs10("td", { style: styles.td, children: [
1366
+ u.suspended ? /* @__PURE__ */ jsx10(Pill, { tone: "warn", children: "suspended" }) : null,
1367
+ u.deletedAt ? /* @__PURE__ */ jsx10(Pill, { tone: "neutral", children: "deleted" }) : null,
1368
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ jsx10(Pill, { tone: "ok", children: "active" }) : null
655
1369
  ] })
656
1370
  ] }, u.id)) })
657
1371
  ] }),
658
- list.isLoading ? /* @__PURE__ */ jsx9("p", { style: styles.status, children: "Loading\u2026" }) : null,
659
- list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx9(
1372
+ list.isLoading ? /* @__PURE__ */ jsx10("p", { style: styles.status, children: "Loading\u2026" }) : null,
1373
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx10(
660
1374
  "button",
661
1375
  {
662
1376
  type: "button",
@@ -666,48 +1380,48 @@ function UsersScreen({ client, pageSize = 50 }) {
666
1380
  }
667
1381
  ) : null
668
1382
  ] }) : null,
669
- user ? /* @__PURE__ */ jsxs9(Fragment5, { children: [
670
- /* @__PURE__ */ jsxs9("h2", { style: styles.subtitle, children: [
1383
+ user ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
1384
+ /* @__PURE__ */ jsxs10("h2", { style: styles.subtitle, children: [
671
1385
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
672
1386
  " ",
673
- user.suspended ? /* @__PURE__ */ jsx9("span", { style: styles.badge, children: "suspended" }) : null,
674
- user.deletedAt ? /* @__PURE__ */ jsx9("span", { style: styles.badge, children: "deleted" }) : null
1387
+ user.suspended ? /* @__PURE__ */ jsx10(Pill, { tone: "warn", children: "suspended" }) : null,
1388
+ user.deletedAt ? /* @__PURE__ */ jsx10(Pill, { tone: "neutral", children: "deleted" }) : null
675
1389
  ] }),
676
- /* @__PURE__ */ jsx9("table", { style: styles.table, children: /* @__PURE__ */ jsxs9("tbody", { children: [
677
- /* @__PURE__ */ jsxs9("tr", { children: [
678
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "id" }),
679
- /* @__PURE__ */ jsx9("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1390
+ /* @__PURE__ */ jsx10("table", { style: styles.table, children: /* @__PURE__ */ jsxs10("tbody", { children: [
1391
+ /* @__PURE__ */ jsxs10("tr", { children: [
1392
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "id" }),
1393
+ /* @__PURE__ */ jsx10("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
680
1394
  ] }),
681
- /* @__PURE__ */ jsxs9("tr", { children: [
682
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "email" }),
683
- /* @__PURE__ */ jsxs9("td", { style: styles.td, children: [
1395
+ /* @__PURE__ */ jsxs10("tr", { children: [
1396
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "email" }),
1397
+ /* @__PURE__ */ jsxs10("td", { style: styles.td, children: [
684
1398
  user.email,
685
1399
  " \xB7 verified ",
686
1400
  yesNo(user.isEmailVerified)
687
1401
  ] })
688
1402
  ] }),
689
- /* @__PURE__ */ jsxs9("tr", { children: [
690
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "MFA" }),
691
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1403
+ /* @__PURE__ */ jsxs10("tr", { children: [
1404
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "MFA" }),
1405
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
692
1406
  ] }),
693
- /* @__PURE__ */ jsxs9("tr", { children: [
694
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "provider" }),
695
- /* @__PURE__ */ jsxs9("td", { style: styles.td, children: [
1407
+ /* @__PURE__ */ jsxs10("tr", { children: [
1408
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "provider" }),
1409
+ /* @__PURE__ */ jsxs10("td", { style: styles.td, children: [
696
1410
  user.provider || "password",
697
1411
  user.hasPassword ? "" : " \xB7 no password set"
698
1412
  ] })
699
1413
  ] }),
700
- /* @__PURE__ */ jsxs9("tr", { children: [
701
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "last login" }),
702
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ jsx9("span", { style: styles.muted, children: "never" }) })
1414
+ /* @__PURE__ */ jsxs10("tr", { children: [
1415
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "last login" }),
1416
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ jsx10("span", { style: styles.muted, children: "never" }) })
703
1417
  ] }),
704
- /* @__PURE__ */ jsxs9("tr", { children: [
705
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: "created" }),
706
- /* @__PURE__ */ jsx9("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1418
+ /* @__PURE__ */ jsxs10("tr", { children: [
1419
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: "created" }),
1420
+ /* @__PURE__ */ jsx10("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
707
1421
  ] })
708
1422
  ] }) }),
709
- /* @__PURE__ */ jsxs9("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
710
- user.suspended ? /* @__PURE__ */ jsx9(
1423
+ /* @__PURE__ */ jsxs10("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1424
+ user.suspended ? /* @__PURE__ */ jsx10(
711
1425
  "button",
712
1426
  {
713
1427
  type: "button",
@@ -716,17 +1430,17 @@ function UsersScreen({ client, pageSize = 50 }) {
716
1430
  disabled: isLoading,
717
1431
  children: "Unsuspend"
718
1432
  }
719
- ) : /* @__PURE__ */ jsx9(
1433
+ ) : /* @__PURE__ */ jsx10(
720
1434
  "button",
721
1435
  {
722
1436
  type: "button",
723
- style: styles.button,
1437
+ style: styles.buttonDanger,
724
1438
  onClick: () => void suspend(),
725
1439
  disabled: isLoading,
726
1440
  children: "Suspend"
727
1441
  }
728
1442
  ),
729
- /* @__PURE__ */ jsx9(
1443
+ /* @__PURE__ */ jsx10(
730
1444
  "button",
731
1445
  {
732
1446
  type: "button",
@@ -737,38 +1451,38 @@ function UsersScreen({ client, pageSize = 50 }) {
737
1451
  }
738
1452
  )
739
1453
  ] }),
740
- /* @__PURE__ */ jsx9("h2", { style: styles.subtitle, children: "Live sessions" }),
741
- sessions.sessions.length === 0 ? /* @__PURE__ */ jsx9("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ jsx9("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ jsxs9("li", { style: styles.row, children: [
742
- /* @__PURE__ */ jsx9("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1454
+ /* @__PURE__ */ jsx10("h2", { style: styles.subtitle, children: "Live sessions" }),
1455
+ sessions.sessions.length === 0 ? /* @__PURE__ */ jsx10("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ jsx10("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ jsxs10("li", { style: styles.row, children: [
1456
+ /* @__PURE__ */ jsx10("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
743
1457
  " ",
744
- describeLocation(s.location) ? /* @__PURE__ */ jsxs9("span", { style: styles.muted, children: [
1458
+ describeLocation(s.location) ? /* @__PURE__ */ jsxs10("span", { style: styles.muted, children: [
745
1459
  describeLocation(s.location),
746
1460
  " "
747
1461
  ] }) : null,
748
- /* @__PURE__ */ jsx9("span", { style: styles.muted, children: s.userAgent ?? "" }),
1462
+ /* @__PURE__ */ jsx10("span", { style: styles.muted, children: s.userAgent ?? "" }),
749
1463
  " ",
750
- /* @__PURE__ */ jsxs9("span", { style: styles.muted, children: [
1464
+ /* @__PURE__ */ jsxs10("span", { style: styles.muted, children: [
751
1465
  "since ",
752
1466
  new Date(s.createdAt).toLocaleString()
753
1467
  ] })
754
1468
  ] }, s.id)) }),
755
- /* @__PURE__ */ jsx9("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
756
- history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ jsx9("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ jsx9("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ jsxs9("li", { style: styles.row, children: [
757
- /* @__PURE__ */ jsx9("span", { style: e.outcome === "success" ? styles.ok : styles.bad, children: e.outcome }),
1469
+ /* @__PURE__ */ jsx10("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1470
+ history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ jsx10("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ jsx10("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ jsxs10("li", { style: styles.row, children: [
1471
+ /* @__PURE__ */ jsx10(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
758
1472
  " ",
759
- /* @__PURE__ */ jsx9("span", { style: styles.muted, children: e.method }),
1473
+ /* @__PURE__ */ jsx10("span", { style: styles.muted, children: e.method }),
760
1474
  " ",
761
- /* @__PURE__ */ jsx9("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1475
+ /* @__PURE__ */ jsx10("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
762
1476
  " ",
763
- describeLocation(e.location) ? /* @__PURE__ */ jsx9("span", { style: styles.muted, children: describeLocation(e.location) }) : null,
764
- e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ jsxs9("span", { style: styles.bad, children: [
1477
+ describeLocation(e.location) ? /* @__PURE__ */ jsx10("span", { style: styles.muted, children: describeLocation(e.location) }) : null,
1478
+ e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ jsxs10(Fragment5, { children: [
765
1479
  " ",
766
- e.location.proxy ? "proxy/VPN" : "hosting"
1480
+ /* @__PURE__ */ jsx10(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
767
1481
  ] }) : null,
768
1482
  " ",
769
- /* @__PURE__ */ jsx9("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1483
+ /* @__PURE__ */ jsx10("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
770
1484
  ] }, e.id)) }),
771
- history.hasMore && !history.isLoading ? /* @__PURE__ */ jsx9(
1485
+ history.hasMore && !history.isLoading ? /* @__PURE__ */ jsx10(
772
1486
  "button",
773
1487
  {
774
1488
  type: "button",
@@ -783,59 +1497,67 @@ function UsersScreen({ client, pageSize = 50 }) {
783
1497
 
784
1498
  // src/screens/CatalogScreen.tsx
785
1499
  import { useAdminCatalog } from "@fonderie/react-admin";
786
- import { Fragment as Fragment6, jsx as jsx10, jsxs as jsxs10 } from "react/jsx-runtime";
1500
+ import { Fragment as Fragment6, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
787
1501
  function CatalogScreen({ client }) {
788
1502
  const { catalog, isLoading, error, refresh, deletePlan } = useAdminCatalog(client);
789
1503
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
790
- return /* @__PURE__ */ jsxs10("div", { style: styles.container, children: [
791
- /* @__PURE__ */ jsxs10("div", { style: styles.toolbar, children: [
792
- /* @__PURE__ */ jsx10("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Catalog" }),
793
- /* @__PURE__ */ jsx10(
794
- "button",
1504
+ return /* @__PURE__ */ jsxs11("div", { style: styles.container, children: [
1505
+ /* @__PURE__ */ jsx11(
1506
+ PageHeader,
1507
+ {
1508
+ title: "Catalog",
1509
+ lead: "What you sell: plans as configured in code, and as stored in the database, side by side.",
1510
+ actions: /* @__PURE__ */ jsx11(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1511
+ }
1512
+ ),
1513
+ error ? /* @__PURE__ */ jsx11("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1514
+ isLoading && !catalog ? /* @__PURE__ */ jsx11("p", { style: styles.status, children: "Loading\u2026" }) : null,
1515
+ catalog ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
1516
+ /* @__PURE__ */ jsx11("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
1517
+ /* @__PURE__ */ jsx11(
1518
+ "pre",
795
1519
  {
796
- type: "button",
797
- style: styles.button,
798
- onClick: () => void refresh(),
799
- disabled: isLoading,
800
- children: "Refresh"
1520
+ style: {
1521
+ ...styles.card,
1522
+ ...styles.mono,
1523
+ margin: 0,
1524
+ overflowX: "auto",
1525
+ maxHeight: 360,
1526
+ lineHeight: 1.6
1527
+ },
1528
+ children: JSON.stringify(catalog.configured, null, 2)
801
1529
  }
802
- )
803
- ] }),
804
- error ? /* @__PURE__ */ jsx10("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
805
- isLoading && !catalog ? /* @__PURE__ */ jsx10("p", { style: styles.status, children: "Loading\u2026" }) : null,
806
- catalog ? /* @__PURE__ */ jsxs10(Fragment6, { children: [
807
- /* @__PURE__ */ jsx10("h2", { style: styles.subtitle, children: "Configured (code)" }),
808
- /* @__PURE__ */ jsx10("pre", { style: { ...styles.mono, background: "var(--fonderie-surface-alt,#fafafa)", padding: 12, overflowX: "auto" }, children: JSON.stringify(catalog.configured, null, 2) }),
809
- /* @__PURE__ */ jsx10("h2", { style: styles.subtitle, children: "Stored (database)" }),
810
- catalog.stored.length === 0 ? /* @__PURE__ */ jsx10("p", { style: styles.muted, children: "No stored plans." }) : /* @__PURE__ */ jsxs10("table", { style: styles.table, children: [
811
- /* @__PURE__ */ jsx10("thead", { children: /* @__PURE__ */ jsxs10("tr", { children: [
812
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Plan" }),
813
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Tier" }),
814
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Seats" }),
815
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Monthly" }),
816
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Yearly" }),
817
- /* @__PURE__ */ jsx10("th", { style: styles.th, children: "Trial" }),
818
- /* @__PURE__ */ jsx10("th", { style: styles.th })
1530
+ ),
1531
+ /* @__PURE__ */ jsx11("h2", { style: styles.subtitle, children: "Stored (database)" }),
1532
+ catalog.stored.length === 0 ? /* @__PURE__ */ jsx11(Empty, { icon: "catalog", title: "No stored plans", children: "Plans are configured in code; nothing has been written to the database." }) : /* @__PURE__ */ jsxs11("table", { style: styles.table, children: [
1533
+ /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs11("tr", { children: [
1534
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Plan" }),
1535
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Tier" }),
1536
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Seats" }),
1537
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Monthly" }),
1538
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Yearly" }),
1539
+ /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Trial" }),
1540
+ /* @__PURE__ */ jsx11("th", { style: styles.th })
819
1541
  ] }) }),
820
- /* @__PURE__ */ jsx10("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ jsxs10("tr", { children: [
821
- /* @__PURE__ */ jsxs10("td", { style: styles.td, children: [
822
- /* @__PURE__ */ jsx10("strong", { children: p.name }),
823
- p.description ? /* @__PURE__ */ jsx10("div", { style: styles.muted, children: p.description }) : null
1542
+ /* @__PURE__ */ jsx11("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ jsxs11("tr", { children: [
1543
+ /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
1544
+ /* @__PURE__ */ jsx11("strong", { children: p.name }),
1545
+ p.description ? /* @__PURE__ */ jsx11("div", { style: styles.muted, children: p.description }) : null
824
1546
  ] }),
825
- /* @__PURE__ */ jsx10("td", { style: styles.td, children: p.tier }),
826
- /* @__PURE__ */ jsx10("td", { style: styles.td, children: p.seats ?? "\u221E" }),
827
- /* @__PURE__ */ jsxs10("td", { style: styles.td, children: [
1547
+ /* @__PURE__ */ jsx11("td", { style: styles.td, children: p.tier }),
1548
+ /* @__PURE__ */ jsx11("td", { style: styles.td, children: p.seats ?? "\u221E" }),
1549
+ /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
828
1550
  money(p.pricing?.monthly),
829
1551
  " ",
830
1552
  p.pricing?.currency ?? ""
831
1553
  ] }),
832
- /* @__PURE__ */ jsx10("td", { style: styles.td, children: money(p.pricing?.yearly) }),
833
- /* @__PURE__ */ jsx10("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
834
- /* @__PURE__ */ jsx10("td", { style: styles.td, children: /* @__PURE__ */ jsx10(
1554
+ /* @__PURE__ */ jsx11("td", { style: styles.td, children: money(p.pricing?.yearly) }),
1555
+ /* @__PURE__ */ jsx11("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
1556
+ /* @__PURE__ */ jsx11("td", { style: styles.td, children: /* @__PURE__ */ jsx11(
835
1557
  "button",
836
1558
  {
837
1559
  type: "button",
838
- style: styles.button,
1560
+ style: styles.buttonDanger,
839
1561
  onClick: () => {
840
1562
  if (window.confirm(`Delete stored plan "${p.name}"?`))
841
1563
  void deletePlan(p.id).catch(() => {
@@ -852,26 +1574,32 @@ function CatalogScreen({ client }) {
852
1574
 
853
1575
  // src/screens/SubscriberScreen.tsx
854
1576
  import { useAdminSubscriber, useAdminSubscribers } from "@fonderie/react-admin";
855
- import { useState as useState3 } from "react";
856
- import { Fragment as Fragment7, jsx as jsx11, jsxs as jsxs11 } from "react/jsx-runtime";
1577
+ import { useState as useState4 } from "react";
1578
+ import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
857
1579
  function SubscriberScreen({ client, pageSize = 50 }) {
858
- const [type, setType] = useState3("user");
859
- const [idInput, setIdInput] = useState3("");
860
- const [subscriber, setSubscriber] = useState3(null);
1580
+ const [type, setType] = useState4("user");
1581
+ const [idInput, setIdInput] = useState4("");
1582
+ const [subscriber, setSubscriber] = useState4(null);
861
1583
  const list = useAdminSubscribers(client, { limit: pageSize });
862
1584
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = useAdminSubscriber(client, subscriber, { limit: 20 });
863
- const [amount, setAmount] = useState3("");
864
- const [note, setNote] = useState3("");
865
- const [granted, setGranted] = useState3(null);
1585
+ const [amount, setAmount] = useState4("");
1586
+ const [note, setNote] = useState4("");
1587
+ const [granted, setGranted] = useState4(null);
866
1588
  const open = (t2, id) => {
867
1589
  setGranted(null);
868
1590
  setType(t2);
869
1591
  setIdInput(id);
870
1592
  setSubscriber({ type: t2, id });
871
1593
  };
872
- return /* @__PURE__ */ jsxs11("div", { style: styles.container, children: [
873
- /* @__PURE__ */ jsx11("h1", { style: styles.title, children: "Subscriber" }),
874
- /* @__PURE__ */ jsxs11(
1594
+ return /* @__PURE__ */ jsxs12("div", { style: styles.container, children: [
1595
+ /* @__PURE__ */ jsx12(
1596
+ PageHeader,
1597
+ {
1598
+ title: "Subscribers",
1599
+ lead: "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
1600
+ }
1601
+ ),
1602
+ /* @__PURE__ */ jsxs12(
875
1603
  "form",
876
1604
  {
877
1605
  style: styles.toolbar,
@@ -881,31 +1609,34 @@ function SubscriberScreen({ client, pageSize = 50 }) {
881
1609
  setSubscriber(idInput.trim() ? { type, id: idInput.trim() } : null);
882
1610
  },
883
1611
  children: [
884
- /* @__PURE__ */ jsxs11(
1612
+ /* @__PURE__ */ jsxs12(
885
1613
  "select",
886
1614
  {
887
1615
  value: type,
888
1616
  onChange: (e) => setType(e.target.value),
889
- style: styles.button,
1617
+ style: styles.input,
890
1618
  "aria-label": "Type",
891
1619
  children: [
892
- /* @__PURE__ */ jsx11("option", { value: "user", children: "user" }),
893
- /* @__PURE__ */ jsx11("option", { value: "workspace", children: "workspace" })
1620
+ /* @__PURE__ */ jsx12("option", { value: "user", children: "user" }),
1621
+ /* @__PURE__ */ jsx12("option", { value: "workspace", children: "workspace" })
894
1622
  ]
895
1623
  }
896
1624
  ),
897
- /* @__PURE__ */ jsx11(
1625
+ /* @__PURE__ */ jsx12(
898
1626
  "input",
899
1627
  {
900
1628
  value: idInput,
901
1629
  onChange: (e) => setIdInput(e.target.value),
902
1630
  placeholder: "subscriber id",
903
- style: { ...styles.button, cursor: "text", minWidth: 280 },
1631
+ style: { ...styles.input, minWidth: 280 },
904
1632
  "aria-label": "Subscriber id"
905
1633
  }
906
1634
  ),
907
- /* @__PURE__ */ jsx11("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" }),
908
- subscriber ? /* @__PURE__ */ jsx11(
1635
+ /* @__PURE__ */ jsxs12("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1636
+ /* @__PURE__ */ jsx12(Icon, { name: "search", size: 14 }),
1637
+ "Look up"
1638
+ ] }),
1639
+ subscriber ? /* @__PURE__ */ jsxs12(
909
1640
  "button",
910
1641
  {
911
1642
  type: "button",
@@ -915,9 +1646,12 @@ function SubscriberScreen({ client, pageSize = 50 }) {
915
1646
  setIdInput("");
916
1647
  setGranted(null);
917
1648
  },
918
- children: "\u2190 All subscribers"
1649
+ children: [
1650
+ /* @__PURE__ */ jsx12(Icon, { name: "back", size: 14 }),
1651
+ "All subscribers"
1652
+ ]
919
1653
  }
920
- ) : /* @__PURE__ */ jsx11(
1654
+ ) : /* @__PURE__ */ jsx12(
921
1655
  "button",
922
1656
  {
923
1657
  type: "button",
@@ -930,22 +1664,22 @@ function SubscriberScreen({ client, pageSize = 50 }) {
930
1664
  ]
931
1665
  }
932
1666
  ),
933
- error ? /* @__PURE__ */ jsx11("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
934
- !subscriber ? /* @__PURE__ */ jsxs11(Fragment7, { children: [
935
- list.error ? /* @__PURE__ */ jsx11("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
936
- list.subscriptions.length === 0 && !list.isLoading ? /* @__PURE__ */ jsx11("p", { style: styles.muted, children: "No subscribers yet." }) : /* @__PURE__ */ jsxs11("table", { style: styles.table, children: [
937
- /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs11("tr", { children: [
938
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Subscriber" }),
939
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Plan" }),
940
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Status" }),
941
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Renews" })
1667
+ error ? /* @__PURE__ */ jsx12("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1668
+ !subscriber ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1669
+ list.error ? /* @__PURE__ */ jsx12("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1670
+ list.subscriptions.length === 0 && !list.isLoading ? /* @__PURE__ */ jsx12(Empty, { icon: "subscriber", title: "No subscribers yet", children: "Subscriptions appear here once someone checks out." }) : /* @__PURE__ */ jsxs12("table", { style: styles.table, children: [
1671
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs12("tr", { children: [
1672
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Subscriber" }),
1673
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Plan" }),
1674
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Status" }),
1675
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Renews" })
942
1676
  ] }) }),
943
- /* @__PURE__ */ jsx11("tbody", { children: list.subscriptions.map((s) => /* @__PURE__ */ jsxs11("tr", { children: [
944
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: /* @__PURE__ */ jsxs11(
1677
+ /* @__PURE__ */ jsx12("tbody", { children: list.subscriptions.map((s) => /* @__PURE__ */ jsxs12("tr", { children: [
1678
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: /* @__PURE__ */ jsxs12(
945
1679
  "button",
946
1680
  {
947
1681
  type: "button",
948
- style: { ...styles.navItem, padding: 0, ...styles.mono },
1682
+ style: { ...styles.link, ...styles.mono },
949
1683
  onClick: () => open(s.subscriberType, s.subscriberId),
950
1684
  children: [
951
1685
  s.subscriberType,
@@ -954,20 +1688,29 @@ function SubscriberScreen({ client, pageSize = 50 }) {
954
1688
  ]
955
1689
  }
956
1690
  ) }),
957
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
958
- /* @__PURE__ */ jsx11("strong", { children: s.plan }),
1691
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1692
+ /* @__PURE__ */ jsx12("strong", { children: s.plan }),
959
1693
  " \xB7 ",
960
1694
  s.interval
961
1695
  ] }),
962
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
963
- s.status,
964
- s.cancelAtPeriodEnd ? /* @__PURE__ */ jsx11("span", { style: styles.badge, children: "cancels" }) : null
1696
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1697
+ /* @__PURE__ */ jsx12(
1698
+ Pill,
1699
+ {
1700
+ tone: s.status === "active" ? "ok" : s.status === "trialing" ? "info" : s.status === "past_due" ? "warn" : "neutral",
1701
+ children: s.status
1702
+ }
1703
+ ),
1704
+ s.cancelAtPeriodEnd ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1705
+ " ",
1706
+ /* @__PURE__ */ jsx12(Pill, { tone: "warn", dot: false, children: "cancels" })
1707
+ ] }) : null
965
1708
  ] }),
966
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : /* @__PURE__ */ jsx11("span", { style: styles.muted, children: "\u2014" }) })
1709
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : /* @__PURE__ */ jsx12("span", { style: styles.muted, children: "\u2014" }) })
967
1710
  ] }, s.id)) })
968
1711
  ] }),
969
- list.isLoading ? /* @__PURE__ */ jsx11("p", { style: styles.status, children: "Loading\u2026" }) : null,
970
- list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx11(
1712
+ list.isLoading ? /* @__PURE__ */ jsx12("p", { style: styles.status, children: "Loading\u2026" }) : null,
1713
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx12(
971
1714
  "button",
972
1715
  {
973
1716
  type: "button",
@@ -977,27 +1720,27 @@ function SubscriberScreen({ client, pageSize = 50 }) {
977
1720
  }
978
1721
  ) : null
979
1722
  ] }) : null,
980
- subscriber && !isLoading ? /* @__PURE__ */ jsxs11(Fragment7, { children: [
981
- /* @__PURE__ */ jsx11("h2", { style: styles.subtitle, children: "Subscription" }),
982
- subscription ? /* @__PURE__ */ jsx11("table", { style: styles.table, children: /* @__PURE__ */ jsxs11("tbody", { children: [
983
- /* @__PURE__ */ jsxs11("tr", { children: [
984
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "plan" }),
985
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
986
- /* @__PURE__ */ jsx11("strong", { children: subscription.plan }),
1723
+ subscriber && !isLoading ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1724
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Subscription" }),
1725
+ subscription ? /* @__PURE__ */ jsx12("table", { style: styles.table, children: /* @__PURE__ */ jsxs12("tbody", { children: [
1726
+ /* @__PURE__ */ jsxs12("tr", { children: [
1727
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "plan" }),
1728
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1729
+ /* @__PURE__ */ jsx12("strong", { children: subscription.plan }),
987
1730
  " \xB7 ",
988
1731
  subscription.interval
989
1732
  ] })
990
1733
  ] }),
991
- /* @__PURE__ */ jsxs11("tr", { children: [
992
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "status" }),
993
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
1734
+ /* @__PURE__ */ jsxs12("tr", { children: [
1735
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "status" }),
1736
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
994
1737
  subscription.status,
995
1738
  subscription.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""
996
1739
  ] })
997
1740
  ] }),
998
- /* @__PURE__ */ jsxs11("tr", { children: [
999
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "period" }),
1000
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
1741
+ /* @__PURE__ */ jsxs12("tr", { children: [
1742
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "period" }),
1743
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1001
1744
  subscription.currentPeriodStart ? new Date(subscription.currentPeriodStart).toLocaleDateString() : "\u2014",
1002
1745
  " ",
1003
1746
  "\u2192",
@@ -1005,28 +1748,49 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1005
1748
  subscription.currentPeriodEnd ? new Date(subscription.currentPeriodEnd).toLocaleDateString() : "\u2014"
1006
1749
  ] })
1007
1750
  ] }),
1008
- /* @__PURE__ */ jsxs11("tr", { children: [
1009
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "provider" }),
1010
- /* @__PURE__ */ jsx11("td", { style: { ...styles.td, ...styles.mono }, children: subscription.providerSubscriptionId ?? "\u2014" })
1751
+ /* @__PURE__ */ jsxs12("tr", { children: [
1752
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "provider" }),
1753
+ /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: subscription.providerSubscriptionId ?? "\u2014" })
1011
1754
  ] })
1012
- ] }) }) : /* @__PURE__ */ jsx11("p", { style: styles.muted, children: "No subscription." }),
1013
- /* @__PURE__ */ jsx11("h2", { style: styles.subtitle, children: "Wallet" }),
1014
- wallet ? /* @__PURE__ */ jsxs11(Fragment7, { children: [
1015
- /* @__PURE__ */ jsxs11("p", { children: [
1016
- /* @__PURE__ */ jsx11("strong", { children: wallet.balance }),
1017
- " ",
1018
- wallet.currency,
1019
- " ",
1020
- /* @__PURE__ */ jsxs11("span", { style: styles.muted, children: [
1021
- "(granted ",
1022
- wallet.granted ?? "0",
1023
- " \xB7 purchased ",
1024
- wallet.purchased ?? "0",
1025
- ") \xB7 minor units, precision ",
1026
- wallet.precision
1027
- ] })
1028
- ] }),
1029
- /* @__PURE__ */ jsxs11(
1755
+ ] }) }) : /* @__PURE__ */ jsx12("p", { style: styles.muted, children: "No subscription." }),
1756
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Wallet" }),
1757
+ wallet ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1758
+ /* @__PURE__ */ jsxs12(
1759
+ "p",
1760
+ {
1761
+ style: {
1762
+ ...styles.card,
1763
+ display: "flex",
1764
+ alignItems: "baseline",
1765
+ gap: 8,
1766
+ flexWrap: "wrap"
1767
+ },
1768
+ children: [
1769
+ /* @__PURE__ */ jsx12(
1770
+ "strong",
1771
+ {
1772
+ style: {
1773
+ fontSize: 22,
1774
+ letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
1775
+ },
1776
+ children: wallet.balance
1777
+ }
1778
+ ),
1779
+ " ",
1780
+ wallet.currency,
1781
+ " ",
1782
+ /* @__PURE__ */ jsxs12("span", { style: styles.muted, children: [
1783
+ "(granted ",
1784
+ wallet.granted ?? "0",
1785
+ " \xB7 purchased ",
1786
+ wallet.purchased ?? "0",
1787
+ ") \xB7 minor units, precision ",
1788
+ wallet.precision
1789
+ ] })
1790
+ ]
1791
+ }
1792
+ ),
1793
+ /* @__PURE__ */ jsxs12(
1030
1794
  "form",
1031
1795
  {
1032
1796
  style: styles.toolbar,
@@ -1045,47 +1809,58 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1045
1809
  });
1046
1810
  },
1047
1811
  children: [
1048
- /* @__PURE__ */ jsx11(
1812
+ /* @__PURE__ */ jsx12(
1049
1813
  "input",
1050
1814
  {
1051
1815
  value: amount,
1052
1816
  onChange: (e) => setAmount(e.target.value),
1053
1817
  placeholder: "amount (minor units)",
1054
- style: { ...styles.button, cursor: "text" },
1818
+ style: styles.input,
1055
1819
  "aria-label": "Amount"
1056
1820
  }
1057
1821
  ),
1058
- /* @__PURE__ */ jsx11(
1822
+ /* @__PURE__ */ jsx12(
1059
1823
  "input",
1060
1824
  {
1061
1825
  value: note,
1062
1826
  onChange: (e) => setNote(e.target.value),
1063
1827
  placeholder: "reason (optional)",
1064
- style: { ...styles.button, cursor: "text", minWidth: 220 },
1828
+ style: { ...styles.input, minWidth: 220 },
1065
1829
  "aria-label": "Reason"
1066
1830
  }
1067
1831
  ),
1068
- /* @__PURE__ */ jsx11("button", { type: "submit", style: styles.button, disabled: !/^\d+$/.test(amount.trim()), children: "Grant" }),
1069
- granted ? /* @__PURE__ */ jsx11("span", { style: styles.ok, children: granted }) : null
1832
+ /* @__PURE__ */ jsxs12(
1833
+ "button",
1834
+ {
1835
+ type: "submit",
1836
+ style: styles.buttonPrimary,
1837
+ disabled: !/^\d+$/.test(amount.trim()),
1838
+ children: [
1839
+ /* @__PURE__ */ jsx12(Icon, { name: "plus", size: 14 }),
1840
+ "Grant"
1841
+ ]
1842
+ }
1843
+ ),
1844
+ granted ? /* @__PURE__ */ jsx12("span", { style: styles.ok, children: granted }) : null
1070
1845
  ]
1071
1846
  }
1072
1847
  ),
1073
- /* @__PURE__ */ jsx11("h2", { style: styles.subtitle, children: "Ledger" }),
1074
- ledger.length === 0 ? /* @__PURE__ */ jsx11("p", { style: styles.muted, children: "Nothing moved yet." }) : /* @__PURE__ */ jsxs11("table", { style: styles.table, children: [
1075
- /* @__PURE__ */ jsx11("thead", { children: /* @__PURE__ */ jsxs11("tr", { children: [
1076
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "When" }),
1077
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Kind" }),
1078
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Amount" }),
1079
- /* @__PURE__ */ jsx11("th", { style: styles.th, children: "Note" })
1848
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Ledger" }),
1849
+ ledger.length === 0 ? /* @__PURE__ */ jsx12(Empty, { icon: "subscriber", title: "Nothing moved yet" }) : /* @__PURE__ */ jsxs12("table", { style: styles.table, children: [
1850
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs12("tr", { children: [
1851
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "When" }),
1852
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Kind" }),
1853
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Amount" }),
1854
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Note" })
1080
1855
  ] }) }),
1081
- /* @__PURE__ */ jsx11("tbody", { children: ledger.map((t2) => /* @__PURE__ */ jsxs11("tr", { children: [
1082
- /* @__PURE__ */ jsx11("td", { style: { ...styles.td, ...styles.muted }, children: new Date(t2.createdAt).toLocaleString() }),
1083
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: t2.type }),
1084
- /* @__PURE__ */ jsx11("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1085
- /* @__PURE__ */ jsx11("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1856
+ /* @__PURE__ */ jsx12("tbody", { children: ledger.map((t2) => /* @__PURE__ */ jsxs12("tr", { children: [
1857
+ /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.muted }, children: new Date(t2.createdAt).toLocaleString() }),
1858
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: t2.type }),
1859
+ /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1860
+ /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1086
1861
  ] }, t2.id)) })
1087
1862
  ] }),
1088
- hasMoreLedger ? /* @__PURE__ */ jsx11(
1863
+ hasMoreLedger ? /* @__PURE__ */ jsx12(
1089
1864
  "button",
1090
1865
  {
1091
1866
  type: "button",
@@ -1094,37 +1869,43 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1094
1869
  children: "Load more"
1095
1870
  }
1096
1871
  ) : null
1097
- ] }) : /* @__PURE__ */ jsx11("p", { style: styles.muted, children: "No wallet \u2014 billing has no wallet configuration." })
1872
+ ] }) : /* @__PURE__ */ jsx12("p", { style: styles.muted, children: "No wallet \u2014 billing has no wallet configuration." })
1098
1873
  ] }) : null
1099
1874
  ] });
1100
1875
  }
1101
1876
 
1102
1877
  // src/screens/AuditScreen.tsx
1103
1878
  import { useAdminAudit } from "@fonderie/react-admin";
1104
- import { useState as useState4 } from "react";
1105
- import { jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1879
+ import { useState as useState5 } from "react";
1880
+ import { jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1106
1881
  function AuditScreen({ client, pageSize = 50 }) {
1107
- const [draft, setDraft] = useState4({ workspaceId: "", type: "", actorId: "" });
1108
- const [filter, setFilter] = useState4(
1882
+ const [draft, setDraft] = useState5({ workspaceId: "", type: "", actorId: "" });
1883
+ const [filter, setFilter] = useState5(
1109
1884
  {}
1110
1885
  );
1111
1886
  const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(client, {
1112
1887
  ...filter,
1113
1888
  limit: pageSize
1114
1889
  });
1115
- const field = (key, placeholder) => /* @__PURE__ */ jsx12(
1890
+ const field = (key, placeholder) => /* @__PURE__ */ jsx13(
1116
1891
  "input",
1117
1892
  {
1118
1893
  value: draft[key],
1119
1894
  onChange: (e) => setDraft({ ...draft, [key]: e.target.value }),
1120
1895
  placeholder,
1121
- style: { ...styles.button, cursor: "text" },
1896
+ style: styles.input,
1122
1897
  "aria-label": placeholder
1123
1898
  }
1124
1899
  );
1125
- return /* @__PURE__ */ jsxs12("div", { style: styles.container, children: [
1126
- /* @__PURE__ */ jsx12("h1", { style: styles.title, children: "Audit" }),
1127
- /* @__PURE__ */ jsxs12(
1900
+ return /* @__PURE__ */ jsxs13("div", { style: styles.container, children: [
1901
+ /* @__PURE__ */ jsx13(
1902
+ PageHeader,
1903
+ {
1904
+ title: "Audit",
1905
+ lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
1906
+ }
1907
+ ),
1908
+ /* @__PURE__ */ jsxs13(
1128
1909
  "form",
1129
1910
  {
1130
1911
  style: styles.toolbar,
@@ -1140,8 +1921,11 @@ function AuditScreen({ client, pageSize = 50 }) {
1140
1921
  field("workspaceId", "workspace id (all if empty)"),
1141
1922
  field("type", "event type"),
1142
1923
  field("actorId", "actor id"),
1143
- /* @__PURE__ */ jsx12("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Filter" }),
1144
- /* @__PURE__ */ jsx12(
1924
+ /* @__PURE__ */ jsxs13("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1925
+ /* @__PURE__ */ jsx13(Icon, { name: "search", size: 14 }),
1926
+ "Filter"
1927
+ ] }),
1928
+ /* @__PURE__ */ jsx13(
1145
1929
  "button",
1146
1930
  {
1147
1931
  type: "button",
@@ -1154,25 +1938,25 @@ function AuditScreen({ client, pageSize = 50 }) {
1154
1938
  ]
1155
1939
  }
1156
1940
  ),
1157
- error ? /* @__PURE__ */ jsx12("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1158
- events.length === 0 && !isLoading ? /* @__PURE__ */ jsx12("p", { style: styles.muted, children: "Nothing recorded for this filter." }) : /* @__PURE__ */ jsxs12("table", { style: styles.table, children: [
1159
- /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs12("tr", { children: [
1160
- /* @__PURE__ */ jsx12("th", { style: styles.th, children: "When" }),
1161
- /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Type" }),
1162
- /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Workspace" }),
1163
- /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Actor" }),
1164
- /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Request" })
1941
+ error ? /* @__PURE__ */ jsx13("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1942
+ events.length === 0 && !isLoading ? /* @__PURE__ */ jsx13(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ jsxs13("table", { style: styles.table, children: [
1943
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsxs13("tr", { children: [
1944
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "When" }),
1945
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Type" }),
1946
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Workspace" }),
1947
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Actor" }),
1948
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Request" })
1165
1949
  ] }) }),
1166
- /* @__PURE__ */ jsx12("tbody", { children: events.map((e) => /* @__PURE__ */ jsxs12("tr", { children: [
1167
- /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
1168
- /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
1169
- /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
1170
- /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
1171
- /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
1950
+ /* @__PURE__ */ jsx13("tbody", { children: events.map((e) => /* @__PURE__ */ jsxs13("tr", { children: [
1951
+ /* @__PURE__ */ jsx13("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
1952
+ /* @__PURE__ */ jsx13("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
1953
+ /* @__PURE__ */ jsx13("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
1954
+ /* @__PURE__ */ jsx13("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
1955
+ /* @__PURE__ */ jsx13("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
1172
1956
  ] }, e.id)) })
1173
1957
  ] }),
1174
- isLoading ? /* @__PURE__ */ jsx12("p", { style: styles.status, children: "Loading\u2026" }) : null,
1175
- hasMore && !isLoading ? /* @__PURE__ */ jsx12(
1958
+ isLoading ? /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Loading\u2026" }) : null,
1959
+ hasMore && !isLoading ? /* @__PURE__ */ jsx13(
1176
1960
  "button",
1177
1961
  {
1178
1962
  type: "button",
@@ -1185,7 +1969,35 @@ function AuditScreen({ client, pageSize = 50 }) {
1185
1969
  }
1186
1970
 
1187
1971
  // src/screens/AdminShell.tsx
1188
- import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1972
+ import { Fragment as Fragment8, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
1973
+ var ICON = {
1974
+ attention: "attention",
1975
+ modules: "modules",
1976
+ environment: "environment",
1977
+ doctor: "doctor",
1978
+ routes: "routes",
1979
+ users: "users",
1980
+ catalog: "catalog",
1981
+ subscriber: "subscriber",
1982
+ settings: "settings",
1983
+ templates: "templates",
1984
+ audit: "audit",
1985
+ log: "log",
1986
+ tokens: "tokens",
1987
+ migrations: "migrations"
1988
+ };
1989
+ var NARROW = "(max-width: 820px)";
1990
+ function useNarrow() {
1991
+ const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
1992
+ const [narrow, setNarrow] = useState6(() => query?.matches ?? false);
1993
+ useEffect(() => {
1994
+ if (!query) return;
1995
+ const on = (e) => setNarrow(e.matches);
1996
+ query.addEventListener("change", on);
1997
+ return () => query.removeEventListener("change", on);
1998
+ }, [query]);
1999
+ return narrow;
2000
+ }
1189
2001
  var NAV = [
1190
2002
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
1191
2003
  {
@@ -1226,15 +2038,21 @@ function AdminShell({
1226
2038
  auditClient,
1227
2039
  environment,
1228
2040
  page,
1229
- onNavigate
2041
+ onNavigate,
2042
+ appName = "Admin",
2043
+ envLabel,
2044
+ footer
1230
2045
  }) {
1231
- const [own, setOwn] = useState5("attention");
2046
+ const narrow = useNarrow();
2047
+ const [drawer, setDrawer] = useState6(false);
2048
+ const [own, setOwn] = useState6("attention");
1232
2049
  const current = page ?? own;
1233
2050
  const go = (p) => {
1234
2051
  if (onNavigate) onNavigate(p);
1235
2052
  if (page === void 0) setOwn(p);
2053
+ setDrawer(false);
1236
2054
  };
1237
- const [editing, setEditing] = useState5(null);
2055
+ const [editing, setEditing] = useState6(null);
1238
2056
  const has = {
1239
2057
  config: Boolean(configClient),
1240
2058
  courier: Boolean(courierClient),
@@ -1245,45 +2063,48 @@ function AdminShell({
1245
2063
  let body;
1246
2064
  switch (current) {
1247
2065
  case "attention":
1248
- body = /* @__PURE__ */ jsx13(AttentionScreen, { client });
2066
+ body = /* @__PURE__ */ jsx14(AttentionScreen, { client });
1249
2067
  break;
1250
2068
  case "modules":
1251
- body = /* @__PURE__ */ jsx13(ModulesScreen, { client });
2069
+ body = /* @__PURE__ */ jsx14(ModulesScreen, { client });
1252
2070
  break;
1253
2071
  case "doctor":
1254
- body = /* @__PURE__ */ jsx13(DoctorScreen, { client });
2072
+ body = /* @__PURE__ */ jsx14(DoctorScreen, { client });
1255
2073
  break;
1256
2074
  case "environment":
1257
- body = /* @__PURE__ */ jsx13(EnvironmentScreen, { client });
2075
+ body = /* @__PURE__ */ jsx14(EnvironmentScreen, { client });
1258
2076
  break;
1259
2077
  case "routes":
1260
- body = /* @__PURE__ */ jsx13(RoutesScreen, { client });
2078
+ body = /* @__PURE__ */ jsx14(RoutesScreen, { client });
1261
2079
  break;
1262
2080
  case "tokens":
1263
- body = /* @__PURE__ */ jsx13(TokensScreen, { client });
2081
+ body = /* @__PURE__ */ jsx14(TokensScreen, { client });
1264
2082
  break;
1265
2083
  case "migrations":
1266
- body = /* @__PURE__ */ jsx13(MigrationsScreen, { client });
2084
+ body = /* @__PURE__ */ jsx14(MigrationsScreen, { client });
1267
2085
  break;
1268
2086
  case "log":
1269
- body = /* @__PURE__ */ jsx13(AdminLogScreen, { client });
2087
+ body = /* @__PURE__ */ jsx14(AdminLogScreen, { client });
1270
2088
  break;
1271
2089
  case "catalog":
1272
- body = billingClient ? /* @__PURE__ */ jsx13(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass a BillingAdminClient to see the catalog here." });
2090
+ body = billingClient ? /* @__PURE__ */ jsx14(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
1273
2091
  break;
1274
2092
  case "subscriber":
1275
- body = billingClient ? /* @__PURE__ */ jsx13(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass a BillingAdminClient to look up subscribers here." });
2093
+ body = billingClient ? /* @__PURE__ */ jsx14(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
1276
2094
  break;
1277
2095
  case "audit":
1278
- body = auditClient ? /* @__PURE__ */ jsx13(AuditScreen, { client: auditClient }) : /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass an AuditAdminClient to see the audit trail here." });
2096
+ body = auditClient ? /* @__PURE__ */ jsx14(AuditScreen, { client: auditClient }) : /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
1279
2097
  break;
1280
2098
  case "users":
1281
- body = authClient ? /* @__PURE__ */ jsx13(UsersScreen, { client: authClient }) : /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass an AuthAdminClient to look up users here." });
2099
+ body = authClient ? /* @__PURE__ */ jsx14(UsersScreen, { client: authClient }) : /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
1282
2100
  break;
1283
2101
  case "settings":
1284
- body = !configClient ? /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ jsxs13(Fragment8, { children: [
1285
- /* @__PURE__ */ jsx13("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
1286
- /* @__PURE__ */ jsx13(
2102
+ body = !configClient ? /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ jsxs14(Fragment8, { children: [
2103
+ /* @__PURE__ */ jsx14("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ jsxs14("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2104
+ /* @__PURE__ */ jsx14(Icon, { name: "back", size: 14 }),
2105
+ "Config & secrets"
2106
+ ] }) }),
2107
+ /* @__PURE__ */ jsx14(
1287
2108
  ConfigEditorScreen,
1288
2109
  {
1289
2110
  client: configClient,
@@ -1293,7 +2114,7 @@ function AdminShell({
1293
2114
  onSaved: () => setEditing(null)
1294
2115
  }
1295
2116
  )
1296
- ] }) : /* @__PURE__ */ jsx13(
2117
+ ] }) : /* @__PURE__ */ jsx14(
1297
2118
  ConfigListScreen,
1298
2119
  {
1299
2120
  client: configClient,
@@ -1306,9 +2127,12 @@ function AdminShell({
1306
2127
  );
1307
2128
  break;
1308
2129
  case "templates":
1309
- body = !courierClient ? /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ jsxs13(Fragment8, { children: [
1310
- /* @__PURE__ */ jsx13("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
1311
- /* @__PURE__ */ jsx13(
2130
+ body = !courierClient ? /* @__PURE__ */ jsx14("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ jsxs14(Fragment8, { children: [
2131
+ /* @__PURE__ */ jsx14("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ jsxs14("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2132
+ /* @__PURE__ */ jsx14(Icon, { name: "back", size: 14 }),
2133
+ "Templates"
2134
+ ] }) }),
2135
+ /* @__PURE__ */ jsx14(
1312
2136
  TemplateEditorScreen,
1313
2137
  {
1314
2138
  client: courierClient,
@@ -1316,7 +2140,7 @@ function AdminShell({
1316
2140
  onSaved: () => setEditing(null)
1317
2141
  }
1318
2142
  )
1319
- ] }) : /* @__PURE__ */ jsx13(
2143
+ ] }) : /* @__PURE__ */ jsx14(
1320
2144
  TemplateListScreen,
1321
2145
  {
1322
2146
  client: courierClient,
@@ -1325,29 +2149,105 @@ function AdminShell({
1325
2149
  );
1326
2150
  break;
1327
2151
  }
1328
- return /* @__PURE__ */ jsxs13("div", { style: styles.shell, children: [
1329
- /* @__PURE__ */ jsx13("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
1330
- const items = g.items.filter((i) => !i.needs || has[i.needs]);
1331
- if (items.length === 0) return null;
1332
- return /* @__PURE__ */ jsxs13("div", { children: [
1333
- /* @__PURE__ */ jsx13("div", { style: styles.navGroup, children: g.group }),
1334
- items.map((i) => /* @__PURE__ */ jsx13(
2152
+ const production = envLabel ? /^prod/i.test(envLabel) : false;
2153
+ const brand = /* @__PURE__ */ jsxs14("div", { style: styles.navBrand, children: [
2154
+ /* @__PURE__ */ jsx14("div", { style: styles.navMark, children: /* @__PURE__ */ jsx14(Icon, { name: "mark", size: 15 }) }),
2155
+ /* @__PURE__ */ jsxs14("div", { style: { minWidth: 0, flex: 1 }, children: [
2156
+ /* @__PURE__ */ jsx14("div", { style: styles.navBrandTitle, children: appName }),
2157
+ envLabel ? /* @__PURE__ */ jsx14(
2158
+ "span",
2159
+ {
2160
+ style: {
2161
+ ...styles.badge,
2162
+ marginTop: 3,
2163
+ padding: "0 7px",
2164
+ fontSize: 11,
2165
+ ...production ? {
2166
+ color: "var(--fonderie-danger,#e00)",
2167
+ borderColor: "color-mix(in srgb, var(--fonderie-danger,#e00) 35%, transparent)"
2168
+ } : {}
2169
+ },
2170
+ children: envLabel
2171
+ }
2172
+ ) : null
2173
+ ] }),
2174
+ narrow ? /* @__PURE__ */ jsx14(
2175
+ "button",
2176
+ {
2177
+ type: "button",
2178
+ style: styles.buttonGhost,
2179
+ "aria-label": "Close menu",
2180
+ onClick: () => setDrawer(false),
2181
+ children: /* @__PURE__ */ jsx14(Icon, { name: "close" })
2182
+ }
2183
+ ) : null
2184
+ ] });
2185
+ const nav = /* @__PURE__ */ jsxs14(
2186
+ "nav",
2187
+ {
2188
+ style: {
2189
+ ...styles.nav,
2190
+ ...narrow ? { width: 280, maxWidth: "85vw", boxShadow: "0 10px 40px rgba(0,0,0,.25)" } : {}
2191
+ },
2192
+ "aria-label": "Admin",
2193
+ children: [
2194
+ brand,
2195
+ /* @__PURE__ */ jsx14("div", { style: styles.navScroll, children: NAV.map((g) => {
2196
+ const items = g.items.filter((i) => !i.needs || has[i.needs]);
2197
+ if (items.length === 0) return null;
2198
+ return /* @__PURE__ */ jsxs14("div", { children: [
2199
+ /* @__PURE__ */ jsx14("div", { style: styles.navGroup, children: g.group }),
2200
+ items.map((i) => {
2201
+ const active = current === i.page;
2202
+ return /* @__PURE__ */ jsxs14(
2203
+ "button",
2204
+ {
2205
+ type: "button",
2206
+ className: "fonderie-admin-nav-item",
2207
+ style: { ...styles.navItem, ...active ? styles.navItemActive : {} },
2208
+ "aria-current": active ? "page" : void 0,
2209
+ onClick: () => {
2210
+ setEditing(null);
2211
+ go(i.page);
2212
+ },
2213
+ children: [
2214
+ /* @__PURE__ */ jsx14(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2215
+ i.label
2216
+ ]
2217
+ },
2218
+ i.page
2219
+ );
2220
+ })
2221
+ ] }, g.group);
2222
+ }) }),
2223
+ footer ? /* @__PURE__ */ jsx14("div", { style: styles.navFooter, children: footer }) : null
2224
+ ]
2225
+ }
2226
+ );
2227
+ const title = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label ?? "";
2228
+ return /* @__PURE__ */ jsxs14("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2229
+ narrow ? /* @__PURE__ */ jsxs14(Fragment8, { children: [
2230
+ /* @__PURE__ */ jsxs14("div", { style: styles.topbar, children: [
2231
+ /* @__PURE__ */ jsx14(
1335
2232
  "button",
1336
2233
  {
1337
2234
  type: "button",
1338
- style: { ...styles.navItem, ...current === i.page ? styles.navItemActive : {} },
1339
- "aria-current": current === i.page ? "page" : void 0,
1340
- onClick: () => {
1341
- setEditing(null);
1342
- go(i.page);
1343
- },
1344
- children: i.label
1345
- },
1346
- i.page
1347
- ))
1348
- ] }, g.group);
1349
- }) }),
1350
- /* @__PURE__ */ jsx13("main", { style: styles.main, children: body })
2235
+ style: styles.buttonGhost,
2236
+ "aria-label": "Open menu",
2237
+ "aria-expanded": drawer,
2238
+ onClick: () => setDrawer(true),
2239
+ children: /* @__PURE__ */ jsx14(Icon, { name: "menu" })
2240
+ }
2241
+ ),
2242
+ /* @__PURE__ */ jsx14("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2243
+ envLabel ? /* @__PURE__ */ jsx14("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2244
+ ] }),
2245
+ drawer ? /* @__PURE__ */ jsxs14("div", { style: styles.drawer, children: [
2246
+ nav,
2247
+ /* @__PURE__ */ jsx14("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2248
+ ] }) : null
2249
+ ] }) : nav,
2250
+ /* @__PURE__ */ jsx14("main", { className: "fonderie-admin-main", style: styles.main, children: body })
1351
2251
  ] });
1352
2252
  }
1353
2253
  export {