@fonderie/react-admin-screens 0.2.0 → 0.4.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
@@ -11,12 +11,13 @@ Status: **experimental** (0.x).
11
11
  | Today | Attention | what needs me today — empty is green |
12
12
  | System | Modules · Configuration · Doctor · Routes | what did I deploy · is it configured · is it working · what is exposed |
13
13
  | People | Users | why can't this person log in — lookup, sessions, sign-ins, suspend, sign out everywhere (`authClient`) |
14
+ | Money | Catalog · Subscriber | what am I selling (configured vs stored) · what is this subscriber on, their wallet, ledger, a manual grant (`billingClient`) |
14
15
  | Settings | Config & secrets | the `@fonderie/config` admin screens, as a sub-page |
15
16
  | Messaging | Templates | the `@fonderie/courier` admin screens, as a sub-page |
16
- | Activity | Admin log · Access | who did what · who can be here |
17
+ | Activity | Audit · Admin log · Access | what happened, every workspace (`auditClient`) · who did what through here · who can be here |
17
18
 
18
19
  ```ts
19
- import { AdminClient, AuthAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
20
+ import { AdminClient, AuditAdminClient, AuthAdminClient, BillingAdminClient, ConfigAdminClient, CourierAdminClient } from '@fonderie/client';
20
21
  import { AdminShell } from '@fonderie/react-admin-screens';
21
22
 
22
23
  const base = { baseUrl: 'https://api.example.com', adminToken, actor: 'louis' };
@@ -25,10 +26,12 @@ const client = new AdminClient(base);
25
26
  const configClient = new ConfigAdminClient({ ...base, prefix: '/_admin' });
26
27
  const courierClient = new CourierAdminClient({ ...base, prefix: '/_admin' });
27
28
  const authClient = new AuthAdminClient(base); // @fonderie/auth ≥ 7.8
29
+ const billingClient = new BillingAdminClient(base); // @fonderie/billing ≥ 9.10
30
+ const auditClient = new AuditAdminClient(base); // @fonderie/audit ≥ 5.2
28
31
  ```
29
32
 
30
33
  ```tsx
31
- <AdminShell client={client} configClient={configClient} courierClient={courierClient} authClient={authClient} />
34
+ <AdminShell client={client} configClient={configClient} courierClient={courierClient} authClient={authClient} billingClient={billingClient} auditClient={auditClient} />
32
35
  ```
33
36
 
34
37
  Pass `page` and `onNavigate` to own the URL (one route per page); omit both
package/dist/index.cjs CHANGED
@@ -23,10 +23,13 @@ __export(index_exports, {
23
23
  AdminLogScreen: () => AdminLogScreen,
24
24
  AdminShell: () => AdminShell,
25
25
  AttentionScreen: () => AttentionScreen,
26
+ AuditScreen: () => AuditScreen,
27
+ CatalogScreen: () => CatalogScreen,
26
28
  ConfigScreen: () => ConfigScreen,
27
29
  DoctorScreen: () => DoctorScreen,
28
30
  ModulesScreen: () => ModulesScreen,
29
31
  RoutesScreen: () => RoutesScreen,
32
+ SubscriberScreen: () => SubscriberScreen,
30
33
  TokensScreen: () => TokensScreen,
31
34
  UsersScreen: () => UsersScreen
32
35
  });
@@ -35,7 +38,7 @@ module.exports = __toCommonJS(index_exports);
35
38
  // src/screens/AdminShell.tsx
36
39
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
37
40
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
38
- var import_react2 = require("react");
41
+ var import_react4 = require("react");
39
42
 
40
43
  // src/styles.ts
41
44
  var styles = {
@@ -509,8 +512,336 @@ function UsersScreen({ client }) {
509
512
  ] });
510
513
  }
511
514
 
512
- // src/screens/AdminShell.tsx
515
+ // src/screens/CatalogScreen.tsx
516
+ var import_react_admin9 = require("@fonderie/react-admin");
513
517
  var import_jsx_runtime9 = require("react/jsx-runtime");
