@fonderie/react-admin-screens 1.2.0 → 1.3.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
@@ -31,6 +31,7 @@ __export(index_exports, {
31
31
  ModulesScreen: () => ModulesScreen,
32
32
  OperatorsScreen: () => OperatorsScreen,
33
33
  RoutesScreen: () => RoutesScreen,
34
+ SubscriberBilling: () => SubscriberBilling,
34
35
  SubscriberScreen: () => SubscriberScreen,
35
36
  TokensScreen: () => TokensScreen,
36
37
  UsersScreen: () => UsersScreen
@@ -40,7 +41,7 @@ module.exports = __toCommonJS(index_exports);
40
41
  // src/screens/AdminShell.tsx
41
42
  var import_react_config_admin_screens = require("@fonderie/react-config-admin-screens");
42
43
  var import_react_courier_admin_screens = require("@fonderie/react-courier-admin-screens");
43
- var import_react7 = require("react");
44
+ var import_react9 = require("react");
44
45
 
45
46
  // src/styles.ts
46
47
  var t = {
@@ -1538,34 +1539,241 @@ function TokensScreen({ client }) {
1538
1539
 
1539
1540
  // src/screens/UsersScreen.tsx
1540
1541
  var import_client = require("@fonderie/client");
1541
- var import_react_admin10 = require("@fonderie/react-admin");
1542
+ var import_react_admin11 = require("@fonderie/react-admin");
1543
+ var import_react6 = require("react");
1544
+
1545
+ // src/billing.ts
1542
1546
  var import_react4 = require("react");
1547
+ function periodEnd(s) {
1548
+ if (!s.currentPeriodEnd) return null;
1549
+ const date = new Date(s.currentPeriodEnd).toLocaleDateString();
1550
+ if (s.status === "canceled")
1551
+ return { label: new Date(s.currentPeriodEnd) < /* @__PURE__ */ new Date() ? "Ended" : "Ends", date };
1552
+ if (s.cancelAtPeriodEnd) return { label: "Ends", date };
1553
+ return { label: "Renews", date };
1554
+ }
1555
+ var statusTone = (status) => status === "active" ? "ok" : status === "trialing" ? "info" : status === "past_due" || status === "unpaid" ? "warn" : "neutral";
1556
+ function useSubscriptionIndex(client) {
1557
+ const [index, setIndex] = (0, import_react4.useState)(null);
1558
+ (0, import_react4.useEffect)(() => {
1559
+ if (!client) return;
1560
+ let live = true;
1561
+ (async () => {
1562
+ const map = /* @__PURE__ */ new Map();
1563
+ let cursor;
1564
+ for (let page = 0; page < 20; page++) {
1565
+ const { result } = await client.listSubscriptions({
1566
+ limit: 100,
1567
+ ...cursor ? { cursor } : {}
1568
+ });
1569
+ for (const s of result.subscriptions) {
1570
+ const key = `${s.subscriberType}/${s.subscriberId}`;
1571
+ if (!map.has(key)) map.set(key, s);
1572
+ }
1573
+ if (!result.nextCursor) break;
1574
+ cursor = result.nextCursor;
1575
+ }
1576
+ if (live) setIndex(map);
1577
+ })().catch(() => {
1578
+ if (live) setIndex(/* @__PURE__ */ new Map());
1579
+ });
1580
+ return () => {
1581
+ live = false;
1582
+ };
1583
+ }, [client]);
1584
+ return index;
1585
+ }
1586
+
1587
+ // src/screens/SubscriberBilling.tsx
1588
+ var import_react_admin10 = require("@fonderie/react-admin");
1589
+ var import_react5 = require("react");
1543
1590
  var import_jsx_runtime11 = require("react/jsx-runtime");
1544
- function UsersScreen({ client, pageSize = 50 }) {
1545
- const [input, setInput] = (0, import_react4.useState)("");
1546
- const [selected, setSelected] = (0, import_react4.useState)(null);
1547
- const list = (0, import_react_admin10.useAdminUsers)(client, { limit: pageSize });
1548
- const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin10.useAdminUser)(
1591
+ function SubscriberBilling({ client, subscriber }) {
1592
+ const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin10.useAdminSubscriber)(client, subscriber, { limit: 20 });
1593
+ const [amount, setAmount] = (0, import_react5.useState)("");
1594
+ const [note, setNote] = (0, import_react5.useState)("");
1595
+ const [granted, setGranted] = (0, import_react5.useState)(null);
1596
+ if (isLoading && !wallet && !subscription) return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading billing\u2026" });
1597
+ const end = subscription ? periodEnd(subscription) : null;
1598
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1599
+ error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1600
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.grid, marginBottom: 16 }, children: [
1601
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1602
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Plan" }),
1603
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1604
+ "div",
1605
+ {
1606
+ style: {
1607
+ display: "flex",
1608
+ alignItems: "center",
1609
+ gap: 8,
1610
+ marginTop: 6,
1611
+ flexWrap: "wrap"
1612
+ },
1613
+ children: [
1614
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 18 }, children: subscription ? subscription.plan : "Free" }),
1615
+ subscription ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1616
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1617
+ "\xB7 ",
1618
+ subscription.interval
1619
+ ] }),
1620
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: statusTone(subscription.status), children: subscription.status })
1621
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: "no subscription" })
1622
+ ]
1623
+ }
1624
+ ),
1625
+ end ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1626
+ end.label,
1627
+ " ",
1628
+ end.date
1629
+ ] }) : null,
1630
+ subscription?.providerSubscriptionId ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: { ...styles.statHint, fontFamily: "var(--fonderie-mono,monospace)" }, children: subscription.providerSubscriptionId }) : null
1631
+ ] }),
1632
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.card, children: [
1633
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statLabel, children: "Credits" }),
1634
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1635
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statValue, children: [
1636
+ wallet.balance,
1637
+ " ",
1638
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: { fontSize: 14, fontWeight: 500 }, children: wallet.currency })
1639
+ ] }),
1640
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.statHint, children: [
1641
+ wallet.purchased ?? "0",
1642
+ " permanent \xB7 ",
1643
+ wallet.granted ?? "0",
1644
+ " plan allowance",
1645
+ wallet.grantedExpiresAt ? ` (expires ${new Date(wallet.grantedExpiresAt).toLocaleDateString()})` : ""
1646
+ ] })
1647
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { style: styles.statHint, children: "No wallet \u2014 billing has no wallet configuration." })
1648
+ ] })
1649
+ ] }),
1650
+ wallet ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1651
+ "form",
1652
+ {
1653
+ style: {
1654
+ ...styles.card,
1655
+ display: "flex",
1656
+ gap: 8,
1657
+ alignItems: "center",
1658
+ flexWrap: "wrap",
1659
+ marginBottom: 16
1660
+ },
1661
+ onSubmit: (e) => {
1662
+ e.preventDefault();
1663
+ const key = `admin-grant-${subscriber.type}-${subscriber.id}-${Date.now()}`;
1664
+ void grant({
1665
+ amount: amount.trim(),
1666
+ ...note ? { description: note } : {},
1667
+ idempotencyKey: key
1668
+ }).then(() => {
1669
+ setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
1670
+ setAmount("");
1671
+ setNote("");
1672
+ }).catch(() => {
1673
+ });
1674
+ },
1675
+ children: [
1676
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("strong", { style: { fontSize: 13.5, marginRight: 4 }, children: "Grant credits" }),
1677
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1678
+ "input",
1679
+ {
1680
+ value: amount,
1681
+ onChange: (e) => setAmount(e.target.value),
1682
+ placeholder: `amount (${wallet.currency})`,
1683
+ inputMode: "numeric",
1684
+ style: { ...styles.input, width: 150 },
1685
+ "aria-label": "Amount"
1686
+ }
1687
+ ),
1688
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1689
+ "input",
1690
+ {
1691
+ value: note,
1692
+ onChange: (e) => setNote(e.target.value),
1693
+ placeholder: "reason (optional)",
1694
+ style: { ...styles.input, flex: 1, minWidth: 180 },
1695
+ "aria-label": "Reason"
1696
+ }
1697
+ ),
1698
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1699
+ "button",
1700
+ {
1701
+ type: "submit",
1702
+ style: styles.buttonPrimary,
1703
+ disabled: !/^\d+$/.test(amount.trim()),
1704
+ children: [
1705
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "plus", size: 14 }),
1706
+ "Grant"
1707
+ ]
1708
+ }
1709
+ ),
1710
+ granted ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.ok, children: granted }) : null
1711
+ ]
1712
+ }
1713
+ ) : null,
1714
+ wallet ? ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "subscriber", title: "No credit movements yet" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1715
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1716
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1717
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "When" }),
1718
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Kind" }),
1719
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Amount" }),
1720
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Note" })
1721
+ ] }) }),
1722
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1723
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted, whiteSpace: "nowrap" }, children: new Date(t2.createdAt).toLocaleString() }),
1724
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: t2.type }),
1725
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
1726
+ /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
1727
+ ] }, t2.id)) })
1728
+ ] }),
1729
+ hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1730
+ "button",
1731
+ {
1732
+ type: "button",
1733
+ style: { ...styles.button, marginTop: 8 },
1734
+ onClick: () => void loadMoreLedger(),
1735
+ children: "Load more"
1736
+ }
1737
+ ) : null
1738
+ ] }) : null
1739
+ ] });
1740
+ }
1741
+
1742
+ // src/screens/UsersScreen.tsx
1743
+ var import_jsx_runtime12 = require("react/jsx-runtime");
1744
+ function UsersScreen({
1745
+ client,
1746
+ pageSize = 50,
1747
+ billingClient,
1748
+ openUserId
1749
+ }) {
1750
+ const [input, setInput] = (0, import_react6.useState)("");
1751
+ const [selected, setSelected] = (0, import_react6.useState)(
1752
+ openUserId ? { id: openUserId } : null
1753
+ );
1754
+ const plans = useSubscriptionIndex(billingClient);
1755
+ const list = (0, import_react_admin11.useAdminUsers)(client, { limit: pageSize });
1756
+ const { user, isLoading, error, suspend, unsuspend, revokeSessions } = (0, import_react_admin11.useAdminUser)(
1549
1757
  client,
1550
1758
  selected ?? {}
1551
1759
  );
1552
1760
  const userId = user?.id ?? null;
1553
- const sessions = (0, import_react_admin10.useAdminUserSessions)(client, userId);
1554
- const history = (0, import_react_admin10.useAdminLoginHistory)(client, userId, { limit: 20 });
1555
- const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "neutral", children: "no" });
1761
+ const sessions = (0, import_react_admin11.useAdminUserSessions)(client, userId);
1762
+ const history = (0, import_react_admin11.useAdminLoginHistory)(client, userId, { limit: 20 });
1763
+ const yesNo = (v) => v ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "yes" }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "no" });
1556
1764
  const clear = () => {
1557
1765
  setSelected(null);
1558
1766
  setInput("");
1559
1767
  };
