@fonderie/react-admin-screens 1.2.0 → 1.3.1

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,37 @@ 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 && !(error.status === 404 && selected?.id) ? /* @__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
+ error?.status === 404 && selected?.id ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1819
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: styles.notice, role: "status", children: [
1820
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: "No account with id " }),
1821
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("code", { style: styles.code, children: selected.id }),
1822
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: "." }),
1823
+ " It was deleted, or never existed here. Its billing records remain."
1824
+ ] }),
1825
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1826
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
1827
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1828
+ SubscriberBilling,
1829
+ {
1830
+ client: billingClient,
1831
+ subscriber: { type: "user", id: selected.id }
1832
+ }
1833
+ )
1834
+ ] }) : null
1835
+ ] }) : null,
1836
+ !selected ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1837
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1838
+ 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: [
1839
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1840
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Email" }),
1841
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Name" }),
1842
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Plan" }) : null,
1843
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Created" }),
1844
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("th", { style: styles.th, children: "Status" })
1618
1845
  ] }) }),
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)(
1846
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("tbody", { children: list.users.map((u) => /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1847
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1621
1848
  "button",
1622
1849
  {
1623
1850
  type: "button",
@@ -1626,17 +1853,28 @@ function UsersScreen({ client, pageSize = 50 }) {
1626
1853
  children: u.email
1627
1854
  }
1628
1855
  ) }),
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
1856
+ /* @__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" }) }),
1857
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: (() => {
1858
+ const sub = plans?.get(`user/${u.id}`);
1859
+ if (!plans) return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "\u2026" });
1860
+ if (!sub || sub.status === "canceled")
1861
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: "free" });
1862
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1863
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("strong", { children: sub.plan }),
1864
+ " ",
1865
+ sub.status !== "active" ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: statusTone(sub.status), children: sub.status }) : null
1866
+ ] });
1867
+ })() }) : null,
1868
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1869
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1870
+ u.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1871
+ u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null,
1872
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "ok", children: "active" }) : null
1635
1873
  ] })
1636
1874
  ] }, u.id)) })
1637
1875
  ] }),
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)(
1876
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
1877
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1640
1878
  "button",
1641
1879
  {
1642
1880
  type: "button",
@@ -1646,48 +1884,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1646
1884
  }
1647
1885
  ) : null
1648
1886
  ] }) : 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: [
1887
+ user ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1888
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("h2", { style: styles.subtitle, children: [
1651
1889
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1652
1890
  " ",
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
1891
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: "suspended" }) : null,
1892
+ user.deletedAt ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "neutral", children: "deleted" }) : null
1655
1893
  ] }),
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 })
1894
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("table", { style: styles.table, children: /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tbody", { children: [
1895
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1896
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "id" }),
1897
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1660
1898
  ] }),
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: [
1899
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1900
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "email" }),
1901
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1664
1902
  user.email,
1665
1903
  " \xB7 verified ",
1666
1904
  yesNo(user.isEmailVerified)
1667
1905
  ] })
1668
1906
  ] }),
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) })
1907
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1908
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "MFA" }),
1909
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1672
1910
  ] }),
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: [
1911
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1912
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "provider" }),
1913
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("td", { style: styles.td, children: [
1676
1914
  user.provider || "password",
1677
1915
  user.hasPassword ? "" : " \xB7 no password set"
1678
1916
  ] })
1679
1917
  ] }),
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" }) })
1918
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1919
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "last login" }),
1920
+ /* @__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
1921
  ] }),
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() })
1922
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("tr", { children: [
1923
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: "created" }),
1924
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1687
1925
  ] })
1688
1926
  ] }) }),
1689
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1690
- user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1927
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1928
+ user.suspended ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1691
1929
  "button",
1692
1930
  {
1693
1931
  type: "button",
@@ -1696,7 +1934,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1696
1934
  disabled: isLoading,
1697
1935
  children: "Unsuspend"
1698
1936
  }
1699
- ) : /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1937
+ ) : /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1700
1938
  "button",
1701
1939
  {
1702
1940
  type: "button",
@@ -1706,7 +1944,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1706
1944
  children: "Suspend"
1707
1945
  }
1708
1946
  ),
1709
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1947
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1710
1948
  "button",
1711
1949
  {
1712
1950
  type: "button",
@@ -1717,38 +1955,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1717
1955
  }
1718
1956
  )
1719
1957
  ] }),
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" }),
1958
+ billingClient ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1959
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Plan & credits" }),
1960
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1961
+ SubscriberBilling,
1962
+ {
1963
+ client: billingClient,
1964
+ subscriber: { type: "user", id: user.id }
1965
+ }
1966
+ )
1967
+ ] }) : null,
1968
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Live sessions" }),
1969
+ 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: [
1970
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1723
1971
  " ",
1724
- (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1972
+ (0, import_client.describeLocation)(s.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1725
1973
  (0, import_client.describeLocation)(s.location),
1726
1974
  " "
1727
1975
  ] }) : null,
1728
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1976
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: s.userAgent ?? "" }),
1729
1977
  " ",
1730
- /* @__PURE__ */ (0, import_jsx_runtime11.jsxs)("span", { style: styles.muted, children: [
1978
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)("span", { style: styles.muted, children: [
1731
1979
  "since ",
1732
1980
  new Date(s.createdAt).toLocaleString()
1733
1981
  ] })
1734
1982
  ] }, 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 }),
