@fonderie/react-admin-screens 0.4.0 → 0.6.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/README.md CHANGED
@@ -14,7 +14,7 @@ Status: **experimental** (0.x).
14
14
  | Money | Catalog · Subscriber | what am I selling (configured vs stored) · what is this subscriber on, their wallet, ledger, a manual grant (`billingClient`) |
15
15
  | Settings | Config & secrets | the `@fonderie/config` admin screens, as a sub-page |
16
16
  | Messaging | Templates | the `@fonderie/courier` admin screens, as a sub-page |
17
- | Activity | Audit · Admin log · Access | what happened, every workspace (`auditClient`) · who did what through here · who can be here |
17
+ | Activity | Audit · Admin log · Access | what happened, every workspace (`auditClient`) · who did what through here · who can be here, and issue or revoke scoped tokens |
18
18
 
19
19
  ```ts
20
20
  import { AdminClient, AuditAdminClient, AuthAdminClient, BillingAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
@@ -38,6 +38,13 @@ Pass `page` and `onNavigate` to own the URL (one route per page); omit both
38
38
  and the shell keeps the page itself. Settings and Messaging appear only when
39
39
  their client is given.
40
40
 
41
+ **Give the shell a `read` token, not the root one.** With `@fonderie/admin`
42
+ ≥ 0.6 the configured `adminToken` is the root — every scope, including the
43
+ plaintext secret reveal, and the only credential that can mint or revoke.
44
+ Issue a scoped one for the dashboard (`fonderie admin token issue dashboard
45
+ --scopes read`, or the Access page itself) and hand the shell that. The
46
+ Access page then shows issuing as unavailable, which is the point.
47
+
41
48
  Nothing here stores the token: the app constructs the clients and decides
42
49
  where the token lives. Every request through the shell lands in the admin
43
50
  log, including the ones the token refuses.
package/dist/index.cjs CHANGED
@@ -38,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
38
38
  // src/screens/AdminShell.tsx
39
39
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
40
40
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
41
- var import_react4 = require("react");
41
+ var import_react5 = require("react");
42
42
 
43
43
  // src/styles.ts
44
44
  var styles = {
@@ -346,42 +346,159 @@ function RoutesScreen({ client }) {
346
346
 
347
347
  // src/screens/TokensScreen.tsx
348
348
  var import_react_admin7 = require("@fonderie/react-admin");
349
+ var import_react = require("react");
349
350
  var import_jsx_runtime7 = require("react/jsx-runtime");
351
+ var ALL = ["read", "write", "secrets"];
350
352
  function TokensScreen({ client }) {
351
- const { report, isLoading, error } = (0, import_react_admin7.useAdminTokens)(client);
353
+ const { report, isLoading, error, issue, revoke } = (0, import_react_admin7.useAdminTokens)(client);
354
+ const [name, setName] = (0, import_react.useState)("");
355
+ const [scopes, setScopes] = (0, import_react.useState)(["read"]);
356
+ const [days, setDays] = (0, import_react.useState)("");
357
+ const [minted, setMinted] = (0, import_react.useState)(null);
358
+ const toggle = (s) => setScopes((prev) => prev.includes(s) ? prev.filter((x) => x !== s) : [...prev, s]);
352
359
  return /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("div", { style: styles.container, children: [
353
360
  /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h1", { style: styles.title, children: "Access" }),
354
- 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 }) : report ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
355
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Admin token" }),
356
- /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
357
- report.admin.ok ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.bad, children: "weak" }),
358
- report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
359
- ] }),
360
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
361
- report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: styles.list, children: report.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { style: styles.row, children: [
362
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: l.module }),
361
+ error ? /* @__PURE__ */ (0, import_jsx_runtime7.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,
362
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Root token" }),
363
+ report ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { children: [
364
+ report.admin.ok ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.ok, children: "strong" }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.bad, children: "weak" }),
365
+ report.admin.problems.map((p) => /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("div", { style: styles.bad, children: p.message }, p.message))
366
+ ] }) : null,
367
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Issued tokens" }),
368
+ report?.issued === null ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "Issuing is off \u2014 give AdminModule a store and run its migrations." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(import_jsx_runtime7.Fragment, { children: [
369
+ minted ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("p", { style: styles.ok, children: [
370
+ "Copy this now \u2014 it is never shown again.",
363
371
  " ",
364
- /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
365
- ] }, l.module)) })
366
- ] }) : null
372
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: minted.token }),
373
+ " (",
374
+ minted.name,
375
+ ")"
376
+ ] }) : null,
377
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)(
378
+ "form",
379
+ {
380
+ style: styles.toolbar,
381
+ onSubmit: (e) => {
382
+ e.preventDefault();
383
+ const d = Number(days);
384
+ void issue({
385
+ name: name.trim(),
386
+ scopes,
387
+ ...days && Number.isInteger(d) && d > 0 ? { expiresInDays: d } : {}
388
+ }).then((t) => {
389
+ setMinted({ name: t.name, token: t.token });
390
+ setName("");
391
+ setDays("");
392
+ }).catch(() => {
393
+ });
394
+ },
395
+ children: [
396
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
397
+ "input",
398
+ {
399
+ value: name,
400
+ onChange: (e) => setName(e.target.value),
401
+ placeholder: "name",
402
+ style: { ...styles.button, cursor: "text" },
403
+ "aria-label": "Name"
404
+ }
405
+ ),
406
+ ALL.map((s) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("label", { style: { ...styles.badge, cursor: "pointer" }, children: [
407
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("input", { type: "checkbox", checked: scopes.includes(s), onChange: () => toggle(s) }),
408
+ " ",
409
+ s
410
+ ] }, s)),
411
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
412
+ "input",
413
+ {
414
+ value: days,
415
+ onChange: (e) => setDays(e.target.value),
416
+ placeholder: "days (optional)",
417
+ style: { ...styles.button, cursor: "text", width: 140 },
418
+ "aria-label": "Days"
419
+ }
420
+ ),
421
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
422
+ "button",
423
+ {
424
+ type: "submit",
425
+ style: styles.button,
426
+ disabled: !name.trim() || scopes.length === 0 || isLoading,
427
+ children: "Issue"
428
+ }
429
+ )
430
+ ]
431
+ }
432
+ ),
433
+ report?.issued && report.issued.length > 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("table", { style: styles.table, children: [
434
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
435
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Name" }),
436
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Scopes" }),
437
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Created" }),
438
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Expires" }),
439
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th, children: "Last used" }),
440
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("th", { style: styles.th })
441
+ ] }) }),
442
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("tbody", { children: report.issued.map((t) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("tr", { children: [
443
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: styles.td, children: [
444
+ t.name,
445
+ t.revokedAt ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.badge, children: " revoked" }) : null
446
+ ] }),
447
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t.scopes.join(", ") }),
448
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("td", { style: { ...styles.td, ...styles.muted }, children: [
449
+ new Date(t.createdAt).toLocaleDateString(),
450
+ " \xB7 ",
451
+ t.createdBy
452
+ ] }),
453
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t.expiresAt ? new Date(t.expiresAt).toLocaleDateString() : "never" }),
454
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t.lastUsedAt ? new Date(t.lastUsedAt).toLocaleString() : "never" }),
455
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("td", { style: styles.td, children: t.revokedAt ? null : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)(
456
+ "button",
457
+ {
458
+ type: "button",
459
+ style: styles.button,
460
+ onClick: () => {
461
+ if (window.confirm(
462
+ `Revoke "${t.name}"? Anything using it stops working immediately.`
463
+ ))
464
+ void revoke(t.id).catch(() => {
465
+ });
466
+ },
467
+ children: "Revoke"
468
+ }
469
+ ) })
470
+ ] }, t.id)) })
471
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None issued." })
472
+ ] }),
473
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("h2", { style: styles.subtitle, children: "Legacy per-brick tokens" }),
474
+ report && report.legacy.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("p", { style: styles.muted, children: "None \u2014 every brick's admin surface goes through this token." }) : /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("ul", { style: styles.list, children: report?.legacy.map((l) => /* @__PURE__ */ (0, import_jsx_runtime7.jsxs)("li", { style: styles.row, children: [
475
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.mono, children: l.module }),
476
+ " ",
477
+ /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("span", { style: styles.muted, children: "still registers its standalone routes with its own token (deprecated)" })
478
+ ] }, l.module)) })
367
479
  ] });
368
480
  }
369
481
 
370
482
  // src/screens/UsersScreen.tsx
371
483
  var import_react_admin8 = require("@fonderie/react-admin");
372
- var import_react = require("react");
484
+ var import_react2 = require("react");
373
485
  var import_jsx_runtime8 = require("react/jsx-runtime");
374
- function UsersScreen({ client }) {
375
- const [input, setInput] = (0, import_react.useState)("");
376
- const [email, setEmail] = (0, import_react.useState)(void 0);
486
+ function UsersScreen({ client, pageSize = 50 }) {
487
+ const [input, setInput] = (0, import_react2.useState)("");
488
+ const [selected, setSelected] = (0, import_react2.useState)(null);
489
+ const list = (0, import_react_admin8.useAdminUsers)(client, { limit: pageSize });
377
490
  const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin8.useAdminUser)(
378
491
  client,
379
- email ? { email } : {}
492
+ selected ?? {}
380
493
  );
381
494
  const userId = user?.id ?? null;
382
495
  const sessions = (0, import_react_admin8.useAdminUserSessions)(client, userId);
383
496
  const history = (0, import_react_admin8.useAdminLoginHistory)(client, userId, { limit: 20 });
384
497
  const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.ok, children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.muted, children: "no" });
498
+ const clear = () => {
499
+ setSelected(null);
500
+ setInput("");
501
+ };
385
502
  return /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("div", { style: styles.container, children: [
386
503
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("h1", { style: styles.title, children: "Users" }),
387
504
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(
@@ -390,7 +507,8 @@ function UsersScreen({ client }) {
390
507
  style: styles.toolbar,
391
508
  onSubmit: (e) => {
392
509
  e.preventDefault();
393
- setEmail(input.trim() || void 0);
510
+ const email = input.trim();
511
+ setSelected(email ? { email } : null);
394
512
  },
395
513
  children: [
396
514
  /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
@@ -404,11 +522,60 @@ function UsersScreen({ client }) {
404
522
  "aria-label": "Email"
405
523
  }
406
524
  ),
407
- /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" })
525
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" }),
526
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("button", { type: "button", style: styles.button, onClick: clear, children: "\u2190 All users" }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
527
+ "button",
528
+ {
529
+ type: "button",
530
+ style: styles.button,
531
+ onClick: () => void list.refresh(),
532
+ disabled: list.isLoading,
533
+ children: "Refresh"
534
+ }
535
+ )
408
536
  ]
409
537
  }
410
538
  ),
411
539
  error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
540
+ !selected ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
541
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
542
+ list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.muted, children: "No users yet." }) : /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("table", { style: styles.table, children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { children: [
544
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { style: styles.th, children: "Email" }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { style: styles.th, children: "Name" }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { style: styles.th, children: "Created" }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("th", { style: styles.th, children: "Status" })
548
+ ] }) }),
549
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("tr", { children: [
550
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
551
+ "button",
552
+ {
553
+ type: "button",
554
+ style: { ...styles.navItem, padding: 0 },
555
+ onClick: () => setSelected({ id: u.id }),
556
+ children: u.email
557
+ }
558
+ ) }),
559
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.muted, children: "\u2014" }) }),
560
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
561
+ /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("td", { style: styles.td, children: [
562
+ u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.badge, children: "suspended" }) : null,
563
+ u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.badge, children: "deleted" }) : null,
564
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { style: styles.muted, children: "active" }) : null
565
+ ] })
566
+ ] }, u.id)) })
567
+ ] }),
568
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
569
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime8.jsx)(
570
+ "button",
571
+ {
572
+ type: "button",
573
+ style: { ...styles.button, marginTop: 8 },
574
+ onClick: () => void list.loadMore(),
575
+ children: "Load more"
576
+ }
577
+ ) : null
578
+ ] }) : null,
412
579
  user ? /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)(import_jsx_runtime8.Fragment, { children: [
413
580
  /* @__PURE__ */ (0, import_jsx_runtime8.jsxs)("h2", { style: styles.subtitle, children: [
414
581
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
@@ -583,16 +750,23 @@ function CatalogScreen({ client }) {
583
750
 
584
751
  // src/screens/SubscriberScreen.tsx
585
752
  var import_react_admin10 = require("@fonderie/react-admin");
586
- var import_react2 = require("react");
753
+ var import_react3 = require("react");
587
754
  var import_jsx_runtime10 = require("react/jsx-runtime");
588
- function SubscriberScreen({ client }) {
589
- const [type, setType] = (0, import_react2.useState)("user");
590
- const [idInput, setIdInput] = (0, import_react2.useState)("");
591
- const [subscriber, setSubscriber] = (0, import_react2.useState)(null);
755
+ function SubscriberScreen({ client, pageSize = 50 }) {
756
+ const [type, setType] = (0, import_react3.useState)("user");
757
+ const [idInput, setIdInput] = (0, import_react3.useState)("");
758
+ const [subscriber, setSubscriber] = (0, import_react3.useState)(null);
759
+ const list = (0, import_react_admin10.useAdminSubscribers)(client, { limit: pageSize });
592
760
  const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
593
- const [amount, setAmount] = (0, import_react2.useState)("");
594
- const [note, setNote] = (0, import_react2.useState)("");
595
- const [granted, setGranted] = (0, import_react2.useState)(null);
761
+ const [amount, setAmount] = (0, import_react3.useState)("");
762
+ const [note, setNote] = (0, import_react3.useState)("");
763
+ const [granted, setGranted] = (0, import_react3.useState)(null);
764
+ const open = (t, id) => {
765
+ setGranted(null);
766
+ setType(t);
767
+ setIdInput(id);
768
+ setSubscriber({ type: t, id });
769
+ };
596
770
  return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
597
771
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { style: styles.title, children: "Subscriber" }),
598
772
  /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
@@ -628,11 +802,79 @@ function SubscriberScreen({ client }) {
628
802
  "aria-label": "Subscriber id"
629
803
  }
630
804
  ),
631
- /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" })
805
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" }),
806
+ subscriber ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
807
+ "button",
808
+ {
809
+ type: "button",
810
+ style: styles.button,
811
+ onClick: () => {
812
+ setSubscriber(null);
813
+ setIdInput("");
814
+ setGranted(null);
815
+ },
816
+ children: "\u2190 All subscribers"
817
+ }
818
+ ) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
819
+ "button",
820
+ {
821
+ type: "button",
822
+ style: styles.button,
823
+ onClick: () => void list.refresh(),
824
+ disabled: list.isLoading,
825
+ children: "Refresh"
826
+ }
827
+ )
632
828
  ]
633
829
  }
634
830
  ),
635
831
  error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
832
+ !subscriber ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
833
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
834
+ list.subscriptions.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "No subscribers yet." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("table", { style: styles.table, children: [
835
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
836
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Subscriber" }),
837
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Plan" }),
838
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Status" }),
839
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Renews" })
840
+ ] }) }),
841
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: list.subscriptions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
842
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
843
+ "button",
844
+ {
845
+ type: "button",
846
+ style: { ...styles.navItem, padding: 0, ...styles.mono },
847
+ onClick: () => open(s.subscriberType, s.subscriberId),
848
+ children: [
849
+ s.subscriberType,
850
+ "/",
851
+ s.subscriberId
852
+ ]
853
+ }
854
+ ) }),
855
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
856
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: s.plan }),
857
+ " \xB7 ",
858
+ s.interval
859
+ ] }),
860
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
861
+ s.status,
862
+ s.cancelAtPeriodEnd ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.badge, children: "cancels" }) : null
863
+ ] }),
864
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.muted, children: "\u2014" }) })
865
+ ] }, s.id)) })
866
+ ] }),
867
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
868
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
869
+ "button",
870
+ {
871
+ type: "button",
872
+ style: { ...styles.button, marginTop: 8 },
873
+ onClick: () => void list.loadMore(),
874
+ children: "Load more"
875
+ }
876
+ ) : null
877
+ ] }) : null,
636
878
  subscriber && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
637
879
  /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Subscription" }),
638
880
  subscription ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tbody", { children: [
@@ -757,11 +999,11 @@ function SubscriberScreen({ client }) {
757
999
 
758
1000
  // src/screens/AuditScreen.tsx
759
1001
  var import_react_admin11 = require("@fonderie/react-admin");
760
- var import_react3 = require("react");
1002
+ var import_react4 = require("react");
761
1003
  var import_jsx_runtime11 = require("react/jsx-runtime");
762
1004
  function AuditScreen({ client, pageSize = 50 }) {
763
- const [draft, setDraft] = (0, import_react3.useState)({ workspaceId: "", type: "", actorId: "" });
764
- const [filter, setFilter] = (0, import_react3.useState)(
1005
+ const [draft, setDraft] = (0, import_react4.useState)({ workspaceId: "", type: "", actorId: "" });
1006
+ const [filter, setFilter] = (0, import_react4.useState)(
765
1007
  {}
766
1008
  );
767
1009
  const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin11.useAdminAudit)(client, {
@@ -883,13 +1125,13 @@ function AdminShell({
883
1125
  page,
884
1126
  onNavigate
885
1127
  }) {
886
- const [own, setOwn] = (0, import_react4.useState)("attention");
1128
+ const [own, setOwn] = (0, import_react5.useState)("attention");
887
1129
  const current = page ?? own;
888
1130
  const go = (p) => {
889
1131
  if (onNavigate) onNavigate(p);
890
1132
  if (page === void 0) setOwn(p);
891
1133
  };
892
- const [editing, setEditing] = (0, import_react4.useState)(null);
1134
+ const [editing, setEditing] = (0, import_react5.useState)(null);
893
1135
  const has = {
894
1136
  config: Boolean(configClient),
895
1137
  courier: Boolean(courierClient),