1560
- return /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: styles.container, children: [
1561
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1768
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1769
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1562
1770
  PageHeader,
1563
1771
  {
1564
1772
  title: "Users",
1565
1773
  lead: "Everyone signed up. Open an account for its sessions, sign-ins and the suspend and sign-out controls."
1566
1774
  }
1567
1775
  ),
1568
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(
1776
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(
1569
1777
  "form",
1570
1778
  {
1571
1779
  style: styles.toolbar,
@@ -1575,7 +1783,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1575
1783
  setSelected(email ? { email } : null);
1576
1784
  },
1577
1785
  children: [
1578
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1786
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1579
1787
  "input",
1580
1788
  {
1581
1789
  type: "email",
@@ -1586,14 +1794,14 @@ function UsersScreen({ client, pageSize = 50 }) {
1586
1794
  "aria-label": "Email"
1587
1795
  }
1588
1796
  ),
1589
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1590
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "search", size: 14 }),
1797
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1798
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "search", size: 14 }),
1591
1799
  "Look up"
1592
1800
  ] }),
1593
- selected ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1594
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Icon, { name: "back", size: 14 }),
1801
+ selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: clear, children: [
1802
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Icon, { name: "back", size: 14 }),
1595
1803
  "All users"
1596
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1804
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1597
1805
  "button",
1598
1806
  {
1599
1807
  type: "button",
@@ -1606,18 +1814,19 @@ function UsersScreen({ client, pageSize = 50 }) {
1606
1814
  ]
1607
1815
  }