1983
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1984
+ 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: [
1985
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1738
1986
  " ",
1739
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: e.method }),
1987
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: e.method }),
1740
1988
  " ",
1741
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1989
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1742
1990
  " ",
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: [
1991
+ (0, import_client.describeLocation)(e.location) ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: (0, import_client.describeLocation)(e.location) }) : null,
1992
+ e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ (0, import_jsx_runtime12.jsxs)(import_jsx_runtime12.Fragment, { children: [
1745
1993
  " ",
1746
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1994
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1747
1995
  ] }) : null,
1748
1996
  " ",
1749
- /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1997
+ /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1750
1998
  ] }, e.id)) }),
1751
- history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime11.jsx)(
1999
+ history.hasMore && !history.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime12.jsx)(
1752
2000
  "button",
1753
2001
  {
1754
2002
  type: "button",
@@ -1762,25 +2010,25 @@ function UsersScreen({ client, pageSize = 50 }) {
1762
2010
  }
1763
2011
 
1764
2012
  // src/screens/CatalogScreen.tsx
1765
- var import_react_admin11 = require("@fonderie/react-admin");
1766
- var import_jsx_runtime12 = require("react/jsx-runtime");
2013
+ var import_react_admin12 = require("@fonderie/react-admin");
2014
+ var import_jsx_runtime13 = require("react/jsx-runtime");
1767
2015
  function CatalogScreen({ client }) {
1768
- const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin11.useAdminCatalog)(client);
2016
+ const { catalog, isLoading, error, refresh, deletePlan } = (0, import_react_admin12.useAdminCatalog)(client);
1769
2017
  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)(
2018
+ return /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("div", { style: styles.container, children: [
2019
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1772
2020
  PageHeader,
1773
2021
  {
1774
2022
  title: "Catalog",
1775
2023
  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 })
2024
+ actions: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1777
2025
  }
1778
2026
  ),
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)(
2027
+ error ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2028
+ isLoading && !catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2029
+ catalog ? /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)(import_jsx_runtime13.Fragment, { children: [
2030
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
2031
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1784
2032
  "pre",
1785
2033
  {
1786
2034
  style: {
@@ -1794,32 +2042,32 @@ function CatalogScreen({ client }) {
1794
2042
  children: JSON.stringify(catalog.configured, null, 2)
1795
2043
  }
1796
2044
  ),
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 })
2045
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("h2", { style: styles.subtitle, children: "Stored (database)" }),
2046
+ 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: [
2047
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2048
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Plan" }),
2049
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Tier" }),
2050
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Seats" }),
2051
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Monthly" }),
2052
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Yearly" }),
2053
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th, children: "Trial" }),
2054
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("th", { style: styles.th })
1807
2055
  ] }) }),
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
2056
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("tr", { children: [
2057
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
2058
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("strong", { children: p.name }),
2059
+ p.description ? /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("div", { style: styles.muted, children: p.description }) : null
1812
2060
  ] }),
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: [
2061
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.tier }),
2062
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.seats ?? "\u221E" }),
2063
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsxs)("td", { style: styles.td, children: [
1816
2064
  money(p.pricing?.monthly),
1817
2065
  " ",
1818
2066
  p.pricing?.currency ?? ""
1819
2067
  ] }),
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)(
2068
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2069
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
2070
+ /* @__PURE__ */ (0, import_jsx_runtime13.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(
1823
2071
  "button",
1824
2072
  {
1825
2073
  type: "button",
@@ -1839,114 +2087,148 @@ function CatalogScreen({ client }) {
1839
2087
  }
1840
2088
 
1841
2089
  // 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)(
2090
+ var import_react_admin13 = require("@fonderie/react-admin");
2091
+ var import_react7 = require("react");
2092
+ var import_jsx_runtime14 = require("react/jsx-runtime");
2093
+ var FILTERS = [
2094
+ { label: "All", match: () => true },
2095
+ { label: "Active", match: (s) => s === "active" },
2096
+ { label: "Trialing", match: (s) => s === "trialing" },
2097
+ { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2098
+ { label: "Canceled", match: (s) => s === "canceled" }
2099
+ ];
2100
+ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
2101
+ const list = (0, import_react_admin13.useAdminSubscribers)(client, { limit: pageSize });
2102
+ const [filter, setFilter] = (0, import_react7.useState)("All");
2103
+ const [open, setOpen] = (0, import_react7.useState)(null);
2104
+ const [type, setType] = (0, import_react7.useState)("workspace");
2105
+ const [idInput, setIdInput] = (0, import_react7.useState)("");
2106
+ if (open) {
2107
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2108
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2109
+ PageHeader,
2110
+ {
2111
+ title: `${open.type}/${open.id}`,
2112
+ lead: "Plan, credits and what moved.",
2113
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2114
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "back", size: 14 }),
2115
+ "All subscriptions"
2116
+ ] })
2117
+ }
2118
+ ),
2119
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(SubscriberBilling, { client, subscriber: open })
2120
+ ] });
2121
+ }
2122
+ const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2123
+ const rows = list.subscriptions.filter((s) => match(s.status));
2124
+ const openRow = (t2, id) => t2 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t2, id });
2125
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2126
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
1862
2127
  PageHeader,