518
+ function CatalogScreen({ client }) {
519
+ const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin9.useAdminCatalog)(client);
520
+ const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
521
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: styles.container, children: [
522
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: styles.toolbar, children: [
523
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h1", { style: { ...styles.title, marginBottom: 0 }, children: "Catalog" }),
524
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
525
+ "button",
526
+ {
527
+ type: "button",
528
+ style: styles.button,
529
+ onClick: () => void refresh(),
530
+ disabled: isLoading,
531
+ children: "Refresh"
532
+ }
533
+ )
534
+ ] }),
535
+ error ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
536
+ isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
537
+ catalog ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
538
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Configured (code)" }),
539
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("pre", { style: { ...styles.mono, background: "#f9fafb", padding: 12, overflowX: "auto" }, children: JSON.stringify(catalog.configured, null, 2) }),
540
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
541
+ catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.muted, children: "No stored plans." }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("table", { style: styles.table, children: [
542
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
543
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Plan" }),
544
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Tier" }),
545
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Seats" }),
546
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Monthly" }),
547
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Yearly" }),
548
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th, children: "Trial" }),
549
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("th", { style: styles.th })
550
+ ] }) }),
551
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("tr", { children: [
552
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
553
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("strong", { children: p.name }),
554
+ p.description ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: styles.muted, children: p.description }) : null
555
+ ] }),
556
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: p.tier }),
557
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
558
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("td", { style: styles.td, children: [
559
+ money(p.pricing?.monthly),
560
+ " ",
561
+ p.pricing?.currency ?? ""
562
+ ] }),
563
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
564
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
565
+ /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
566
+ "button",
567
+ {
568
+ type: "button",
569
+ style: styles.button,
570
+ onClick: () => {
571
+ if (window.confirm(`Delete stored plan "${p.name}"?`))
572
+ void deletePlan(p.id).catch(() => {
573
+ });
574
+ },
575
+ children: "Delete"
576
+ }
577
+ ) })
578
+ ] }, p.id)) })
579
+ ] })
580
+ ] }) : null
581
+ ] });
582
+ }
583
+
584
+ // src/screens/SubscriberScreen.tsx
585
+ var import_react_admin10 = require("@fonderie/react-admin");
586
+ var import_react2 = require("react");
587
+ 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);
592
+ 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);
596
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("div", { style: styles.container, children: [
597
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h1", { style: styles.title, children: "Subscriber" }),
598
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
599
+ "form",
600
+ {
601
+ style: styles.toolbar,
602
+ onSubmit: (e) => {
603
+ e.preventDefault();
604
+ setGranted(null);
605
+ setSubscriber(idInput.trim() ? { type, id: idInput.trim() } : null);
606
+ },
607
+ children: [
608
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
609
+ "select",
610
+ {
611
+ value: type,
612
+ onChange: (e) => setType(e.target.value),
613
+ style: styles.button,
614
+ "aria-label": "Type",
615
+ children: [
616
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "user", children: "user" }),
617
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("option", { value: "workspace", children: "workspace" })
618
+ ]
619
+ }
620
+ ),
621
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
622
+ "input",
623
+ {
624
+ value: idInput,
625
+ onChange: (e) => setIdInput(e.target.value),
626
+ placeholder: "subscriber id",
627
+ style: { ...styles.button, cursor: "text", minWidth: 280 },
628
+ "aria-label": "Subscriber id"
629
+ }
630
+ ),
631
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Look up" })
632
+ ]
633
+ }
634
+ ),
635
+ error ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
636
+ subscriber && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
637
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Subscription" }),
638
+ subscription ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tbody", { children: [
639
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
640
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "plan" }),
641
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
642
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: subscription.plan }),
643
+ " \xB7 ",
644
+ subscription.interval
645
+ ] })
646
+ ] }),
647
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
648
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "status" }),
649
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
650
+ subscription.status,
651
+ subscription.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""
652
+ ] })
653
+ ] }),
654
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
655
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "period" }),
656
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("td", { style: styles.td, children: [
657
+ subscription.currentPeriodStart ? new Date(subscription.currentPeriodStart).toLocaleDateString() : "\u2014",
658
+ " ",
659
+ "\u2192",
660
+ " ",
661
+ subscription.currentPeriodEnd ? new Date(subscription.currentPeriodEnd).toLocaleDateString() : "\u2014"
662
+ ] })
663
+ ] }),
664
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
665
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: "provider" }),
666
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: subscription.providerSubscriptionId ?? "\u2014" })
667
+ ] })
668
+ ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "No subscription." }),
669
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Wallet" }),
670
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(import_jsx_runtime10.Fragment, { children: [
671
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("p", { children: [
672
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("strong", { children: wallet.balance }),
673
+ " ",
674
+ wallet.currency,
675
+ " ",
676
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("span", { style: styles.muted, children: [
677
+ "(granted ",
678
+ wallet.granted ?? "0",
679
+ " \xB7 purchased ",
680
+ wallet.purchased ?? "0",
681
+ ") \xB7 minor units, precision ",
682
+ wallet.precision
683
+ ] })
684
+ ] }),
685
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)(
686
+ "form",
687
+ {
688
+ style: styles.toolbar,
689
+ onSubmit: (e) => {
690
+ e.preventDefault();
691
+ const key = `admin-grant-${subscriber.type}-${subscriber.id}-${Date.now()}`;
692
+ void grant({
693
+ amount: amount.trim(),
694
+ ...note ? { description: note } : {},
695
+ idempotencyKey: key
696
+ }).then(() => {
697
+ setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
698
+ setAmount("");
699
+ setNote("");
700
+ }).catch(() => {
701
+ });
702
+ },
703
+ children: [
704
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
705
+ "input",
706
+ {
707
+ value: amount,
708
+ onChange: (e) => setAmount(e.target.value),
709
+ placeholder: "amount (minor units)",
710
+ style: { ...styles.button, cursor: "text" },
711
+ "aria-label": "Amount"
712
+ }
713
+ ),
714
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
715
+ "input",
716
+ {
717
+ value: note,
718
+ onChange: (e) => setNote(e.target.value),
719
+ placeholder: "reason (optional)",
720
+ style: { ...styles.button, cursor: "text", minWidth: 220 },
721
+ "aria-label": "Reason"
722
+ }
723
+ ),
724
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("button", { type: "submit", style: styles.button, disabled: !/^\d+$/.test(amount.trim()), children: "Grant" }),
725
+ granted ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { style: styles.ok, children: granted }) : null
726
+ ]
727
+ }
728
+ ),
729
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("h2", { style: styles.subtitle, children: "Ledger" }),
730
+ ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "Nothing moved yet." }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("table", { style: styles.table, children: [
731
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
732
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "When" }),
733
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Kind" }),
734
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Amount" }),
735
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("th", { style: styles.th, children: "Note" })
736
+ ] }) }),
737
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("tbody", { children: ledger.map((t) => /* @__PURE__ */ (0, import_jsx_runtime10.jsxs)("tr", { children: [
738
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(t.createdAt).toLocaleString() }),
739
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: styles.td, children: t.type }),
740
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t.amount }),
741
+ /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t.description ?? "" })
742
+ ] }, t.id)) })
743
+ ] }),
744
+ hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime10.jsx)(
745
+ "button",
746
+ {
747
+ type: "button",
748
+ style: { ...styles.button, marginTop: 8 },
749
+ onClick: () => void loadMoreLedger(),
750
+ children: "Load more"
751
+ }
752
+ ) : null
753
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("p", { style: styles.muted, children: "No wallet \u2014 billing has no wallet configuration." })
754
+ ] }) : null
755
+ ] });
756
+ }
757
+
758
+ // src/screens/AuditScreen.tsx
759
+ var import_react_admin11 = require("@fonderie/react-admin");
760
+ var import_react3 = require("react");
761
+ var import_jsx_runtime11 = require("react/jsx-runtime");
762
+ function AuditScreen({ client, pageSize = 50 }) {
763
+ const [draft, setDraft] = (0, import_react3.useState)({ workspaceId: "", type: "", actorId: "" });
764
+ const [filter, setFilter] = (0, import_react3.useState)(
765
+ {}
766
+ );
767
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin11.useAdminAudit)(client, {
768
+ ...filter,
769
+ limit: pageSize
770
+ });
771
+ const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
772
+ "input",
773
+ {
774
+ value: draft[key],
775
+ onChange: (e) => setDraft({ ...draft, [key]: e.target.value }),
776
+ placeholder,
777
+ style: { ...styles.button, cursor: "text" },
778
+ "aria-label": placeholder
779
+ }
780
+ );
781
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.container, children: [
782
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h1", { style: styles.title, children: "Audit" }),
783
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
784
+ "form",
785
+ {
786
+ style: styles.toolbar,
787
+ onSubmit: (e) => {
788
+ e.preventDefault();
789
+ setFilter({
790
+ ...draft.workspaceId.trim() ? { workspaceId: draft.workspaceId.trim() } : {},
791
+ ...draft.type.trim() ? { type: draft.type.trim() } : {},
792
+ ...draft.actorId.trim() ? { actorId: draft.actorId.trim() } : {}
793
+ });
794
+ },
795
+ children: [
796
+ field("workspaceId", "workspace id (all if empty)"),
797
+ field("type", "event type"),
798
+ field("actorId", "actor id"),
799
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("button", { type: "submit", style: styles.button, disabled: isLoading, children: "Filter" }),
800
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
801
+ "button",
802
+ {
803
+ type: "button",
804
+ style: styles.button,
805
+ onClick: () => void refresh(),
806
+ disabled: isLoading,
807
+ children: "Refresh"
808
+ }
809
+ )
810
+ ]
811
+ }
812
+ ),
813
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
814
+ events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.muted, children: "Nothing recorded for this filter." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
815
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
816
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
817
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Type" }),
818
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Workspace" }),
819
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Actor" }),
820
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Request" })
821
+ ] }) }),
822
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
823
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
824
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
825
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
826
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
827
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
828
+ ] }, e.id)) })
829
+ ] }),
830
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
831
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
832
+ "button",
833
+ {
834
+ type: "button",
835
+ style: { ...styles.button, marginTop: 8 },
836
+ onClick: () => void loadMore(),
837
+ children: "Load more"
838
+ }
839
+ ) : null
840
+ ] });
841
+ }
842
+
843
+ // src/screens/AdminShell.tsx
844
+ var import_jsx_runtime12 = require("react/jsx-runtime");
514
845
  var NAV = [
515
846
  { group: "Today", items: [{ page: "attention", label: "Attention" }] },
516
847
  {
@@ -523,11 +854,19 @@ var NAV = [
523
854
  ]
524
855
  },
525
856
  { group: "People", items: [{ page: "users", label: "Users", needs: "auth" }] },
857
+ {
858
+ group: "Money",
859
+ items: [
860
+ { page: "catalog", label: "Catalog", needs: "billing" },
861
+ { page: "subscriber", label: "Subscriber", needs: "billing" }
862
+ ]
863
+ },
526
864
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
527
865
  { group: "Messaging", items: [{ page: "templates", label: "Templates", needs: "courier" }] },
528
866
  {
529
867
  group: "Activity",
530
868
  items: [
869
+ { page: "audit", label: "Audit", needs: "audit" },
531
870
  { page: "log", label: "Admin log" },
532
871
  { page: "tokens", label: "Access" }
533
872
  ]
@@ -538,52 +877,65 @@ function AdminShell({
538
877
  configClient,
539
878
  courierClient,
540
879
  authClient,
880
+ billingClient,
881
+ auditClient,
541
882
  environment,
542
883
  page,
543
884
  onNavigate
544
885
  }) {
545
- const [own, setOwn] = (0, import_react2.useState)("attention");
886
+ const [own, setOwn] = (0, import_react4.useState)("attention");
546
887
  const current = page ?? own;
547
888
  const go = (p) => {
548
889
  if (onNavigate) onNavigate(p);
549
890
  if (page === void 0) setOwn(p);
550
891
  };
551
- const [editing, setEditing] = (0, import_react2.useState)(null);
892
+ const [editing, setEditing] = (0, import_react4.useState)(null);
552
893
  const has = {
553
894
  config: Boolean(configClient),
554
895
  courier: Boolean(courierClient),
555
- auth: Boolean(authClient)
896
+ auth: Boolean(authClient),
897
+ billing: Boolean(billingClient),
898
+ audit: Boolean(auditClient)
556
899
  };
557
900
  let body;
558
901
  switch (current) {
559
902
  case "attention":
560
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AttentionScreen, { client });
903
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AttentionScreen, { client });
561
904
  break;
562
905
  case "modules":
563
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ModulesScreen, { client });
906
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ModulesScreen, { client });
564
907
  break;