1608
1816
  ),
1609
- error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1610
- !selected ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1611
- list.error ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1612
- list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("table", { style: styles.table, children: [
1613
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1614
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Email" }),
1615
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Name" }),
1616
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Created" }),
1617
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("th", { style: styles.th, children: "Status" })
1817
+ error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1818
+ !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1819
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1820
+ list.users.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Empty, { icon: "users", title: "No users yet", children: "Sign-ups appear here as they happen." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("table", { style: styles.table, children: [
1821
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1822
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Email" }),
1823
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Name" }),
1824
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }) : null,
1825
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Created" }),
1826
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Status" })
1618
1827
  ] }) }),
1619
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1620
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1828
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1829
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1621
1830
  "button",
1622
1831
  {
1623
1832
  type: "button",
@@ -1626,17 +1835,28 @@ function UsersScreen({ client, pageSize = 50 }) {
1626
1835
  children: u.email
1627
1836
  }
1628
1837
  ) }),
1629
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: "\u2014" }) }),
1630
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1631
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: styles.td, children: [
1632
- u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1633
- u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1634
- !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "ok", children: "active" }) : null
1838
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2014" }) }),
1839
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: (() => {
1840
+ const sub = plans?.get(`user/${u.id}`);
1841
+ if (!plans) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2026" });
1842
+ if (!sub || sub.status === "canceled")
1843
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "free" });
1844
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1845
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: sub.plan }),
1846
+ " ",
1847
+ sub.status !== "active" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: statusTone(sub.status), children: sub.status }) : null
1848
+ ] });
1849
+ })() }) : null,
1850
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1851
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1852
+ u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1853
+ u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1854
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "active" }) : null
1635
1855
  ] })
1636
1856
  ] }, u.id)) })
1637
1857
  ] }),
1638
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1639
- list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1858
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1859
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1640
1860
  "button",
1641
1861
  {
1642
1862
  type: "button",
@@ -1646,48 +1866,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1646
1866
  }
1647
1867
  ) : null
1648
1868
  ] }) : null,
1649
- user ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1650
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("h2", { style: styles.subtitle, children: [
1869
+ user ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1870
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("h2", { style: styles.subtitle, children: [
1651
1871
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1652
1872
  " ",
1653
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1654
- user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
1873
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1874
+ user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
1655
1875
  ] }),
1656
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tbody", { children: [
1657
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1658
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "id" }),
1659
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1876
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1877
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1878
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "id" }),
1879
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1660
1880
  ] }),
1661
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1662
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "email" }),
1663
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: styles.td, children: [
1881
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1882
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "email" }),
1883
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1664
1884
  user.email,
1665
1885
  " \xB7 verified ",
1666
1886
  yesNo(user.isEmailVerified)
1667
1887
  ] })
1668
1888
  ] }),
1669
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1670
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "MFA" }),
1671
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1889
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1890
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "MFA" }),
1891
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1672
1892
  ] }),
1673
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1674
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "provider" }),
1675
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("td", { style: styles.td, children: [
1893
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1894
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "provider" }),
1895
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1676
1896
  user.provider || "password",
1677
1897
  user.hasPassword ? "" : " \xB7 no password set"
1678
1898
  ] })
1679
1899
  ] }),
1680
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1681
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "last login" }),
1682
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: "never" }) })
1900
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1901
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "last login" }),
1902
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "never" }) })
1683
1903
  ] }),
1684
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("tr", { children: [
1685
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: "created" }),
1686
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1904
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1905
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "created" }),
1906
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1687
1907
  ] })
1688
1908
  ] }) }),
1689
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1690
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1909
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1910
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1691
1911
  "button",
1692
1912
  {
1693
1913
  type: "button",
@@ -1696,7 +1916,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1696
1916
  disabled: isLoading,
1697
1917
  children: "Unsuspend"
1698
1918
  }
1699
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1919
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1700
1920
  "button",
1701
1921
  {
1702
1922
  type: "button",
@@ -1706,7 +1926,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1706
1926
  children: "Suspend"
1707
1927
  }
1708
1928
  ),
1709
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1929
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1710
1930
  "button",
1711
1931
  {
1712
1932
  type: "button",
@@ -1717,38 +1937,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1717
1937
  }
1718
1938
  )
1719
1939
  ] }),
1720
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
1721
- sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { style: styles.row, children: [
1722
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1940
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1941
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
1942
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1943
+ SubscriberBilling,
1944
+ {
1945
+ client: billingClient,
1946
+ subscriber: { type: "user", id: user.id }
1947
+ }
1948
+ )
1949
+ ] }) : null,
1950
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
1951
+ sessions.sessions.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: sessions.sessions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
1952
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1723
1953
  " ",
1724
- (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1954
+ (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1725
1955
  (0, import_client.describeLocation)(s.location),
1726
1956
  " "
1727
1957
  ] }) : null,
1728
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1958
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1729
1959
  " ",
1730
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1731
1961
  "since ",
1732
1962
  new Date(s.createdAt).toLocaleString()
1733
1963
  ] })
1734
1964
  ] }, s.id)) }),
1735
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1736
- history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("li", { style: styles.row, children: [
1737
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1965
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1966
+ history.events.length === 0 && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.muted, children: "None recorded." }) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("ul", { style: styles.list, children: history.events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("li", { style: styles.row, children: [
1967
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1738
1968
  " ",
1739
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: e.method }),
1969
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
1740
1970
  " ",
1741
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1971
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1742
1972
  " ",
1743
- (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
1744
- e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)(import_jsx_runtime11.Fragment, { children: [
1973
+ (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
1974
+ e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1745
1975
  " ",
1746
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1976
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1747
1977
  ] }) : null,
1748
1978
  " ",
1749
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1979
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1750
1980
  ] }, e.id)) }),
1751
- history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1981
+ history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1752
1982
  "button",