1863
2128
  {
1864
- title: "Subscribers",
1865
- lead: "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
2129
+ title: "Subscriptions",
2130
+ 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.",
2131
+ actions: /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(RefreshButton, { onClick: () => void list.refresh(), busy: list.isLoading })
1866
2132
  }
1867
2133
  ),
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);
2134
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
2135
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2136
+ "fieldset",
2137
+ {
2138
+ style: {
2139
+ display: "flex",
2140
+ gap: 6,
2141
+ flexWrap: "wrap",
2142
+ border: "none",
2143
+ margin: 0,
2144
+ padding: 0,
2145
+ minWidth: 0
2146
+ },
2147
+ "aria-label": "Filter by status",
2148
+ children: FILTERS.map((f) => {
2149
+ const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2150
+ const on = filter === f.label;
2151
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2152
+ "button",
2153
+ {
2154
+ type: "button",
2155
+ "aria-pressed": on,
2156
+ onClick: () => setFilter(f.label),
2157
+ style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2158
+ children: [
2159
+ f.label,
2160
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: { opacity: 0.7 }, children: n })
2161
+ ]
1914
2162
  },
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
- ]
2163
+ f.label
2164
+ );
2165
+ })
2166
+ }
2167
+ ),
2168
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2169
+ "form",
2170
+ {
2171
+ style: { display: "flex", gap: 6 },
2172
+ onSubmit: (e) => {
2173
+ e.preventDefault();
2174
+ if (idInput.trim()) openRow(type, idInput.trim());
2175
+ },
2176
+ children: [
2177
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2178
+ "select",
2179
+ {
2180
+ value: type,
2181
+ onChange: (e) => setType(e.target.value),
2182
+ style: styles.input,
2183
+ "aria-label": "Type",
2184
+ children: [
2185
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "workspace", children: "workspace" }),
2186
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("option", { value: "user", children: "user" })
2187
+ ]
2188
+ }
2189
+ ),
2190
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2191
+ "input",
2192
+ {
2193
+ value: idInput,
2194
+ onChange: (e) => setIdInput(e.target.value),
2195
+ placeholder: "subscriber id",
2196
+ style: { ...styles.input, width: 220 },
2197
+ "aria-label": "Subscriber id"
2198
+ }
2199
+ ),
2200
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2201
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Icon, { name: "search", size: 14 }),
2202
+ "Open"
2203
+ ] })
2204
+ ]
2205
+ }
2206
+ )
2207
+ ] }),
2208
+ list.error ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2209
+ rows.length === 0 && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2210
+ Empty,
2211
+ {
2212
+ icon: "subscriber",
2213
+ title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2214
+ children: "Subscriptions appear here once someone checks out."
1931
2215
  }
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)(
2216
+ ) : /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("table", { style: styles.table, children: [
2217
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2218
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Subscriber" }),
2219
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Plan" }),
2220
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Status" }),
2221
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("th", { style: styles.th, children: "Renews / ends" })
2222
+ ] }) }),
2223
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("tbody", { children: rows.map((s) => {
2224
+ const end = periodEnd(s);
2225
+ return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("tr", { children: [
2226
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("td", { style: styles.td, children: /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
1945
2227
  "button",
1946
2228
  {
1947
2229
  type: "button",
1948
2230
  style: { ...styles.link, ...styles.mono },
1949
- onClick: () => open(s.subscriberType, s.subscriberId),
2231
+ onClick: () => openRow(s.subscriberType, s.subscriberId),
1950
2232
  children: [
1951
2233
  s.subscriberType,
1952
2234
  "/",
@@ -1954,206 +2236,56 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1954
2236
  ]
1955
2237
  }
1956
2238
  ) }),
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
2239
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2240
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("strong", { children: s.plan }),
2241
+ " ",
2242
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("span", { style: styles.muted, children: [
2243
+ "\xB7 ",
2244
+ s.interval
2245
+ ] })
1961
2246
  ] }),
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: [
2247
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("td", { style: styles.td, children: [
2248
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: statusTone(s.status), children: s.status }),
2249
+ s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(import_jsx_runtime14.Fragment, { children: [
1971
2250
  " ",
1972
- /* @__PURE__ */ (0, import_jsx_runtime13.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
2251
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(Pill, { tone: "warn", dot: false, children: "cancels" })
1973
2252
  ] }) : null
1974
2253
  ] }),
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",
2011
- " ",
2012
- "\u2192",
2254
+ /* @__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: [
2255
+ /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: end.label }),
2013
2256
  " ",
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
2257
+ end.date
2258
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("span", { style: styles.muted, children: "\u2014" }) })
2259
+ ] }, s.id);
2260
+ }) })
2261
+ ] }),
2262
+ list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2263
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2264
+ "button",
2265
+ {
2266
+ type: "button",
2267
+ style: { ...styles.button, marginTop: 8 },
2268
+ onClick: () => void list.loadMore(),
2269
+ children: "Load more"
2270
+ }
2271
+ ) : null
2140
2272
  ] });
2141
2273
  }
2142
2274
 
2143
2275
  // 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");
2276
+ var import_react_admin14 = require("@fonderie/react-admin");
2277
+ var import_react8 = require("react");
2278
+ var import_jsx_runtime15 = require("react/jsx-runtime");
2147
2279
  function AuditScreen({ client, pageSize = 50 }) {
2148
- const [draft, setDraft] = (0, import_react6.useState)({ workspaceId: "", type: "", actorId: "" });
2149
- const [filter, setFilter] = (0, import_react6.useState)(
2280
+ const [draft, setDraft] = (0, import_react8.useState)({ workspaceId: "", type: "", actorId: "" });
2281
+ const [filter, setFilter] = (0, import_react8.useState)(
2150
2282
  {}
2151
2283
  );
2152
- const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin13.useAdminAudit)(client, {
2284
+ const { events, hasMore, isLoading, error, refresh, loadMore } = (0, import_react_admin14.useAdminAudit)(client, {
2153
2285
  ...filter,
2154
2286
  limit: pageSize
2155
2287
  });
2156
- const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2288
+ const field = (key, placeholder) => /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2157
2289
  "input",
2158
2290
  {
2159
2291
  value: draft[key],
@@ -2163,15 +2295,15 @@ function AuditScreen({ client, pageSize = 50 }) {
2163
2295
  "aria-label": placeholder
2164
2296
  }
2165
2297
  );
2166
- return /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)("div", { style: styles.container, children: [
2167
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2298
+ return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.container, children: [
2299
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2168
2300
  PageHeader,
2169
2301
  {
2170
2302
  title: "Audit",
2171
2303
  lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
2172
2304
  }
2173
2305
  ),
2174
- /* @__PURE__ */ (0, import_jsx_runtime14.jsxs)(
2306
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2175
2307
  "form",
2176
2308
  {
2177
2309
  style: styles.toolbar,
@@ -2187,11 +2319,11 @@ function AuditScreen({ client, pageSize = 50 }) {
2187
2319
  field("workspaceId", "workspace id (all if empty)"),
2188
2320
  field("type", "event type"),
2189
2321
  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 }),
2322
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2323
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "search", size: 14 }),
2192
2324
  "Filter"
2193
2325
  ] }),
