@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.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,37 @@ 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 && !(error.status === 404 && selected?.id) ? /* @__PURE__ */ jsx12("p", { style: styles.error, role: "alert", children: error.status === 404 ? "No user with that email." : error.explanation }) : null,
1783
+ error?.status === 404 && selected?.id ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1784
+ /* @__PURE__ */ jsxs12("div", { style: styles.notice, role: "status", children: [
1785
+ /* @__PURE__ */ jsx12("strong", { children: "No account with id " }),
1786
+ /* @__PURE__ */ jsx12("code", { style: styles.code, children: selected.id }),
1787
+ /* @__PURE__ */ jsx12("strong", { children: "." }),
1788
+ " It was deleted, or never existed here. Its billing records remain."
1789
+ ] }),
1790
+ billingClient ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1791
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Plan & credits" }),
1792
+ /* @__PURE__ */ jsx12(
1793
+ SubscriberBilling,
1794
+ {
1795
+ client: billingClient,
1796
+ subscriber: { type: "user", id: selected.id }
1797
+ }
1798
+ )
1799
+ ] }) : null
1800
+ ] }) : null,
1801
+ !selected ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1802
+ list.error ? /* @__PURE__ */ jsx12("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
1803
+ 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: [
1804
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs12("tr", { children: [
1805
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Email" }),
1806
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Name" }),
1807
+ billingClient ? /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Plan" }) : null,
1808
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Created" }),
1809
+ /* @__PURE__ */ jsx12("th", { style: styles.th, children: "Status" })
1584
1810
  ] }) }),
1585
- /* @__PURE__ */ jsx11("tbody", { children: list.users.map((u) => /* @__PURE__ */ jsxs11("tr", { children: [
1586
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: /* @__PURE__ */ jsx11(
1811
+ /* @__PURE__ */ jsx12("tbody", { children: list.users.map((u) => /* @__PURE__ */ jsxs12("tr", { children: [
1812
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: /* @__PURE__ */ jsx12(
1587
1813
  "button",
1588
1814
  {
1589
1815
  type: "button",
@@ -1592,17 +1818,28 @@ function UsersScreen({ client, pageSize = 50 }) {
1592
1818
  children: u.email
1593
1819
  }
1594
1820
  ) }),
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
1821
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: `${u.firstName ?? ""} ${u.lastName ?? ""}`.trim() || /* @__PURE__ */ jsx12("span", { style: styles.muted, children: "\u2014" }) }),
1822
+ billingClient ? /* @__PURE__ */ jsx12("td", { style: styles.td, children: (() => {
1823
+ const sub = plans?.get(`user/${u.id}`);
1824
+ if (!plans) return /* @__PURE__ */ jsx12("span", { style: styles.muted, children: "\u2026" });
1825
+ if (!sub || sub.status === "canceled")
1826
+ return /* @__PURE__ */ jsx12("span", { style: styles.muted, children: "free" });
1827
+ return /* @__PURE__ */ jsxs12(Fragment7, { children: [
1828
+ /* @__PURE__ */ jsx12("strong", { children: sub.plan }),
1829
+ " ",
1830
+ sub.status !== "active" ? /* @__PURE__ */ jsx12(Pill, { tone: statusTone(sub.status), children: sub.status }) : null
1831
+ ] });
1832
+ })() }) : null,
1833
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: new Date(u.createdAt).toLocaleDateString() }),
1834
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1835
+ u.suspended ? /* @__PURE__ */ jsx12(Pill, { tone: "warn", children: "suspended" }) : null,
1836
+ u.deletedAt ? /* @__PURE__ */ jsx12(Pill, { tone: "neutral", children: "deleted" }) : null,
1837
+ !u.suspended && !u.deletedAt ? /* @__PURE__ */ jsx12(Pill, { tone: "ok", children: "active" }) : null
1601
1838
  ] })
1602
1839
  ] }, u.id)) })
1603
1840
  ] }),
1604
- list.isLoading ? /* @__PURE__ */ jsx11("p", { style: styles.status, children: "Loading\u2026" }) : null,
1605
- list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx11(
1841
+ list.isLoading ? /* @__PURE__ */ jsx12("p", { style: styles.status, children: "Loading\u2026" }) : null,
1842
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx12(
1606
1843
  "button",
1607
1844
  {
1608
1845
  type: "button",
@@ -1612,48 +1849,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1612
1849
  }
1613
1850
  ) : null
1614
1851
  ] }) : null,
1615
- user ? /* @__PURE__ */ jsxs11(Fragment6, { children: [
1616
- /* @__PURE__ */ jsxs11("h2", { style: styles.subtitle, children: [
1852
+ user ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1853
+ /* @__PURE__ */ jsxs12("h2", { style: styles.subtitle, children: [
1617
1854
  user.firstName || user.lastName ? `${user.firstName} ${user.lastName}`.trim() : user.email,
1618
1855
  " ",
1619
- user.suspended ? /* @__PURE__ */ jsx11(Pill, { tone: "warn", children: "suspended" }) : null,
1620
- user.deletedAt ? /* @__PURE__ */ jsx11(Pill, { tone: "neutral", children: "deleted" }) : null
1856
+ user.suspended ? /* @__PURE__ */ jsx12(Pill, { tone: "warn", children: "suspended" }) : null,
1857
+ user.deletedAt ? /* @__PURE__ */ jsx12(Pill, { tone: "neutral", children: "deleted" }) : null
1621
1858
  ] }),
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 })
1859
+ /* @__PURE__ */ jsx12("table", { style: styles.table, children: /* @__PURE__ */ jsxs12("tbody", { children: [
1860
+ /* @__PURE__ */ jsxs12("tr", { children: [
1861
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "id" }),
1862
+ /* @__PURE__ */ jsx12("td", { style: { ...styles.td, ...styles.mono }, children: user.id })
1626
1863
  ] }),
1627
- /* @__PURE__ */ jsxs11("tr", { children: [
1628
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "email" }),
1629
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
1864
+ /* @__PURE__ */ jsxs12("tr", { children: [
1865
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "email" }),
1866
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1630
1867
  user.email,
1631
1868
  " \xB7 verified ",
1632
1869
  yesNo(user.isEmailVerified)
1633
1870
  ] })
1634
1871
  ] }),
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) })
1872
+ /* @__PURE__ */ jsxs12("tr", { children: [
1873
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "MFA" }),
1874
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: yesNo(user.mfaEnabled) })
1638
1875
  ] }),