1753
1983
  {
1754
1984
  type: "button",
@@ -1762,25 +1992,25 @@ function UsersScreen({ client, pageSize = 50 }) {
1762
1992
  }
1763
1993
 
1764
1994
  // src/screens/CatalogScreen.tsx
1765
- var import_react_admin11 = require("@fonderie/react-admin");
1766
- var import_jsx_runtime12 = require("react/jsx-runtime");
1995
+ var import_react_admin12 = require("@fonderie/react-admin");
1996
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1767
1997
  function CatalogScreen({ client }) {
1768
- const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin11.useAdminCatalog)(client);
1998
+ const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin12.useAdminCatalog)(client);
1769
1999
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
1770
- return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.container, children: [
1771
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2000
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
2001
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1772
2002
  PageHeader,
1773
2003
  {
1774
2004
  title: "Catalog",
1775
2005
  lead: "What you sell: plans as configured in code, and as stored in the database, side by side.",
1776
- actions: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
2006
+ actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1777
2007
  }
1778
2008
  ),
1779
- error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1780
- isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1781
- catalog ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1782
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
1783
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2009
+ error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2010
+ isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2011
+ catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2012
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
2013
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1784
2014
  "pre",
1785
2015
  {
1786
2016
  style: {
@@ -1794,32 +2024,32 @@ function CatalogScreen({ client }) {
1794
2024
  children: JSON.stringify(catalog.configured, null, 2)
1795
2025
  }
1796
2026
  ),
1797
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
1798
- catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime12.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_runtime12.jsxs)("table", { style: styles.table, children: [
1799
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1800
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }),
1801
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Tier" }),
1802
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Seats" }),
1803
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Monthly" }),
1804
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Yearly" }),
1805
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Trial" }),
1806
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th })
2027
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
2028
+ catalog.stored.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.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_runtime13.jsxs)("table", { style: styles.table, children: [
2029
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2030
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
2031
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Tier" }),
2032
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Seats" }),
2033
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Monthly" }),
2034
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Yearly" }),
2035
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Trial" }),
2036
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th })
1807
2037
  ] }) }),
1808
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1809
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1810
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: p.name }),
1811
- p.description ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("div", { style: styles.muted, children: p.description }) : null
2038
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2039
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
2040
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: p.name }),
2041
+ p.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: styles.muted, children: p.description }) : null
1812
2042
  ] }),
1813
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: p.tier }),
1814
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
1815
- /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
2043
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.tier }),
2044
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
2045
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1816
2046
  money(p.pricing?.monthly),
1817
2047
  " ",
1818
2048
  p.pricing?.currency ?? ""
1819
2049
  ] }),
1820
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
1821
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
1822
- /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
2050
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2051
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
2052
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1823
2053
  "button",
1824
2054
  {
1825
2055
  type: "button",
@@ -1839,114 +2069,148 @@ function CatalogScreen({ client }) {
1839
2069
  }
1840
2070
 
1841
2071
  // src/screens/SubscriberScreen.tsx
1842
- var import_react_admin12 = require("@fonderie/react-admin");
1843
- var import_react5 = require("react");
1844
- var import_jsx_runtime13 = require("react/jsx-runtime");
1845
- function SubscriberScreen({ client, pageSize = 50 }) {
1846
- const [type, setType] = (0, import_react5.useState)("user");
1847
- const [idInput, setIdInput] = (0, import_react5.useState)("");
1848
- const [subscriber, setSubscriber] = (0, import_react5.useState)(null);
1849
- const list = (0, import_react_admin12.useAdminSubscribers)(client, { limit: pageSize });
1850
- const { subscription, wallet, ledger, hasMoreLedger, isLoading, error, loadMoreLedger, grant } = (0, import_react_admin12.useAdminSubscriber)(client, subscriber, { limit: 20 });
1851
- const [amount, setAmount] = (0, import_react5.useState)("");
1852
- const [note, setNote] = (0, import_react5.useState)("");
1853
- const [granted, setGranted] = (0, import_react5.useState)(null);
1854
- const open = (t2, id) => {
1855
- setGranted(null);
1856
- setType(t2);
1857
- setIdInput(id);
1858
- setSubscriber({ type: t2, id });
1859
- };
1860
- return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
1861
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2072
+ var import_react_admin13 = require("@fonderie/react-admin");
2073
+ var import_react7 = require("react");
2074
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2075
+ var FILTERS = [
2076
+ { label: "All", match: () => true },
2077
+ { label: "Active", match: (s) => s === "active" },
2078
+ { label: "Trialing", match: (s) => s === "trialing" },
2079
+ { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2080
+ { label: "Canceled", match: (s) => s === "canceled" }
2081
+ ];
2082
+ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2083
+ const list = (0, import_react_admin13.useAdminSubscribers)(client, { limit: pageSize });
2084
+ const [filter, setFilter] = (0, import_react7.useState)("All");
2085
+ const [open, setOpen] = (0, import_react7.useState)(null);
2086
+ const [type, setType] = (0, import_react7.useState)("workspace");
2087
+ const [idInput, setIdInput] = (0, import_react7.useState)("");
2088
+ if (open) {
2089
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2090
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2091
+ PageHeader,
2092
+ {
2093
+ title: `${open.type}/${open.id}`,
2094
+ lead: "Plan, credits and what moved.",
2095
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2096
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2097
+ "All subscriptions"
2098
+ ] })
2099
+ }
2100
+ ),
2101
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open })
2102
+ ] });
2103
+ }
2104
+ const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2105
+ const rows = list.subscriptions.filter((s) => match(s.status));
2106
+ const openRow = (t2, id) => t2 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t2, id });
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1862
2109
  PageHeader,
1863
2110
  {
1864
- title: "Subscribers",
1865
- lead: "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
2111
+ title: "Subscriptions",
2112
+ lead: "Who pays, and who is behind. A user's plan and credits are also on their page under Users \u2014 free users appear only there.",
2113
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefreshButton, { onClick: () => void list.refresh(), busy: list.isLoading })
1866
2114
  }
1867
2115
  ),
