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