@fonderie/react-admin-screens 1.2.0 → 1.3.0

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