1868
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1869
- "form",
1870
- {
1871
- style: styles.toolbar,
1872
- onSubmit: (e) => {
1873
- e.preventDefault();
1874
- setGranted(null);
1875
- setSubscriber(idInput.trim() ? { type, id: idInput.trim() } : null);
1876
- },
1877
- children: [
1878
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1879
- "select",
1880
- {
1881
- value: type,
1882
- onChange: (e) => setType(e.target.value),
1883
- style: styles.input,
1884
- "aria-label": "Type",
1885
- children: [
1886
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "user", children: "user" }),
1887
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("option", { value: "workspace", children: "workspace" })
1888
- ]
1889
- }
1890
- ),
1891
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1892
- "input",
1893
- {
1894
- value: idInput,
1895
- onChange: (e) => setIdInput(e.target.value),
1896
- placeholder: "subscriber id",
1897
- style: { ...styles.input, minWidth: 280 },
1898
- "aria-label": "Subscriber id"
1899
- }
1900
- ),
1901
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
1902
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { name: "search", size: 14 }),
1903
- "Look up"
1904
- ] }),
1905
- subscriber ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
1906
- "button",
1907
- {
1908
- type: "button",
1909
- style: styles.button,
1910
- onClick: () => {
1911
- setSubscriber(null);
1912
- setIdInput("");
1913
- setGranted(null);
2116
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
2117
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2118
+ "fieldset",
2119
+ {
2120
+ style: {
2121
+ display: "flex",
2122
+ gap: 6,
2123
+ flexWrap: "wrap",
2124
+ border: "none",
2125
+ margin: 0,
2126
+ padding: 0,
2127
+ minWidth: 0
2128
+ },
2129
+ "aria-label": "Filter by status",
2130
+ children: FILTERS.map((f) => {
2131
+ const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2132
+ const on = filter === f.label;
2133
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2134
+ "button",
2135
+ {
2136
+ type: "button",
2137
+ "aria-pressed": on,
2138
+ onClick: () => setFilter(f.label),
2139
+ style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2140
+ children: [
2141
+ f.label,
2142
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.7 }, children: n })
2143
+ ]
1914
2144
  },
1915
- children: [
1916
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { name: "back", size: 14 }),
1917
- "All subscribers"
1918
- ]
1919
- }
1920
- ) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1921
- "button",
1922
- {
1923
- type: "button",
1924
- style: styles.button,
1925
- onClick: () => void list.refresh(),
1926
- disabled: list.isLoading,
1927
- children: "Refresh"
1928
- }
1929
- )
1930
- ]
2145
+ f.label
2146
+ );
2147
+ })
2148
+ }
2149
+ ),
2150
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2151
+ "form",
2152
+ {
2153
+ style: { display: "flex", gap: 6 },
2154
+ onSubmit: (e) => {
2155
+ e.preventDefault();
2156
+ if (idInput.trim()) openRow(type, idInput.trim());
2157
+ },
2158
+ children: [
2159
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2160
+ "select",
2161
+ {
2162
+ value: type,
2163
+ onChange: (e) => setType(e.target.value),
2164
+ style: styles.input,
2165
+ "aria-label": "Type",
2166
+ children: [
2167
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: "workspace" }),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "user", children: "user" })
2169
+ ]
2170
+ }
2171
+ ),
2172
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2173
+ "input",
2174
+ {
2175
+ value: idInput,
2176
+ onChange: (e) => setIdInput(e.target.value),
2177
+ placeholder: "subscriber id",
2178
+ style: { ...styles.input, width: 220 },
2179
+ "aria-label": "Subscriber id"
2180
+ }
2181
+ ),
2182
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2183
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2184
+ "Open"
2185
+ ] })
2186
+ ]
2187
+ }
2188
+ )
2189
+ ] }),
2190
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2191
+ rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2192
+ Empty,
2193
+ {
2194
+ icon: "subscriber",
2195
+ title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2196
+ children: "Subscriptions appear here once someone checks out."
1931
2197
  }
1932
- ),
1933
- error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1934
- !subscriber ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1935
- list.error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1936
- list.subscriptions.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "subscriber", title: "No subscribers yet", children: "Subscriptions appear here once someone checks out." }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
1937
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
1938
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Subscriber" }),
1939
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
1940
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Status" }),
1941
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Renews" })
1942
- ] }) }),
1943
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: list.subscriptions.map((s) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
1944
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2198
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2199
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2200
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Subscriber" }),
2201
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Plan" }),
2202
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Status" }),
2203
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Renews / ends" })
2204
+ ] }) }),
2205
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: rows.map((s) => {
2206
+ const end = periodEnd(s);
2207
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2208
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1945
2209
  "button",
1946
2210
  {
1947
2211
  type: "button",
1948
2212
  style: { ...styles.link, ...styles.mono },
1949
- onClick: () => open(s.subscriberType, s.subscriberId),
2213
+ onClick: () => openRow(s.subscriberType, s.subscriberId),
1950
2214
  children: [
1951
2215
  s.subscriberType,
1952
2216
  "/",
@@ -1954,206 +2218,56 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1954
2218
  ]
1955
2219
  }
1956
2220
  ) }),
1957
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1958
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: s.plan }),
1959
- " \xB7 ",
1960
- s.interval
2221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2222
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("strong", { children: s.plan }),
2223
+ " ",
2224
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: styles.muted, children: [
2225
+ "\xB7 ",
2226
+ s.interval
2227
+ ] })
1961
2228
  ] }),
1962
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1963
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1964
- Pill,
1965
- {
1966
- tone: s.status === "active" ? "ok" : s.status === "trialing" ? "info" : s.status === "past_due" ? "warn" : "neutral",
1967
- children: s.status
1968
- }
1969
- ),
1970
- s.cancelAtPeriodEnd ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2229
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2230
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: statusTone(s.status), children: s.status }),
2231
+ s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1971
2232
  " ",
1972
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
2233
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
1973
2234
  ] }) : null
1974
2235
  ] }),
1975
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: s.currentPeriodEnd ? new Date(s.currentPeriodEnd).toLocaleDateString() : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: styles.muted, children: "\u2014" }) })
1976
- ] }, s.id)) })
1977
- ] }),
1978
- list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1979
- list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1980
- "button",
1981
- {
1982
- type: "button",
1983
- style: { ...styles.button, marginTop: 8 },
1984
- onClick: () => void list.loadMore(),
1985
- children: "Load more"
1986
- }
1987
- ) : null
1988
- ] }) : null,
1989
- subscriber && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
1990
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Subscription" }),
1991
- subscription ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tbody", { children: [
1992
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
1993
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: "plan" }),
1994
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1995
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: subscription.plan }),
1996
- " \xB7 ",
1997
- subscription.interval
1998
- ] })
1999
- ] }),
2000
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2001
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: "status" }),
2002
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
2003
- subscription.status,
2004
- subscription.cancelAtPeriodEnd ? " \xB7 cancels at period end" : ""
2005
- ] })
2006
- ] }),
2007
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2008
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: "period" }),
2009
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
2010
- subscription.currentPeriodStart ? new Date(subscription.currentPeriodStart).toLocaleDateString() : "\u2014",
2236
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, whiteSpace: "nowrap" }, children: end ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
2237
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: end.label }),
2011
2238
  " ",