1639
- /* @__PURE__ */ jsxs11("tr", { children: [
1640
- /* @__PURE__ */ jsx11("td", { style: styles.td, children: "provider" }),
1641
- /* @__PURE__ */ jsxs11("td", { style: styles.td, children: [
1876
+ /* @__PURE__ */ jsxs12("tr", { children: [
1877
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "provider" }),
1878
+ /* @__PURE__ */ jsxs12("td", { style: styles.td, children: [
1642
1879
  user.provider || "password",
1643
1880
  user.hasPassword ? "" : " \xB7 no password set"
1644
1881
  ] })
1645
1882
  ] }),
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" }) })
1883
+ /* @__PURE__ */ jsxs12("tr", { children: [
1884
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "last login" }),
1885
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: user.lastLogin ? new Date(user.lastLogin).toLocaleString() : /* @__PURE__ */ jsx12("span", { style: styles.muted, children: "never" }) })
1649
1886
  ] }),
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() })
1887
+ /* @__PURE__ */ jsxs12("tr", { children: [
1888
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: "created" }),
1889
+ /* @__PURE__ */ jsx12("td", { style: styles.td, children: new Date(user.createdAt).toLocaleString() })
1653
1890
  ] })
1654
1891
  ] }) }),
1655
- /* @__PURE__ */ jsxs11("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1656
- user.suspended ? /* @__PURE__ */ jsx11(
1892
+ /* @__PURE__ */ jsxs12("div", { style: { ...styles.toolbar, marginTop: 12 }, children: [
1893
+ user.suspended ? /* @__PURE__ */ jsx12(
1657
1894
  "button",
1658
1895
  {
1659
1896
  type: "button",
@@ -1662,7 +1899,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1662
1899
  disabled: isLoading,
1663
1900
  children: "Unsuspend"
1664
1901
  }
1665
- ) : /* @__PURE__ */ jsx11(
1902
+ ) : /* @__PURE__ */ jsx12(
1666
1903
  "button",
1667
1904
  {
1668
1905
  type: "button",
@@ -1672,7 +1909,7 @@ function UsersScreen({ client, pageSize = 50 }) {
1672
1909
  children: "Suspend"
1673
1910
  }
1674
1911
  ),
1675
- /* @__PURE__ */ jsx11(
1912
+ /* @__PURE__ */ jsx12(
1676
1913
  "button",
1677
1914
  {
1678
1915
  type: "button",
@@ -1683,38 +1920,48 @@ function UsersScreen({ client, pageSize = 50 }) {
1683
1920
  }
1684
1921
  )
1685
1922
  ] }),
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" }),
1923
+ billingClient ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1924
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Plan & credits" }),
1925
+ /* @__PURE__ */ jsx12(
1926
+ SubscriberBilling,
1927
+ {
1928
+ client: billingClient,
1929
+ subscriber: { type: "user", id: user.id }
1930
+ }
1931
+ )
1932
+ ] }) : null,
1933
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Live sessions" }),
1934
+ 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: [
1935
+ /* @__PURE__ */ jsx12("span", { style: styles.mono, children: s.ipAddress ?? "\u2014" }),
1689
1936
  " ",
1690
- describeLocation(s.location) ? /* @__PURE__ */ jsxs11("span", { style: styles.muted, children: [
1937
+ describeLocation(s.location) ? /* @__PURE__ */ jsxs12("span", { style: styles.muted, children: [
1691
1938
  describeLocation(s.location),
1692
1939
  " "
1693
1940
  ] }) : null,
1694
- /* @__PURE__ */ jsx11("span", { style: styles.muted, children: s.userAgent ?? "" }),
1941
+ /* @__PURE__ */ jsx12("span", { style: styles.muted, children: s.userAgent ?? "" }),
1695
1942
  " ",
1696
- /* @__PURE__ */ jsxs11("span", { style: styles.muted, children: [
1943
+ /* @__PURE__ */ jsxs12("span", { style: styles.muted, children: [
1697
1944
  "since ",
1698
1945
  new Date(s.createdAt).toLocaleString()
1699
1946
  ] })
1700
1947
  ] }, 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 }),
1948
+ /* @__PURE__ */ jsx12("h2", { style: styles.subtitle, children: "Recent sign-ins" }),
1949
+ 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: [
1950
+ /* @__PURE__ */ jsx12(Pill, { tone: e.outcome === "success" ? "ok" : "bad", children: e.outcome }),
1704
1951
  " ",
1705
- /* @__PURE__ */ jsx11("span", { style: styles.muted, children: e.method }),
1952
+ /* @__PURE__ */ jsx12("span", { style: styles.muted, children: e.method }),
1706
1953
  " ",
1707
- /* @__PURE__ */ jsx11("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1954
+ /* @__PURE__ */ jsx12("span", { style: styles.mono, children: e.ipAddress ?? "\u2014" }),
1708
1955
  " ",
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: [
1956
+ describeLocation(e.location) ? /* @__PURE__ */ jsx12("span", { style: styles.muted, children: describeLocation(e.location) }) : null,
1957
+ e.location?.proxy || e.location?.hosting ? /* @__PURE__ */ jsxs12(Fragment7, { children: [
1711
1958
  " ",
1712
- /* @__PURE__ */ jsx11(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1959
+ /* @__PURE__ */ jsx12(Pill, { tone: "warn", children: e.location.proxy ? "proxy/VPN" : "hosting" })
1713
1960
  ] }) : null,
1714
1961
  " ",
1715
- /* @__PURE__ */ jsx11("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1962
+ /* @__PURE__ */ jsx12("span", { style: styles.muted, children: new Date(e.createdAt).toLocaleString() })
1716
1963
  ] }, e.id)) }),
1717
- history.hasMore && !history.isLoading ? /* @__PURE__ */ jsx11(
1964
+ history.hasMore && !history.isLoading ? /* @__PURE__ */ jsx12(
1718
1965
  "button",
1719
1966
  {
1720
1967
  type: "button",
@@ -1729,24 +1976,24 @@ function UsersScreen({ client, pageSize = 50 }) {
1729
1976
 
1730
1977
  // src/screens/CatalogScreen.tsx
1731
1978
  import { useAdminCatalog } from "@fonderie/react-admin";
1732
- import { Fragment as Fragment7, jsx as jsx12, jsxs as jsxs12 } from "react/jsx-runtime";
1979
+ import { Fragment as Fragment8, jsx as jsx13, jsxs as jsxs13 } from "react/jsx-runtime";
1733
1980
  function CatalogScreen({ client }) {
1734
1981
  const { catalog, isLoading, error, refresh, deletePlan } = useAdminCatalog(client);
1735
1982
  const money = (v) => v == null ? "\u2014" : (v / 100).toFixed(2);
1736
- return /* @__PURE__ */ jsxs12("div", { style: styles.container, children: [
1737
- /* @__PURE__ */ jsx12(
1983
+ return /* @__PURE__ */ jsxs13("div", { style: styles.container, children: [
1984
+ /* @__PURE__ */ jsx13(
1738
1985
  PageHeader,
1739
1986
  {
1740
1987
  title: "Catalog",
1741
1988
  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 })
1989
+ actions: /* @__PURE__ */ jsx13(RefreshButton, { onClick: () => void refresh(), busy: isLoading })
1743
1990
  }
1744
1991
  ),
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(
1992
+ error ? /* @__PURE__ */ jsx13("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
1993
+ isLoading && !catalog ? /* @__PURE__ */ jsx13("p", { style: styles.status, children: "Loading\u2026" }) : null,
1994
+ catalog ? /* @__PURE__ */ jsxs13(Fragment8, { children: [
1995
+ /* @__PURE__ */ jsx13("h2", { style: { ...styles.subtitle, marginTop: 0 }, children: "Configured (code)" }),
1996
+ /* @__PURE__ */ jsx13(
1750
1997
  "pre",
1751
1998
  {
1752
1999
  style: {
@@ -1760,32 +2007,32 @@ function CatalogScreen({ client }) {
1760
2007
  children: JSON.stringify(catalog.configured, null, 2)
1761
2008
  }
1762
2009
  ),
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 })
2010
+ /* @__PURE__ */ jsx13("h2", { style: styles.subtitle, children: "Stored (database)" }),
2011
+ 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: [
2012
+ /* @__PURE__ */ jsx13("thead", { children: /* @__PURE__ */ jsxs13("tr", { children: [
2013
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Plan" }),
2014
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Tier" }),
2015
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Seats" }),
2016
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Monthly" }),
2017
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Yearly" }),
2018
+ /* @__PURE__ */ jsx13("th", { style: styles.th, children: "Trial" }),
2019
+ /* @__PURE__ */ jsx13("th", { style: styles.th })
1773
2020
  ] }) }),
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
2021
+ /* @__PURE__ */ jsx13("tbody", { children: catalog.stored.map((p) => /* @__PURE__ */ jsxs13("tr", { children: [
2022
+ /* @__PURE__ */ jsxs13("td", { style: styles.td, children: [
2023
+ /* @__PURE__ */ jsx13("strong", { children: p.name }),
2024
+ p.description ? /* @__PURE__ */ jsx13("div", { style: styles.muted, children: p.description }) : null
1778
2025
  ] }),
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: [
2026
+ /* @__PURE__ */ jsx13("td", { style: styles.td, children: p.tier }),
2027
+ /* @__PURE__ */ jsx13("td", { style: styles.td, children: p.seats ?? "\u221E" }),
2028
+ /* @__PURE__ */ jsxs13("td", { style: styles.td, children: [
1782
2029
  money(p.pricing?.monthly),
1783
2030
  " ",
1784
2031
  p.pricing?.currency ?? ""
1785
2032
  ] }),
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(
2033
+ /* @__PURE__ */ jsx13("td", { style: styles.td, children: money(p.pricing?.yearly) }),
2034
+ /* @__PURE__ */ jsx13("td", { style: styles.td, children: p.trialDays ? `${p.trialDays} d` : "\u2014" }),
2035
+ /* @__PURE__ */ jsx13("td", { style: styles.td, children: /* @__PURE__ */ jsx13(
1789
2036
  "button",
1790
2037
  {
1791
2038
  type: "button",
@@ -1805,114 +2052,148 @@ function CatalogScreen({ client }) {
1805
2052
  }
1806
2053
 
1807
2054
  // 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);
2055
+ import { useAdminSubscribers } from "@fonderie/react-admin";
2056
+ import { useState as useState7 } from "react";
2057
+ import { Fragment as Fragment9, jsx as jsx14, jsxs as jsxs14 } from "react/jsx-runtime";
2058
+ var FILTERS = [
2059
+ { label: "All", match: () => true },
2060
+ { label: "Active", match: (s) => s === "active" },
2061
+ { label: "Trialing", match: (s) => s === "trialing" },
2062
+ { label: "Past due", match: (s) => s === "past_due" || s === "unpaid" },
2063
+ { label: "Canceled", match: (s) => s === "canceled" }
2064
+ ];
2065
+ function SubscriberScreen({ client, pageSize = 50, onOpenUser }) {
1815
2066
  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(
2067
+ const [filter, setFilter] = useState7("All");
2068
+ const [open, setOpen] = useState7(null);
2069
+ const [type, setType] = useState7("workspace");
2070
+ const [idInput, setIdInput] = useState7("");
2071
+ if (open) {
2072
+ return /* @__PURE__ */ jsxs14("div", { style: styles.container, children: [
2073
+ /* @__PURE__ */ jsx14(
2074
+ PageHeader,
2075
+ {
2076
+ title: `${open.type}/${open.id}`,
2077
+ lead: "Plan, credits and what moved.",
2078
+ actions: /* @__PURE__ */ jsxs14("button", { type: "button", style: styles.buttonGhost, onClick: () => setOpen(null), children: [
2079
+ /* @__PURE__ */ jsx14(Icon, { name: "back", size: 14 }),
2080
+ "All subscriptions"
2081
+ ] })
2082
+ }
2083
+ ),
2084
+ /* @__PURE__ */ jsx14(SubscriberBilling, { client, subscriber: open })
2085
+ ] });
2086
+ }
2087
+ const match = FILTERS.find((f) => f.label === filter)?.match ?? (() => true);
2088
+ const rows = list.subscriptions.filter((s) => match(s.status));
2089
+ const openRow = (t2, id) => t2 === "user" && onOpenUser ? onOpenUser(id) : setOpen({ type: t2, id });
2090
+ return /* @__PURE__ */ jsxs14("div", { style: styles.container, children: [
2091
+ /* @__PURE__ */ jsx14(
1828
2092
  PageHeader,
1829
2093
  {
1830
- title: "Subscribers",
1831
- lead: "Who is subscribed. Open one for their plan, wallet and ledger, and to grant credit."
2094
+ title: "Subscriptions",
2095
+ 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.",
2096
+ actions: /* @__PURE__ */ jsx14(RefreshButton, { onClick: () => void list.refresh(), busy: list.isLoading })
1832
2097
  }
1833
2098
  ),
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);
2099
+ /* @__PURE__ */ jsxs14("div", { style: { ...styles.toolbar, justifyContent: "space-between" }, children: [
2100
+ /* @__PURE__ */ jsx14(
2101
+ "fieldset",
2102
+ {
2103
+ style: {
2104
+ display: "flex",
2105
+ gap: 6,
2106
+ flexWrap: "wrap",
2107
+ border: "none",
2108
+ margin: 0,
2109
+ padding: 0,
2110
+ minWidth: 0
2111
+ },
2112
+ "aria-label": "Filter by status",
2113
+ children: FILTERS.map((f) => {
2114
+ const n = list.subscriptions.filter((s) => f.match(s.status)).length;
2115
+ const on = filter === f.label;
2116
+ return /* @__PURE__ */ jsxs14(
2117
+ "button",
2118
+ {
2119
+ type: "button",
2120
+ "aria-pressed": on,
2121
+ onClick: () => setFilter(f.label),
2122
+ style: { ...on ? styles.buttonPrimary : styles.button, height: 28 },
2123
+ children: [
2124
+ f.label,
2125
+ /* @__PURE__ */ jsx14("span", { style: { opacity: 0.7 }, children: n })
2126
+ ]
1880
2127
  },
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
- ]
2128
+ f.label
2129
+ );
2130
+ })
2131
+ }
2132
+ ),
2133
+ /* @__PURE__ */ jsxs14(
2134
+ "form",
2135
+ {
2136
+ style: { display: "flex", gap: 6 },
2137
+ onSubmit: (e) => {
2138
+ e.preventDefault();
2139
+ if (idInput.trim()) openRow(type, idInput.trim());
2140
+ },
2141
+ children: [
2142
+ /* @__PURE__ */ jsxs14(
2143
+ "select",
2144
+ {
2145
+ value: type,
2146
+ onChange: (e) => setType(e.target.value),
2147
+ style: styles.input,
2148
+ "aria-label": "Type",
2149
+ children: [
2150
+ /* @__PURE__ */ jsx14("option", { value: "workspace", children: "workspace" }),
2151
+ /* @__PURE__ */ jsx14("option", { value: "user", children: "user" })
2152
+ ]
2153
+ }
2154
+ ),
2155
+ /* @__PURE__ */ jsx14(
2156
+ "input",
2157
+ {
2158
+ value: idInput,
2159
+ onChange: (e) => setIdInput(e.target.value),
2160
+ placeholder: "subscriber id",
2161
+ style: { ...styles.input, width: 220 },
2162
+ "aria-label": "Subscriber id"
2163
+ }
2164
+ ),
2165
+ /* @__PURE__ */ jsxs14("button", { type: "submit", style: styles.button, disabled: !idInput.trim(), children: [
2166
+ /* @__PURE__ */ jsx14(Icon, { name: "search", size: 14 }),
2167
+ "Open"
2168
+ ] })
2169
+ ]
2170
+ }
2171
+ )
2172
+ ] }),
2173
+ list.error ? /* @__PURE__ */ jsx14("p", { style: styles.error, role: "alert", children: list.error.explanation }) : null,
2174
+ rows.length === 0 && !list.isLoading ? /* @__PURE__ */ jsx14(
2175
+ Empty,
2176
+ {
2177
+ icon: "subscriber",
2178
+ title: filter === "All" ? "No subscriptions yet" : `No ${filter.toLowerCase()} subscriptions`,
2179
+ children: "Subscriptions appear here once someone checks out."
1897
2180
  }
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(
2181
+ ) : /* @__PURE__ */ jsxs14("table", { style: styles.table, children: [
2182
+ /* @__PURE__ */ jsx14("thead", { children: /* @__PURE__ */ jsxs14("tr", { children: [
2183
+ /* @__PURE__ */ jsx14("th", { style: styles.th, children: "Subscriber" }),
2184
+ /* @__PURE__ */ jsx14("th", { style: styles.th, children: "Plan" }),
2185
+ /* @__PURE__ */ jsx14("th", { style: styles.th, children: "Status" }),
2186
+ /* @__PURE__ */ jsx14("th", { style: styles.th, children: "Renews / ends" })
2187
+ ] }) }),
2188
+ /* @__PURE__ */ jsx14("tbody", { children: rows.map((s) => {
2189
+ const end = periodEnd(s);
2190
+ return /* @__PURE__ */ jsxs14("tr", { children: [
2191
+ /* @__PURE__ */ jsx14("td", { style: styles.td, children: /* @__PURE__ */ jsxs14(
1911
2192
  "button",
1912
2193
  {
1913
2194
  type: "button",
1914
2195
  style: { ...styles.link, ...styles.mono },
1915
- onClick: () => open(s.subscriberType, s.subscriberId),
2196
+ onClick: () => openRow(s.subscriberType, s.subscriberId),
1916
2197
  children: [
1917
2198
  s.subscriberType,
1918
2199
  "/",
@@ -1920,206 +2201,56 @@ function SubscriberScreen({ client, pageSize = 50 }) {
1920
2201
  ]
1921
2202
  }
1922
2203
  ) }),
1923
- /* @__PURE__ */ jsxs13("td", { style: styles.td, children: [
1924
- /* @__PURE__ */ jsx13("strong", { children: s.plan }),
1925
- " \xB7 ",
1926
- s.interval
2204
+ /* @__PURE__ */ jsxs14("td", { style: styles.td, children: [
2205
+ /* @__PURE__ */ jsx14("strong", { children: s.plan }),
2206
+ " ",
2207
+ /* @__PURE__ */ jsxs14("span", { style: styles.muted, children: [
2208
+ "\xB7 ",
2209
+ s.interval
2210
+ ] })
1927
2211
  ] }),
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: [
2212
+ /* @__PURE__ */ jsxs14("td", { style: styles.td, children: [
2213
+ /* @__PURE__ */ jsx14(Pill, { tone: statusTone(s.status), children: s.status }),
2214
+ s.cancelAtPeriodEnd && s.status !== "canceled" ? /* @__PURE__ */ jsxs14(Fragment9, { children: [
1937
2215
  " ",
1938
- /* @__PURE__ */ jsx13(Pill, { tone: "warn", dot: false, children: "cancels" })
2216
+ /* @__PURE__ */ jsx14(Pill, { tone: "warn", dot: false, children: "cancels" })
1939
2217
  ] }) : null
1940
2218
  ] }),
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",
2219
+ /* @__PURE__ */ jsx14("td", { style: { ...styles.td, whiteSpace: "nowrap" }, children: end ? /* @__PURE__ */ jsxs14(Fragment9, { children: [
2220
+ /* @__PURE__ */ jsx14("span", { style: styles.muted, children: end.label }),
1977
2221
  " ",
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
2222
+ end.date
2223
+ ] }) : /* @__PURE__ */ jsx14("span", { style: styles.muted, children: "\u2014" }) })
2224
+ ] }, s.id);
2225
+ }) })
2226
+ ] }),
2227
+ list.isLoading ? /* @__PURE__ */ jsx14("p", { style: styles.status, children: "Loading\u2026" }) : null,
2228
+ list.hasMore && !list.isLoading ? /* @__PURE__ */ jsx14(
2229
+ "button",
2230
+ {
2231
+ type: "button",
2232
+ style: { ...styles.button, marginTop: 8 },
2233
+ onClick: () => void list.loadMore(),
2234
+ children: "Load more"
2235
+ }
2236
+ ) : null
2106
2237
  ] });
2107
2238
  }
2108
2239
 
2109
2240
  // src/screens/AuditScreen.tsx
2110
2241
  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";
2242
+ import { useState as useState8 } from "react";
2243
+ import { jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2113
2244
  function AuditScreen({ client, pageSize = 50 }) {
2114
- const [draft, setDraft] = useState6({ workspaceId: "", type: "", actorId: "" });
2115
- const [filter, setFilter] = useState6(
2245
+ const [draft, setDraft] = useState8({ workspaceId: "", type: "", actorId: "" });
2246
+ const [filter, setFilter] = useState8(
2116
2247
  {}
2117
2248
  );
2118
2249
  const { events, hasMore, isLoading, error, refresh, loadMore } = useAdminAudit(client, {
2119
2250
  ...filter,
2120
2251
  limit: pageSize
2121
2252
  });
2122
- const field = (key, placeholder) => /* @__PURE__ */ jsx14(
2253
+ const field = (key, placeholder) => /* @__PURE__ */ jsx15(
2123
2254
  "input",
2124
2255
  {
2125
2256
  value: draft[key],
@@ -2129,15 +2260,15 @@ function AuditScreen({ client, pageSize = 50 }) {
2129
2260
  "aria-label": placeholder
2130
2261
  }
2131
2262
  );
2132
- return /* @__PURE__ */ jsxs14("div", { style: styles.container, children: [
2133
- /* @__PURE__ */ jsx14(
2263
+ return /* @__PURE__ */ jsxs15("div", { style: styles.container, children: [
2264
+ /* @__PURE__ */ jsx15(
2134
2265
  PageHeader,
2135
2266
  {
2136
2267
  title: "Audit",
2137
2268
  lead: "What happened, across every workspace unless you name one. The chain's integrity verdict is on the Doctor page."
2138
2269
  }
2139
2270
  ),
2140
- /* @__PURE__ */ jsxs14(
2271
+ /* @__PURE__ */ jsxs15(
2141
2272
  "form",
2142
2273
  {
2143
2274
  style: styles.toolbar,
@@ -2153,11 +2284,11 @@ function AuditScreen({ client, pageSize = 50 }) {
2153
2284
  field("workspaceId", "workspace id (all if empty)"),
2154
2285
  field("type", "event type"),
2155
2286
  field("actorId", "actor id"),
2156
- /* @__PURE__ */ jsxs14("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2157
- /* @__PURE__ */ jsx14(Icon, { name: "search", size: 14 }),
2287
+ /* @__PURE__ */ jsxs15("button", { type: "submit", style: styles.buttonPrimary, disabled: isLoading, children: [
2288
+ /* @__PURE__ */ jsx15(Icon, { name: "search", size: 14 }),
2158
2289
  "Filter"
2159
2290
  ] }),
2160
- /* @__PURE__ */ jsx14(
2291
+ /* @__PURE__ */ jsx15(
2161
2292
  "button",
2162
2293
  {
2163
2294
  type: "button",
@@ -2170,25 +2301,25 @@ function AuditScreen({ client, pageSize = 50 }) {
2170
2301
  ]
2171
2302
  }
2172
2303
  ),
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" })
2304
+ error ? /* @__PURE__ */ jsx15("p", { style: styles.error, role: "alert", children: error.explanation }) : null,
2305
+ events.length === 0 && !isLoading ? /* @__PURE__ */ jsx15(Empty, { icon: "audit", title: "Nothing recorded for this filter" }) : /* @__PURE__ */ jsxs15("table", { style: styles.table, children: [
2306
+ /* @__PURE__ */ jsx15("thead", { children: /* @__PURE__ */ jsxs15("tr", { children: [
2307
+ /* @__PURE__ */ jsx15("th", { style: styles.th, children: "When" }),
2308
+ /* @__PURE__ */ jsx15("th", { style: styles.th, children: "Type" }),
2309
+ /* @__PURE__ */ jsx15("th", { style: styles.th, children: "Workspace" }),
2310
+ /* @__PURE__ */ jsx15("th", { style: styles.th, children: "Actor" }),
2311
+ /* @__PURE__ */ jsx15("th", { style: styles.th, children: "Request" })
2181
2312
  ] }) }),
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 ?? "" })
2313
+ /* @__PURE__ */ jsx15("tbody", { children: events.map((e) => /* @__PURE__ */ jsxs15("tr", { children: [
2314
+ /* @__PURE__ */ jsx15("td", { style: { ...styles.td, ...styles.muted }, children: new Date(e.createdAt).toLocaleString() }),
2315
+ /* @__PURE__ */ jsx15("td", { style: { ...styles.td, ...styles.mono }, children: e.type }),
2316
+ /* @__PURE__ */ jsx15("td", { style: { ...styles.td, ...styles.mono }, children: String(e.payload["workspaceId"] ?? "\u2014") }),
2317
+ /* @__PURE__ */ jsx15("td", { style: { ...styles.td, ...styles.mono }, children: e.actorId ?? "\u2014" }),
2318
+ /* @__PURE__ */ jsx15("td", { style: { ...styles.td, ...styles.muted }, children: e.requestId ?? "" })
2188
2319
  ] }, e.id)) })
2189
2320
  ] }),
2190
- isLoading ? /* @__PURE__ */ jsx14("p", { style: styles.status, children: "Loading\u2026" }) : null,
2191
- hasMore && !isLoading ? /* @__PURE__ */ jsx14(
2321
+ isLoading ? /* @__PURE__ */ jsx15("p", { style: styles.status, children: "Loading\u2026" }) : null,
2322
+ hasMore && !isLoading ? /* @__PURE__ */ jsx15(
2192
2323
  "button",
2193
2324
  {
2194
2325
  type: "button",
@@ -2201,7 +2332,7 @@ function AuditScreen({ client, pageSize = 50 }) {
2201
2332
  }
2202
2333
 
2203
2334
  // src/screens/AdminShell.tsx
2204
- import { Fragment as Fragment9, jsx as jsx15, jsxs as jsxs15 } from "react/jsx-runtime";
2335
+ import { Fragment as Fragment10, jsx as jsx16, jsxs as jsxs16 } from "react/jsx-runtime";
2205
2336
  var ICON = {
2206
2337
  attention: "attention",
2207
2338
  modules: "modules",
@@ -2222,8 +2353,8 @@ var ICON = {
2222
2353
  var NARROW = "(max-width: 820px)";
2223
2354
  function useNarrow() {
2224
2355
  const query = typeof window !== "undefined" && window.matchMedia ? window.matchMedia(NARROW) : null;
2225
- const [narrow, setNarrow] = useState7(() => query?.matches ?? false);
2226
- useEffect(() => {
2356
+ const [narrow, setNarrow] = useState9(() => query?.matches ?? false);
2357
+ useEffect2(() => {
2227
2358
  if (!query) return;
2228
2359
  const on = (e) => setNarrow(e.matches);
2229
2360
  query.addEventListener("change", on);
@@ -2247,7 +2378,7 @@ var NAV = [
2247
2378
  group: "Money",
2248
2379
  items: [
2249
2380
  { page: "catalog", label: "Catalog", needs: "billing" },
2250
- { page: "subscriber", label: "Subscriber", needs: "billing" }
2381
+ { page: "subscriber", label: "Subscriptions", needs: "billing" }
2251
2382
  ]
2252
2383
  },
2253
2384
  { group: "Settings", items: [{ page: "settings", label: "Config & secrets", needs: "config" }] },
@@ -2280,15 +2411,16 @@ function AdminShell({
2280
2411
  currentOperator
2281
2412
  }) {
2282
2413
  const narrow = useNarrow();
2283
- const [drawer, setDrawer] = useState7(false);
2284
- const [own, setOwn] = useState7("attention");
2414
+ const [drawer, setDrawer] = useState9(false);
2415
+ const [own, setOwn] = useState9("attention");
2416
+ const [openUser, setOpenUser] = useState9(void 0);
2285
2417
  const current = page ?? own;
2286
2418
  const go = (p) => {
2287
2419
  if (onNavigate) onNavigate(p);
2288
2420
  if (page === void 0) setOwn(p);
2289
2421
  setDrawer(false);
2290
2422
  };
2291
- const [editing, setEditing] = useState7(null);
2423
+ const [editing, setEditing] = useState9(null);
2292
2424
  const has = {
2293
2425
  config: Boolean(configClient),
2294
2426
  courier: Boolean(courierClient),
@@ -2300,51 +2432,70 @@ function AdminShell({
2300
2432
  let body;
2301
2433
  switch (current) {
2302
2434
  case "attention":
2303
- body = /* @__PURE__ */ jsx15(AttentionScreen, { client });
2435
+ body = /* @__PURE__ */ jsx16(AttentionScreen, { client });
2304
2436
  break;
2305
2437
  case "modules":
2306
- body = /* @__PURE__ */ jsx15(ModulesScreen, { client });
2438
+ body = /* @__PURE__ */ jsx16(ModulesScreen, { client });
2307
2439
  break;
2308
2440
  case "doctor":
2309
- body = /* @__PURE__ */ jsx15(DoctorScreen, { client });
2441
+ body = /* @__PURE__ */ jsx16(DoctorScreen, { client });
2310
2442
  break;
2311
2443
  case "environment":
2312
- body = /* @__PURE__ */ jsx15(EnvironmentScreen, { client });
2444
+ body = /* @__PURE__ */ jsx16(EnvironmentScreen, { client });
2313
2445
  break;
2314
2446
  case "routes":
2315
- body = /* @__PURE__ */ jsx15(RoutesScreen, { client });
2447
+ body = /* @__PURE__ */ jsx16(RoutesScreen, { client });
2316
2448
  break;
2317
2449
  case "tokens":
2318
- body = /* @__PURE__ */ jsx15(TokensScreen, { client });
2450
+ body = /* @__PURE__ */ jsx16(TokensScreen, { client });
2319
2451
  break;
2320
2452
  case "operators":
2321
- body = /* @__PURE__ */ jsx15(OperatorsScreen, { client, me: currentOperator });
2453
+ body = /* @__PURE__ */ jsx16(OperatorsScreen, { client, me: currentOperator });
2322
2454
  break;
2323
2455
  case "migrations":
2324
- body = /* @__PURE__ */ jsx15(MigrationsScreen, { client });
2456
+ body = /* @__PURE__ */ jsx16(MigrationsScreen, { client });
2325
2457
  break;
2326
2458
  case "log":
2327
- body = /* @__PURE__ */ jsx15(AdminLogScreen, { client });
2459
+ body = /* @__PURE__ */ jsx16(AdminLogScreen, { client });
2328
2460
  break;
2329
2461
  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." });
2462
+ 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
2463
  break;
2332
2464
  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." });
2465
+ body = billingClient ? /* @__PURE__ */ jsx16(
2466
+ SubscriberScreen,
2467
+ {
2468
+ client: billingClient,
2469
+ ...authClient ? {
2470
+ onOpenUser: (id) => {
2471
+ setOpenUser(id);
2472
+ go("users");
2473
+ }
2474
+ } : {}
2475
+ }
2476
+ ) : /* @__PURE__ */ jsx16("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass a BillingAdminClient to look up subscribers here." });
2334
2477
  break;
2335
2478
  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." });
2479
+ 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
2480
  break;
2338
2481
  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." });
2482
+ body = authClient ? /* @__PURE__ */ jsx16(
2483
+ UsersScreen,
2484
+ {
2485
+ client: authClient,
2486
+ billingClient,
2487
+ openUserId: openUser
2488
+ },
2489
+ openUser ?? "list"
2490
+ ) : /* @__PURE__ */ jsx16("p", { style: { ...styles.status, padding: "32px 40px" }, children: "Pass an AuthAdminClient to look up users here." });
2340
2491
  break;
2341
2492
  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 }),
2493
+ 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: [
2494
+ /* @__PURE__ */ jsx16("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ jsxs16("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2495
+ /* @__PURE__ */ jsx16(Icon, { name: "back", size: 14 }),
2345
2496
  "Config & secrets"
2346
2497
  ] }) }),
2347
- /* @__PURE__ */ jsx15(
2498
+ /* @__PURE__ */ jsx16(
2348
2499
  ConfigEditorScreen,
2349
2500
  {
2350
2501
  client: configClient,
@@ -2354,7 +2505,7 @@ function AdminShell({
2354
2505
  onSaved: () => setEditing(null)
2355
2506
  }
2356
2507
  )
2357
- ] }) : /* @__PURE__ */ jsx15(
2508
+ ] }) : /* @__PURE__ */ jsx16(
2358
2509
  ConfigListScreen,
2359
2510
  {
2360
2511
  client: configClient,
@@ -2367,12 +2518,12 @@ function AdminShell({
2367
2518
  );
2368
2519
  break;
2369
2520
  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 }),
2521
+ 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: [
2522
+ /* @__PURE__ */ jsx16("div", { style: { padding: "24px 40px 0" }, children: /* @__PURE__ */ jsxs16("button", { type: "button", style: styles.buttonGhost, onClick: () => setEditing(null), children: [
2523
+ /* @__PURE__ */ jsx16(Icon, { name: "back", size: 14 }),
2373
2524
  "Templates"
2374
2525
  ] }) }),
2375
- /* @__PURE__ */ jsx15(
2526
+ /* @__PURE__ */ jsx16(
2376
2527
  TemplateEditorScreen,
2377
2528
  {
2378
2529
  client: courierClient,
@@ -2380,7 +2531,7 @@ function AdminShell({
2380
2531
  onSaved: () => setEditing(null)
2381
2532
  }
2382
2533
  )
2383
- ] }) : /* @__PURE__ */ jsx15(
2534
+ ] }) : /* @__PURE__ */ jsx16(
2384
2535
  TemplateListScreen,
2385
2536
  {
2386
2537
  client: courierClient,
@@ -2390,11 +2541,11 @@ function AdminShell({
2390
2541
  break;
2391
2542
  }
2392
2543
  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(
2544
+ const brand = /* @__PURE__ */ jsxs16("div", { style: styles.navBrand, children: [
2545
+ /* @__PURE__ */ jsx16("div", { style: styles.navMark, children: /* @__PURE__ */ jsx16(Icon, { name: "mark", size: 15 }) }),
2546
+ /* @__PURE__ */ jsxs16("div", { style: { minWidth: 0, flex: 1 }, children: [
2547
+ /* @__PURE__ */ jsx16("div", { style: styles.navBrandTitle, children: appName }),
2548
+ envLabel ? /* @__PURE__ */ jsx16(
2398
2549
  "span",
2399
2550
  {
2400
2551
  style: {
@@ -2411,18 +2562,18 @@ function AdminShell({
2411
2562
  }
2412
2563
  ) : null
2413
2564
  ] }),
2414
- narrow ? /* @__PURE__ */ jsx15(
2565
+ narrow ? /* @__PURE__ */ jsx16(
2415
2566
  "button",
2416
2567
  {
2417
2568
  type: "button",
2418
2569
  style: styles.buttonGhost,
2419
2570
  "aria-label": "Close menu",
2420
2571
  onClick: () => setDrawer(false),
2421
- children: /* @__PURE__ */ jsx15(Icon, { name: "close" })
2572
+ children: /* @__PURE__ */ jsx16(Icon, { name: "close" })
2422
2573
  }
2423
2574
  ) : null
2424
2575
  ] });
2425
- const nav = /* @__PURE__ */ jsxs15(
2576
+ const nav = /* @__PURE__ */ jsxs16(
2426
2577
  "nav",
2427
2578
  {
2428
2579
  style: {
@@ -2432,14 +2583,14 @@ function AdminShell({
2432
2583
  "aria-label": "Admin",
2433
2584
  children: [
2434
2585
  brand,
2435
- /* @__PURE__ */ jsx15("div", { style: styles.navScroll, children: NAV.map((g) => {
2586
+ /* @__PURE__ */ jsx16("div", { style: styles.navScroll, children: NAV.map((g) => {
2436
2587
  const items = g.items.filter((i) => !i.needs || has[i.needs]);
2437
2588
  if (items.length === 0) return null;
2438
- return /* @__PURE__ */ jsxs15("div", { children: [
2439
- /* @__PURE__ */ jsx15("div", { style: styles.navGroup, children: g.group }),
2589
+ return /* @__PURE__ */ jsxs16("div", { children: [
2590
+ /* @__PURE__ */ jsx16("div", { style: styles.navGroup, children: g.group }),
2440
2591
  items.map((i) => {
2441
2592
  const active = current === i.page;
2442
- return /* @__PURE__ */ jsxs15(
2593
+ return /* @__PURE__ */ jsxs16(
2443
2594
  "button",
2444
2595
  {
2445
2596
  type: "button",
@@ -2448,10 +2599,11 @@ function AdminShell({
2448
2599
  "aria-current": active ? "page" : void 0,
2449
2600
  onClick: () => {
2450
2601
  setEditing(null);
2602
+ setOpenUser(void 0);
2451
2603
  go(i.page);
2452
2604
  },
2453
2605
  children: [
2454
- /* @__PURE__ */ jsx15(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2606
+ /* @__PURE__ */ jsx16(Icon, { name: ICON[i.page], style: active ? styles.navIconActive : void 0 }),
2455
2607
  i.label
2456
2608
  ]
2457
2609
  },
@@ -2460,15 +2612,15 @@ function AdminShell({
2460
2612
  })
2461
2613
  ] }, g.group);
2462
2614
  }) }),
2463
- footer ? /* @__PURE__ */ jsx15("div", { style: styles.navFooter, children: footer }) : null
2615
+ footer ? /* @__PURE__ */ jsx16("div", { style: styles.navFooter, children: footer }) : null
2464
2616
  ]
2465
2617
  }
2466
2618
  );
2467
2619
  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(
2620
+ return /* @__PURE__ */ jsxs16("div", { style: { ...styles.shell, ...narrow ? { flexDirection: "column" } : {} }, children: [
2621
+ narrow ? /* @__PURE__ */ jsxs16(Fragment10, { children: [
2622
+ /* @__PURE__ */ jsxs16("div", { style: styles.topbar, children: [
2623
+ /* @__PURE__ */ jsx16(
2472
2624
  "button",
2473
2625
  {
2474
2626
  type: "button",
@@ -2476,18 +2628,18 @@ function AdminShell({
2476
2628
  "aria-label": "Open menu",
2477
2629
  "aria-expanded": drawer,
2478
2630
  onClick: () => setDrawer(true),
2479
- children: /* @__PURE__ */ jsx15(Icon, { name: "menu" })
2631
+ children: /* @__PURE__ */ jsx16(Icon, { name: "menu" })
2480
2632
  }
2481
2633
  ),
2482
- /* @__PURE__ */ jsx15("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2483
- envLabel ? /* @__PURE__ */ jsx15("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2634
+ /* @__PURE__ */ jsx16("span", { style: { fontWeight: 600, fontSize: 14 }, children: title }),
2635
+ envLabel ? /* @__PURE__ */ jsx16("span", { style: { ...styles.badge, marginLeft: "auto" }, children: envLabel }) : null
2484
2636
  ] }),
2485
- drawer ? /* @__PURE__ */ jsxs15("div", { style: styles.drawer, children: [
2637
+ drawer ? /* @__PURE__ */ jsxs16("div", { style: styles.drawer, children: [
2486
2638
  nav,
2487
- /* @__PURE__ */ jsx15("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2639
+ /* @__PURE__ */ jsx16("div", { style: styles.scrim, onClick: () => setDrawer(false), "aria-hidden": "true" })
2488
2640
  ] }) : null
2489
2641
  ] }) : nav,
2490
- /* @__PURE__ */ jsx15("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2642
+ /* @__PURE__ */ jsx16("main", { className: "fonderie-admin-main", style: styles.main, children: body })
2491
2643
  ] });
2492
2644
  }
2493
2645
  export {
@@ -2502,6 +2654,7 @@ export {
2502
2654
  ModulesScreen,
2503
2655
  OperatorsScreen,
2504
2656
  RoutesScreen,
2657
+ SubscriberBilling,
2505
2658
  SubscriberScreen,
2506
2659
  TokensScreen,
2507
2660
  UsersScreen