2194
- /* @__PURE__ */ (0, import_jsx_runtime14.jsx)(
2326
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2195
2327
  "button",
2196
2328
  {
2197
2329
  type: "button",
@@ -2204,25 +2336,25 @@ function AuditScreen({ client, pageSize = 50 }) {
2204
2336
  ]
2205
2337
  }
2206
2338
  ),
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" })
2339
+ error ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2340
+ 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: [
2341
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("thead", { children: /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2342
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "When" }),
2343
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Type" }),
2344
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Workspace" }),
2345
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Actor" }),
2346
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("th", { style: styles.th, children: "Request" })
2215
2347
  ] }) }),
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 ?? "" })
2348
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("tbody", { children: events.map((e) => /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("tr", { children: [
2349
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2350
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2351
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2352
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2353
+ /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2222
2354
  ] }, e.id)) })
2223
2355
  ] }),
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)(
2356
+ isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("p", { style: styles.status, children: "Loading\u2026" }) : null,
2357
+ hasMore && !isLoading ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2226
2358
  "button",
2227
2359
  {
2228
2360
  type: "button",
@@ -2235,7 +2367,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2235
2367
  }
2236
2368
 
2237
2369
  // src/screens/AdminShell.tsx
2238
- var import_jsx_runtime15 = require("react/jsx-runtime");
2370
+ var import_jsx_runtime16 = require("react/jsx-runtime");
2239
2371
  var ICON = {
2240
2372
  attention: "attention",
2241
2373
  modules: "modules",
@@ -2256,8 +2388,8 @@ var ICON = {
2256
2388
  var NARROW = "(max-width: 820px)";
2257
2389
  function useNarrow() {
2258
2390
  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)(() => {
2391
+ const [narrow, setNarrow] = (0, import_react9.useState)(() => query?.matches ?? false);
2392
+ (0, import_react9.useEffect)(() => {
2261
2393
  if (!query) return;
2262
2394
  const on = (e) => setNarrow(e.matches);
2263
2395
  query.addEventListener("change", on);
@@ -2281,7 +2413,7 @@ var NAV = [
2281
2413
  group: "Money",
2282
2414
  items: [
2283
2415
  { page: "catalog", label: "Catalog", needs: "billing" },
2284
- { page: "subscriber", label: "Subscriber", needs: "billing" }
2416
+ { page: "subscriber", label: "Subscriptions", needs: "billing" }
2285
2417
  ]
2286
2418
  },
2287
2419
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
@@ -2314,15 +2446,16 @@ function AdminShell({
2314
2446
  currentOperator
2315
2447
  }) {
2316
2448
  const narrow = useNarrow();
2317
- const [drawer, setDrawer] = (0, import_react7.useState)(false);
2318
- const [own, setOwn] = (0, import_react7.useState)("attention");
2449
+ const [drawer, setDrawer] = (0, import_react9.useState)(false);
2450
+ const [own, setOwn] = (0, import_react9.useState)("attention");
2451
+ const [openUser, setOpenUser] = (0, import_react9.useState)(void 0);
2319
2452
  const current = page ?? own;
2320
2453
  const go = (p) => {
2321
2454
  if (onNavigate) onNavigate(p);
2322
2455
  if (page === void 0) setOwn(p);
2323
2456
  setDrawer(false);
2324
2457
  };
2325
- const [editing, setEditing] = (0, import_react7.useState)(null);
2458
+ const [editing, setEditing] = (0, import_react9.useState)(null);
2326
2459
  const has = {
2327
2460
  config: Boolean(configClient),
2328
2461
  courier: Boolean(courierClient),
@@ -2334,51 +2467,70 @@ function AdminShell({
2334
2467
  let body;
2335
2468
  switch (current) {
2336
2469
  case "attention":
2337
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AttentionScreen, { client });
2470
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AttentionScreen, { client });
2338
2471
  break;
2339
2472
  case "modules":
2340
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(ModulesScreen, { client });
2473
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(ModulesScreen, { client });
2341
2474
  break;
2342
2475
  case "doctor":
2343
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(DoctorScreen, { client });
2476
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(DoctorScreen, { client });
2344
2477
  break;
2345
2478
  case "environment":
2346
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(EnvironmentScreen, { client });
2479
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(EnvironmentScreen, { client });
2347
2480
  break;
2348
2481
  case "routes":
2349
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(RoutesScreen, { client });
2482
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(RoutesScreen, { client });
2350
2483
  break;
2351
2484
  case "tokens":
2352
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(TokensScreen, { client });
2485
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(TokensScreen, { client });
2353
2486
  break;
2354
2487
  case "operators":
2355
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(OperatorsScreen, { client, me: currentOperator });
2488
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(OperatorsScreen, { client, me: currentOperator });
2356
2489
  break;
2357
2490
  case "migrations":
2358
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(MigrationsScreen, { client });
2491
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(MigrationsScreen, { client });
2359
2492
  break;
2360
2493
  case "log":
2361
- body = /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(AdminLogScreen, { client });
2494
+ body = /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(AdminLogScreen, { client });
2362
2495
  break;
2363
2496
  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." });
2497
+ 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
2498
  break;
2366
2499
  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." });
2500
+ body = billingClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2501
+ SubscriberScreen,
2502
+ {
2503
+ client: billingClient,
2504
+ ...authClient ? {
2505
+ onOpenUser: (id) => {
2506
+ setOpenUser(id);
2507
+ go("users");
2508
+ }
2509
+ } : {}
2510
+ }
2511
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2368
2512
  break;
2369
2513
  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." });
2514
+ 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
2515
  break;
2372
2516
  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." });
2517
+ body = authClient ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2518
+ UsersScreen,
2519
+ {
2520
+ client: authClient,
2521
+ billingClient,
2522
+ openUserId: openUser
2523
+ },
2524
+ openUser ?? "list"
2525
+ ) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2374
2526
  break;
2375
2527
  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 }),
2528
+ 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: [
2529
+ /* @__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: [
2530
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2379
2531
  "Config & secrets"
2380
2532
  ] }) }),
2381
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2533
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2382
2534
  import_react_config_admin_screens.ConfigEditorScreen,
2383
2535
  {
2384
2536
  client: configClient,
@@ -2388,7 +2540,7 @@ function AdminShell({
2388
2540
  onSaved: () => setEditing(null)
2389
2541
  }
2390
2542
  )
2391
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2543
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2392
2544
  import_react_config_admin_screens.ConfigListScreen,
2393
2545
  {
2394
2546
  client: configClient,
@@ -2401,12 +2553,12 @@ function AdminShell({
2401
2553
  );
2402
2554
  break;
2403
2555
  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 }),
2556
+ 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: [
2557
+ /* @__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: [
2558
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "back", size: 14 }),
2407
2559
  "Templates"
2408
2560
  ] }) }),
2409
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2561
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2410
2562
  import_react_courier_admin_screens.TemplateEditorScreen,
2411
2563
  {
2412
2564
  client: courierClient,
@@ -2414,7 +2566,7 @@ function AdminShell({
2414
2566
  onSaved: () => setEditing(null)
2415
2567
  }
2416
2568
  )
2417
- ] }) : /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2569
+ ] }) : /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2418
2570
  import_react_courier_admin_screens.TemplateListScreen,
2419
2571
  {
2420
2572
  client: courierClient,
@@ -2424,11 +2576,11 @@ function AdminShell({
2424
2576
  break;
2425
2577
  }
2426
2578
  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)(
2579
+ const brand = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.navBrand, children: [
2580
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navMark, children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "mark", size: 15 }) }),
2581
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { minWidth: 0, flex: 1 }, children: [
2582
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navBrandTitle, children: appName }),
2583
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2432
2584
  "span",
2433
2585
  {
2434
2586
  style: {
@@ -2445,18 +2597,18 @@ function AdminShell({
2445
2597
  }
2446
2598
  ) : null
2447
2599
  ] }),
2448
- narrow ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(
2600
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2449
2601
  "button",
2450
2602
  {
2451
2603
  type: "button",
2452
2604
  style: styles.buttonGhost,
2453
2605
  "aria-label": "Close menu",
2454
2606
  onClick: () => setDrawer(false),
2455
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "close" })
2607
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "close" })
2456
2608
  }
2457
2609
  ) : null
2458
2610
  ] });
2459
- const nav = /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2611
+ const nav = /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2460
2612
  "nav",
2461
2613
  {
2462
2614
  style: {
@@ -2466,14 +2618,14 @@ function AdminShell({
2466
2618
  "aria-label": "Admin",
2467
2619
  children: [
2468
2620
  brand,
2469
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2621
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navScroll, children: NAV.map((g) => {
2470
2622
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2471
2623
  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 }),
2624
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { children: [
2625
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navGroup, children: g.group }),
2474
2626
  items.map((i) => {
2475
2627
  const active = current === i.page;
2476
- return /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)(
2628
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(
2477
2629
  "button",
2478
2630
  {
2479
2631
  type: "button",
@@ -2482,10 +2634,11 @@ function AdminShell({
2482
2634
  "aria-current": active ? "page" : void 0,
2483
2635
  onClick: () => {
2484
2636
  setEditing(null);
2637
+ setOpenUser(void 0);
2485
2638
  go(i.page);
2486
2639
  },
2487
2640
  children: [
2488
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2641
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2489
2642
  i.label
2490
2643
  ]
2491
2644
  },
@@ -2494,15 +2647,15 @@ function AdminShell({
2494
2647
  })
2495
2648
  ] }, g.group);
2496
2649
  }) }),
2497
- footer ? /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.navFooter, children: footer }) : null
2650
+ footer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.navFooter, children: footer }) : null
2498
2651
  ]
2499
2652
  }
2500
2653
  );
2501
2654
  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)(
2655
+ return /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2656
+ narrow ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)(import_jsx_runtime16.Fragment, { children: [
2657
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.topbar, children: [
2658
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(
2506
2659
  "button",
2507
2660
  {
2508
2661
  type: "button",
@@ -2510,18 +2663,18 @@ function AdminShell({
2510
2663
  "aria-label": "Open menu",
2511
2664
  "aria-expanded": drawer,
2512
2665
  onClick: () => setDrawer(true),
2513
- children: /* @__PURE__ */ (0, import_jsx_runtime15.jsx)(Icon, { name: "menu" })
2666
+ children: /* @__PURE__ */ (0, import_jsx_runtime16.jsx)(Icon, { name: "menu" })
2514
2667
  }
2515
2668
  ),
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
2669
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2670
+ envLabel ? /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2518
2671
  ] }),
2519
- drawer ? /* @__PURE__ */ (0, import_jsx_runtime15.jsxs)("div", { style: styles.drawer, children: [
2672
+ drawer ? /* @__PURE__ */ (0, import_jsx_runtime16.jsxs)("div", { style: styles.drawer, children: [
2520
2673
  nav,
2521
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2674
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2522
2675
  ] }) : null
2523
2676
  ] }) : nav,
2524
- /* @__PURE__ */ (0, import_jsx_runtime15.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2677
+ /* @__PURE__ */ (0, import_jsx_runtime16.jsx)("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2525
2678
  ] });
2526
2679
  }
2527
2680
  // Annotate the CommonJS export names for ESM import in node:
@@ -2537,6 +2690,7 @@ function AdminShell({
2537
2690
  ModulesScreen,
2538
2691
  OperatorsScreen,
2539
2692
  RoutesScreen,
2693
+ SubscriberBilling,
2540
2694
  SubscriberScreen,
2541
2695
  TokensScreen,
2542
2696
  UsersScreen