2012
- "\u2192",
2013
- " ",
2014
- subscription.currentPeriodEnd ? new Date(subscription.currentPeriodEnd).toLocaleDateString() : "\u2014"
2015
- ] })
2016
- ] }),
2017
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2018
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: "provider" }),
2019
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: subscription.providerSubscriptionId ?? "\u2014" })
2020
- ] })
2021
- ] }) }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.muted, children: "No subscription." }),
2022
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Wallet" }),
2023
- wallet ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2024
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2025
- "p",
2026
- {
2027
- style: {
2028
- ...styles.card,
2029
- display: "flex",
2030
- alignItems: "baseline",
2031
- gap: 8,
2032
- flexWrap: "wrap"
2033
- },
2034
- children: [
2035
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2036
- "strong",
2037
- {
2038
- style: {
2039
- fontSize: 22,
2040
- letterSpacing: "var(--fonderie-tracking-display,-0.05em)"
2041
- },
2042
- children: wallet.balance
2043
- }
2044
- ),
2045
- " ",
2046
- wallet.currency,
2047
- " ",
2048
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("span", { style: styles.muted, children: [
2049
- "(granted ",
2050
- wallet.granted ?? "0",
2051
- " \xB7 purchased ",
2052
- wallet.purchased ?? "0",
2053
- ") \xB7 minor units, precision ",
2054
- wallet.precision
2055
- ] })
2056
- ]
2057
- }
2058
- ),
2059
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2060
- "form",
2061
- {
2062
- style: styles.toolbar,
2063
- onSubmit: (e) => {
2064
- e.preventDefault();
2065
- const key = `admin-grant-${subscriber.type}-${subscriber.id}-${Date.now()}`;
2066
- void grant({
2067
- amount: amount.trim(),
2068
- ...note ? { description: note } : {},
2069
- idempotencyKey: key
2070
- }).then(() => {
2071
- setGranted(`Granted ${amount.trim()} ${wallet.currency}.`);
2072
- setAmount("");
2073
- setNote("");
2074
- }).catch(() => {
2075
- });
2076
- },
2077
- children: [
2078
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2079
- "input",
2080
- {
2081
- value: amount,
2082
- onChange: (e) => setAmount(e.target.value),
2083
- placeholder: "amount (minor units)",
2084
- style: styles.input,
2085
- "aria-label": "Amount"
2086
- }
2087
- ),
2088
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2089
- "input",
2090
- {
2091
- value: note,
2092
- onChange: (e) => setNote(e.target.value),
2093
- placeholder: "reason (optional)",
2094
- style: { ...styles.input, minWidth: 220 },
2095
- "aria-label": "Reason"
2096
- }
2097
- ),
2098
- /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(
2099
- "button",
2100
- {
2101
- type: "submit",
2102
- style: styles.buttonPrimary,
2103
- disabled: !/^\d+$/.test(amount.trim()),
2104
- children: [
2105
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Icon, { name: "plus", size: 14 }),
2106
- "Grant"
2107
- ]
2108
- }
2109
- ),
2110
- granted ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("span", { style: styles.ok, children: granted }) : null
2111
- ]
2112
- }
2113
- ),
2114
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Ledger" }),
2115
- ledger.length === 0 ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Empty, { icon: "subscriber", title: "Nothing moved yet" }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("table", { style: styles.table, children: [
2116
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2117
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "When" }),
2118
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Kind" }),
2119
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Amount" }),
2120
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Note" })
2121
- ] }) }),
2122
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: ledger.map((t2) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2123
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(t2.createdAt).toLocaleString() }),
2124
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: t2.type }),
2125
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: t2.amount }),
2126
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: t2.description ?? "" })
2127
- ] }, t2.id)) })
2128
- ] }),
2129
- hasMoreLedger ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
2130
- "button",
2131
- {
2132
- type: "button",
2133
- style: { ...styles.button, marginTop: 8 },
2134
- onClick: () => void loadMoreLedger(),
2135
- children: "Load more"
2136
- }
2137
- ) : null
2138
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.muted, children: "No wallet \u2014 billing has no wallet configuration." })
2139
- ] }) : null
2239
+ end.date
2240
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: "\u2014" }) })
2241
+ ] }, s.id);
2242
+ }) })
2243
+ ] }),
2244
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2245
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2246
+ "button",
2247
+ {
2248
+ type: "button",
2249
+ style: { ...styles.button, marginTop: 8 },
2250
+ onClick: () => void list.loadMore(),
2251
+ children: "Load more"
2252
+ }
2253
+ ) : null
2140
2254
  ] });
2141
2255
  }
2142
2256
 
2143
2257
  // src/screens/AuditScreen.tsx
2144
- var import_react_admin13 = require("@fonderie/react-admin");
2145
- var import_react6 = require("react");
2146
- var import_jsx_runtime14 = require("react/jsx-runtime");
2258
+ var import_react_admin14 = require("@fonderie/react-admin");
2259
+ var import_react8 = require("react");
2260
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2147
2261
  function AuditScreen({ client, pageSize = 50 }) {
2148
- const [draft, setDraft] = (0, import_react6.useState)({ workspaceId: "", type: "", actorId: "" });
2149
- const [filter, setFilter] = (0, import_react6.useState)(
2262
+ const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "" });
2263
+ const [filter, setFilter] = (0, import_react8.useState)(
2150
2264
  {}
2151
2265
  );