565
908
  case "doctor":
566
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(DoctorScreen, { client });
909
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(DoctorScreen, { client });
567
910
  break;
568
911
  case "config":
569
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(ConfigScreen, { client });
912
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(ConfigScreen, { client });
570
913
  break;
571
914
  case "routes":
572
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(RoutesScreen, { client });
915
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RoutesScreen, { client });
573
916
  break;
574
917
  case "tokens":
575
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(TokensScreen, { client });
918
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(TokensScreen, { client });
576
919
  break;
577
920
  case "log":
578
- body = /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(AdminLogScreen, { client });
921
+ body = /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AdminLogScreen, { client });
922
+ break;
923
+ case "catalog":
924
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to see the catalog here." });
925
+ break;
926
+ case "subscriber":
927
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a BillingAdminClient to look up subscribers here." });
928
+ break;
929
+ case "audit":
930
+ body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass an AuditAdminClient to see the audit trail here." });
579
931
  break;
580
932
  case "users":
581
- body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Pass an AuthAdminClient to look up users here." });
933
+ body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass an AuthAdminClient to look up users here." });
582
934
  break;
583
935
  case "settings":
584
- body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
585
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
586
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
936
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a ConfigAdminClient to manage config and secrets here." }) : editing && editing.kind !== "template" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
937
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
938
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
587
939
  import_react_config_admin_screens.ConfigEditorScreen,