2152
- const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin13.useAdminAudit)(client, {
2266
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin14.useAdminAudit)(client, {
2153
2267
  ...filter,
2154
2268
  limit: pageSize
2155
2269
  });
2156
- const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2270
+ const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2157
2271
  "input",
2158
2272
  {
2159
2273
  value: draft[key],
@@ -2163,15 +2277,15 @@ function AuditScreen({ client, pageSize = 50 }) {
2163
2277
  "aria-label": placeholder
2164
2278
  }
2165
2279
  );
2166
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2280
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.container, children: [
2281
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2168
2282
  PageHeader,
2169
2283
  {
2170
2284
  title: "Audit",
2171
2285
  lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
2172
2286
  }
2173
2287
  ),
2174
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2288
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2175
2289
  "form",
2176
2290
  {
2177
2291
  style: styles.toolbar,
@@ -2187,11 +2301,11 @@ function AuditScreen({ client, pageSize = 50 }) {
2187
2301
  field("workspaceId", "workspace id (all if empty)"),
2188
2302
  field("type", "event type"),
2189
2303
  field("actorId", "actor id"),
2190
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2191
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2304
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2305
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "search", size: 14 }),
2192
2306
  "Filter"
2193
2307
  ] }),
2194
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2308
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2195
2309
  "button",
2196
2310
  {
2197
2311
  type: "button",
@@ -2204,25 +2318,25 @@ function AuditScreen({ client, pageSize = 50 }) {
2204
2318
  ]
2205
2319
  }
2206
2320
  ),
2207
- error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2208
- events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2209
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2210
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "When" }),
2211
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Type" }),
2212
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Workspace" }),
2213
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Actor" }),
2214
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Request" })
2321
+ error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2322
+ events.length === 0 && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("table", { style: styles.table, children: [
2323
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2324
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "When" }),
2325
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Type" }),
2326
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Workspace" }),
2327
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Actor" }),
2328
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Request" })
2215
2329
  ] }) }),
2216
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2217
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2218
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2219
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2220
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2221
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2330
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2331
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2332
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2333
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2334
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2335
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2222
2336
  ] }, e.id)) })
2223
2337
  ] }),
2224
- isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2225
- hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2338
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2339
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2226
2340
  "button",
2227
2341
  {
2228
2342
  type: "button",
@@ -2235,7 +2349,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2235
2349
  }
2236
2350
 
2237
2351
  // src/screens/AdminShell.tsx
2238
- var import_jsx_runtime15 = require("react/jsx-runtime");
2352
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2239
2353
  var ICON = {
2240
2354
  attention: "attention",
2241
2355
  modules: "modules",
@@ -2256,8 +2370,8 @@ var ICON = {
2256
2370
  var NARROW = "(max-width: 820px)";
2257
2371
  function useNarrow() {
2258
2372
  const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
2259
- const [narrow, setNarrow] = (0, import_react7.useState)(() => query?.matches ?? false);
2260
- (0, import_react7.useEffect)(() => {
2373
+ const [narrow, setNarrow] = (0, import_react9.useState)(() => query?.matches ?? false);
2374
+ (0, import_react9.useEffect)(() => {
2261
2375
  if (!query) return;
2262
2376
  const on = (e) => setNarrow(e.matches);
2263
2377
  query.addEventListener("change", on);
@@ -2281,7 +2395,7 @@ var NAV = [
2281
2395
  group: "Money",
2282
2396
  items: [
2283
2397
  { page: "catalog", label: "Catalog", needs: "billing" },
2284
- { page: "subscriber", label: "Subscriber", needs: "billing" }
2398
+ { page: "subscriber", label: "Subscriptions", needs: "billing" }
2285
2399
  ]
2286
2400
  },
2287
2401
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
@@ -2314,15 +2428,16 @@ function AdminShell({
2314
2428
  currentOperator
2315
2429
  }) {
2316
2430
  const narrow = useNarrow();
2317
- const [drawer, setDrawer] = (0, import_react7.useState)(false);
2318
- const [own, setOwn] = (0, import_react7.useState)("attention");
2431
+ const [drawer, setDrawer] = (0, import_react9.useState)(false);
2432
+ const [own, setOwn] = (0, import_react9.useState)("attention");
2433
+ const [openUser, setOpenUser] = (0, import_react9.useState)(void 0);
2319
2434
  const current = page ?? own;
2320
2435
  const go = (p) => {
2321
2436
  if (onNavigate) onNavigate(p);
2322
2437
  if (page === void 0) setOwn(p);
2323
2438
  setDrawer(false);
2324
2439
  };
2325
- const [editing, setEditing] = (0, import_react7.useState)(null);
2440
+ const [editing, setEditing] = (0, import_react9.useState)(null);
2326
2441
  const has = {
2327
2442
  config: Boolean(configClient),
2328
2443
  courier: Boolean(courierClient),
@@ -2334,51 +2449,70 @@ function AdminShell({
2334
2449
  let body;
2335
2450
  switch (current) {
2336
2451
  case "attention":
2337
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AttentionScreen, { client });
2452
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
2338
2453
  break;
2339
2454
  case "modules":
2340
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ModulesScreen, { client });
2455
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
2341
2456
  break;
2342
2457
  case "doctor":
2343
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DoctorScreen, { client });
2458
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
2344
2459
  break;
2345
2460
  case "environment":
2346
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(EnvironmentScreen, { client });
2461
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
2347
2462
  break;
2348
2463
  case "routes":
2349
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(RoutesScreen, { client });
2464
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
2350
2465
  break;
2351
2466
  case "tokens":
2352
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TokensScreen, { client });
2467
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client });
2353
2468
  break;
2354
2469
  case "operators":
2355
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(OperatorsScreen, { client, me: currentOperator });
2470
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator });
2356
2471
  break;
2357
2472
  case "migrations":
2358
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MigrationsScreen, { client });
2473
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
2359
2474
  break;
2360
2475
  case "log":
2361
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AdminLogScreen, { client });
2476
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
2362
2477
  break;
2363
2478
  case "catalog":
2364
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
2479
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(CatalogScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to see the catalog here." });
2365
2480
  break;
2366
2481
  case "subscriber":
2367
- body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(SubscriberScreen, { client: billingClient }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2482
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2483
+ SubscriberScreen,
2484
+ {
2485
+ client: billingClient,
2486
+ ...authClient ? {
2487
+ onOpenUser: (id) => {
2488
+ setOpenUser(id);
2489
+ go("users");
2490
+ }
2491
+ } : {}
2492
+ }
2493
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2368
2494
  break;
2369
2495
  case "audit":
2370
- body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
2496
+ body = auditClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AuditScreen, { client: auditClient }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuditAdminClient to see the audit trail here." });
2371
2497
  break;
2372
2498
  case "users":
2373
- body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(UsersScreen, { client: authClient }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2499
+ body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2500
+ UsersScreen,
2501
+ {
2502
+ client: authClient,
2503
+ billingClient,
2504
+ openUserId: openUser
2505
+ },
2506
+ openUser ?? "list"
2507
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2374
2508
  break;
2375
2509
  case "settings":
2376
- body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime15.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_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2377
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2378
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "back", size: 14 }),
2510
+ body = !configClient ? /* @__PURE__ */ (0, import_jsx_runtime16.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_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2511
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2512
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2379
2513
  "Config & secrets"
2380
2514
  ] }) }),
2381
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2515
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2382
2516
  import_react_config_admin_screens.ConfigEditorScreen,
2383
2517
  {
2384
2518
  client: configClient,
@@ -2388,7 +2522,7 @@ function AdminShell({
2388
2522
  onSaved: () => setEditing(null)
2389
2523
  }
2390
2524
  )
2391
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2525
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2392
2526
  import_react_config_admin_screens.ConfigListScreen,
2393
2527
  {
2394
2528
  client: configClient,
@@ -2401,12 +2535,12 @@ function AdminShell({
2401
2535
  );
2402
2536
  break;
2403
2537
  case "templates":
2404
- body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2405
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2406
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "back", size: 14 }),
2538
+ body = !courierClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a CourierAdminClient to manage templates here." }) : editing?.kind === "template" ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2539
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2540
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2407
2541
  "Templates"
2408
2542
  ] }) }),
2409
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2543
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2410
2544
  import_react_courier_admin_screens.TemplateEditorScreen,
2411
2545
  {
2412
2546
  client: courierClient,
@@ -2414,7 +2548,7 @@ function AdminShell({
2414
2548
  onSaved: () => setEditing(null)
2415
2549
  }
2416
2550
  )
2417
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2551
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2418
2552
  import_react_courier_admin_screens.TemplateListScreen,
2419
2553
  {
2420
2554
  client: courierClient,
@@ -2424,11 +2558,11 @@ function AdminShell({
2424
2558
  break;
2425
2559
  }
2426
2560
  const production = envLabel ? /^prod/i.test(envLabel) : false;
2427
- const brand = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.navBrand, children: [
2428
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navMark, children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "mark", size: 15 }) }),
2429
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2430
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navBrandTitle, children: appName }),
2431
- envLabel ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2561
+ const brand = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.navBrand, children: [
2562
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navMark, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "mark", size: 15 }) }),
2563
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2564
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navBrandTitle, children: appName }),
2565
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2432
2566
  "span",
2433
2567
  {
2434
2568
  style: {
@@ -2445,18 +2579,18 @@ function AdminShell({
2445
2579
  }
2446
2580
  ) : null
2447
2581
  ] }),
2448
- narrow ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2582
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2449
2583
  "button",
2450
2584
  {
2451
2585
  type: "button",
2452
2586
  style: styles.buttonGhost,
2453
2587
  "aria-label": "Close menu",
2454
2588
  onClick: () => setDrawer(false),
2455
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "close" })
2589
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
2456
2590
  }
2457
2591
  ) : null
2458
2592
  ] });
2459
- const nav = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2593
+ const nav = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2460
2594
  "nav",
2461
2595
  {
2462
2596
  style: {
@@ -2466,14 +2600,14 @@ function AdminShell({
2466
2600
  "aria-label": "Admin",
2467
2601
  children: [
2468
2602
  brand,
2469
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2603
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2470
2604
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2471
2605
  if (items.length === 0) return null;
2472
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { children: [
2473
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navGroup, children: g.group }),
2606
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
2607
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: g.group }),
2474
2608
  items.map((i) => {
2475
2609
  const active = current === i.page;
2476
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2610
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2477
2611
  "button",
2478
2612
  {
2479
2613
  type: "button",
@@ -2482,10 +2616,11 @@ function AdminShell({
2482
2616
  "aria-current": active ? "page" : void 0,
2483
2617
  onClick: () => {
2484
2618
  setEditing(null);
2619
+ setOpenUser(void 0);
2485
2620
  go(i.page);
2486
2621
  },
2487
2622
  children: [
2488
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2623
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2489
2624
  i.label
2490
2625
  ]
2491
2626
  },
@@ -2494,15 +2629,15 @@ function AdminShell({
2494
2629
  })
2495
2630
  ] }, g.group);
2496
2631
  }) }),
2497
- footer ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navFooter, children: footer }) : null
2632
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navFooter, children: footer }) : null
2498
2633
  ]
2499
2634
  }
2500
2635
  );
2501
2636
  const title = NAV.flatMap((g) => g.items).find((i) => i.page === current)?.label ?? "";
2502
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2503
- narrow ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(import_jsx_runtime15.Fragment, { children: [
2504
- /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.topbar, children: [
2505
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2637
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2638
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2639
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.topbar, children: [
2640
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2506
2641
  "button",
2507
2642
  {
2508
2643
  type: "button",
@@ -2510,18 +2645,18 @@ function AdminShell({
2510
2645
  "aria-label": "Open menu",
2511
2646
  "aria-expanded": drawer,
2512
2647
  onClick: () => setDrawer(true),
2513
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "menu" })
2648
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })
2514
2649
  }
2515
2650
  ),
2516
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2517
- envLabel ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2651
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2652
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2518
2653
  ] }),
2519
- drawer ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.drawer, children: [
2654
+ drawer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.drawer, children: [
2520
2655
  nav,
2521
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2656
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2522
2657
  ] }) : null
2523
2658
  ] }) : nav,
2524
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2659
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2525
2660
  ] });
2526
2661
  }
2527
2662
  // Annotate the CommonJS export names for ESM import in node:
@@ -2537,6 +2672,7 @@ function AdminShell({
2537
2672
  ModulesScreen,
2538
2673
  OperatorsScreen,
2539
2674
  RoutesScreen,
2675
+ SubscriberBilling,
2540
2676
  SubscriberScreen,
2541
2677
  TokensScreen,
2542
2678
  UsersScreen