588
940
  {
589
941
  client: configClient,
@@ -593,7 +945,7 @@ function AdminShell({
593
945
  onSaved: () => setEditing(null)
594
946
  }
595
947
  )
596
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
948
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
597
949
  import_react_config_admin_screens.ConfigListScreen,
598
950
  {
599
951
  client: configClient,
@@ -604,9 +956,9 @@ function AdminShell({
604
956
  );
605
957
  break;
606
958
  case "templates":
607
- body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)(import_jsx_runtime9.Fragment, { children: [
608
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
609
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
959
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("button", { type: "button", style: styles.button, onClick: () => setEditing(null), children: "\u2190 Back" }),
961
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
610
962
  import_react_courier_admin_screens.TemplateEditorScreen,
611
963
  {
612
964
  client: courierClient,
@@ -614,7 +966,7 @@ function AdminShell({
614
966
  onSaved: () => setEditing(null)
615
967
  }
616
968
  )
617
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
969
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
618
970
  import_react_courier_admin_screens.TemplateListScreen,
619
971
  {
620
972
  client: courierClient,
@@ -623,13 +975,13 @@ function AdminShell({
623
975
  );
624
976
  break;
625
977
  }
626
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { style: styles.shell, children: [
627
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
978
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.shell, children: [
979
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("nav", { style: styles.nav, "aria-label": "Admin", children: NAV.map((g) => {
628
980
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
629
981
  if (items.length === 0) return null;
630
- return /* @__PURE__ */ (0, import_jsx_runtime9.jsxs)("div", { children: [
631
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("div", { style: styles.navGroup, children: g.group }),
632
- items.map((i) => /* @__PURE__ */ (0, import_jsx_runtime9.jsx)(
982
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { children: [
983
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: styles.navGroup, children: g.group }),
984
+ items.map((i) => /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
633
985
  "button",
634
986
  {
635
987
  type: "button",
@@ -645,7 +997,7 @@ function AdminShell({
645
997
  ))
646
998
  ] }, g.group);
647
999
  }) }),
648
- /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("main", { style: styles.main, children: body })
1000
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("main", { style: styles.main, children: body })
649
1001
  ] });
650
1002
  }
651
1003
  // Annotate the CommonJS export names for ESM import in node:
@@ -653,10 +1005,13 @@ function AdminShell({
653
1005
  AdminLogScreen,
654
1006
  AdminShell,
655
1007
  AttentionScreen,
1008
+ AuditScreen,
1009
+ CatalogScreen,
656
1010
  ConfigScreen,
657
1011
  DoctorScreen,
658
1012
  ModulesScreen,
659
1013
  RoutesScreen,
1014
+ SubscriberScreen,
660
1015
  TokensScreen,
661
1016
  UsersScreen
662
1017
  });